Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New process launch API #19108

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open

New process launch API #19108

wants to merge 13 commits into from

Conversation

smashery
Copy link
Contributor

@smashery smashery commented Apr 18, 2024

This creates a new API, create_process, which allows the creation of processes from an array of args, rather than from a commandline string that needs to go through a subshell. This places the escaping logic in one place, and lets module developers create more robust code.

Verification

You'll need to pull in mettle, as well as the various metasploit-payloads (php, py, c, java)

rapid7/metasploit-payloads#701
rapid7/mettle#258

Test for each of the following:

  • Windows
  • Linux
  • Python
  • Java
  • PHP
  • PHP < 7.4
  • Win cmdshell
  • Linux cmdshell
  • PowerShell

For each of the above:

  • Verify that the new create_process passes parameters exactly as provided. You can run it directly in irb by setting a session, then using create_process(cmd, args:[...]). I created a test program to do this - just ask ChatGPT to write you a program that will show you what args were passed to it, each on a new linen.
  • Verify that special characters for that
  • Verify that cmd_exec still works as it did before (including buggy calls)
  • Verify that new Metasploit correctly works with old Meterpreters
  • As you go, verify that subshells are only used when using old meterps, when using the old-style cmd_exec, and then using create_process on PHP < 7.4 (not supported)

You can observe process launches (to check for the presence/absence of subshells) using:

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_exec*{ printf("pid: %d, comm: %s, args: ", pid, comm); join(args->argv); }'

Tests

Windows, new Metasploit, old Meterp

>> print_line(create_process('showargs.exe'))

>> print_line(create_process("%showargs'.exe", args:['', '', '']))



>> print_line(create_process('showargs.exe', args:['basic','args']))
basic
args
>> print_line(create_process('showargs.exe', args:['with space']))
with space
>> print_line(create_process('show args.exe', args:['with space']))
with space
>> print_line(create_process("%showargs'.exe", args:['with space']))
with space
>> print_line(create_process('showargs.exe', args:['%PATH%']))
%PATH%

>> print_line(cmd_exec('"show args.exe"', 'test words "with space"'))
test
words
with space
>> print_line(cmd_exec('showargs.exe', 'test words'))
test
words
>> print_line(cmd_exec('showargs.exe', '%PATH%'))
%PATH%

Windows, new Metasploit, new Meterp

>> print_line(create_process('showargs.exe'))

>> print_line(create_process('showargs.exe', args:['basic','args']))
basic
args
>> print_line(create_process('showargs.exe', args:['with space']))
with space
>> print_line(create_process('show args.exe', args:['with space']))
with space
>> print_line(create_process("%showargs'.exe", args:['with space']))
with space
>> print_line(create_process('showargs.exe', args:['%PATH%']))
%PATH%

>> print_line(cmd_exec('"show args.exe"', 'test words "with space"'))
test
words
with space
>> print_line(cmd_exec('showargs.exe', 'test words'))
test
words
>> print_line(cmd_exec('showargs.exe', '%PATH%'))
%PATH%

Linux, new Metasploit, old Meterp

>> print_line(create_process('./run', args:[]))

Usage: ./run [argument1] [argument2] ... [argumentN]
>> print_line(create_process('./run', args:['a','','b','','c']))
./run
a

b

c
>> print_line(create_process('./run', args:['basic','args']))
./run
basic
args
>> print_line(create_process('./run', args:['with spaces']))
./run
with spaces
>> print_line(create_process('./run', args:['$PATH']))
./run
$PATH
>> print_line(create_process('./run', args:["it's $PATH"]))
./run
it's $PATH
>> print_line(create_process('./run', args:['~!@#$%^&*(){`1234567890[]",.\'<>']))
./run
~!@#$%^&*(){`1234567890[]",.'<>
>> print_line(create_process('./run', args:["run&echo"]))
./run
run&echo
>> print_line(create_process('./run', args:["run&echo;test"]))
./run
run&echo;test
>> print_line(create_process('./run file', args:["arg1"]))
./run file
arg1
>> print_line(create_process('./~!@#$%^&*(){}', args:["arg1"]))
./~!@#$%^&*(){}
arg1
> print_line(cmd_exec('./run', 'something here'))
./run
something
here
>> print_line(cmd_exec('./run', '$PATH'))
./run
/home/smash/.local/bin:/home/smash/.rbenv/shims:/home/smash/.local/bin:/home/smash/.rbenv/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/tools:/opt/apache-maven-3.5.4/bin:/tools:/opt/apache-maven-3.5.4/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/system/bin:/system/sbin:/system/xbin

