By Mathew Payne - @GeekMasher
Mathew Payne - @GeekMasher
Senior Field Security Specialist - GitHub
Abertay University Alumni
Focus on:
Questions during presentation are welcome!
Poll Time
Track data through an application
from flask import Flask, request, render_template # ... @app.route("/search") def search(): search = request.args.get("search") results = lookup(search) return render_template( "search.html", results=results )
from flask import Flask, request, render_template # ... @app.route("/search") def search(): search = request.args.get("search") # <- source, request parameter results = lookup(search) # <- sink? return render_template( "search.html", results=results # <- sink? )
from flask import Flask, request, render_template def lookup(data): cursor = conn.cursor() query = f"SELECT * FROM metadata WHERE name='{data}' OR data='{data}'" cursor.execute(query) return cursor.fetchall() @app.route("/search") def search(): search = request.args.get("search") results = lookup(search) # ...
from flask import Flask, request, render_template def lookup(data): # <- 3. function definition cursor = conn.cursor() query = f"SELECT * FROM metadata WHERE name='{data}' OR data='{data}'" # ^ 4. string format, tainting query cursor.execute(query) # <- 5. SINK! return cursor.fetchall() @app.route("/search") def search(): search = request.args.get("search") # <- 1. source, request parameter results = lookup(search) # <- 2. function call # ...
from flask import Flask, request, render_template # ... @app.route("/search") def search(): search = request.args.get("search") results = lookup(search) return render_template( "search.html", results=results # <- sink? )
HTML Escaping / Jinja Templates
When returning HTML (the default response type in Flask), any user-provided values rendered in the output must be escaped to protect from injection attacks. HTML templates rendered with Jinja, introduced later, will do this automatically.
Code -> Database -> Queries -> Results
# vscode starter git clone --depth=1 https://github.com/github/vscode-codeql-starter # create database (codeql cli) codeql database create --language python ./python-DC44131-workshop
note: a little different for compiled languages
/** * @name SQL Injection * @kind problem * ... */ import python import DataFlow::PathGraph // Predicates and Classes class Sources extends DataFlow::Node { /* class */ } // Query Output / Results from Call call select call, "Calls in the code"
from flask import request request.args.get("search") # ^ Source!
import python import semmle.python.Concepts import semmle.python.ApiGraphs /* * How do we find the source? */ from DataFlow::Node request, Attribute attr where request = API::moduleImport("flask").getMember("request").getAValueReachableFromSource() and attr.getObject() = request.asExpr() select attr, "Source"
import psycopg2 conn = psycopg2.connect("dbname=workshop user=postgres") cursor = conn.cursor() cursor.execute(query) # ^ Sink: execute(query)
import python import semmle.python.Concepts import semmle.python.ApiGraphs /* * What is the sink? */ from CallNode call, DataFlow::Node sink where // Find all functions called "execute" call.getFunction().(AttrNode).getName() in ["execute"] and // The first argument is what we are interested in sink.asCfgNode() = call.getArg(0) select sink, "Sink"
Note: being lazy and looking for execute(...)
execute(...)
class SqlInjectionConfig extends TaintTracking::Configuration { SqlInjectionConfig() { this = "SqlInjectionConfig" } override predicate isSource(DataFlow::Node source) { source instanceof Sources } override predicate isSink(DataFlow::Node sink) { sink instanceof Sinks } } from SqlInjectionConfig config, DataFlow::PathNode source, DataFlow::PathNode sink where config.hasFlowPath(source, sink) select sink.getNode(), source, sink, "This SQL query depends on $@.", source.getNode(), "a user-provided value"
- Data-flow, Control-flow, and SSA Graph's Sources: - https://owasp.org/www-community/controls/Static_Code_Analysis
Resources
CodeQL
Memes