Python: Model wsgiref.simple_server applications

This commit is contained in:
Rasmus Wriedt Larsen
2021-11-15 11:28:13 +01:00
parent e812029c03
commit 6eb4525ab2
3 changed files with 167 additions and 8 deletions

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* Added modeling of `wsgiref.simple_server` applications, leading to new remote flow sources.

View File

@@ -10,6 +10,8 @@ private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.PEP249
private import semmle.python.frameworks.internal.PoorMansFunctionResolution
private import semmle.python.frameworks.internal.SelfRefMixin
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
/** Provides models for the Python standard library. */
@@ -1316,6 +1318,161 @@ private module StdlibPrivate {
}
}
// ---------------------------------------------------------------------------
// wsgiref.simple_server
// ---------------------------------------------------------------------------
/** Provides models for the `wsgiref.simple_server` module. */
private module WsgirefSimpleServer {
class WsgiServerSubclass extends Class, SelfRefMixin {
WsgiServerSubclass() {
this.getABase() =
API::moduleImport("wsgiref")
.getMember("simple_server")
.getMember("WSGIServer")
.getASubclass*()
.getAUse()
.asExpr()
}
}
/**
* A function that was passed to the `set_app` method of a
* `wsgiref.simple_server.WSGIServer` instance.
*
* See https://docs.python.org/3.10/library/wsgiref.html#wsgiref.simple_server.WSGIServer.set_app
*
* See https://github.com/python/cpython/blob/b567b9d74bd9e476a3027335873bb0508d6e450f/Lib/wsgiref/handlers.py#L137
* for how a request is processed and given to an application.
*/
class WsgirefSimpleServerApplication extends HTTP::Server::RequestHandler::Range {
WsgirefSimpleServerApplication() {
exists(DataFlow::Node appArg, DataFlow::CallCfgNode setAppCall |
(
setAppCall =
API::moduleImport("wsgiref")
.getMember("simple_server")
.getMember("WSGIServer")
.getASubclass*()
.getReturn()
.getMember("set_app")
.getACall()
or
setAppCall
.(DataFlow::MethodCallNode)
.calls(any(WsgiServerSubclass cls).getASelfRef(), "set_app")
) and
appArg in [setAppCall.getArg(0), setAppCall.getArgByName("application")]
|
appArg = poorMansFunctionTracker(this)
)
}
override Parameter getARoutedParameter() { none() }
override string getFramework() { result = "Stdlib: wsgiref.simple_server application" }
}
/**
* The parameter of a `WsgirefSimpleServerApplication` that takes the WSGI environment
* when processing a request.
*
* See https://docs.python.org/3.10/library/wsgiref.html#wsgiref.simple_server.WSGIRequestHandler.get_environ
*/
class WSGIEnvirontParameter extends RemoteFlowSource::Range, DataFlow::ParameterNode {
WSGIEnvirontParameter() {
exists(WsgirefSimpleServerApplication func |
if func.isMethod()
then this.getParameter() = func.getArg(1)
else this.getParameter() = func.getArg(0)
)
}
override string getSourceType() {
result = "Stdlib: wsgiref.simple_server application: WSGI environment parameter"
}
}
/**
* Gets a reference to the parameter of a `WsgirefSimpleServerApplication` that
* takes the `start_response` function.
*
* See https://github.com/python/cpython/blob/b567b9d74bd9e476a3027335873bb0508d6e450f/Lib/wsgiref/handlers.py#L225-L252
*/
private DataFlow::TypeTrackingNode startResponse(DataFlow::TypeTracker t) {
t.start() and
exists(WsgirefSimpleServerApplication func |
if func.isMethod()
then result.(DataFlow::ParameterNode).getParameter() = func.getArg(2)
else result.(DataFlow::ParameterNode).getParameter() = func.getArg(1)
)
or
exists(DataFlow::TypeTracker t2 | result = startResponse(t2).track(t2, t))
}
/**
* Gets a reference to the parameter of a `WsgirefSimpleServerApplication` that
* takes the `start_response` function.
*
* See https://github.com/python/cpython/blob/b567b9d74bd9e476a3027335873bb0508d6e450f/Lib/wsgiref/handlers.py#L225-L252
*/
DataFlow::Node startResponse() { startResponse(DataFlow::TypeTracker::end()).flowsTo(result) }
/**
* Gets a reference to the `write` function (that will write data to the response),
* which is the return value from calling the `start_response` function.
*
* See https://github.com/python/cpython/blob/b567b9d74bd9e476a3027335873bb0508d6e450f/Lib/wsgiref/handlers.py#L225-L252
*/
private DataFlow::TypeTrackingNode writeFunction(DataFlow::TypeTracker t) {
t.start() and
result.(DataFlow::CallCfgNode).getFunction() = startResponse()
or
exists(DataFlow::TypeTracker t2 | result = writeFunction(t2).track(t2, t))
}
/**
* Gets a reference to the `write` function (that will write data to the response),
* which is the return value from calling the `start_response` function.
*
* See https://github.com/python/cpython/blob/b567b9d74bd9e476a3027335873bb0508d6e450f/Lib/wsgiref/handlers.py#L225-L252
*/
DataFlow::Node writeFunction() { writeFunction(DataFlow::TypeTracker::end()).flowsTo(result) }
/**
* A call to the `write` function.
*
* See https://github.com/python/cpython/blob/b567b9d74bd9e476a3027335873bb0508d6e450f/Lib/wsgiref/handlers.py#L276
*/
class WsgirefSimpleServerApplicationWriteCall extends HTTP::Server::HttpResponse::Range,
DataFlow::CallCfgNode {
WsgirefSimpleServerApplicationWriteCall() { this.getFunction() = writeFunction() }
override DataFlow::Node getBody() { result in [this.getArg(0), this.getArgByName("data")] }
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { none() }
}
/**
* A return from a `WsgirefSimpleServerApplication`, which is included in the response body.
*/
class WsgirefSimpleServerApplicationReturn extends HTTP::Server::HttpResponse::Range,
DataFlow::CfgNode {
WsgirefSimpleServerApplicationReturn() {
exists(WsgirefSimpleServerApplication requestHandler |
node = requestHandler.getAReturnValueFlowNode()
)
}
override DataFlow::Node getBody() { result = this }
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { none() }
}
}
// ---------------------------------------------------------------------------
// sqlite3
// ---------------------------------------------------------------------------

