Skip to content

Commit da64ea4

Browse files
authored
Merge pull request #13782 from jorgectf/jorgectf/shlex-quote
Python: Add `shlex.quote` as `py/shell-command-constructed-from-input` sanitizer
2 parents 10548b5 + 8f8c064 commit da64ea4

File tree

4 files changed

+24
-2
lines changed

4 files changed

+24
-2
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Added `shlex.quote` as a sanitizer for the `py/shell-command-constructed-from-input` query.

python/ql/lib/semmle/python/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ private import semmle.python.dataflow.new.DataFlow
99
private import semmle.python.dataflow.new.TaintTracking
1010
private import CommandInjectionCustomizations::CommandInjection as CommandInjection
1111
private import semmle.python.Concepts as Concepts
12+
private import semmle.python.ApiGraphs
1213

1314
/**
1415
* Module containing sources, sinks, and sanitizers for shell command constructed from library input.
@@ -17,6 +18,9 @@ module UnsafeShellCommandConstruction {
1718
/** A source for shell command constructed from library input vulnerabilities. */
1819
abstract class Source extends DataFlow::Node { }
1920

21+
/** A sanitizer for shell command constructed from library input vulnerabilities. */
22+
abstract class Sanitizer extends DataFlow::Node { }
23+
2024
private import semmle.python.frameworks.Setuptools
2125

2226
/** An input parameter to a gem seen as a source. */
@@ -156,4 +160,13 @@ module UnsafeShellCommandConstruction {
156160

157161
override DataFlow::Node getStringConstruction() { result = formatCall }
158162
}
163+
164+
/**
165+
* A call to `shlex.quote`, considered as a sanitizer.
166+
*/
167+
class ShlexQuoteAsSanitizer extends Sanitizer, DataFlow::Node {
168+
ShlexQuoteAsSanitizer() {
169+
this = API::moduleImport("shlex").getMember("quote").getACall().getArg(0)
170+
}
171+
}
159172
}

python/ql/lib/semmle/python/security/dataflow/UnsafeShellCommandConstructionQuery.qll

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ class Configuration extends TaintTracking::Configuration {
2424
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
2525

2626
override predicate isSanitizer(DataFlow::Node node) {
27-
node instanceof CommandInjection::Sanitizer // using all sanitizers from `rb/command-injection`
27+
node instanceof Sanitizer or
28+
node instanceof CommandInjection::Sanitizer // using all sanitizers from `py/command-injection`
2829
}
2930

3031
// override to require the path doesn't have unmatched return steps

python/ql/test/query-tests/Security/CWE-078-UnsafeShellCommandConstruction/src/unsafe_shell_test.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,8 @@ def indirect(flag, x):
4646
subprocess.Popen("ping " + name, shell=unknownValue) # OK - shell assumed to be False
4747

4848
def intentional(command):
49-
os.system("fish -ic " + command) # $result=OK - intentional
49+
os.system("fish -ic " + command) # $result=OK - intentional
50+
51+
import shlex
52+
def unsafe_shell_sanitized(name):
53+
os.system("ping " + shlex.quote(name)) # $result=OK - sanitized

0 commit comments

Comments
 (0)