Skip to content

Commit

Permalink
add support for shell injection on subprocess
Browse files Browse the repository at this point in the history
  • Loading branch information
christophe-papazian committed Jan 9, 2025
1 parent 428537f commit 1e3c3bf
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 12 deletions.
2 changes: 1 addition & 1 deletion ddtrace/appsec/_common_module_patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ def popen_FD233052260D8B4D(arg_list: Union[List[str], str]) -> None:

if in_asm_context():
res = call_waf_callback(
{EXPLOIT_PREVENTION.ADDRESS.CMDI: arg_list},
{EXPLOIT_PREVENTION.ADDRESS.CMDI: arg_list if isinstance(arg_list, list) else [arg_list]},
crop_trace="popen_FD233052260D8B4D",
rule_type=EXPLOIT_PREVENTION.TYPE.CMDI,
)
Expand Down
8 changes: 6 additions & 2 deletions ddtrace/contrib/internal/subprocess/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,12 @@ def _traced_subprocess_init(module, pin, wrapped, instance, args, kwargs):
try:
cmd_args = args[0] if len(args) else kwargs["args"]
if isinstance(cmd_args, (list, tuple, str)):
for callback in _LST_CALLBACKS.values():
callback(cmd_args)
if kwargs.get("shell", False):
for callback in _STR_CALLBACKS.values():
callback(cmd_args)
else:
for callback in _LST_CALLBACKS.values():
callback(cmd_args)
cmd_args_list = shlex.split(cmd_args) if isinstance(cmd_args, str) else cmd_args
is_shell = kwargs.get("shell", False)
shellcmd = SubprocessCmdLine(cmd_args_list, shell=is_shell) # nosec
Expand Down
13 changes: 11 additions & 2 deletions tests/appsec/contrib_appsec/django_app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,20 +136,29 @@ def rasp(request, endpoint: str):
if param.startswith("cmd"):
cmd = query_params[param]
try:
res.append(f'cmd stdout: {os.system(f"ls {cmd}")}')
if param.startswith("cmdsys"):
res.append(f'cmd stdout: {os.system(f"ls {cmd}")}')

Check failure

Code scanning / CodeQL

Uncontrolled command line Critical test

This command line depends on a
user-provided value
.
else:
res.append(f'cmd stdout: {subprocess.run(f"ls {cmd}", shell=True)}')

Check failure

Code scanning / CodeQL

Uncontrolled command line Critical test

This command line depends on a
user-provided value
.
except Exception as e:
res.append(f"Error: {e}")
tracer.current_span()._local_root.set_tag("rasp.request.done", endpoint)
return HttpResponse("<\\br>\n".join(res))
elif endpoint == "command_injection":
res = ["command_injection endpoint"]
for param in query_params:
if param.startswith("cmd"):
if param.startswith("cmda"):
cmd = query_params[param]
try:
res.append(f'cmd stdout: {subprocess.run([cmd, "-c", "3", "localhost"])}')
except Exception as e:
res.append(f"Error: {e}")
elif param.startswith("cmds"):
cmd = query_params[param]
try:
res.append(f"cmd stdout: {subprocess.run(cmd)}")

Check failure

Code scanning / CodeQL

Uncontrolled command line Critical test

