Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Django.qll
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,49 @@ module PrivateDjango {
* See https://docs.djangoproject.com/en/3.1/topics/http/shortcuts/#redirect
*/
API::Node redirect() { result = shortcuts().getMember("redirect") }

/**
* A call to the `django.shortcuts.render` function.
*
* See https://docs.djangoproject.com/en/3.1/topics/http/shortcuts/#render
*/
class ShortcutsRenderCall extends HTTP::Server::HttpResponse::Range, DataFlow::CallCfgNode {
ShortcutsRenderCall() {
this = API::moduleImport("django").getMember("shortcuts").getMember("render").getACall()
}

override DataFlow::Node getBody() {
result in [this.getArg(2), this.getArgByName("context")]
}

override DataFlow::Node getMimetypeOrContentTypeArg() { none() }

override string getMimetypeDefault() { result = "text/html" }
}

/**
* A call to the `django.shortcuts.render_to_response` function.
*
* See https://docs.djangoproject.com/en/2.2/topics/http/shortcuts/#render_to_response
*/
class ShortcutsRenderToResponseCall extends HTTP::Server::HttpResponse::Range,
DataFlow::CallCfgNode {
ShortcutsRenderToResponseCall() {
this =
API::moduleImport("django")
.getMember("shortcuts")
.getMember("render_to_response")
.getACall()
}

override DataFlow::Node getBody() {
result in [this.getArg(1), this.getArgByName("context")]
}

override DataFlow::Node getMimetypeOrContentTypeArg() { none() }

override string getMimetypeDefault() { result = "text/html" }
}
}
}

Expand Down
70 changes: 70 additions & 0 deletions python/ql/lib/semmle/python/frameworks/FastApi.qll
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.Pydantic
private import semmle.python.frameworks.Starlette
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper

/**
* Provides models for the `fastapi` PyPI package.
Expand Down Expand Up @@ -55,6 +56,14 @@ private module FastApi {
this = App::instance().getMember(routeAddingMethod).getACall()
or
this = APIRouter::instance().getMember(routeAddingMethod).getACall()
or
exists(DataFlow::AttrRead read, CallNode callNode, ClassValue cv |
cv.getASuperType() = Value::named("fastapi.APIRouter") and
callNode.getFunction().pointsTo(cv) and
read.(DataFlow::AttrRead).getObject().asCfgNode().refersTo(callNode) and
read.(DataFlow::AttrRead).getAttributeName() = routeAddingMethod and
this.getFunction() = read
)
)
}

Expand Down Expand Up @@ -349,4 +358,65 @@ private module FastApi {
override DataFlow::Node getValueArg() { none() }
}
}

// ---------------------------------------------------------------------------
// request modeling
// ---------------------------------------------------------------------------
/**
* Provides models for the `fastapi.Request` class.
*
* See https://fastapi.tiangolo.com/advanced/using-request-directly
*/
module Request {
/**
* A source of instances of `fastapi.Request`, extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by an external
* library.
*
* Use `Request::instance()` predicate to get
* references to instances of `fastapi.Request`.
*/
abstract class InstanceSource extends DataFlow::LocalSourceNode { }

/** Gets a reference to an instance of `twisted.web.server.Request`. */
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}

/** Gets a reference to an instance of `fastapi.Request`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }

/**
* Taint propagation for `fastapi.Request`.
*/
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
InstanceTaintSteps() { this = "fastapi.Request" }

override DataFlow::Node getInstance() { result = instance() }

override string getAttributeName() { result in ["query_params", "headers", "cookies"] }

override string getMethodName() { none() }

override string getAsyncMethodName() { none() }
}
}

/**
* A parameter that will receive a `fastapi.Request` instance,
* when a twisted request handler is called.
*/
class FastAPIRequestHandlerRequestParam extends RemoteFlowSource::Range, Request::InstanceSource,
DataFlow::ParameterNode {
FastAPIRequestHandlerRequestParam() {
this.getParameter() = any(FastApiRouteSetup handler).getARoutedParameter()
}

override string getSourceType() { result = "fastapi.Request" }
}
}
56 changes: 56 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Flask.qll
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,62 @@ module Flask {
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
}

/**
* A call to the `flask.jsonify` or `flask.json.jsonify` function.
*
* See https://flask.palletsprojects.com/en/2.0.x/api/#flask.json.jsonify
*/
private class FlaskJsonifyCall extends HTTP::Server::HttpResponse::Range, DataFlow::CallCfgNode {
FlaskJsonifyCall() {
this = API::moduleImport("flask").getMember("jsonify").getACall() or
this = API::moduleImport("flask").getMember("json").getMember("jsonify").getACall()
}

override DataFlow::Node getBody() { result = this.getArg(_) }

override string getMimetypeDefault() { result = "application/json" }

override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
}

/**
* A call to the `flask.render_template` function.
*
* See https://flask.palletsprojects.com/en/2.0.x/api/#flask.render_template
*/
private class FlaskRenderTemplateCall extends HTTP::Server::HttpResponse::Range,
DataFlow::CallCfgNode {
FlaskRenderTemplateCall() {
this = API::moduleImport("flask").getMember("render_template").getACall()
}

override DataFlow::Node getBody() {
result = this.getArgByName(any(string s | s != "template_name_or_list"))
}

override string getMimetypeDefault() { result = "text/html" }

override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
}

/**
* A call to the `flask.render_template_string` function.
*
* See https://flask.palletsprojects.com/en/2.0.x/api/#flask.render_template_string
*/
private class FlaskRenderTemplateStringCall extends HTTP::Server::HttpResponse::Range,
DataFlow::CallCfgNode {
FlaskRenderTemplateStringCall() {
this = API::moduleImport("flask").getMember("render_template_string").getACall()
}

override DataFlow::Node getBody() { result = this.getArgByName(_) }

override string getMimetypeDefault() { result = "text/html" }

override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
}

// ---------------------------------------------------------------------------
// routing modeling
// ---------------------------------------------------------------------------
Expand Down
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 @@ -1320,6 +1320,26 @@ private module StdlibPrivate {
}
}

// ---------------------------------------------------------------------------
// Implicit response from returns of http request handlers
// ---------------------------------------------------------------------------
private class HTTPRequestHandlerResponse extends HTTP::Server::HttpResponse::Range,
DataFlow::CallCfgNode {
HTTPRequestHandlerResponse() {
exists(DataFlow::AttrRead read |
this.getFunction().(DataFlow::AttrRead).getObject() = read and
read.getObject() = instance() and
read.getAttributeName() = "wfile"
)
}

override DataFlow::Node getBody() { result = this.getArg(_) }

override DataFlow::Node getMimetypeOrContentTypeArg() { none() }

override string getMimetypeDefault() { result = "application/json" }
}

/** An `HTTPMessage` instance that originates from a `BaseHTTPRequestHandler` instance. */
private class BaseHTTPRequestHandlerHeadersInstances extends Stdlib::HTTPMessage::InstanceSource {
BaseHTTPRequestHandlerHeadersInstances() {
Expand Down
28 changes: 28 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Tornado.qll
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,17 @@ private module Tornado {
/** Gets a reference to the `write` method. */
DataFlow::Node writeMethod() { writeMethod(DataFlow::TypeTracker::end()).flowsTo(result) }

/** Gets a reference to the `render` or `render_string` method. */
private DataFlow::TypeTrackingNode renderMethod(DataFlow::TypeTracker t) {
t.startInAttr(["render", "render_string"]) and
result = instance()
or
exists(DataFlow::TypeTracker t2 | result = renderMethod(t2).track(t2, t))
}

/** Gets a reference to the `render` or `render_string` method. */
DataFlow::Node renderMethod() { renderMethod(DataFlow::TypeTracker::end()).flowsTo(result) }

/**
* Taint propagation for `tornado.web.RequestHandler`.
*/
Expand Down Expand Up @@ -494,6 +505,23 @@ private module Tornado {
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
}

/**
* A call to the `tornado.web.RequestHandler.render` or `tornado.web.RequestHandler.render_string` method.
*
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.render
*/
private class TornadoRenderCall extends HTTP::Server::HttpResponse::Range, DataFlow::CallCfgNode {
TornadoRenderCall() { this.getFunction() = tornado::web::RequestHandler::renderMethod() }

override DataFlow::Node getBody() {
result = this.getArgByName(any(string s | s != "template_name"))
}

override string getMimetypeDefault() { result = "text/html" }

override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
}

/**
* A call to the `tornado.web.RequestHandler.set_cookie` method.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Desc :
"""
from fastapi import FastAPI, Request
from flask import Flask, request, send_file, make_response, helpers, jsonify, render_template,render_template_string, Response
from flask.json import jsonify
from pathlib import Path
from werkzeug.utils import secure_filename
import io
import os
import builtins

app = Flask(__name__)


@app.route('/fileRead1')
def fileRead1():
filename = request.args.get('filename')
context = io.open(filename)
response = make_response(context)
return response

@app.route('/fileRead2')
def fileRead2():
filename = request.args.get('filename')
context = open(filename, encoding="utf-8").read()
return context

@app.route('/fileRead3')
def fileRead3():
filename = request.args.get('filename')
context = ""
with builtins.open(filename, encoding="utf-8") as f:
context = f.readlines()
return context

@app.route('/fileRead4')
def fileRead4():
filename = request.args.get('filename')
response = send_file(path_or_file = "/home/work/" + filename)
return response

@app.route('/fileRead5')
def fileRead5():
filename = request.args.get('filename')
context = helpers.send_file(filename)
return context

@app.route('/fileRead6')
def fileRead6():
filename = request.args.get('filename')
context = os.listdir(filename)
return render_template_string("filedelete.html", contents = context)

@app.route('/fileRead7')
def fileRead7():
filename = request.args.get('filename')
fd = os.open(filename, os.O_RDWR|os.O_CREAT)
result = os.fdopen(fd).read()
return jsonify(result)

@app.route('/fileRead8')
def fileRead8():
filename = request.args.get('filename')
result = ""
for dir in os.scandir(filename):
result += str(dir)
return render_template_string(filename)

@app.route('/fileDelete1')
def fileDelete1():
filename = request.args.get('filename')
result = ""
try:
result = os.remove(filename)
except Exception as e:
result = "failed"
response = make_response(result)
return response

@app.route('/fileDelete2')
def fileDelete2():
filename = request.args.get('filename')
result = ""
try:
result = os.removedirs(filename)
except Exception as e:
result = "failed"
return result, 200

@app.route('/fileDelete3')
def fileDelete3():
filename = request.args.get('filename')
result = ""
try:
result = os.unlink(filename)
except Exception as e:
result = "failed"
return render_template(template_name_or_list = "filedelete.html", contents = filename)

@app.route('/good1')
def good1():
filename = request.args.get('filename')
sec_filename = secure_filename(filename)
try:
result = os.unlink(sec_filename)
except Exception as e:
result = "failed"

if __name__ == '__main__':
app.debug = True
app.run(host = '0.0.0.0')
Loading