Linux, new Metasploit, new Meterp

>> print_line(create_process('./run', args:[]))

Usage: ./run [argument1] [argument2] ... [argumentN]
>> print_line(create_process('./run', args:['a','','b','','c']))
./run
a

b

c
>> print_line(create_process('./run', args:['basic','args']))
./run
basic
args
>> print_line(create_process('./run', args:['with spaces']))
./run
with spaces
>> print_line(create_process('./run', args:['$PATH']))
./run
$PATH
>> print_line(create_process('./run', args:["it's $PATH"]))
./run
it's $PATH
>> print_line(create_process('./run', args:['~!@#$%^&*(){`1234567890[]",.\'<>']))
./run
~!@#$%^&*(){`1234567890[]",.'<>
>> print_line(create_process('./run', args:["run&echo"]))
./run
run&echo
>> print_line(create_process('./run', args:["run&echo;test"]))
./run
run&echo;test
>> print_line(create_process('./run file', args:["arg1"]))
./run file
arg1
>> print_line(create_process('./~!@#$%^&*(){}', args:["arg1"]))
./~!@#$%^&*(){}
arg1
>> print_line(cmd_exec('./run', 'something here'))
./run
something
here
>> print_line(cmd_exec('./run', '$PATH'))
./run
/home/smash/.local/bin:/home/smash/.rbenv/shims:/home/smash/.local/bin:/home/smash/.rbenv/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/tools:/opt/apache-maven-3.5.4/bin:/tools:/opt/apache-maven-3.5.4/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/system/bin:/system/sbin:/system/xbin

Java, new Metasploit, old Meterp

>> print_line(create_process('./run', args:[]))

Usage: ./run [argument1] [argument2] ... [argumentN]
>> print_line(create_process('./run', args:['a','','b','','c']))
./run
a

b

c
>> print_line(create_process('./run', args:['basic','args']))
./run
basic
args
>> print_line(create_process('./run', args:['with spaces']))
./run
with spaces
>> print_line(create_process('./run', args:['$PATH']))
./run
$PATH
>> print_line(create_process('./run', args:["it's $PATH"]))
./run
it's $PATH
>> print_line(create_process('./run', args:['~!@#$%^&*(){`1234567890[]",.\'<>']))
./run
~!@#$%^&*(){`1234567890[]",.'<>
>> print_line(create_process('./run', args:["run&echo"]))
./run
run&echo
>> print_line(create_process('./run', args:["run&echo;test"]))
./run
run&echo;test
>> print_line(create_process('./run file', args:["arg1"]))
./run file
arg1
>> print_line(create_process('./~!@#$%^&*(){}', args:["arg1"]))
./~!@#$%^&*(){}
arg1
>> print_line(cmd_exec('./run', 'something here'))
./run
something
here
>> print_line(cmd_exec('./run', '$PATH'))
./run
/home/smash/.local/bin:/home/smash/.rbenv/shims:/home/smash/.local/bin:/home/smash/.rbenv/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/tools:/opt/apache-maven-3.5.4/bin:/tools:/opt/apache-maven-3.5.4/bin

Java, new Metasploit, new Meterp

>> print_line(create_process('./run', args:[]))

Usage: ./run [argument1] [argument2] ... [argumentN]
>> print_line(create_process('./run', args:['a','','b','','c']))
./run
a

b