This command line depends on a
user-provided value
.
except Exception as e:
res.append(f"Error: {e}")
tracer.current_span()._local_root.set_tag("rasp.request.done", endpoint)
return HttpResponse("<\\br>\n".join(res))
tracer.current_span()._local_root.set_tag("rasp.request.done", endpoint)
Expand Down
13 changes: 11 additions & 2 deletions tests/appsec/contrib_appsec/fastapi_app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,20 +185,29 @@ async def rasp(endpoint: str, request: Request):
if param.startswith("cmd"):
cmd = query_params[param]
try:
res.append(f'cmd stdout: {os.system(f"ls {cmd}")}')
if param.startswith("cmdsys"):
res.append(f'cmd stdout: {os.system(f"ls {cmd}")}')
else:
res.append(f'cmd stdout: {subprocess.run(f"ls {cmd}", shell=True)}')
except Exception as e:
res.append(f"Error: {e}")
tracer.current_span()._local_root.set_tag("rasp.request.done", endpoint)
return HTMLResponse("<\\br>\n".join(res))
elif endpoint == "command_injection":
res = ["command_injection endpoint"]
for param in query_params:
if param.startswith("cmd"):
if param.startswith("cmda"):
cmd = query_params[param]
try:
res.append(f'cmd stdout: {subprocess.run([cmd, "-c", "3", "localhost"])}')
except Exception as e:
res.append(f"Error: {e}")
elif param.startswith("cmds"):
cmd = query_params[param]
try:
res.append(f"cmd stdout: {subprocess.run(cmd)}")
except Exception as e:
res.append(f"Error: {e}")
tracer.current_span()._local_root.set_tag("rasp.request.done", endpoint)
return HTMLResponse("<\\br>\n".join(res))
tracer.current_span()._local_root.set_tag("rasp.request.done", endpoint)
Expand Down
13 changes: 11 additions & 2 deletions tests/appsec/contrib_appsec/flask_app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,20 +133,29 @@ def rasp(endpoint: str):
if param.startswith("cmd"):
cmd = query_params[param]
try:
res.append(f'cmd stdout: {os.system(f"ls {cmd}")}')
if param.startswith("cmdsys"):
res.append(f'cmd stdout: {os.system(f"ls {cmd}")}')

Check failure

Code scanning / CodeQL

Uncontrolled command line Critical test

This command line depends on a
user-provided value
.
else:
res.append(f'cmd stdout: {subprocess.run(f"ls {cmd}", shell=True)}')

Check failure

Code scanning / CodeQL

Uncontrolled command line Critical test

This command line depends on a
user-provided value
.
except Exception as e:
res.append(f"Error: {e}")
tracer.current_span()._local_root.set_tag("rasp.request.done", endpoint)
return "<\\br>\n".join(res)
elif endpoint == "command_injection":
res = ["command_injection endpoint"]
for param in query_params:
if param.startswith("cmd"):
if param.startswith("cmda"):
cmd = query_params[param]
try:
res.append(f'cmd stdout: {subprocess.run([cmd, "-c", "3", "localhost"])}')
except Exception as e:
res.append(f"Error: {e}")
elif param.startswith("cmds"):
cmd = query_params[param]
try:
res.append(f"cmd stdout: {subprocess.run(cmd)}")

Check failure

Code scanning / CodeQL

Uncontrolled command line Critical test

This command line depends on a
user-provided value
.
except Exception as e:
res.append(f"Error: {e}")
tracer.current_span()._local_root.set_tag("rasp.request.done", endpoint)
return "<\\br>\n".join(res)
tracer.current_span()._local_root.set_tag("rasp.request.done", endpoint)
Expand Down
6 changes: 3 additions & 3 deletions tests/appsec/contrib_appsec/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1309,15 +1309,15 @@ def test_stream_response(
+ [
(
"shell_injection",
"cmd_1=$(cat /etc/passwd 1>%262 ; echo .)&cmd_2=$(uname -a 1>%262 ; echo .)",
"cmdsys_1=$(cat /etc/passwd 1>%262 ; echo .)&cmdrun_2=$(uname -a 1>%262 ; echo .)",
"rasp-932-100",
("system", "rasp"),
)
]
+ [
(
"command_injection",
"cmd_1=/sbin/ping&cmd_2=/usr/bin/ls",
"cmda_1=/sbin/ping&cmds_2=/usr/bin/ls%20-la",
"rasp-932-110",
("Popen", "rasp"),
)
Expand Down Expand Up @@ -1529,7 +1529,7 @@ def test_fingerprinting(self, interface, root_span, get_tag, asm_enabled, user_a
def test_iast(self, interface, root_span, get_tag):
from ddtrace.ext import http

url = "/rasp/command_injection/?cmd=."
url = "/rasp/command_injection/?cmds=."
self.update_tracer(interface)
response = interface.client.get(url)
assert self.status(response) == 200
Expand Down

0 comments on commit 1e3c3bf

Please sign in to comment.