Skip to content

Commit 6c8cc79

Browse files
committed
v1
1 parent eb3f196 commit 6c8cc79

File tree

16 files changed

+237
-1
lines changed

16 files changed

+237
-1
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ private import semmle.python.frameworks.Dill
2121
private import semmle.python.frameworks.Django
2222
private import semmle.python.frameworks.Fabric
2323
private import semmle.python.frameworks.FastApi
24+
private import semmle.python.frameworks.FileSystemAccess
2425
private import semmle.python.frameworks.Flask
2526
private import semmle.python.frameworks.FlaskAdmin
2627
private import semmle.python.frameworks.FlaskSqlAlchemy
@@ -51,6 +52,7 @@ private import semmle.python.frameworks.Requests
5152
private import semmle.python.frameworks.RestFramework
5253
private import semmle.python.frameworks.Rsa
5354
private import semmle.python.frameworks.RuamelYaml
55+
private import semmle.python.frameworks.Sanic
5456
private import semmle.python.frameworks.ServerLess
5557
private import semmle.python.frameworks.Simplejson
5658
private import semmle.python.frameworks.SqlAlchemy
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the I/O write or read operations
3+
*/
4+
5+
private import python
6+
private import semmle.python.dataflow.new.DataFlow
7+
private import semmle.python.dataflow.new.RemoteFlowSources
8+
private import semmle.python.dataflow.new.TaintTracking
9+
private import semmle.python.Concepts
10+
private import semmle.python.ApiGraphs
11+
12+
/**
13+
* Provides models for the `aiofile` PyPI package.
14+
* See https://github.com/agronholm/anyio.
15+
*/
16+
private module Aiofile {
17+
/**
18+
* A call to the `async_open` function or `AIOFile` constructor from `aiofile` as a sink for Filesystem access.
19+
*/
20+
class FileResponseCall extends FileSystemAccess::Range, API::CallNode {
21+
string methodName;
22+
23+
FileResponseCall() {
24+
this = API::moduleImport("aiofile").getMember("async_open").getACall() and
25+
methodName = "async_open"
26+
or
27+
this = API::moduleImport("aiofile").getMember("AIOFile").getACall() and
28+
methodName = "AIOFile"
29+
}
30+
31+
override DataFlow::Node getAPathArgument() {
32+
result = this.getParameter(0, "file_specifier").asSink() and
33+
methodName = "async_open"
34+
or
35+
result = this.getParameter(0, "filename").asSink() and
36+
methodName = "AIOFile"
37+
}
38+
}
39+
}
40+
41+
/**
42+
* Provides models for the `aiofiles` PyPI package.
43+
* See https://github.com/Tinche/aiofiles.
44+
*/
45+
private module Aiofiles {
46+
/**
47+
* A call to the `open` function from `aiofiles` as a sink for Filesystem access.
48+
*/
49+
class FileResponseCall extends FileSystemAccess::Range, API::CallNode {
50+
FileResponseCall() { this = API::moduleImport("aiofiles").getMember("open").getACall() }
51+
52+
override DataFlow::Node getAPathArgument() { result = this.getParameter(0, "file").asSink() }
53+
}
54+
}
55+
56+
/**
57+
* Provides models for the `anyio` PyPI package.
58+
* See https://github.com/agronholm/anyio.
59+
*/
60+
private module Anyio {
61+
/**
62+
* A call to the `from_path` function from `FileReadStream` or `FileWriteStream` constructors of `anyio.streams.file` as a sink for Filesystem access.
63+
*/
64+
class FileStreamCall extends FileSystemAccess::Range, API::CallNode {
65+
FileStreamCall() {
66+
this =
67+
API::moduleImport("anyio")
68+
.getMember("streams")
69+
.getMember("file")
70+
.getMember(["FileReadStream", "FileWriteStream"])
71+
.getMember("from_path")
72+
.getACall()
73+
}
74+
75+
override DataFlow::Node getAPathArgument() { result = this.getParameter(0, "path").asSink() }
76+
}
77+
78+
/**
79+
* A call to the `Path` constructor from `anyio` as a sink for Filesystem access.
80+
*/
81+
class PathCall extends FileSystemAccess::Range, API::CallNode {
82+
PathCall() { this = API::moduleImport("anyio").getMember("Path").getACall() }
83+
84+
override DataFlow::Node getAPathArgument() { result = this.getParameter(0).asSink() }
85+
}
86+
87+
/**
88+
* A call to the `open_file` function from `anyio` as a sink for Filesystem access.
89+
*/
90+
class OpenFileCall extends FileSystemAccess::Range, API::CallNode {
91+
OpenFileCall() { this = API::moduleImport("anyio").getMember("open_file").getACall() }
92+
93+
override DataFlow::Node getAPathArgument() { result = this.getParameter(0, "file").asSink() }
94+
}
95+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `sanic` PyPI package.
3+
* See https://sanic.dev/.
4+
*/
5+
6+
private import python
7+
private import semmle.python.dataflow.new.DataFlow
8+
private import semmle.python.dataflow.new.RemoteFlowSources
9+
private import semmle.python.dataflow.new.TaintTracking
10+
private import semmle.python.Concepts
11+
private import semmle.python.ApiGraphs
12+
13+
/**
14+
* Provides models for the `sanic` PyPI package.
15+
* See https://sanic.dev/.
16+
*/
17+
private module Sanic {
18+
/**
19+
* Provides models for Sanic applications (an instance of `sanic.Sanic`).
20+
*/
21+
module App {
22+
/** Gets a reference to a Sanic application (an instance of `sanic.Sanic`). */
23+
API::Node instance() { result = API::moduleImport("sanic").getMember("Sanic").getReturn() }
24+
}
25+
26+
/**
27+
* A call to the `file` or `file_stream` functions of `sanic.response` as a sink for Filesystem access.
28+
*/
29+
class FileResponseCall extends FileSystemAccess::Range, API::CallNode {
30+
FileResponseCall() {
31+
this =
32+
API::moduleImport("sanic")
33+
.getMember("response")
34+
.getMember(["file", "file_stream"])
35+
.getACall()
36+
}
37+
38+
override DataFlow::Node getAPathArgument() {
39+
result = this.getParameter(0, "location").asSink()
40+
}
41+
}
42+
}

python/ql/lib/semmle/python/frameworks/Starlette.qll

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,31 @@ module Starlette {
163163

164164
/** DEPRECATED: Alias for Url */
165165
deprecated module URL = Url;
166+
167+
/**
168+
* A call to the `starlette.responses.FileResponse` constructor as a sink for Filesystem access.
169+
*/
170+
class FileResponseCall extends FileSystemAccess::Range, API::CallNode {
171+
FileResponseCall() {
172+
this =
173+
API::moduleImport("starlette").getMember("responses").getMember("FileResponse").getACall()
174+
}
175+
176+
override DataFlow::Node getAPathArgument() { result = this.getParameter(0, "path").asSink() }
177+
}
178+
179+
/**
180+
* A call to the `baize.asgi.FileResponse` constructor as a sink for Filesystem access.
181+
*
182+
* it is not contained to Starlette source code but it is mentioned as an alternative to Starlette FileResponse
183+
*/
184+
class BaizeFileResponseCall extends FileSystemAccess::Range, API::CallNode {
185+
BaizeFileResponseCall() {
186+
this = API::moduleImport("baize").getMember("asgi").getMember("FileResponse").getACall()
187+
}
188+
189+
override DataFlow::Node getAPathArgument() {
190+
result = this.getParameter(0, "filepath").asSink()
191+
}
192+
}
166193
}

python/ql/lib/semmle/python/frameworks/Stdlib.qll

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1479,6 +1479,26 @@ private module StdlibPrivate {
14791479
}
14801480
}
14811481