View File

@@ -12,18 +12,18 @@ ADDRESS = ("localhost", 8000)
# I wanted to showcase that we handle both functions and bound-methods, so it's possible
# to run this test-file in 2 different ways.
def func(environ, start_response): # $ MISSING: requestHandler
def func(environ, start_response): # $ requestHandler
ensure_tainted(
environ, # $ MISSING: tainted
environ["PATH_INFO"], # $ MISSING: tainted
environ, # $ tainted
environ["PATH_INFO"], # $ tainted
)
write = start_response("200 OK", [("Content-Type", "text/plain")])
write(b"hello") # $ MISSING: HttpResponse responseBody=b"hello"
write(data=b" ") # $ MISSING: HttpResponse responseBody=b" "
write(b"hello") # $ HttpResponse responseBody=b"hello"
write(data=b" ") # $ HttpResponse responseBody=b" "
# function return value should be an iterable that will also be written to to the
# response.
return [b"world", b"!"] # $ MISSING: HttpResponse responseBody=List
return [b"world", b"!"] # $ HttpResponse responseBody=List
class MyServer(wsgiref.simple_server.WSGIServer):
@@ -44,9 +44,9 @@ elif case == "2":
server = MyServer()
elif case == "3":
server = MyServer()
def func3(_env, start_response): # $ MISSING: requestHandler
def func3(_env, start_response): # $ requestHandler
start_response("200 OK", [])
return [b"foo"] # $ MISSING: HttpResponse responseBody=List
return [b"foo"] # $ HttpResponse responseBody=List
server.set_app(func3)
else:
sys.exit("wrong case")