mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
Python: Add support for bottle framework routing and requests.
This commit is contained in:
@@ -3,3 +3,4 @@ import semmle.python.web.flask.Request
|
||||
import semmle.python.web.tornado.Request
|
||||
import semmle.python.web.pyramid.Request
|
||||
import semmle.python.web.twisted.Request
|
||||
import semmle.python.web.bottle.Request
|
||||
|
||||
79
python/ql/src/semmle/python/web/bottle/General.qll
Normal file
79
python/ql/src/semmle/python/web/bottle/General.qll
Normal file
@@ -0,0 +1,79 @@
|
||||
import python
|
||||
import semmle.python.web.Http
|
||||
import semmle.python.types.Extensions
|
||||
|
||||
/** The flask module */
|
||||
ModuleObject theBottleModule() {
|
||||
result = ModuleObject::named("bottle")
|
||||
}
|
||||
|
||||
/** The flask app class */
|
||||
ClassObject theBottleClass() {
|
||||
result = ModuleObject::named("bottle").getAttribute("Bottle")
|
||||
}
|
||||
|
||||
/** Holds if `route` is routed to `func`
|
||||
* by decorating `func` with `app.route(route)` or `route(route)`
|
||||
*/
|
||||
predicate bottle_route(CallNode route_call, ControlFlowNode route, Function func) {
|
||||
exists(CallNode decorator_call, string name |
|
||||
route_call.getFunction().(AttrNode).getObject(name).refersTo(_, theBottleClass(), _) or
|
||||
route_call.getFunction().refersTo(theBottleModule().getAttribute(name))
|
||||
|
|
||||
(name = "route" or name = httpVerbLower()) and
|
||||
decorator_call.getFunction() = route_call and
|
||||
route_call.getArg(0) = route and
|
||||
decorator_call.getArg(0).getNode().(FunctionExpr).getInnerScope() = func
|
||||
)
|
||||
}
|
||||
|
||||
class BottleRoute extends ControlFlowNode {
|
||||
|
||||
BottleRoute() {
|
||||
bottle_route(this, _, _)
|
||||
}
|
||||
|
||||
string getUrl() {
|
||||
exists(StrConst url |
|
||||
bottle_route(this, url.getAFlowNode(), _) and
|
||||
result = url.getText()
|
||||
)
|
||||
}
|
||||
|
||||
Function getFunction() {
|
||||
bottle_route(this, _, result)
|
||||
}
|
||||
|
||||
Parameter getNamedArgument() {
|
||||
exists(string name, Function func |
|
||||
func = this.getFunction() and
|
||||
func.getArgByName(name) = result and
|
||||
this.getUrl().matches("%<" + name + ">%")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* bottle module route constants */
|
||||
|
||||
class BottleRoutePointToExtension extends CustomPointsToFact {
|
||||
|
||||
string name;
|
||||
|
||||
BottleRoutePointToExtension() {
|
||||
exists(DefinitionNode defn |
|
||||
defn.getScope().(Module).getName() = "bottle" and
|
||||
this = defn.getValue() and
|
||||
name = defn.(NameNode).getId()
|
||||
|
|
||||
name = "route" or
|
||||
name = httpVerbLower()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate pointsTo(Context context, Object value, ClassObject cls, ControlFlowNode origin) {
|
||||
context.isImport() and
|
||||
ModuleObject::named("bottle").getAttribute("Bottle").(ClassObject).attributeRefersTo(name, value, cls, origin)
|
||||
}
|
||||
}
|
||||
|
||||
115
python/ql/src/semmle/python/web/bottle/Request.qll
Normal file
115
python/ql/src/semmle/python/web/bottle/Request.qll
Normal file
@@ -0,0 +1,115 @@
|
||||
import python
|
||||
|
||||
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.security.strings.Untrusted
|
||||
import semmle.python.web.Http
|
||||
import semmle.python.web.bottle.General
|
||||
|
||||
private Object theBottleRequestObject() {
|
||||
result = theBottleModule().getAttribute("request")
|
||||
}
|
||||
|
||||
class BottleRequestKind extends TaintKind {
|
||||
|
||||
BottleRequestKind() {
|
||||
this = "bottle.request"
|
||||
}
|
||||
|
||||
override TaintKind getTaintOfAttribute(string name) {
|
||||
result instanceof BottleFormsDict and
|
||||
(name = "cookies" or name = "query" or name = "form")
|
||||
or
|
||||
result instanceof UntrustedStringKind and
|
||||
(name = "query_string" or name = "url_args")
|
||||
or
|
||||
result.(DictKind).getValue() instanceof FileUpload and
|
||||
name = "files"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class RequestSource extends TaintSource {
|
||||
|
||||
RequestSource() {
|
||||
this.(ControlFlowNode).refersTo(theBottleRequestObject())
|
||||
}
|
||||
|
||||
override predicate isSourceOf(TaintKind kind) {
|
||||
kind instanceof BottleRequestKind
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class BottleFormsDict extends TaintKind {
|
||||
|
||||
BottleFormsDict() {
|
||||
this = "bottle.FormsDict"
|
||||
}
|
||||
|
||||
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
|
||||
/* Cannot use `getTaintOfAttribute()` as it doesn't bind name */
|
||||
exists(string name |
|
||||
tonode = fromnode.(AttrNode).getObject(name) and
|
||||
result instanceof UntrustedStringKind
|
||||
|
|
||||
name != "get" and name != "getunicode" and name != "getall"
|
||||
)
|
||||
}
|
||||
|
||||
override TaintKind getTaintOfMethodResult(string name) {
|
||||
(name = "get" or name = "getunicode") and
|
||||
result instanceof UntrustedStringKind
|
||||
or
|
||||
name = "getall" and result.(SequenceKind).getItem() instanceof UntrustedStringKind
|
||||
}
|
||||
}
|
||||
|
||||
class FileUpload extends TaintKind {
|
||||
|
||||
FileUpload() {
|
||||
this = "bottle.FileUpload"
|
||||
}
|
||||
|
||||
override TaintKind getTaintOfAttribute(string name) {
|
||||
name = "filename" and result instanceof UntrustedStringKind
|
||||
or
|
||||
name = "raw_filename" and result instanceof UntrustedStringKind
|
||||
or
|
||||
name = "file" and result instanceof UntrustedFile
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class UntrustedFile extends TaintKind {
|
||||
|
||||
UntrustedFile() { this = "Untrusted file" }
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// TO DO.. File uploads -- Should check about file uploads for other frameworks as well.
|
||||
// Move UntrustedFile to shared location
|
||||
//
|
||||
|
||||
|
||||
/** Parameter to a bottle request handler function */
|
||||
class BottleRequestParameter extends TaintSource {
|
||||
|
||||
BottleRequestParameter() {
|
||||
exists(BottleRoute route |
|
||||
route.getNamedArgument() = this.(ControlFlowNode).getNode()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSourceOf(TaintKind kind) {
|
||||
kind instanceof UntrustedStringKind
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
result = "flask.request.args"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
3
python/ql/test/library-tests/web/bottle/Routing.expected
Normal file
3
python/ql/test/library-tests/web/bottle/Routing.expected
Normal file
@@ -0,0 +1,3 @@
|
||||
| /bye/<name> | test.py:12:1:12:25 | Function bye |
|
||||
| /hello/<name> | test.py:8:1:8:27 | Function hello |
|
||||
| /other | test.py:17:1:17:12 | Function other |
|
||||
7
python/ql/test/library-tests/web/bottle/Routing.ql
Normal file
7
python/ql/test/library-tests/web/bottle/Routing.ql
Normal file
@@ -0,0 +1,7 @@
|
||||
import python
|
||||
|
||||
import semmle.python.web.bottle.General
|
||||
|
||||
from BottleRoute route
|
||||
|
||||
select route.getUrl(), route.getFunction()
|
||||
7
python/ql/test/library-tests/web/bottle/Sources.expected
Normal file
7
python/ql/test/library-tests/web/bottle/Sources.expected
Normal file
@@ -0,0 +1,7 @@
|
||||
| ../../../query-tests/Security/lib/bottle.py:64 | LocalRequest() | bottle.request |
|
||||
| ../../../query-tests/Security/lib/bottle.py:64 | request | bottle.request |
|
||||
| test.py:3 | ImportMember | bottle.request |
|
||||
| test.py:3 | request | bottle.request |
|
||||
| test.py:8 | name | externally controlled string |
|
||||
| test.py:12 | name | externally controlled string |
|
||||
| test.py:18 | request | bottle.request |
|
||||
10
python/ql/test/library-tests/web/bottle/Sources.ql
Normal file
10
python/ql/test/library-tests/web/bottle/Sources.ql
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
import python
|
||||
|
||||
import semmle.python.web.HttpRequest
|
||||
import semmle.python.security.strings.Untrusted
|
||||
|
||||
|
||||
from TaintSource src, TaintKind kind
|
||||
where src.isSourceOf(kind) and not kind.matches("tornado%")
|
||||
select src.getLocation().toString(), src.(ControlFlowNode).getNode().toString(), kind
|
||||
2
python/ql/test/library-tests/web/bottle/options
Normal file
2
python/ql/test/library-tests/web/bottle/options
Normal file
@@ -0,0 +1,2 @@
|
||||
semmle-extractor-options: --max-import-depth=3 --lang=3 -p ../../../query-tests/Security/lib/
|
||||
optimize: true
|
||||
19
python/ql/test/library-tests/web/bottle/test.py
Normal file
19
python/ql/test/library-tests/web/bottle/test.py
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
|
||||
from bottle import Bottle, route, request
|
||||
|
||||
app = Bottle()
|
||||
|
||||
@app.route('/hello/<name>')
|
||||
def hello(name = "World!"):
|
||||
return "Hello " + name
|
||||
|
||||
@route('/bye/<name>')
|
||||
def bye(name = "World!"):
|
||||
return "Bye " + name
|
||||
|
||||
|
||||
@route('/other')
|
||||
def other():
|
||||
name = request.cookies.username
|
||||
return "User name is " + name
|
||||
66
python/ql/test/query-tests/Security/lib/bottle.py
Normal file
66
python/ql/test/query-tests/Security/lib/bottle.py
Normal file
@@ -0,0 +1,66 @@
|
||||
|
||||
class Bottle(object):
|
||||
|
||||
def route(self, path=None, method='GET', **options):
|
||||
pass
|
||||
|
||||
def get(self, path=None, method='GET', **options):
|
||||
""" Equals :meth:`route`. """
|
||||
return self.route(path, method, **options)
|
||||
|
||||
def post(self, path=None, method='POST', **options):
|
||||
""" Equals :meth:`route` with a ``POST`` method parameter. """
|
||||
return self.route(path, method, **options)
|
||||
|
||||
def put(self, path=None, method='PUT', **options):
|
||||
""" Equals :meth:`route` with a ``PUT`` method parameter. """
|
||||
return self.route(path, method, **options)
|
||||
|
||||
def delete(self, path=None, method='DELETE', **options):
|
||||
""" Equals :meth:`route` with a ``DELETE`` method parameter. """
|
||||
return self.route(path, method, **options)
|
||||
|
||||
def error(self, code=500):
|
||||
""" Decorator: Register an output handler for a HTTP error code"""
|
||||
def wrapper(handler):
|
||||
self.error_handler[int(code)] = handler
|
||||
return handler
|
||||
return wrapper
|
||||
|
||||
#Use same wrapper logic as the original `bottle` code.
|
||||
|
||||
def make_default_app_wrapper(name):
|
||||
""" Return a callable that relays calls to the current default app. """
|
||||
|
||||
@functools.wraps(getattr(Bottle, name))
|
||||
def wrapper(*a, **ka):
|
||||
return getattr(app(), name)(*a, **ka)
|
||||
|
||||
return wrapper
|
||||
|
||||
route = make_default_app_wrapper('route')
|
||||
get = make_default_app_wrapper('get')
|
||||
post = make_default_app_wrapper('post')
|
||||
put = make_default_app_wrapper('put')
|
||||
delete = make_default_app_wrapper('delete')
|
||||
patch = make_default_app_wrapper('patch')
|
||||
error = make_default_app_wrapper('error')
|
||||
mount = make_default_app_wrapper('mount')
|
||||
hook = make_default_app_wrapper('hook')
|
||||
install = make_default_app_wrapper('install')
|
||||
uninstall = make_default_app_wrapper('uninstall')
|
||||
url = make_default_app_wrapper('get_url')
|
||||
|
||||
class LocalProxy(object):
|
||||
pass
|
||||
|
||||
class LocalRequest(LocalProxy):
|
||||
pass
|
||||
|
||||
class LocalResponse(LocalProxy):
|
||||
pass
|
||||
|
||||
|
||||
request = LocalRequest()
|
||||
response = LocalResponse()
|
||||
|
||||
Reference in New Issue
Block a user