c
>> print_line(create_process('./run', args:['basic','args']))
./run
basic
args
>> print_line(create_process('./run', args:['with spaces']))
./run
with spaces
>> print_line(create_process('./run', args:['$PATH']))
./run
$PATH
>> print_line(create_process('./run', args:["it's $PATH"]))
./run
it's $PATH
>> print_line(create_process('./run', args:['~!@#$%^&*(){`1234567890[]",.\'<>']))
./run
~!@#$%^&*(){`1234567890[]",.'<>
>> print_line(create_process('./run', args:["run&echo"]))
./run
run&echo
>> print_line(create_process('./run', args:["run&echo;test"]))
./run
run&echo;test
>> print_line(create_process('./run file', args:["arg1"]))
./run file
arg1
>> print_line(create_process('./~!@#$%^&*(){}', args:["arg1"]))
./~!@#$%^&*(){}
arg1
>> print_line(cmd_exec('./run', 'something here'))
./run
something
here
>> print_line(cmd_exec('./run', '$PATH'))
./run
/home/smash/.local/bin:/home/smash/.rbenv/shims:/home/smash/.local/bin:/home/smash/.rbenv/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/tools:/opt/apache-maven-3.5.4/bin:/tools:/opt/apache-maven-3.5.4/bin

Python, new Metasploit, old Meterp

>> print_line(create_process('./run', args:[]))

Usage: ./run [argument1] [argument2] ... [argumentN]
>> print_line(create_process('./run', args:['a','','b','','c']))
./run
a

b

c
>> print_line(create_process('./run', args:['basic','args']))
./run
basic
args
>> print_line(create_process('./run', args:['with spaces']))
./run
with spaces
>> print_line(create_process('./run', args:['$PATH']))
./run
$PATH
>> print_line(create_process('./run', args:["it's $PATH"]))
./run
it's $PATH
>> print_line(create_process('./run', args:['~!@#$%^&*(){`1234567890[]",.\'<>']))
./run
~!@#$%^&*(){`1234567890[]",.'<>
>> print_line(create_process('./run', args:["run&echo"]))
./run
run&echo
>> print_line(create_process('./run', args:["run&echo;test"]))
./run
run&echo;test
>> print_line(create_process('./run file', args:["arg1"]))
./run file
arg1
>> print_line(create_process('./~!@#$%^&*(){}', args:["arg1"]))
./~!@#$%^&*(){}
arg1
>> print_line(cmd_exec('./run', 'something here'))
./run
something
here
>> print_line(cmd_exec('./run', '$PATH'))
./run
/home/smash/.local/bin:/home/smash/.rbenv/shims:/home/smash/.local/bin:/home/smash/.rbenv/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/tools:/opt/apache-maven-3.5.4/bin:/tools:/opt/apache-maven-3.5.4/bin

Python, new Metasploit, new Meterp

>> print_line(create_process('./run', args:[]))

Usage: ./run [argument1] [argument2] ... [argumentN]
>> print_line(create_process('./run', args:['a','','b','','c']))
./run
a

b

c
>> print_line(create_process('./run', args:['basic','args']))
./run
basic
args
>> print_line(create_process('./run', args:['with spaces']))
./run
with spaces
>> print_line(create_process('./run', args:['$PATH']))
./run
$PATH
>> print_line(create_process('./run', args:["it's $PATH"]))
./run
it's $PATH
>> print_line(create_process('./run', args:['~!@#$%^&*(){`1234567890[]",.\'<>']))
./run
~!@#$%^&*(){`1234567890[]",.'<>
>> print_line(create_process('./run', args:["run&echo"]))
./run
run&echo
>> print_line(create_process('./run', args:["run&echo;test"]))
./run
run&echo;test
>> print_line(create_process('./run file', args:["arg1"]))
./run file
arg1
>> print_line(create_process('./~!@#$%^&*(){}', args:["arg1"]))
./~!@#$%^&*(){}
arg1
>> print_line(cmd_exec('./run', 'something here'))
./run
something
here
>> print_line(cmd_exec('./run', '$PATH'))
./run
/home/smash/.local/bin:/home/smash/.rbenv/shims:/home/smash/.local/bin:/home/smash/.rbenv/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/tools:/opt/apache-maven-3.5.4/bin:/tools:/opt/apache-maven-3.5.4/bin

PHP, new Metasploit, old Meterp

>> print_line(create_process('./run', args:[]))