1482+
/**
1483+
* A call to the `io.FileIO` constructor.
1484+
* See https://docs.python.org/3/library/io.html#io.FileIO
1485+
*/
1486+
private class FileIOCall extends FileSystemAccess::Range, API::CallNode {
1487+
FileIOCall() { this = API::moduleImport("io").getMember("FileIO").getACall() }
1488+
1489+
override DataFlow::Node getAPathArgument() { result = this.getParameter(0, "file").asSink() }
1490+
}
1491+
1492+
/**
1493+
* A call to the `io.open_code` function.
1494+
* See https://docs.python.org/3/library/io.html#io.FileIO
1495+
*/
1496+
private class OpenCodeCall extends FileSystemAccess::Range, API::CallNode {
1497+
OpenCodeCall() { this = API::moduleImport("io").getMember("open_code").getACall() }
1498+
1499+
override DataFlow::Node getAPathArgument() { result = this.getParameter(0, "path").asSink() }
1500+
}
1501+
14821502
/** Gets a reference to an open file. */
14831503
private DataFlow::TypeTrackingNode openFile(DataFlow::TypeTracker t, FileSystemAccess openCall) {
14841504
t.start() and
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
testFailures
2+
failures
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import python
2+
import experimental.meta.ConceptsTest

python/ql/test/library-tests/frameworks/FileSystemAccess/Query.expected

Whitespace-only changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import python
2+
import semmle.python.dataflow.new.DataFlow
3+
import semmle.python.Concepts
4+
import TestUtilities.InlineExpectationsTest
5+
private import semmle.python.dataflow.new.internal.PrintNode
6+
7+
module FileSystemAccessTest implements TestSig {
8+
string getARelevantTag() { result = "getAPathArgument" }
9+
10+
predicate hasActualResult(Location location, string element, string tag, string value) {
11+
exists(location.getFile().getRelativePath()) and
12+
exists(FileSystemAccess a, DataFlow::Node path |
13+
path = a.getAPathArgument() and
14+
location = a.getLocation() and
15+
element = path.toString() and
16+
value = prettyNodeForInlineTest(path) and
17+
tag = "getAPathArgument"
18+
)
19+
}
20+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from aiofile import async_open, AIOFile
2+
3+
AIOFile("file", 'r') # $ getAPathArgument="file"
4+
async_open("file", "r") # $ getAPathArgument="file"

0 commit comments

Comments
 (0)