Skip to content

Commit 47a06d2

Browse files
committed
add library inputs as a source, and get minimal test to work
1 parent 7fcc548 commit 47a06d2

File tree

11 files changed

+97
-4
lines changed

11 files changed

+97
-4
lines changed

python/ql/lib/semmle/python/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ private import semmle.python.frameworks.Simplejson
5151
private import semmle.python.frameworks.SqlAlchemy
5252
private import semmle.python.frameworks.Starlette
5353
private import semmle.python.frameworks.Stdlib
54+
private import semmle.python.frameworks.Setuptools
5455
private import semmle.python.frameworks.Toml
5556
private import semmle.python.frameworks.Tornado
5657
private import semmle.python.frameworks.Twisted
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Provides classes modeling package setup as defined by `setuptools`.
3+
*/
4+
5+
private import python
6+
private import semmle.python.dataflow.new.DataFlow
7+
8+
/** Provides models for the use of `setuptools` in setup scripts, and the APIs exported by the library defined using `setuptools`. */
9+
module Setuptools {
10+
/**
11+
* Gets a file that sets up a package using `setuptools` (or the deprecated `distutils`).
12+
*/
13+
private File setupFile() {
14+
// all of these might not be extracted, but the support is ready for when they are
15+
result.getBaseName() = ["setup.py", "setup.cfg", "pyproject.toml"]
16+
}
17+
18+
/**
19+
* Gets a file or folder that is exported by a library.
20+
*/
21+
private Container getALibraryExportedContainer() {
22+
result = setupFile().getParent()
23+
or
24+
// child of a library exported container
25+
result = getALibraryExportedContainer().getAChildContainer() and
26+
(
27+
// either any file
28+
not result instanceof Folder
29+
or
30+
// or a folder with an __init__.py file
31+
exists(result.(Folder).getFile("__init__.py"))
32+
) and
33+
// that is not a test folder
34+
not result.(Folder).getBaseName() = ["test", "tests", "testing"]
35+
}
36+
37+
/**
38+
* Gets an AST node that is exported by a library.
39+
*/
40+
private AstNode getAnExportedLibraryFeature() {
41+
result.(Module).getFile() = getALibraryExportedContainer()
42+
or
43+
result = getAnExportedLibraryFeature().(Module).getAStmt()
44+
or
45+
result = getAnExportedLibraryFeature().(ClassDef).getDefinedClass().getAMethod()
46+
or
47+
result = getAnExportedLibraryFeature().(ClassDef).getDefinedClass().getInitMethod()
48+
or
49+
result = getAnExportedLibraryFeature().(FunctionDef).getDefinedFunction()
50+
}
51+
52+
/**
53+
* Gets a public function (or __init__) that is exported by a library.
54+
*/
55+
private Function getAnExportedFunction() {
56+
result = getAnExportedLibraryFeature() and
57+
(
58+
result.isPublic()
59+
or
60+
result.isInitMethod()
61+
)
62+
}
63+
64+
/**
65+
* Gets a parameter to a public function that is exported by a library.
66+
*/
67+
DataFlow::ParameterNode getALibraryInput() {
68+
result.getParameter() = getAnExportedFunction().getAnArg() and
69+
not result.getParameter().isSelf()
70+
}
71+
}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@ module UnsafeShellCommandConstruction {
1717
/** A source for shell command constructed from library input vulnerabilities. */
1818
abstract class Source extends DataFlow::Node { }
1919

20+
private import semmle.python.frameworks.Setuptools
21+
2022
/** An input parameter to a gem seen as a source. */
2123
private class LibraryInputAsSource extends Source instanceof DataFlow::ParameterNode {
22-
LibraryInputAsSource() {
23-
none() // TODO: Do something here, put it in a shared library.
24-
}
24+
LibraryInputAsSource() { this = Setuptools::getALibraryInput() }
2525
}
2626

2727
/** A sink for shell command constructed from library input vulnerabilities. */
2828
abstract class Sink extends DataFlow::Node {
29+
Sink() { not this.asExpr() instanceof StrConst } // filter out string constants, makes testing easier
30+
2931
/** Gets a description of how the string in this sink was constructed. */
3032
abstract string describe();
3133

@@ -80,7 +82,6 @@ module UnsafeShellCommandConstruction {
8082
* where the resulting string ends up being executed as a shell command.
8183
*/
8284
class StringConcatAsSink extends Sink {
83-
// TODO: Add test.
8485
Concepts::SystemCommandExecution s;
8586
BinaryExpr add;
8687

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
missingAnnotationOnSink
2+
failures
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import python
2+
import experimental.dataflow.TestUtil.DataflowQueryTest
3+
import semmle.python.security.dataflow.UnsafeShellCommandConstructionQuery
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
edges
2+
| src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:5:25:5:28 | ControlFlowNode for name |
3+
nodes
4+
| src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
5+
| src/unsafe_shell_test.py:5:25:5:28 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
6+
subpaths
7+
#select
8+
| src/unsafe_shell_test.py:5:15:5:28 | ControlFlowNode for BinaryExpr | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:5:25:5:28 | ControlFlowNode for name | This string concatenation which depends on $@ is later used in a $@. | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | library input | src/unsafe_shell_test.py:5:5:5:29 | ControlFlowNode for Attribute() | shell command |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE-078/UnsafeShellCommandConstruction.ql
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
semmle-extractor-options: --lang=3 --max-import-depth=0 -r src

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

Whitespace-only changes.

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

Whitespace-only changes.

0 commit comments

Comments
 (0)