Usage: ./run [argument1] [argument2] ... [argumentN]
>> print_line(create_process('./run', args:['a','','b','','c']))
./run
a

b

c
>> print_line(create_process('./run', args:['basic','args']))
./run
basic
args
>> print_line(create_process('./run', args:['with spaces']))
./run
with spaces
>> print_line(create_process('./run', args:['$PATH']))
./run
$PATH
>> print_line(create_process('./run', args:["it's $PATH"]))
./run
it's $PATH
>> print_line(create_process('./run', args:['~!@#$%^&*(){`1234567890[]",.\'<>']))
./run
~!@#$%^&*(){`1234567890[]",.'<>
>> print_line(create_process('./run', args:["run&echo"]))
./run
run&echo
>> print_line(create_process('./run', args:["run&echo;test"]))
./run
run&echo;test
>> print_line(create_process('./run file', args:["arg1"]))
./run file
arg1
>> print_line(create_process('./~!@#$%^&*(){}', args:["arg1"]))
./~!@#$%^&*(){}
arg1
>> print_line(cmd_exec('./run', 'something here'))
./run
something
here
>> print_line(cmd_exec('./run', '$PATH'))
./run
/home/smash/.local/bin:/home/smash/.rbenv/shims:/home/smash/.local/bin:/home/smash/.rbenv/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/tools:/opt/apache-maven-3.5.4/bin:/tools:/opt/apache-maven-3.5.4/bin

PHP, new Metasploit, new Meterp

>> print_line(create_process('./run', args:[]))
Usage: ./run [argument1] [argument2] ... [argumentN]
>> print_line(create_process('./run', args:['a','','b','','c']))
./run
a

b

c
>> print_line(create_process('./run', args:['basic','args']))
./run
basic
args
>> print_line(create_process('./run', args:['with spaces']))
./run
with spaces
>> print_line(create_process('./run', args:['$PATH']))
./run
$PATH
>> print_line(create_process('./run', args:["it's $PATH"]))
./run
it's $PATH
>> print_line(create_process('./run', args:['~!@#$%^&*(){`1234567890[]",.\'<>']))
./run
~!@#$%^&*(){`1234567890[]",.'<>
>> print_line(create_process('./run', args:["run&echo"]))
./run
run&echo
>> print_line(create_process('./run', args:["run&echo;test"]))
./run
run&echo;test
>> print_line(create_process('./run file', args:["arg1"]))
./run file
arg1
>> print_line(create_process('./~!@#$%^&*(){}', args:["arg1"]))
./~!@#$%^&*(){}
arg1
>> print_line(cmd_exec('./run', 'something here'))
./run
something
here
>> print_line(cmd_exec('./run', '$PATH'))
./run
/home/smash/.local/bin:/home/smash/.rbenv/shims:/home/smash/.local/bin:/home/smash/.rbenv/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/tools:/opt/apache-maven-3.5.4/bin:/tools:/opt/apache-maven-3.5.4/bin

PHP < 7.4, new Metasploit, new Meterp

>> print_line(create_process('./run', args:[]))

Usage: ./run [argument1] [argument2] ... [argumentN]
>> print_line(create_process('./run', args:['a','','b','','c']))
./run
a

b

c
>> print_line(create_process('./run', args:['basic','args']))
./run
basic
args
>> print_line(create_process('./run', args:['with spaces']))
./run
with spaces
>> print_line(create_process('./run', args:['$PATH']))
./run
$PATH
>> print_line(create_process('./run', args:["it's $PATH"]))
./run
it's $PATH
>> print_line(create_process('./run', args:['~!@#$%^&*(){`1234567890[]",.\'<>']))
./run
~!@#$%^&*(){`1234567890[]",.'<>
>> print_line(create_process('./run', args:["run&echo"]))
./run
run&echo
>> print_line(create_process('./run', args:["run&echo;test"]))
./run
run&echo;test
>> print_line(create_process('./run file', args:["arg1"]))
./run file
arg1
>> print_line(create_process('./~!@#$%^&*(){}', args:["arg1"]))
./~!@#$%^&*(){}
arg1
>> print_line(cmd_exec('./run', 'something here'))
./run
something
here
>> print_line(cmd_exec('./run', '$PATH'))
./run
/home/smash/.local/bin:/home/smash/.rbenv/shims:/home/smash/.local/bin:/home/smash/.rbenv/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/tools:/opt/apache-maven-3.5.4/bin:/tools:/opt/apache-maven-3.5.4/bin

