Merge pull request #2129 from RasmusWL/python-update-django

Python: update django support
This commit is contained in:
Taus 2019-11-05 20:51:55 +01:00 коммит произвёл GitHub
Родитель c90fa1b3b1 fc851b46c3
Коммит e9336fe30e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
35 изменённых файлов: 301 добавлений и 315 удалений

Просмотреть файл

@ -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