зеркало из https://github.com/github/codeql.git
Merge pull request #2129 from RasmusWL/python-update-django
Python: update django support
This commit is contained in:
Коммит
e9336fe30e
|
@ -20,3 +20,8 @@
|
|||
|----------------------------|------------------------|------------|
|
||||
| Unreachable code | Fewer false positives | Analysis now accounts for uses of `contextlib.suppress` to suppress exceptions. |
|
||||
| `__iter__` method returns a non-iterator | Better alert message | Alert now highlights which class is expected to be an iterator. |
|
||||
|
||||
|
||||
## Changes to QL libraries
|
||||
|
||||
* Django library now recognizes positional arguments from a `django.conf.urls.url` regex (Django version 1.x)
|
||||
|
|
|
@ -21,20 +21,29 @@ or prepared statements.
|
|||
|
||||
<example>
|
||||
<p>
|
||||
In the following snippet, from an example django app,
|
||||
a name is stored in the database using two different queries.
|
||||
In the following snippet, a user is fetched from the database using three
|
||||
different queries.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In the first case, the query string is built by
|
||||
directly using string formatting from a user-supplied request attribute.
|
||||
directly using string formatting from a user-supplied request parameter.
|
||||
The parameter may include quote characters, so this
|
||||
code is vulnerable to a SQL injection attack.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In the second case, the user-supplied request attribute is passed
|
||||
to the database using query parameters.
|
||||
to the database using query parameters. The database connector library will
|
||||
take care of escaping and inserting quotes as needed.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In the third case, the placeholder in the SQL string has been manually quoted. Since most
|
||||
databaseconnector libraries will insert their own quotes, doing so yourself will make the code
|
||||
vulnerable to SQL injection attacks. In this example, if <code>username</code> was
|
||||
<code>; DROP ALL TABLES -- </code>, the final SQL query would be
|
||||
<code>SELECT * FROM users WHERE username = ''; DROP ALL TABLES -- ''</code>
|
||||
</p>
|
||||
|
||||
<sample src="examples/sql_injection.py" />
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
|
||||
from django.conf.urls import patterns, url
|
||||
from django.conf.urls import url
|
||||
from django.db import connection
|
||||
|
||||
|
||||
def save_name(request):
|
||||
def show_user(request, username):
|
||||
with connection.cursor() as cursor:
|
||||
# BAD -- Using string formatting
|
||||
cursor.execute("SELECT * FROM users WHERE username = '%s'" % username)
|
||||
user = cursor.fetchone()
|
||||
|
||||
if request.method == 'POST':
|
||||
name = request.POST.get('name')
|
||||
curs = connection.cursor()
|
||||
#BAD -- Using string formatting
|
||||
curs.execute(
|
||||
"insert into names_file ('name') values ('%s')" % name)
|
||||
#GOOD -- Using parameters
|
||||
curs.execute(
|
||||
"insert into names_file ('name') values ('%s')", name)
|
||||
# GOOD -- Using parameters
|
||||
cursor.execute("SELECT * FROM users WHERE username = %s", username)
|
||||
user = cursor.fetchone()
|
||||
|
||||
# BAD -- Manually quoting placeholder (%s)
|
||||
cursor.execute("SELECT * FROM users WHERE username = '%s'", username)
|
||||
user = cursor.fetchone()
|
||||
|
||||
urlpatterns = patterns(url(r'^save_name/$',
|
||||
upload, name='save_name'))
|
||||
|
||||
urlpatterns = [url(r'^users/(?P<username>[^/]+)$', show_user)]
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import python
|
||||
import semmle.python.regex
|
||||
import semmle.python.web.Http
|
||||
|
||||
predicate django_route(CallNode call, ControlFlowNode regex, FunctionValue view) {
|
||||
exists(FunctionValue url |
|
||||
Value::named("django.conf.urls.url") = url and
|
||||
url.getArgumentForCall(call, 0) = regex and
|
||||
url.getArgumentForCall(call, 1).pointsTo(view)
|
||||
)
|
||||
}
|
||||
|
||||
class DjangoRouteRegex extends RegexString {
|
||||
DjangoRouteRegex() { django_route(_, this.getAFlowNode(), _) }
|
||||
}
|
||||
|
||||
class DjangoRoute extends CallNode {
|
||||
DjangoRoute() { django_route(this, _, _) }
|
||||
|
||||
FunctionValue getViewFunction() { django_route(this, _, result) }
|
||||
|
||||
string getNamedArgument() {
|
||||
exists(DjangoRouteRegex regex |
|
||||
django_route(this, regex.getAFlowNode(), _) and
|
||||
regex.getGroupName(_, _) = result
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of positional arguments that will be passed to the view.
|
||||
* Will only return a result if there are no named arguments.
|
||||
*/
|
||||
int getNumPositionalArguments() {
|
||||
exists(DjangoRouteRegex regex |
|
||||
django_route(this, regex.getAFlowNode(), _) and
|
||||
not exists(string s | s = regex.getGroupName(_, _)) and
|
||||
result = count(regex.getGroupNumber(_, _))
|
||||
)
|
||||
}
|
||||
}
|
|
@ -54,33 +54,6 @@ class DjangoModelObjects extends TaintSource {
|
|||
override string toString() { result = "django.db.models.Model.objects" }
|
||||
}
|
||||
|
||||
/** A write to a field of a django model, which is a vulnerable to external data. */
|
||||
class DjangoModelFieldWrite extends SqlInjectionSink {
|
||||
DjangoModelFieldWrite() {
|
||||
exists(AttrNode attr, DjangoModel model |
|
||||
this = attr and attr.isStore() and attr.getObject(_).pointsTo(model)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
|
||||
|
||||
override string toString() { result = "django model field write" }
|
||||
}
|
||||
|
||||
/** A direct reference to a django model object, which is vulnerable to external data. */
|
||||
class DjangoModelDirectObjectReference extends TaintSink {
|
||||
DjangoModelDirectObjectReference() {
|
||||
exists(CallNode objects_get_call, ControlFlowNode objects | this = objects_get_call.getAnArg() |
|
||||
objects_get_call.getFunction().(AttrNode).getObject("get") = objects and
|
||||
any(DjangoDbTableObjects objs).taints(objects)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
|
||||
|
||||
override string toString() { result = "django model object reference" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `raw` method on a django model. This allows a raw SQL query
|
||||
* to be sent to the database, which is a security risk.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import python
|
||||
import semmle.python.regex
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.web.Http
|
||||
import semmle.python.web.django.General
|
||||
|
||||
/** A django.request.HttpRequest object */
|
||||
class DjangoRequest extends TaintKind {
|
||||
|
@ -52,7 +52,7 @@ abstract class DjangoRequestSource extends HttpRequestTaintSource {
|
|||
private class DjangoFunctionBasedViewRequestArgument extends DjangoRequestSource {
|
||||
DjangoFunctionBasedViewRequestArgument() {
|
||||
exists(FunctionValue view |
|
||||
url_dispatch(_, _, view) and
|
||||
django_route(_, _, view) and
|
||||
this = view.getScope().getArg(0).asName().getAFlowNode()
|
||||
)
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ private class DjangoView extends ClassValue {
|
|||
}
|
||||
|
||||
private FunctionValue djangoViewHttpMethod() {
|
||||
exists(DjangoView view | view.attr(httpVerbLower()) = result)
|
||||
exists(DjangoView view | view.lookup(httpVerbLower()) = result)
|
||||
}
|
||||
|
||||
class DjangoClassBasedViewRequestArgument extends DjangoRequestSource {
|
||||
|
@ -76,41 +76,18 @@ class DjangoClassBasedViewRequestArgument extends DjangoRequestSource {
|
|||
}
|
||||
}
|
||||
|
||||
/* *********** Routing ********* */
|
||||
/* Function based views */
|
||||
predicate url_dispatch(CallNode call, ControlFlowNode regex, FunctionValue view) {
|
||||
exists(FunctionValue url |
|
||||
Value::named("django.conf.urls.url") = url and
|
||||
url.getArgumentForCall(call, 0) = regex and
|
||||
url.getArgumentForCall(call, 1).pointsTo(view)
|
||||
)
|
||||
}
|
||||
|
||||
class UrlRegex extends RegexString {
|
||||
UrlRegex() { url_dispatch(_, this.getAFlowNode(), _) }
|
||||
}
|
||||
|
||||
class UrlRouting extends CallNode {
|
||||
UrlRouting() { url_dispatch(this, _, _) }
|
||||
|
||||
FunctionValue getViewFunction() { url_dispatch(this, _, result) }
|
||||
|
||||
string getNamedArgument() {
|
||||
exists(UrlRegex regex |
|
||||
url_dispatch(this, regex.getAFlowNode(), _) and
|
||||
regex.getGroupName(_, _) = result
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An argument specified in a url routing table */
|
||||
class HttpRequestParameter extends HttpRequestTaintSource {
|
||||
HttpRequestParameter() {
|
||||
exists(UrlRouting url |
|
||||
this.(ControlFlowNode).getNode() = url
|
||||
.getViewFunction()
|
||||
.getScope()
|
||||
.getArgByName(url.getNamedArgument())
|
||||
class DjangoRequestParameter extends HttpRequestTaintSource {
|
||||
DjangoRequestParameter() {
|
||||
exists(DjangoRoute route, Function f |
|
||||
f = route.getViewFunction().getScope() |
|
||||
this.(ControlFlowNode).getNode() = f.getArgByName(route.getNamedArgument())
|
||||
or
|
||||
exists(int i | i >= 0 |
|
||||
i < route.getNumPositionalArguments() and
|
||||
// +1 because first argument is always the request
|
||||
this.(ControlFlowNode).getNode() = f.getArg(i+1)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
| models.py:9 | key | externally controlled string |
|
||||
| rawsql.py:4 | BinaryExpr | externally controlled string |
|
||||
| rawsql.py:13 | BinaryExpr | externally controlled string |
|
||||
| rawsql.py:18 | BinaryExpr | externally controlled string |
|
||||
| rawsql.py:22 | BinaryExpr | externally controlled string |
|
||||
| views.py:8 | Attribute() | externally controlled string |
|
|
@ -1,13 +0,0 @@
|
|||
|
||||
import python
|
||||
|
||||
|
||||
import semmle.python.web.django.Request
|
||||
import semmle.python.web.django.Model
|
||||
import semmle.python.web.django.Db
|
||||
import semmle.python.web.django.Response
|
||||
import semmle.python.security.strings.Untrusted
|
||||
|
||||
from TaintSink sink, TaintKind kind
|
||||
where sink.sinks(kind)
|
||||
select sink.getLocation().toString(), sink.(ControlFlowNode).getNode().toString(), kind.toString()
|
|
@ -1,8 +0,0 @@
|
|||
| models.py:9 | Attribute | django.db.models.Model.objects |
|
||||
| rawsql.py:13 | Attribute | django.db.models.Model.objects |
|
||||
| rawsql.py:16 | Attribute | django.db.models.Model.objects |
|
||||
| rawsql.py:21 | Attribute | django.db.models.Model.objects |
|
||||
| views.py:6 | request | django.request.HttpRequest |
|
||||
| views.py:8 | HttpResponse() | django.response.HttpResponse |
|
||||
| views.py:11 | path | externally controlled string |
|
||||
| views.py:11 | request | django.request.HttpRequest |
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
import python
|
||||
|
||||
|
||||
import semmle.python.web.django.Request
|
||||
import semmle.python.web.django.Model
|
||||
import semmle.python.web.django.Response
|
||||
import semmle.python.security.strings.Untrusted
|
||||
|
||||
from TaintSource src, TaintKind kind
|
||||
where src.isSourceOf(kind)
|
||||
select src.getLocation().toString(), src.(ControlFlowNode).getNode().toString(), kind.toString()
|
|
@ -1,24 +0,0 @@
|
|||
| models.py:9 | Attribute | django.db.models.Model.objects |
|
||||
| rawsql.py:13 | Attribute | django.db.models.Model.objects |
|
||||
| rawsql.py:13 | Attribute() | django.db.models.Model.objects |
|
||||
| rawsql.py:16 | Attribute | django.db.models.Model.objects |
|
||||
| rawsql.py:16 | Attribute() | django.db.models.Model.objects |
|
||||
| rawsql.py:17 | Attribute() | django.db.models.Model.objects |
|
||||
| rawsql.py:17 | m | django.db.models.Model.objects |
|
||||
| rawsql.py:18 | Attribute() | django.db.models.Model.objects |
|
||||
| rawsql.py:18 | m | django.db.models.Model.objects |
|
||||
| rawsql.py:21 | Attribute | django.db.models.Model.objects |
|
||||
| rawsql.py:21 | Attribute() | django.db.models.Model.objects |
|
||||
| rawsql.py:22 | Attribute() | django.db.models.Model.objects |
|
||||
| rawsql.py:22 | m | django.db.models.Model.objects |
|
||||
| views.py:6 | request | django.request.HttpRequest |
|
||||
| views.py:8 | Attribute | django.http.request.QueryDict |
|
||||
| views.py:8 | Attribute() | externally controlled string |
|
||||
| views.py:8 | HttpResponse() | django.response.HttpResponse |
|
||||
| views.py:8 | request | django.request.HttpRequest |
|
||||
| views.py:11 | path | externally controlled string |
|
||||
| views.py:11 | request | django.request.HttpRequest |
|
||||
| views.py:12 | Dict | {externally controlled string} |
|
||||
| views.py:12 | path | externally controlled string |
|
||||
| views.py:13 | env | {externally controlled string} |
|
||||
| views.py:13 | request | django.request.HttpRequest |
|
|
@ -1,14 +0,0 @@
|
|||
|
||||
import python
|
||||
|
||||
|
||||
import semmle.python.web.django.Request
|
||||
import semmle.python.web.django.Model
|
||||
import semmle.python.web.django.Response
|
||||
import semmle.python.security.strings.Untrusted
|
||||
|
||||
|
||||
from TaintedNode node
|
||||
|
||||
select node.getLocation().toString(), node.getAstNode().toString(), node.getTaintKind().toString()
|
||||
|
|
@ -1 +0,0 @@
|
|||
#Fake django package
|
|
@ -1 +0,0 @@
|
|||
#Fake django package
|
|
@ -1,3 +0,0 @@
|
|||
|
||||
def url(regex, view):
|
||||
pass
|
|
@ -1 +0,0 @@
|
|||
#Fake django package
|
|
@ -1,2 +0,0 @@
|
|||
class Model:
|
||||
pass
|
|
@ -1,2 +0,0 @@
|
|||
class RawSQL:
|
||||
pass
|
|
@ -1,2 +0,0 @@
|
|||
|
||||
from .response import HttpResponse
|
|
@ -1,5 +0,0 @@
|
|||
|
||||
class HttpResponse:
|
||||
|
||||
def __init__(self, *args):
|
||||
pass
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
from django.db import models
|
||||
|
||||
class MyModel(models.Model):
|
||||
title = models.CharField(max_length=500)
|
||||
summary = models.TextField(blank=True)
|
||||
|
||||
def update_my_model(key, title):
|
||||
item = MyModel.objects.get(pk=key)
|
||||
item.title = title
|
|
@ -1,23 +0,0 @@
|
|||
from django.db.models.expressions import RawSQL
|
||||
|
||||
def raw1(arg):
|
||||
return RawSQL("select foo from bar where baz = %s" % arg, "")
|
||||
|
||||
|
||||
from django.db import models
|
||||
|
||||
class MyModel(models.Model):
|
||||
pass
|
||||
|
||||
def raw2(arg):
|
||||
MyModel.objects.raw("select foo from bar where baz = %s" % arg)
|
||||
|
||||
def raw3(arg):
|
||||
m = MyModel.objects.filter('foo')
|
||||
m = m.filter('bar')
|
||||
m.raw("select foo from bar where baz = %s" % arg)
|
||||
|
||||
def raw4(arg):
|
||||
m = MyModel.objects.filter('foo')
|
||||
m.extra("select foo from bar where baz = %s" % arg)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
from django.conf.urls import url
|
||||
import views
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
url(r'^route1$', views.view_func1),
|
||||
url(r'^(?P<path>.*)$', views.view_func2),
|
||||
url(r'^route2$', views.ClassView.as_view())
|
||||
]
|
|
@ -1,19 +0,0 @@
|
|||
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect, render
|
||||
from django.views.generic import View
|
||||
|
||||
def view_func1(request):
|
||||
# Whether this is safe depends on template.html -- annoyingly
|
||||
return HttpResponse(request.GET.get("untrusted"))
|
||||
|
||||
|
||||
def view_func2(request, path='default'):
|
||||
env = {'path': path}
|
||||
return render(request, 'vulnerable-path.html', env)
|
||||
|
||||
|
||||
class ClassView(View):
|
||||
|
||||
def get(self, request):
|
||||
pass
|
|
@ -1 +0,0 @@
|
|||
semmle-extractor-options: --max-import-depth=3 --lang=3
|
|
@ -1,6 +1,16 @@
|
|||
| test.py:18 | Str | externally controlled string |
|
||||
| test.py:21 | BinaryExpr | externally controlled string |
|
||||
| test.py:24 | BinaryExpr | externally controlled string |
|
||||
| test.py:25 | BinaryExpr | externally controlled string |
|
||||
| test.py:26 | BinaryExpr | externally controlled string |
|
||||
| test.py:34 | BinaryExpr | externally controlled string |
|
||||
| sql.py:13 | Str | externally controlled string |
|
||||
| sql.py:14 | Str | externally controlled string |
|
||||
| sql.py:17 | BinaryExpr | externally controlled string |
|
||||
| sql.py:20 | BinaryExpr | externally controlled string |
|
||||
| sql.py:21 | BinaryExpr | externally controlled string |
|
||||
| sql.py:22 | BinaryExpr | externally controlled string |
|
||||
| sql.py:36 | Str | externally controlled string |
|
||||
| sql.py:42 | BinaryExpr | externally controlled string |
|
||||
| sql.py:47 | BinaryExpr | externally controlled string |
|
||||
| views.py:7 | Attribute() | externally controlled string |
|
||||
| views.py:11 | Attribute() | externally controlled string |
|
||||
| views.py:15 | Attribute() | externally controlled string |
|
||||
| views.py:23 | Attribute() | externally controlled string |
|
||||
| views.py:29 | Attribute() | externally controlled string |
|
||||
| views.py:34 | Attribute() | externally controlled string |
|
||||
| views.py:38 | Attribute() | externally controlled string |
|
||||
|
|
|
@ -1,2 +1,19 @@
|
|||
| test.py:5 | path | externally controlled string |
|
||||
| test.py:5 | request | django.request.HttpRequest |
|
||||
| test.py:11 | path | externally controlled string |
|
||||
| test.py:11 | request | django.request.HttpRequest |
|
||||
| test.py:31 | request | django.request.HttpRequest |
|
||||
| views.py:6 | bar | externally controlled string |
|
||||
| views.py:6 | foo | externally controlled string |
|
||||
| views.py:6 | request | django.request.HttpRequest |
|
||||
| views.py:10 | request | django.request.HttpRequest |
|
||||
| views.py:14 | request | django.request.HttpRequest |
|
||||
| views.py:22 | request | django.request.HttpRequest |
|
||||
| views.py:28 | request | django.request.HttpRequest |
|
||||
| views.py:32 | page_number | externally controlled string |
|
||||
| views.py:32 | request | django.request.HttpRequest |
|
||||
| views.py:37 | arg0 | externally controlled string |
|
||||
| views.py:37 | arg1 | externally controlled string |
|
||||
| views.py:37 | request | django.request.HttpRequest |
|
||||
| views.py:57 | request | django.request.HttpRequest |
|
||||
| views.py:57 | username | externally controlled string |
|
||||
| views.py:66 | request | django.request.HttpRequest |
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
from django.db import connection, models
|
||||
from django.db.models.expressions import RawSQL
|
||||
|
||||
|
||||
class User(models.Model):
|
||||
username = models.CharField(max_length=100)
|
||||
description = models.TextField(blank=True)
|
||||
|
||||
|
||||
def show_user(username):
|
||||
with connection.cursor() as cursor:
|
||||
# GOOD -- Using parameters
|
||||
cursor.execute("SELECT * FROM users WHERE username = %s", username)
|
||||
User.objects.raw("SELECT * FROM users WHERE username = %s", (username,))
|
||||
|
||||
# BAD -- Using string formatting
|
||||
cursor.execute("SELECT * FROM users WHERE username = '%s'" % username)
|
||||
|
||||
# BAD -- other ways of executing raw SQL code with string interpolation
|
||||
User.objects.annotate(RawSQL("insert into names_file ('name') values ('%s')" % username))
|
||||
User.objects.raw("insert into names_file ('name') values ('%s')" % username)
|
||||
User.objects.extra("insert into names_file ('name') values ('%s')" % username)
|
||||
|
||||
# BAD (but currently no custom query to find this)
|
||||
#
|
||||
# It is exposed to SQL injection (https://docs.djangoproject.com/en/2.2/ref/models/querysets/#extra)
|
||||
# For example, using name = "; DROP ALL TABLES -- "
|
||||
# will result in SQL: SELECT * FROM name WHERE name = ''; DROP ALL TABLES -- ''
|
||||
#
|
||||
# This shouldn't be very widespread, since using a normal string will result in invalid SQL
|
||||
# Using name = "example", will result in SQL: SELECT * FROM name WHERE name = ''example''
|
||||
# which in MySQL will give a syntax error
|
||||
#
|
||||
# When testing this out locally, none of the queries worked against SQLite3, but I could use
|
||||
# the SQL injection against MySQL.
|
||||
User.objects.raw("SELECT * FROM users WHERE username = '%s'", (username,))
|
||||
|
||||
|
||||
def raw3(arg):
|
||||
m = User.objects.filter('foo')
|
||||
m = m.filter('bar')
|
||||
m.raw("select foo from bar where baz = %s" % arg)
|
||||
|
||||
|
||||
def raw4(arg):
|
||||
m = User.objects.filter('foo')
|
||||
m.extra("select foo from bar where baz = %s" % arg)
|
||||
|
||||
|
||||
def update_user(key, description1):
|
||||
# Neither of these are exposed to sql-injections
|
||||
user = User.objects.get(pk=key)
|
||||
item.description = description
|
|
@ -1,40 +1,18 @@
|
|||
from django.conf.urls import url
|
||||
from django.shortcuts import redirect, render
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from django.db import connection, models
|
||||
from django.db.models.expressions import RawSQL
|
||||
from django.http.response import HttpResponse
|
||||
import base64
|
||||
|
||||
class Name(models.Model):
|
||||
pass
|
||||
def with_template(request, path='default'):
|
||||
env = {'path': path}
|
||||
# We would need to understand django templates to know if this is safe or not
|
||||
return render(request, 'possibly-vulnerable-template.html', env)
|
||||
|
||||
def save_name(request):
|
||||
|
||||
if request.method == 'POST':
|
||||
name = request.POST.get('name')
|
||||
curs = connection.cursor()
|
||||
#GOOD -- Using parameters
|
||||
curs.execute(
|
||||
"insert into names_file ('name') values ('%s')", name)
|
||||
#BAD -- Using string formatting
|
||||
curs.execute(
|
||||
"insert into names_file ('name') values ('%s')" % name)
|
||||
def vuln_redirect(request, path):
|
||||
return redirect(path)
|
||||
|
||||
#BAD -- other ways of executing raw SQL code with string interpolation
|
||||
Name.objects.annotate(RawSQL("insert into names_file ('name') values ('%s')" % name))
|
||||
Name.objects.raw("insert into names_file ('name') values ('%s')" % name)
|
||||
Name.objects.extra("insert into names_file ('name') values ('%s')" % name)
|
||||
|
||||
urlpatterns1 = patterns(url(r'^save_name/$',
|
||||
save_name, name='save_name'))
|
||||
|
||||
def maybe_xss(request):
|
||||
first_name = request.POST.get('first_name', '')
|
||||
resp = HttpResponse()
|
||||
resp.write("first name is " + first_name)
|
||||
return resp
|
||||
|
||||
urlpatterns2 = [
|
||||
# Route to code_execution
|
||||
url(r'^maybe_xss$', maybe_xss, name='maybe_xss')
|
||||
urlpatterns = [
|
||||
url(r'^(?P<path>.*)$', with_template),
|
||||
url(r'^redirect/(?P<path>.*)$', vuln_redirect),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
from django.conf.urls import patterns, url
|
||||
from django.http.response import HttpResponse
|
||||
from django.views.generic import View
|
||||
|
||||
|
||||
def url_match_xss(request, foo, bar, no_taint=None):
|
||||
return HttpResponse('url_match_xss: {} {}'.format(foo, bar))
|
||||
|
||||
|
||||
def get_params_xss(request):
|
||||
return HttpResponse(request.GET.get("untrusted"))
|
||||
|
||||
|
||||
def post_params_xss(request):
|
||||
return HttpResponse(request.POST.get("untrusted"))
|
||||
|
||||
|
||||
class Foo(object):
|
||||
# Note: since Foo is used as the super type in a class view, it will be able to handle requests.
|
||||
|
||||
# TODO: Currently we don't flag `untrusted` as a DjangoRequestParameter
|
||||
def post(self, request, untrusted):
|
||||
return HttpResponse('Foo post: {}'.format(untrusted))
|
||||
|
||||
|
||||
class ClassView(View, Foo):
|
||||
# TODO: Currently we don't flag `untrusted` as a DjangoRequestParameter
|
||||
def get(self, request, untrusted):
|
||||
return HttpResponse('ClassView get: {}'.format(untrusted))
|
||||
|
||||
|
||||
def show_articles(request, page_number=1):
|
||||
page_number = int(page_number)
|
||||
return HttpResponse('articles page: {}'.format(page_number))
|
||||
|
||||
|
||||
def xxs_positional_arg(request, arg0, arg1, no_taint=None):
|
||||
return HttpResponse('xxs_positional_arg: {} {}'.format(arg0, arg1))
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^url_match/(?P<foo>[^/]+)/(?P<bar>[^/]+)$', url_match_xss),
|
||||
url(r'^get_params$', get_params_xss),
|
||||
url(r'^post_params$', post_params_xss),
|
||||
url(r'^class_view/(?P<untrusted>.+)$', ClassView.as_view()),
|
||||
|
||||
# one pattern to support `articles/page-<n>` and ensuring that articles/ goes to page-1
|
||||
url(r'articles/^(?:page-(?P<page_number>\d+)/)?$', show_articles),
|
||||
# passing as positional argument is not the recommended way of doing things, but it is certainly
|
||||
# possible
|
||||
url(r'^([^/]+)/(?:foo|bar)/([^/]+)$', xxs_positional_arg, name='xxs_positional_arg'),
|
||||
]
|
||||
|
||||
|
||||
# Using patterns() for routing
|
||||
|
||||
def show_user(request, username):
|
||||
pass
|
||||
|
||||
|
||||
urlpatterns = patterns(url(r'^users/(?P<username>[^/]+)$', show_user))
|
||||
|
||||
|
||||
# Show we understand the keyword arguments to django.conf.urls.url
|
||||
|
||||
def we_understand_url_kwargs(request):
|
||||
pass
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(view=we_understand_url_kwargs, regex=r'^specifying-as-kwargs-is-not-a-problem$')
|
||||
]
|
|
@ -1,17 +1,14 @@
|
|||
edges
|
||||
| sql_injection.py:9:15:9:21 | django.request.HttpRequest | sql_injection.py:12:16:12:22 | django.request.HttpRequest |
|
||||
| sql_injection.py:12:16:12:22 | django.request.HttpRequest | sql_injection.py:12:16:12:27 | django.http.request.QueryDict |
|
||||
| sql_injection.py:12:16:12:27 | django.http.request.QueryDict | sql_injection.py:12:16:12:39 | externally controlled string |
|
||||
| sql_injection.py:12:16:12:39 | externally controlled string | sql_injection.py:19:63:19:66 | externally controlled string |
|
||||
| sql_injection.py:12:16:12:39 | externally controlled string | sql_injection.py:22:88:22:91 | externally controlled string |
|
||||
| sql_injection.py:12:16:12:39 | externally controlled string | sql_injection.py:23:76:23:79 | externally controlled string |
|
||||
| sql_injection.py:12:16:12:39 | externally controlled string | sql_injection.py:24:78:24:81 | externally controlled string |
|
||||
| sql_injection.py:19:63:19:66 | externally controlled string | sql_injection.py:19:13:19:66 | externally controlled string |
|
||||
| sql_injection.py:22:88:22:91 | externally controlled string | sql_injection.py:22:38:22:91 | externally controlled string |
|
||||
| sql_injection.py:23:76:23:79 | externally controlled string | sql_injection.py:23:26:23:79 | externally controlled string |
|
||||
| sql_injection.py:24:78:24:81 | externally controlled string | sql_injection.py:24:28:24:81 | externally controlled string |
|
||||
| sql_injection.py:12:24:12:31 | externally controlled string | sql_injection.py:19:70:19:77 | externally controlled string |
|
||||
| sql_injection.py:12:24:12:31 | externally controlled string | sql_injection.py:22:88:22:95 | externally controlled string |
|
||||
| sql_injection.py:12:24:12:31 | externally controlled string | sql_injection.py:23:76:23:83 | externally controlled string |
|
||||
| sql_injection.py:12:24:12:31 | externally controlled string | sql_injection.py:24:78:24:85 | externally controlled string |
|
||||
| sql_injection.py:19:70:19:77 | externally controlled string | sql_injection.py:19:24:19:77 | externally controlled string |
|
||||
| sql_injection.py:22:88:22:95 | externally controlled string | sql_injection.py:22:38:22:95 | externally controlled string |
|
||||
| sql_injection.py:23:76:23:83 | externally controlled string | sql_injection.py:23:26:23:83 | externally controlled string |
|
||||
| sql_injection.py:24:78:24:85 | externally controlled string | sql_injection.py:24:28:24:85 | externally controlled string |
|
||||
#select
|
||||
| sql_injection.py:19:13:19:66 | BinaryExpr | sql_injection.py:9:15:9:21 | django.request.HttpRequest | sql_injection.py:19:13:19:66 | externally controlled string | This SQL query depends on $@. | sql_injection.py:9:15:9:21 | request | a user-provided value |
|
||||
| sql_injection.py:22:38:22:91 | BinaryExpr | sql_injection.py:9:15:9:21 | django.request.HttpRequest | sql_injection.py:22:38:22:91 | externally controlled string | This SQL query depends on $@. | sql_injection.py:9:15:9:21 | request | a user-provided value |
|
||||
| sql_injection.py:23:26:23:79 | BinaryExpr | sql_injection.py:9:15:9:21 | django.request.HttpRequest | sql_injection.py:23:26:23:79 | externally controlled string | This SQL query depends on $@. | sql_injection.py:9:15:9:21 | request | a user-provided value |
|
||||
| sql_injection.py:24:28:24:81 | BinaryExpr | sql_injection.py:9:15:9:21 | django.request.HttpRequest | sql_injection.py:24:28:24:81 | externally controlled string | This SQL query depends on $@. | sql_injection.py:9:15:9:21 | request | a user-provided value |
|
||||
| sql_injection.py:19:24:19:77 | BinaryExpr | sql_injection.py:12:24:12:31 | externally controlled string | sql_injection.py:19:24:19:77 | externally controlled string | This SQL query depends on $@. | sql_injection.py:12:24:12:31 | username | a user-provided value |
|
||||
| sql_injection.py:22:38:22:95 | BinaryExpr | sql_injection.py:12:24:12:31 | externally controlled string | sql_injection.py:22:38:22:95 | externally controlled string | This SQL query depends on $@. | sql_injection.py:12:24:12:31 | username | a user-provided value |
|
||||
| sql_injection.py:23:26:23:83 | BinaryExpr | sql_injection.py:12:24:12:31 | externally controlled string | sql_injection.py:23:26:23:83 | externally controlled string | This SQL query depends on $@. | sql_injection.py:12:24:12:31 | username | a user-provided value |
|
||||
| sql_injection.py:24:28:24:85 | BinaryExpr | sql_injection.py:12:24:12:31 | externally controlled string | sql_injection.py:24:28:24:85 | externally controlled string | This SQL query depends on $@. | sql_injection.py:12:24:12:31 | username | a user-provided value |
|
||||
|
|
|
@ -1,28 +1,40 @@
|
|||
"""This is copied from ql/python/ql/test/library-tests/web/django/test.py
|
||||
and a only a slight extension of ql/python/ql/src/Security/CWE-089/examples/sql_injection.py
|
||||
"""
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from django.conf.urls import url
|
||||
from django.db import connection, models
|
||||
from django.db.models.expressions import RawSQL
|
||||
|
||||
class Name(models.Model):
|
||||
class User(models.Model):
|
||||
pass
|
||||
|
||||
def save_name(request):
|
||||
def show_user(request, username):
|
||||
with connection.cursor() as cursor:
|
||||
# GOOD -- Using parameters
|
||||
cursor.execute("SELECT * FROM users WHERE username = %s", username)
|
||||
User.objects.raw("SELECT * FROM users WHERE username = %s", (username,))
|
||||
|
||||
if request.method == 'POST':
|
||||
name = request.POST.get('name')
|
||||
curs = connection.cursor()
|
||||
#GOOD -- Using parameters
|
||||
curs.execute(
|
||||
"insert into names_file ('name') values ('%s')", name)
|
||||
#BAD -- Using string formatting
|
||||
curs.execute(
|
||||
"insert into names_file ('name') values ('%s')" % name)
|
||||
# BAD -- Using string formatting
|
||||
cursor.execute("SELECT * FROM users WHERE username = '%s'" % username)
|
||||
|
||||
#BAD -- other ways of executing raw SQL code with string interpolation
|
||||
Name.objects.annotate(RawSQL("insert into names_file ('name') values ('%s')" % name))
|
||||
Name.objects.raw("insert into names_file ('name') values ('%s')" % name)
|
||||
Name.objects.extra("insert into names_file ('name') values ('%s')" % name)
|
||||
# BAD -- other ways of executing raw SQL code with string interpolation
|
||||
User.objects.annotate(RawSQL("insert into names_file ('name') values ('%s')" % username))
|
||||
User.objects.raw("insert into names_file ('name') values ('%s')" % username)
|
||||
User.objects.extra("insert into names_file ('name') values ('%s')" % username)
|
||||
|
||||
urlpatterns = patterns(url(r'^save_name/$',
|
||||
save_name, name='save_name'))
|
||||
# BAD (but currently no custom query to find this)
|
||||
#
|
||||
# It is exposed to SQL injection (https://docs.djangoproject.com/en/2.2/ref/models/querysets/#extra)
|
||||
# For example, using name = "; DROP ALL TABLES -- "
|
||||
# will result in SQL: SELECT * FROM name WHERE name = ''; DROP ALL TABLES -- ''
|
||||
#
|
||||
# This shouldn't be very widespread, since using a normal string will result in invalid SQL
|
||||
# Using name = "example", will result in SQL: SELECT * FROM name WHERE name = ''example''
|
||||
# which in MySQL will give a syntax error
|
||||
#
|
||||
# When testing this out locally, none of the queries worked against SQLite3, but I could use
|
||||
# the SQL injection against MySQL.
|
||||
User.objects.raw("SELECT * FROM users WHERE username = '%s'", (username,))
|
||||
|
||||
urlpatterns = [url(r'^users/(?P<username>[^/]+)$', show_user)]
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
def url(pattern, *args):
|
||||
# https://docs.djangoproject.com/en/1.11/_modules/django/conf/urls/#url
|
||||
def url(regex, view, kwargs=None, name=None):
|
||||
pass
|
||||
|
||||
def patterns(*urls):
|
||||
pass
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
class View:
|
||||
pass
|
Загрузка…
Ссылка в новой задаче