Windows, Command shell

>> print_line(create_process('showargs.exe'))

>> print_line(create_process("%showargs'.exe", args:['', '', '']))



>> print_line(create_process('showargs.exe', args:['basic','args']))
basic
args
>> print_line(create_process('showargs.exe', args:['with space']))
with space
>> print_line(create_process('show args.exe', args:['with space']))
with space
>> print_line(create_process("%showargs'.exe", args:['with space']))
with space
>> print_line(create_process('showargs.exe', args:['%PATH%']))
%PATH%
>>
>> print_line(cmd_exec('"show args.exe"', 'test words "with space"'))
test
words
with space
>> print_line(cmd_exec('showargs.exe', 'test words'))
test
words
>> print_line(cmd_exec('showargs.exe', '%PATH%'))
C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;

Linux, Command shell

>> print_line(create_process('./run', args:[]))

Usage: ./run [argument1] [argument2] ... [argumentN]
>> print_line(create_process('./run', args:['basic','args']))
./run
basic
args
>> print_line(create_process('./run', args:['with spaces']))
./run
with spaces
>> print_line(create_process('./run', args:['$PATH']))
./run
$PATH
>> print_line(create_process('./run', args:["it's $PATH"]))
./run
it's $PATH
>> print_line(create_process('./run', args:['~!@#$%^&*(){`1234567890[]",.\'<>']))
./run
~!@#$%^&*(){`1234567890[]",.'<>
>> print_line(create_process('./run', args:["run&echo"]))
./run
run&echo
>> print_line(create_process('./run', args:["run&echo;test"]))
./run
run&echo;test
>> print_line(create_process('./run file', args:["arg1"]))
./run file
arg1
>> print_line(create_process('./~!@#$%^&*(){}', args:["arg1"]))
./~!@#$%^&*(){}
arg1
>> print_line(cmd_exec('./run', 'something here'))
./run
something
here
>> print_line(cmd_exec('./run', '$PATH'))
./run
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

PowerShell

>> print_line(create_process('.\\showargs.exe'))

>> print_line(create_process(".\\%showargs'.exe", args:['', '', '']))



>> print_line(create_process('.\\showargs.exe', args:['basic','args']))
basic
args
>> print_line(create_process('.\\showargs.exe', args:['with space']))
with space
>> print_line(create_process('.\\show args.exe', args:['with space']))
with space
>> print_line(create_process(".\\%showargs'.exe", args:['with space']))
with space
>> print_line(create_process('.\\showargs.exe', args:['$env:path']))
$env:path

>> print_line(cmd_exec('& ".\show args.exe"', 'test words "with space"'))
test
words
with space
>> print_line(cmd_exec('.\showargs.exe', 'test words'))
test
words
>> print_line(cmd_exec('.\showargs.exe', '$env:PATH'))
C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;

@adfoster-r7
Copy link
Contributor

So we actually have a set of integration tests now that run through the meterpreter test suite on multiple different host environments, i.e. windows/ubuntu/osx, so potentially we could update these tests:

https://github.com/rapid7/metasploit-framework/blob/b607c70611b2427ff8cb277acbcb4c8233300f7d/test/modules/post/test/cmd_exec.rb

And it should automatically run through all of the meterpreters on different runtimes - which would give more confidence that things will work beyond just the unit tests that have been added

# @option Subshell [Boolean] Execute process in a subshell
# @option Pty [Boolean] Execute process in a pty (if available)
# @option ParentId [Integer] Spoof the parent PID (if possible)
# @option InMemory [Boolean,String] Execute from memory (`path` is treated as a local file to upload, and the actual path passed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems strange InMemory could be both a String and a Boolean, this could lead to some confusion. Does it need to be a String?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely agree that it's strange. For clarity, I didn't change the expected types - I'm just commenting the existing (admittedly confusing) behaviour.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants