Skip to content

Commit

Permalink
Merge pull request #14406 from amammad/amammad-python-FileSystemAccess
Browse files Browse the repository at this point in the history
Python: New FileSystem Access
  • Loading branch information
RasmusWL authored Nov 16, 2023
2 parents 14268f3 + e349891 commit df144f3
Show file tree
Hide file tree
Showing 33 changed files with 355 additions and 1 deletion.
6 changes: 6 additions & 0 deletions python/ql/lib/semmle/python/Frameworks.qll
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@
// If you add modeling of a new framework/library, remember to add it to the docs in
// `docs/codeql/reusables/supported-frameworks.rst`
private import semmle.python.frameworks.Aioch
private import semmle.python.frameworks.Aiofile
private import semmle.python.frameworks.Aiofiles
private import semmle.python.frameworks.Aiohttp
private import semmle.python.frameworks.Aiomysql
private import semmle.python.frameworks.Aiopg
private import semmle.python.frameworks.Aiosqlite
private import semmle.python.frameworks.Anyio
private import semmle.python.frameworks.Asyncpg
private import semmle.python.frameworks.Baize
private import semmle.python.frameworks.BSon
private import semmle.python.frameworks.CassandraDriver
private import semmle.python.frameworks.Cherrypy
private import semmle.python.frameworks.ClickhouseDriver
private import semmle.python.frameworks.Cryptodome
private import semmle.python.frameworks.Cryptography
Expand Down Expand Up @@ -54,6 +59,7 @@ private import semmle.python.frameworks.Requests
private import semmle.python.frameworks.RestFramework
private import semmle.python.frameworks.Rsa
private import semmle.python.frameworks.RuamelYaml
private import semmle.python.frameworks.Sanic
private import semmle.python.frameworks.ServerLess
private import semmle.python.frameworks.Setuptools
private import semmle.python.frameworks.Simplejson
Expand Down
42 changes: 42 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Aiofile.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Provides classes modeling security-relevant aspects of the `aiofile` PyPI package.
*
* See https://pypi.org/project/aiofile.
*/

private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs

/**
* Provides models for the `aiofile` PyPI package.
*
* See https://pypi.org/project/aiofile.
*/
private module Aiofile {
/**
* A call to the `async_open` function or `AIOFile` constructor from `aiofile` as a sink for Filesystem access.
*/
class FileResponseCall extends FileSystemAccess::Range, API::CallNode {
string methodName;

FileResponseCall() {
this = API::moduleImport("aiofile").getMember("async_open").getACall() and
methodName = "async_open"
or
this = API::moduleImport("aiofile").getMember("AIOFile").getACall() and
methodName = "AIOFile"
}

override DataFlow::Node getAPathArgument() {
result = this.getParameter(0, "file_specifier").asSink() and
methodName = "async_open"
or
result = this.getParameter(0, "filename").asSink() and
methodName = "AIOFile"
}
}
}
28 changes: 28 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Aiofiles.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Provides classes modeling security-relevant aspects of the `aiofiles` PyPI package.
*
* See https://pypi.org/project/aiofiles.
*/

private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs

/**
* Provides models for the `aiofiles` PyPI package.
*
* See https://pypi.org/project/aiofiles.
*/
private module Aiofiles {
/**
* A call to the `open` function from `aiofiles` as a sink for Filesystem access.
*/
class FileResponseCall extends FileSystemAccess::Range, API::CallNode {
FileResponseCall() { this = API::moduleImport("aiofiles").getMember("open").getACall() }

override DataFlow::Node getAPathArgument() { result = this.getParameter(0, "file").asSink() }
}
}
54 changes: 54 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Anyio.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Provides classes modeling security-relevant aspects of the `anyio` PyPI package.
*
* See https://pypi.org/project/anyio.
*/

private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs

/**
* Provides models for the `anyio` PyPI package.
*
* See https://pypi.org/project/anyio.
*/
private module Anyio {
/**
* A call to the `from_path` function from `FileReadStream` or `FileWriteStream` constructors of `anyio.streams.file` as a sink for Filesystem access.
*/
class FileStreamCall extends FileSystemAccess::Range, API::CallNode {
FileStreamCall() {
this =
API::moduleImport("anyio")
.getMember("streams")
.getMember("file")
.getMember(["FileReadStream", "FileWriteStream"])
.getMember("from_path")
.getACall()
}

override DataFlow::Node getAPathArgument() { result = this.getParameter(0, "path").asSink() }
}

/**
* A call to the `Path` constructor from `anyio` as a sink for Filesystem access.
*/
class PathCall extends FileSystemAccess::Range, API::CallNode {
PathCall() { this = API::moduleImport("anyio").getMember("Path").getACall() }

override DataFlow::Node getAPathArgument() { result = this.getParameter(0).asSink() }
}

/**
* A call to the `open_file` function from `anyio` as a sink for Filesystem access.
*/
class OpenFileCall extends FileSystemAccess::Range, API::CallNode {
OpenFileCall() { this = API::moduleImport("anyio").getMember("open_file").getACall() }

override DataFlow::Node getAPathArgument() { result = this.getParameter(0, "file").asSink() }
}
}
35 changes: 35 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Baize.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Provides classes modeling security-relevant aspects of the `baize` PyPI package.
*
* See https://pypi.org/project/baize.
*/

private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
private import semmle.python.frameworks.Stdlib

/**
* Provides models for `baize` PyPI package.
*
* See https://pypi.org/project/baize.
*/
module Baize {
/**
* A call to the `baize.asgi.FileResponse` constructor as a sink for Filesystem access.
*
* it is not contained to Starlette source code but it is mentioned in documents as an alternative to Starlette FileResponse
*/
class BaizeFileResponseCall extends FileSystemAccess::Range, API::CallNode {
BaizeFileResponseCall() {
this = API::moduleImport("baize").getMember("asgi").getMember("FileResponse").getACall()
}

override DataFlow::Node getAPathArgument() {
result = this.getParameter(0, "filepath").asSink()
}
}
}
48 changes: 48 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Cherrypy.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Provides classes modeling security-relevant aspects of the `cherrypy` PyPI package.
*/

private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs

/**
* Provides models for the `cherrypy` PyPI package.
* See https://cherrypy.dev/.
*/
private module Cherrypy {
/**
* Holds for an instance of `cherrypy.lib.static`
*/
API::Node libStatic() {
result = API::moduleImport("cherrypy").getMember("lib").getMember("static")
}

/**
* A call to the `serve_file` or `serve_download`or `staticfile` functions of `cherrypy.lib.static` as a sink for Filesystem access.
*/
class FileResponseCall extends FileSystemAccess::Range, API::CallNode {
string funcName;

FileResponseCall() {
this = libStatic().getMember("staticfile").getACall() and
funcName = "staticfile"
or
this = libStatic().getMember("serve_file").getACall() and
funcName = "serve_file"
or
this = libStatic().getMember("serve_download").getACall() and
funcName = "serve_download"
}

override DataFlow::Node getAPathArgument() {
result = this.getParameter(0, "path").asSink() and funcName = ["serve_download", "serve_file"]
or
result = this.getParameter(0, "filename").asSink() and
funcName = "staticfile"
}
}
}
42 changes: 42 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Sanic.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Provides classes modeling security-relevant aspects of the `sanic` PyPI package.
* See https://sanic.dev/.
*/

private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs

/**
* Provides models for the `sanic` PyPI package.
* See https://sanic.dev/.
*/
private module Sanic {
/**
* Provides models for Sanic applications (an instance of `sanic.Sanic`).
*/
module App {
/** Gets a reference to a Sanic application (an instance of `sanic.Sanic`). */
API::Node instance() { result = API::moduleImport("sanic").getMember("Sanic").getReturn() }
}

/**
* A call to the `file` or `file_stream` functions of `sanic.response` as a sink for Filesystem access.
*/
class FileResponseCall extends FileSystemAccess::Range, API::CallNode {
FileResponseCall() {
this =
API::moduleImport("sanic")
.getMember("response")
.getMember(["file", "file_stream"])
.getACall()
}

override DataFlow::Node getAPathArgument() {
result = this.getParameter(0, "location").asSink()
}
}
}
12 changes: 12 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Starlette.qll
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,16 @@ module Starlette {

/** DEPRECATED: Alias for Url */
deprecated module URL = Url;

/**
* A call to the `starlette.responses.FileResponse` constructor as a sink for Filesystem access.
*/
class FileResponseCall extends FileSystemAccess::Range, API::CallNode {
FileResponseCall() {
this =
API::moduleImport("starlette").getMember("responses").getMember("FileResponse").getACall()
}

override DataFlow::Node getAPathArgument() { result = this.getParameter(0, "path").asSink() }
}
}
20 changes: 20 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Stdlib.qll
Original file line number Diff line number Diff line change
Expand Up @@ -1479,6 +1479,26 @@ private module StdlibPrivate {
}
}

/**
* A call to the `io.FileIO` constructor.
* See https://docs.python.org/3/library/io.html#io.FileIO
*/
private class FileIOCall extends FileSystemAccess::Range, API::CallNode {
FileIOCall() { this = API::moduleImport("io").getMember("FileIO").getACall() }

override DataFlow::Node getAPathArgument() { result = this.getParameter(0, "file").asSink() }
}

/**
* A call to the `io.open_code` function.
* See https://docs.python.org/3.11/library/io.html#io.open_code
*/
private class OpenCodeCall extends FileSystemAccess::Range, API::CallNode {
OpenCodeCall() { this = API::moduleImport("io").getMember("open_code").getACall() }

override DataFlow::Node getAPathArgument() { result = this.getParameter(0, "path").asSink() }
}

/** Gets a reference to an open file. */
private DataFlow::TypeTrackingNode openFile(DataFlow::TypeTracker t, FileSystemAccess openCall) {
t.start() and
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added modeling of more `FileSystemAccess` in packages `cherrypy`, `aiofile`, `aiofiles`, `anyio`, `sanic`, `starlette`, `baize`, and `io`. This will mainly affect the _Uncontrolled data used in path expression_ (`py/path-injection`) query.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
testFailures
failures
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from aiofile import async_open, AIOFile

AIOFile("file", 'r') # $ getAPathArgument="file"
async_open("file", "r") # $ getAPathArgument="file"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
testFailures
failures
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import aiofiles

aiofiles.open("file", mode='r') # $ getAPathArgument="file"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
testFailures
failures
2 changes: 2 additions & 0 deletions python/ql/test/library-tests/frameworks/anyio/ConceptsTest.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import anyio
from anyio.streams.file import FileReadStream, FileWriteStream
from anyio import Path

anyio.open_file("file", 'r') # $ getAPathArgument="file"
FileReadStream.from_path("file") # $ getAPathArgument="file"
FileWriteStream.from_path("file") # $ getAPathArgument="file"
Path("file") # $ getAPathArgument="file"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
testFailures
failures
2 changes: 2 additions & 0 deletions python/ql/test/library-tests/frameworks/baize/ConceptsTest.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from baize.asgi import FileResponse as baizeFileResponse

baizeFileResponse("file") # $ getAPathArgument="file"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
testFailures
failures
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import cherrypy
from cherrypy.lib.static import serve_file, serve_download, staticfile

serve_file("file") # $ getAPathArgument="file"
serve_download("file") # $ getAPathArgument="file"
staticfile("file") # $ getAPathArgument="file"
# root won't make this safe
staticfile("file", root="/path/to/safe/dir") # $ getAPathArgument="file"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
testFailures
failures
2 changes: 2 additions & 0 deletions python/ql/test/library-tests/frameworks/sanic/ConceptsTest.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest
Loading

0 comments on commit df144f3

Please sign in to comment.