coretrace-python-analyzer 0.11.0__py3-none-any.whl → 0.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coretrace_python/__init__.py +1 -1
- coretrace_python/bundled/models/aiohttp/aiohttp_models.py +22 -10
- coretrace_python/bundled/models/bottle/bottle_models.py +10 -6
- coretrace_python/bundled/models/cli/cli_models.py +4 -4
- coretrace_python/bundled/models/django/django_models.py +19 -0
- coretrace_python/bundled/models/flask/flask_models.py +12 -9
- coretrace_python/bundled/models/python_stdlib/python_stdlib.py +5 -3
- coretrace_python/bundled/models/tornado/tornado_models.py +25 -5
- coretrace_python/bundled/security/nosql_injection/nosql_injection.py +20 -0
- coretrace_python/bundled/security/nosql_injection/plugin.toml +9 -0
- coretrace_python/engine.py +7 -1
- coretrace_python/interprocedural/callgraph.py +14 -12
- coretrace_python/ir/lowering.py +1 -1
- coretrace_python/semantic/__init__.py +3 -3
- coretrace_python/semantic/symbols.py +106 -16
- coretrace_python/taint/__init__.py +6 -0
- coretrace_python/taint/engine.py +105 -15
- coretrace_python/taint/models.py +41 -1
- {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.13.0.dist-info}/METADATA +1 -1
- {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.13.0.dist-info}/RECORD +24 -22
- {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.13.0.dist-info}/WHEEL +0 -0
- {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.13.0.dist-info}/entry_points.txt +0 -0
- {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.13.0.dist-info}/licenses/LICENSE +0 -0
- {coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.13.0.dist-info}/licenses/NOTICE +0 -0
coretrace_python/__init__.py
CHANGED
|
@@ -12,6 +12,7 @@ from coretrace_python.semantic.symbols import SymbolId
|
|
|
12
12
|
from coretrace_python.taint import (
|
|
13
13
|
EntryPoint,
|
|
14
14
|
Model,
|
|
15
|
+
RequestObject,
|
|
15
16
|
RouteRegistrar,
|
|
16
17
|
Sink,
|
|
17
18
|
Source,
|
|
@@ -22,6 +23,23 @@ from coretrace_python.taint import (
|
|
|
22
23
|
_METHODS = ("get", "post", "put", "patch", "delete", "head", "options")
|
|
23
24
|
_REQUEST_CLASSES = ("aiohttp.web.Request", "aiohttp.web_request.Request")
|
|
24
25
|
_ROUTERS = ("aiohttp.web.Application.router", "aiohttp.web.UrlDispatcher")
|
|
26
|
+
# ``@routes.get('/')`` on a ``RouteTableDef``.
|
|
27
|
+
_ROUTE_TABLES = tuple(f"aiohttp.web.RouteTableDef.{method}" for method in (*_METHODS, "route", "view"))
|
|
28
|
+
# ``app.router.add_route('GET', '/', handler)``, ``app.router.add_get('/', handler)`` and
|
|
29
|
+
# ``app.add_routes([web.get('/', handler)])``, each with the argument naming the handler.
|
|
30
|
+
_ROUTE_CALLS = (
|
|
31
|
+
*((f"{router}.add_route", 2) for router in _ROUTERS),
|
|
32
|
+
*((f"{router}.add_{method}", 1) for router in _ROUTERS for method in (*_METHODS, "view")),
|
|
33
|
+
("aiohttp.web.route", 2),
|
|
34
|
+
*((f"aiohttp.web.{method}", 1) for method in (*_METHODS, "view")),
|
|
35
|
+
)
|
|
36
|
+
# What a request gives as text; its body (``json()``, ``text()``, ``read()``) may hold a
|
|
37
|
+
# structure.
|
|
38
|
+
_REQUEST_TEXT = (
|
|
39
|
+
"query", "rel_url", "url", "path", "raw_path", "path_qs", "query_string", "match_info", "headers",
|
|
40
|
+
"raw_headers", "cookies", "method", "host", "scheme", "remote", "forwarded", "content_type", "charset",
|
|
41
|
+
"if_modified_since", "post",
|
|
42
|
+
)
|
|
25
43
|
_REDIRECTS = ("HTTPFound", "HTTPMovedPermanently", "HTTPSeeOther", "HTTPTemporaryRedirect", "HTTPPermanentRedirect")
|
|
26
44
|
_CLIENT_FUNCTIONS = (
|
|
27
45
|
*(f"aiohttp.ClientSession.{method}" for method in (*_METHODS, "request")),
|
|
@@ -40,18 +58,12 @@ class AiohttpModels(ModelPlugin):
|
|
|
40
58
|
# The whole request is attacker-controlled: its query, match info, headers,
|
|
41
59
|
# cookies and body all come from the client.
|
|
42
60
|
*(TypedParameter(_sym(cls), "http") for cls in _REQUEST_CLASSES),
|
|
43
|
-
|
|
44
|
-
*(
|
|
45
|
-
# ``app.router.add_route('GET', '/', handler)`` and ``app.router.add_get('/', handler)``.
|
|
46
|
-
*(RouteRegistrar(_sym(f"{router}.add_route"), 2, "http", keyword="handler") for router in _ROUTERS),
|
|
61
|
+
*(EntryPoint(_sym(table), "http") for table in _ROUTE_TABLES),
|
|
62
|
+
*(RouteRegistrar(_sym(call), argument, "http", keyword="handler") for call, argument in _ROUTE_CALLS),
|
|
47
63
|
*(
|
|
48
|
-
|
|
49
|
-
for
|
|
50
|
-
for method in (*_METHODS, "view")
|
|
64
|
+
RequestObject(_sym(symbol), _REQUEST_TEXT)
|
|
65
|
+
for symbol in (*_REQUEST_CLASSES, *_ROUTE_TABLES, *(call for call, _ in _ROUTE_CALLS))
|
|
51
66
|
),
|
|
52
|
-
# ``app.add_routes([web.get('/', handler)])``: the route definitions name the handler.
|
|
53
|
-
RouteRegistrar(_sym("aiohttp.web.route"), 2, "http", keyword="handler"),
|
|
54
|
-
*(RouteRegistrar(_sym(f"aiohttp.web.{method}"), 1, "http", keyword="handler") for method in (*_METHODS, "view")),
|
|
55
67
|
Sink(_sym("aiohttp.web.Response"), TaintKind.HTML),
|
|
56
68
|
Sink(_sym("aiohttp.web.FileResponse"), TaintKind.PATH),
|
|
57
69
|
*(Sink(_sym(f"aiohttp.web.{exception}"), TaintKind.REDIRECT) for exception in _REDIRECTS),
|
|
@@ -8,13 +8,15 @@ from typing import ClassVar
|
|
|
8
8
|
|
|
9
9
|
from coretrace_python.plugins import ModelPlugin
|
|
10
10
|
from coretrace_python.semantic.symbols import SymbolId
|
|
11
|
-
from coretrace_python.taint import EntryPoint, Model, Sanitizer, Sink, Source, TaintKind
|
|
11
|
+
from coretrace_python.taint import TEXT_KINDS, EntryPoint, Model, Sanitizer, Sink, Source, TaintKind
|
|
12
12
|
|
|
13
13
|
_METHODS = ("route", "get", "post", "put", "delete", "patch")
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
# Text, which cannot hold a query operator; JSON, raw bodies and files may hold a structure.
|
|
15
|
+
_TEXT_ATTRIBUTES = (
|
|
16
|
+
"query", "forms", "params", "headers", "cookies", "GET", "POST",
|
|
16
17
|
"url", "path", "fullpath", "query_string", "get_cookie", "get_header",
|
|
17
18
|
)
|
|
19
|
+
_PAYLOAD_ATTRIBUTES = ("json", "body", "files")
|
|
18
20
|
_FIRST_ONLY = lambda kind: ((kind, (0,)),)
|
|
19
21
|
|
|
20
22
|
|
|
@@ -25,9 +27,11 @@ def _sym(path: str) -> SymbolId:
|
|
|
25
27
|
class BottleModels(ModelPlugin):
|
|
26
28
|
name: ClassVar[str] = "bottle-models"
|
|
27
29
|
models: ClassVar[tuple[Model, ...]] = (
|
|
28
|
-
|
|
29
|
-
*(EntryPoint(_sym(f"bottle.
|
|
30
|
-
*(
|
|
30
|
+
# A route's parameters are segments of its URL.
|
|
31
|
+
*(EntryPoint(_sym(f"bottle.{method}"), "http", TEXT_KINDS) for method in _METHODS),
|
|
32
|
+
*(EntryPoint(_sym(f"bottle.Bottle.{method}"), "http", TEXT_KINDS) for method in _METHODS),
|
|
33
|
+
*(Source(_sym(f"bottle.request.{attribute}"), "http", TEXT_KINDS) for attribute in _TEXT_ATTRIBUTES),
|
|
34
|
+
*(Source(_sym(f"bottle.request.{attribute}"), "http") for attribute in _PAYLOAD_ATTRIBUTES),
|
|
31
35
|
Sink(_sym("bottle.redirect"), TaintKind.REDIRECT, _FIRST_ONLY(TaintKind.REDIRECT)),
|
|
32
36
|
# ``static_file(filename, root)``: the root is the application's own.
|
|
33
37
|
Sink(_sym("bottle.static_file"), TaintKind.PATH, _FIRST_ONLY(TaintKind.PATH)),
|
|
@@ -6,11 +6,11 @@ from typing import ClassVar
|
|
|
6
6
|
|
|
7
7
|
from coretrace_python.plugins import ModelPlugin
|
|
8
8
|
from coretrace_python.semantic.symbols import SymbolId
|
|
9
|
-
from coretrace_python.taint import EntryPoint, Model, TaintKind
|
|
9
|
+
from coretrace_python.taint import TEXT_KINDS, EntryPoint, Model, TaintKind
|
|
10
10
|
|
|
11
|
-
# A command-line tool is expected to open the paths it is given: argv input
|
|
12
|
-
#
|
|
13
|
-
ARGV_KINDS =
|
|
11
|
+
# A command-line tool is expected to open the paths it is given: argv input is text,
|
|
12
|
+
# without PATH.
|
|
13
|
+
ARGV_KINDS = TEXT_KINDS & ~TaintKind.PATH
|
|
14
14
|
|
|
15
15
|
_COMMANDS = (
|
|
16
16
|
"click.command",
|
|
@@ -17,6 +17,7 @@ from coretrace_python.taint import (
|
|
|
17
17
|
AuthorizationGuard,
|
|
18
18
|
EntryPoint,
|
|
19
19
|
Model,
|
|
20
|
+
RequestObject,
|
|
20
21
|
RouteRegistrar,
|
|
21
22
|
Sanitizer,
|
|
22
23
|
Sink,
|
|
@@ -88,6 +89,23 @@ _AUTHORIZATION_DECORATORS = (
|
|
|
88
89
|
)
|
|
89
90
|
|
|
90
91
|
|
|
92
|
+
# What a request object gives as text: the query string, form fields, headers, cookies
|
|
93
|
+
# and path. Its body, its files and REST framework's parsed ``data`` may hold a structure.
|
|
94
|
+
_REQUEST_TEXT = (
|
|
95
|
+
"GET", "POST", "COOKIES", "META", "headers", "path", "path_info", "method", "scheme", "encoding",
|
|
96
|
+
"content_type", "content_params", "resolver_match", "get_full_path", "get_full_path_info",
|
|
97
|
+
"build_absolute_uri", "get_host", "get_port", "get_signed_cookie", "query_params",
|
|
98
|
+
)
|
|
99
|
+
# Every input a request object comes from: an annotated parameter, a class-based view's
|
|
100
|
+
# method or ``self.request``, a decorated view, a view registered in a URL configuration.
|
|
101
|
+
_REQUEST_INPUTS = (
|
|
102
|
+
*_REQUEST_CLASSES,
|
|
103
|
+
*_VIEW_BASES,
|
|
104
|
+
*(f"{base}.request" for base in _VIEW_BASES),
|
|
105
|
+
*_VIEW_DECORATORS,
|
|
106
|
+
*_ROUTE_REGISTRARS,
|
|
107
|
+
)
|
|
108
|
+
|
|
91
109
|
_TARGET_ONLY = ((TaintKind.REDIRECT, (0,)),)
|
|
92
110
|
|
|
93
111
|
|
|
@@ -103,6 +121,7 @@ class DjangoModels(ModelPlugin):
|
|
|
103
121
|
# ``self.request`` in a class-based view: the attribute is inherited from the base.
|
|
104
122
|
*(Source(_sym(f"{base}.request"), "http") for base in _VIEW_BASES),
|
|
105
123
|
*(EntryPoint(_sym(decorator), "http") for decorator in _VIEW_DECORATORS),
|
|
124
|
+
*(RequestObject(_sym(symbol), _REQUEST_TEXT) for symbol in _REQUEST_INPUTS),
|
|
106
125
|
Sink(_sym("django.db.connection.cursor.execute"), TaintKind.SQL | TaintKind.CREDENTIAL, ((TaintKind.SQL, (0,)),)),
|
|
107
126
|
Sink(_sym("django.db.connection.cursor.executemany"), TaintKind.SQL | TaintKind.CREDENTIAL, ((TaintKind.SQL, (0,)),)),
|
|
108
127
|
Sink(_sym("django.db.models.expressions.RawSQL"), TaintKind.SQL | TaintKind.CREDENTIAL),
|
|
@@ -7,6 +7,7 @@ from typing import ClassVar
|
|
|
7
7
|
from coretrace_python.plugins import ModelPlugin
|
|
8
8
|
from coretrace_python.semantic.symbols import SymbolId
|
|
9
9
|
from coretrace_python.taint import (
|
|
10
|
+
TEXT_KINDS,
|
|
10
11
|
AuthorizationGuard,
|
|
11
12
|
EntryPoint,
|
|
12
13
|
Model,
|
|
@@ -17,10 +18,10 @@ from coretrace_python.taint import (
|
|
|
17
18
|
TaintKind,
|
|
18
19
|
)
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
)
|
|
21
|
+
# Strings, or mappings of strings: text, which cannot hold a query operator.
|
|
22
|
+
_TEXT_ATTRIBUTES = ("args", "form", "values", "cookies", "headers", "url", "full_path", "path", "query_string")
|
|
23
|
+
# JSON, and the raw bodies and files a ``json.loads`` may decode into a structure.
|
|
24
|
+
_PAYLOAD_ATTRIBUTES = ("json", "get_json", "data", "get_data", "stream", "files")
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
_TARGET_ONLY = ((TaintKind.REDIRECT, (0,)),)
|
|
@@ -33,11 +34,13 @@ def _sym(path: str) -> SymbolId:
|
|
|
33
34
|
class FlaskModels(ModelPlugin):
|
|
34
35
|
name: ClassVar[str] = "flask-models"
|
|
35
36
|
models: ClassVar[tuple[Model, ...]] = (
|
|
36
|
-
*(Source(_sym(f"flask.request.{attribute}"), "http") for attribute in
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
*(Source(_sym(f"flask.request.{attribute}"), "http", TEXT_KINDS) for attribute in _TEXT_ATTRIBUTES),
|
|
38
|
+
*(Source(_sym(f"flask.request.{attribute}"), "http") for attribute in _PAYLOAD_ATTRIBUTES),
|
|
39
|
+
# A route's parameters are segments of its URL.
|
|
40
|
+
EntryPoint(_sym("flask.Flask.route"), "http", TEXT_KINDS),
|
|
41
|
+
EntryPoint(_sym("flask.Blueprint.route"), "http", TEXT_KINDS),
|
|
42
|
+
RouteRegistrar(_sym("flask.Flask.add_url_rule"), 2, "http", TEXT_KINDS, keyword="view_func"),
|
|
43
|
+
RouteRegistrar(_sym("flask.Blueprint.add_url_rule"), 2, "http", TEXT_KINDS, keyword="view_func"),
|
|
41
44
|
Sink(_sym("flask.render_template_string"), TaintKind.HTML),
|
|
42
45
|
Sink(_sym("flask.make_response"), TaintKind.HTML),
|
|
43
46
|
Sink(_sym("flask.Response"), TaintKind.HTML),
|
|
@@ -7,6 +7,7 @@ from typing import ClassVar
|
|
|
7
7
|
from coretrace_python.plugins import ModelPlugin
|
|
8
8
|
from coretrace_python.semantic.symbols import SymbolId
|
|
9
9
|
from coretrace_python.taint import (
|
|
10
|
+
TEXT_KINDS,
|
|
10
11
|
Model,
|
|
11
12
|
SafeArgument,
|
|
12
13
|
Sanitizer,
|
|
@@ -16,7 +17,7 @@ from coretrace_python.taint import (
|
|
|
16
17
|
Validator,
|
|
17
18
|
)
|
|
18
19
|
|
|
19
|
-
_ENVIRONMENT_KINDS =
|
|
20
|
+
_ENVIRONMENT_KINDS = TEXT_KINDS & ~(TaintKind.COMMAND | TaintKind.PATH)
|
|
20
21
|
# SafeLoader and BaseLoader build plain data only, in Python and in C, under every spelling.
|
|
21
22
|
_SAFE_YAML_LOADERS = (
|
|
22
23
|
"python.yaml.SafeLoader",
|
|
@@ -38,13 +39,14 @@ def _sym(path: str) -> SymbolId:
|
|
|
38
39
|
class PythonStdlibModels(ModelPlugin):
|
|
39
40
|
name: ClassVar[str] = "python-stdlib-models"
|
|
40
41
|
models: ClassVar[tuple[Model, ...]] = (
|
|
41
|
-
|
|
42
|
+
# A line of text; the stream may be a payload ``json.load`` decodes.
|
|
43
|
+
Source(_sym("builtins.input"), "stdin", TEXT_KINDS),
|
|
42
44
|
Source(_sym("sys.stdin"), "stdin"),
|
|
43
45
|
# Operator-controlled inputs. A command-line tool is expected to open the paths
|
|
44
46
|
# it is given; the environment is set by whoever runs the program, so a command
|
|
45
47
|
# or a path built from it is not an injection; the output of a local process is
|
|
46
48
|
# not a path either, but a downloaded script piped into a shell is a real flaw.
|
|
47
|
-
Source(_sym("sys.argv"), "argv",
|
|
49
|
+
Source(_sym("sys.argv"), "argv", TEXT_KINDS & ~TaintKind.PATH),
|
|
48
50
|
Source(_sym("os.environ"), "environment", _ENVIRONMENT_KINDS),
|
|
49
51
|
Source(_sym("subprocess.run.stdout"), "process-output", _PROCESS_OUTPUT_KINDS),
|
|
50
52
|
Source(_sym("subprocess.check_output"), "process-output", _PROCESS_OUTPUT_KINDS),
|
|
@@ -8,12 +8,27 @@ from typing import ClassVar
|
|
|
8
8
|
|
|
9
9
|
from coretrace_python.plugins import ModelPlugin
|
|
10
10
|
from coretrace_python.semantic.symbols import SymbolId
|
|
11
|
-
from coretrace_python.taint import
|
|
11
|
+
from coretrace_python.taint import (
|
|
12
|
+
TEXT_KINDS,
|
|
13
|
+
EntryPoint,
|
|
14
|
+
Model,
|
|
15
|
+
RequestObject,
|
|
16
|
+
Sanitizer,
|
|
17
|
+
Sink,
|
|
18
|
+
Source,
|
|
19
|
+
TaintKind,
|
|
20
|
+
)
|
|
12
21
|
|
|
13
22
|
_HANDLERS = ("tornado.web.RequestHandler", "tornado.websocket.WebSocketHandler")
|
|
14
|
-
|
|
23
|
+
# Text, which cannot hold a query operator; the request object holds the raw body too.
|
|
24
|
+
_TEXT_METHODS = (
|
|
15
25
|
"get_argument", "get_arguments", "get_query_argument", "get_query_arguments",
|
|
16
|
-
"get_body_argument", "get_body_arguments", "get_cookie", "
|
|
26
|
+
"get_body_argument", "get_body_arguments", "get_cookie", "path_args", "path_kwargs",
|
|
27
|
+
)
|
|
28
|
+
# What ``self.request`` gives as text; its body and files may hold a structure.
|
|
29
|
+
_REQUEST_TEXT = (
|
|
30
|
+
"arguments", "query_arguments", "body_arguments", "query", "uri", "path", "headers", "cookies",
|
|
31
|
+
"method", "host", "host_name", "remote_ip", "protocol", "version", "full_url",
|
|
17
32
|
)
|
|
18
33
|
_CLIENTS = ("tornado.httpclient.AsyncHTTPClient.fetch", "tornado.httpclient.HTTPClient.fetch")
|
|
19
34
|
_TARGET_ONLY = ((TaintKind.REDIRECT, (0,)),)
|
|
@@ -26,8 +41,13 @@ def _sym(path: str) -> SymbolId:
|
|
|
26
41
|
class TornadoModels(ModelPlugin):
|
|
27
42
|
name: ClassVar[str] = "tornado-models"
|
|
28
43
|
models: ClassVar[tuple[Model, ...]] = (
|
|
29
|
-
|
|
30
|
-
|
|
44
|
+
# A request handler's parameters are segments of its URL; a WebSocket handler also
|
|
45
|
+
# receives the messages, raw payloads a ``json.loads`` may decode.
|
|
46
|
+
EntryPoint(_sym("tornado.web.RequestHandler"), "http", TEXT_KINDS),
|
|
47
|
+
EntryPoint(_sym("tornado.websocket.WebSocketHandler"), "http"),
|
|
48
|
+
*(Source(_sym(f"{handler}.{method}"), "http", TEXT_KINDS) for handler in _HANDLERS for method in _TEXT_METHODS),
|
|
49
|
+
*(Source(_sym(f"{handler}.request"), "http") for handler in _HANDLERS),
|
|
50
|
+
*(RequestObject(_sym(f"{handler}.request"), _REQUEST_TEXT) for handler in _HANDLERS),
|
|
31
51
|
*(Sink(_sym(f"{handler}.write"), TaintKind.HTML) for handler in _HANDLERS),
|
|
32
52
|
*(Sink(_sym(f"{handler}.finish"), TaintKind.HTML) for handler in _HANDLERS),
|
|
33
53
|
*(Sink(_sym(f"{handler}.redirect"), TaintKind.REDIRECT, _TARGET_ONLY) for handler in _HANDLERS),
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""NoSQL injection: attacker input that may hold a structure reaching a NOSQL sink, such
|
|
2
|
+
as a MongoDB filter, where a JSON body giving ``{"$ne": null}`` matches any value. Text
|
|
3
|
+
input carries no ``NOSQL``: a string cannot hold an operator. The engine ships no NOSQL
|
|
4
|
+
sink; model plugins declare them."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import ClassVar
|
|
9
|
+
|
|
10
|
+
from coretrace_python.findings import Severity
|
|
11
|
+
from coretrace_python.plugins import TaintDetector
|
|
12
|
+
from coretrace_python.taint import TaintKind
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class NoSqlInjectionPlugin(TaintDetector):
|
|
16
|
+
name: ClassVar[str] = "nosql-injection"
|
|
17
|
+
rule_id: ClassVar[str] = "nosql-injection"
|
|
18
|
+
kind: ClassVar[TaintKind] = TaintKind.NOSQL
|
|
19
|
+
severity: ClassVar[Severity] = Severity.HIGH
|
|
20
|
+
title: ClassVar[str] = "NoSQL injection"
|
coretrace_python/engine.py
CHANGED
|
@@ -109,7 +109,7 @@ from coretrace_python.reporters import Report
|
|
|
109
109
|
from coretrace_python.semantic import SEMANTIC_ANALYSES
|
|
110
110
|
from coretrace_python.semantic.imports import ImportAnalysis, ImportResolutionError, ImportTable
|
|
111
111
|
from coretrace_python.semantic.scopes import ScopeAnalysis, ScopeError
|
|
112
|
-
from coretrace_python.semantic.symbols import SymbolAnalysis, SymbolId
|
|
112
|
+
from coretrace_python.semantic.symbols import MembersAnalysis, SymbolAnalysis, SymbolId
|
|
113
113
|
from coretrace_python.source import SourceFile, SourceId, SourceManager, SourceSpan, decode_text
|
|
114
114
|
from coretrace_python.taint import (
|
|
115
115
|
EntryPoint,
|
|
@@ -257,6 +257,7 @@ def build_manager(
|
|
|
257
257
|
manager = _register_all(module)
|
|
258
258
|
table = (models or SecurityModelRegistry()).freeze()
|
|
259
259
|
manager.provide(SecurityModelAnalysis, table)
|
|
260
|
+
manager.provide(MembersAnalysis, table.members_by_class())
|
|
260
261
|
manager.provide(ClearingAnalysis, table.clearing())
|
|
261
262
|
manager.provide(ProjectSummaries, SummaryIndex())
|
|
262
263
|
manager.provide(RegisteredRoutes, _routes_of(manager))
|
|
@@ -311,6 +312,7 @@ def analyze_file(source: SourceFile, plugin_roots: Sequence[Path]) -> FileAnalys
|
|
|
311
312
|
registry = load_plugins(plugin_roots, manager)
|
|
312
313
|
table = plugin_models(loaded.plugin for loaded in registry)
|
|
313
314
|
manager.provide(SecurityModelAnalysis, table)
|
|
315
|
+
manager.provide(MembersAnalysis, table.members_by_class())
|
|
314
316
|
manager.provide(ClearingAnalysis, table.clearing())
|
|
315
317
|
manager.provide(ProjectSummaries, SummaryIndex())
|
|
316
318
|
manager.provide(RegisteredRoutes, _routes_of(manager))
|
|
@@ -402,8 +404,10 @@ def analyze_project(
|
|
|
402
404
|
advisories, origins = _merge_advisories(_contributions(registry, plugins, root, file_advisories))
|
|
403
405
|
affected = affected_symbols(dependencies, advisories)
|
|
404
406
|
models = plugin_models(all_plugins, root).extended(*advisory_sinks(affected))
|
|
407
|
+
members = models.members_by_class()
|
|
405
408
|
for manager in managers.values():
|
|
406
409
|
manager.provide(SecurityModelAnalysis, models)
|
|
410
|
+
manager.provide(MembersAnalysis, members)
|
|
407
411
|
manager.provide(DependencyAnalysis, dependencies)
|
|
408
412
|
|
|
409
413
|
imports: dict[str, ImportTable] = {}
|
|
@@ -698,8 +702,10 @@ def _analyse_batch(batch: _Batch) -> dict[str, dict[str, Any]]:
|
|
|
698
702
|
models = plugin_models(all_plugins, batch.root).extended(*advisory_sinks(affected))
|
|
699
703
|
routes = _decode_routes(batch.routes)
|
|
700
704
|
escaped = frozenset(batch.escaped)
|
|
705
|
+
members = models.members_by_class()
|
|
701
706
|
for manager in managers.values():
|
|
702
707
|
manager.provide(SecurityModelAnalysis, models)
|
|
708
|
+
manager.provide(MembersAnalysis, members)
|
|
703
709
|
manager.provide(DependencyAnalysis, dependencies)
|
|
704
710
|
manager.provide(RegisteredRoutes, routes)
|
|
705
711
|
manager.provide(EscapedTemplates, escaped)
|
|
@@ -176,12 +176,12 @@ class CallGraph:
|
|
|
176
176
|
|
|
177
177
|
|
|
178
178
|
def derive_symbols(
|
|
179
|
-
function: FunctionIR, initial: Mapping[Value, SymbolId] | None = None
|
|
179
|
+
function: FunctionIR, table: SymbolTable, initial: Mapping[Value, SymbolId] | None = None
|
|
180
180
|
) -> dict[Value, SymbolId]:
|
|
181
|
-
"""Symbols of values: ``Symbol`` results, attributes and items of symbol values,
|
|
182
|
-
results of calling a symbol (``sqlite3.connect(p)`` denotes
|
|
183
|
-
and the values a ``with`` on such a result binds. Known
|
|
184
|
-
parameters only through ``initial``, their annotated classes."""
|
|
181
|
+
"""Symbols of values: ``Symbol`` results, attributes and items of symbol values, as
|
|
182
|
+
``table`` types them, results of calling a symbol (``sqlite3.connect(p)`` denotes
|
|
183
|
+
``python.sqlite3.connect``) and the values a ``with`` on such a result binds. Known
|
|
184
|
+
functions derive nothing; parameters only through ``initial``, their annotated classes."""
|
|
185
185
|
|
|
186
186
|
symbols: dict[Value, SymbolId] = dict(initial or {})
|
|
187
187
|
changed = True
|
|
@@ -195,11 +195,9 @@ def derive_symbols(
|
|
|
195
195
|
if isinstance(instruction, Symbol):
|
|
196
196
|
symbol = instruction.symbol_id
|
|
197
197
|
elif isinstance(instruction, GetAttr) and instruction.object in symbols:
|
|
198
|
-
symbol = symbols[instruction.object]
|
|
198
|
+
symbol = table.attribute(symbols[instruction.object], instruction.attribute)
|
|
199
199
|
elif isinstance(instruction, GetItem) and instruction.object in symbols:
|
|
200
|
-
|
|
201
|
-
# carries the container's symbol, so its methods resolve.
|
|
202
|
-
symbol = symbols[instruction.object]
|
|
200
|
+
symbol = table.item(symbols[instruction.object])
|
|
203
201
|
elif isinstance(instruction, Call | WithEnter):
|
|
204
202
|
origin = (
|
|
205
203
|
instruction.callee if isinstance(instruction, Call) else instruction.context
|
|
@@ -263,6 +261,8 @@ def resolve_targets(
|
|
|
263
261
|
owner: str | None = None,
|
|
264
262
|
typed: Mapping[Value, str] | None = None,
|
|
265
263
|
bases: Mapping[str, SymbolId] | None = None,
|
|
264
|
+
*,
|
|
265
|
+
table: SymbolTable,
|
|
266
266
|
) -> tuple[dict[Value, Target], dict[Value, SymbolId]]:
|
|
267
267
|
"""Map every callee value of ``function`` to its target, and every symbol value.
|
|
268
268
|
``nested`` names the functions defined inside this one (``outer.inner``);
|
|
@@ -271,7 +271,8 @@ def resolve_targets(
|
|
|
271
271
|
call to ``App.__init__``, ``app.run()`` and ``self.run()`` calls to ``App.run``.
|
|
272
272
|
``bases`` maps a module class to the symbol of its base: an attribute the class does
|
|
273
273
|
not define is inherited, so ``self.get_argument`` in a ``RequestHandler`` subclass
|
|
274
|
-
denotes ``tornado.web.RequestHandler.get_argument``.
|
|
274
|
+
denotes ``tornado.web.RequestHandler.get_argument``. ``table`` says what the
|
|
275
|
+
attributes and items of a class a ``Members`` model describes give."""
|
|
275
276
|
|
|
276
277
|
module = scopes.module_scope
|
|
277
278
|
classes = classes or {}
|
|
@@ -294,8 +295,8 @@ def resolve_targets(
|
|
|
294
295
|
class_name = instance_of[instruction.object]
|
|
295
296
|
base = (bases or {}).get(class_name)
|
|
296
297
|
if base is not None and instruction.attribute not in classes.get(class_name, ()):
|
|
297
|
-
inherited[instruction.result] =
|
|
298
|
-
symbols = derive_symbols(function, inherited)
|
|
298
|
+
inherited[instruction.result] = table.attribute(base, instruction.attribute)
|
|
299
|
+
symbols = derive_symbols(function, table, inherited)
|
|
299
300
|
targets: dict[Value, Target] = {
|
|
300
301
|
value: ExternalSymbol(symbol) for value, symbol in symbols.items()
|
|
301
302
|
}
|
|
@@ -427,6 +428,7 @@ class CallGraphAnalysis(Analysis[CallGraph]):
|
|
|
427
428
|
owner,
|
|
428
429
|
_typed_with_module_classes(function, ssa, classes),
|
|
429
430
|
bases,
|
|
431
|
+
table=table,
|
|
430
432
|
)
|
|
431
433
|
defs = {i.result: i for block in ssa.blocks for i in block.instructions if i.result is not None}
|
|
432
434
|
found: list[CallSite] = []
|
coretrace_python/ir/lowering.py
CHANGED
|
@@ -141,7 +141,7 @@ class _FunctionLowerer:
|
|
|
141
141
|
return self.symbols.resolve(self.scope.id, node.identifier)
|
|
142
142
|
if isinstance(node, nodes.Attribute):
|
|
143
143
|
parent = self.imported_symbol(node.value)
|
|
144
|
-
return
|
|
144
|
+
return self.symbols.attribute(parent, node.name) if parent is not None else None
|
|
145
145
|
return None
|
|
146
146
|
|
|
147
147
|
def expression(self, node: nodes.Expression) -> Value:
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from coretrace_python.semantic.imports import ImportAnalysis
|
|
4
4
|
from coretrace_python.semantic.scopes import ScopeAnalysis
|
|
5
|
-
from coretrace_python.semantic.symbols import SymbolAnalysis
|
|
5
|
+
from coretrace_python.semantic.symbols import MembersAnalysis, SymbolAnalysis
|
|
6
6
|
|
|
7
|
-
SEMANTIC_ANALYSES = (ScopeAnalysis, ImportAnalysis, SymbolAnalysis)
|
|
7
|
+
SEMANTIC_ANALYSES = (ScopeAnalysis, ImportAnalysis, MembersAnalysis, SymbolAnalysis)
|
|
8
8
|
|
|
9
|
-
__all__ = ["SEMANTIC_ANALYSES", "ImportAnalysis", "ScopeAnalysis", "SymbolAnalysis"]
|
|
9
|
+
__all__ = ["SEMANTIC_ANALYSES", "ImportAnalysis", "MembersAnalysis", "ScopeAnalysis", "SymbolAnalysis"]
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import builtins
|
|
6
6
|
from collections.abc import Mapping
|
|
7
|
+
from dataclasses import dataclass
|
|
7
8
|
from types import MappingProxyType
|
|
8
9
|
from typing import ClassVar
|
|
9
10
|
|
|
@@ -16,19 +17,69 @@ from coretrace_python.semantic.scopes import ResolutionKind, ScopeAnalysis, Scop
|
|
|
16
17
|
BUILTIN_NAMES = frozenset(name for name in dir(builtins) if not name.startswith("_"))
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class Members:
|
|
22
|
+
"""What the attributes and items of an instance of ``symbol`` give, for a class whose
|
|
23
|
+
instances give other objects by name, as a pymongo client gives the database it is
|
|
24
|
+
dotted or indexed with (``client.shop``, ``client['shop']``).
|
|
25
|
+
|
|
26
|
+
``typed`` maps an attribute to the class of what it gives, called or not, since a
|
|
27
|
+
call denotes its callee: ``get_database`` gives a database, as a ``db`` property does.
|
|
28
|
+
``dynamic``, when set, is the class of every item and of every attribute outside
|
|
29
|
+
``defined`` and ``typed``. Otherwise an attribute keeps its path and an item its
|
|
30
|
+
container's symbol, as for any other class."""
|
|
31
|
+
|
|
32
|
+
symbol: SymbolId
|
|
33
|
+
dynamic: SymbolId | None = None
|
|
34
|
+
defined: tuple[str, ...] = ()
|
|
35
|
+
typed: tuple[tuple[str, SymbolId], ...] = ()
|
|
36
|
+
|
|
37
|
+
def __post_init__(self) -> None:
|
|
38
|
+
# Sorted: the models' repr is part of the cache key, the same in every process.
|
|
39
|
+
object.__setattr__(self, "defined", tuple(sorted(set(self.defined))))
|
|
40
|
+
|
|
41
|
+
def attribute(self, name: str) -> SymbolId:
|
|
42
|
+
for attribute, gives in self.typed:
|
|
43
|
+
if attribute == name:
|
|
44
|
+
return gives
|
|
45
|
+
if self.dynamic is not None and name not in self.defined:
|
|
46
|
+
return self.dynamic
|
|
47
|
+
return self.symbol.attribute(name)
|
|
48
|
+
|
|
49
|
+
def item(self) -> SymbolId:
|
|
50
|
+
return self.symbol if self.dynamic is None else self.dynamic
|
|
51
|
+
|
|
52
|
+
|
|
19
53
|
class SymbolTable:
|
|
20
54
|
"""Resolve names to canonical symbols through scopes, imports, builtins and the
|
|
21
|
-
module-level
|
|
55
|
+
module-level names bound to a value denoting a symbol (``app = Flask(__name__)``,
|
|
56
|
+
``cursor = conn.cursor()``). ``members`` holds the ``Members`` models by class."""
|
|
22
57
|
|
|
23
58
|
def __init__(
|
|
24
59
|
self,
|
|
25
60
|
scopes: ScopeTable,
|
|
26
61
|
imports: ImportTable,
|
|
27
62
|
instances: Mapping[str, SymbolId] | None = None,
|
|
63
|
+
members: Mapping[SymbolId, Members] | None = None,
|
|
28
64
|
) -> None:
|
|
29
65
|
self._scopes = scopes
|
|
30
66
|
self._imports = imports
|
|
31
67
|
self._instances = MappingProxyType(dict(instances or {}))
|
|
68
|
+
self._members = MappingProxyType(dict(members or {}))
|
|
69
|
+
|
|
70
|
+
def attribute(self, symbol: SymbolId, name: str) -> SymbolId:
|
|
71
|
+
"""The symbol of attribute ``name`` of what ``symbol`` denotes: its path, unless
|
|
72
|
+
a ``Members`` model describes the class."""
|
|
73
|
+
|
|
74
|
+
members = self._members.get(symbol)
|
|
75
|
+
return symbol.attribute(name) if members is None else members.attribute(name)
|
|
76
|
+
|
|
77
|
+
def item(self, symbol: SymbolId) -> SymbolId:
|
|
78
|
+
"""The symbol of an item of what ``symbol`` denotes: the container's own, so its
|
|
79
|
+
methods resolve (``request.files['f'].save``), unless a ``Members`` model says."""
|
|
80
|
+
|
|
81
|
+
members = self._members.get(symbol)
|
|
82
|
+
return symbol if members is None else members.item()
|
|
32
83
|
|
|
33
84
|
def resolve(self, scope_id: ScopeId, name: str) -> SymbolId | None:
|
|
34
85
|
resolution = self._scopes.resolve(scope_id, name)
|
|
@@ -49,39 +100,78 @@ class SymbolTable:
|
|
|
49
100
|
return self.resolve(scope_id, node.identifier)
|
|
50
101
|
if isinstance(node, nodes.Attribute):
|
|
51
102
|
parent = self.resolve_expression(scope_id, node.value)
|
|
52
|
-
return
|
|
103
|
+
return self.attribute(parent, node.name) if parent is not None else None
|
|
53
104
|
if isinstance(node, nodes.Call):
|
|
54
105
|
return self.resolve_expression(scope_id, node.callee)
|
|
55
106
|
return None
|
|
56
107
|
|
|
57
108
|
|
|
58
109
|
def analyze_symbols(
|
|
59
|
-
scopes: ScopeTable,
|
|
110
|
+
scopes: ScopeTable,
|
|
111
|
+
imports: ImportTable,
|
|
112
|
+
module: nodes.Module | None = None,
|
|
113
|
+
members: Mapping[SymbolId, Members] | None = None,
|
|
60
114
|
) -> SymbolTable:
|
|
61
|
-
table = SymbolTable(scopes, imports)
|
|
115
|
+
table = SymbolTable(scopes, imports, members=members)
|
|
62
116
|
if module is None:
|
|
63
117
|
return table
|
|
64
118
|
instances: dict[str, SymbolId] = {}
|
|
65
119
|
module_scope = scopes.module_scope.id
|
|
66
120
|
for statement in module.body:
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
and isinstance(statement.target, nodes.Name)
|
|
70
|
-
and isinstance(statement.value, nodes.Call)
|
|
71
|
-
):
|
|
72
|
-
symbol = table.resolve_expression(module_scope, statement.value.callee)
|
|
121
|
+
if isinstance(statement, nodes.Assign) and isinstance(statement.target, nodes.Name):
|
|
122
|
+
symbol = _denoted(table, module_scope, statement.value)
|
|
73
123
|
if symbol is not None:
|
|
74
124
|
instances[statement.target.identifier] = symbol
|
|
75
|
-
|
|
125
|
+
# Later bindings resolve through this one, in statement order.
|
|
126
|
+
table = SymbolTable(scopes, imports, instances, members)
|
|
127
|
+
return table
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _denoted(table: SymbolTable, scope_id: ScopeId, node: nodes.Expression) -> SymbolId | None:
|
|
131
|
+
"""What a value denotes, as a function body derives it (``derive_symbols``): a
|
|
132
|
+
resolved name or an attribute of one, the result of calling one, or an item of one,
|
|
133
|
+
which carries its container's symbol (``os.environ['HOME']``) unless a ``Members``
|
|
134
|
+
model says what its items are."""
|
|
135
|
+
|
|
136
|
+
if isinstance(node, nodes.Subscript):
|
|
137
|
+
container = _denoted(table, scope_id, node.value)
|
|
138
|
+
return table.item(container) if container is not None else None
|
|
139
|
+
if isinstance(node, nodes.Attribute):
|
|
140
|
+
parent = _denoted(table, scope_id, node.value)
|
|
141
|
+
return table.attribute(parent, node.name) if parent is not None else None
|
|
142
|
+
if isinstance(node, nodes.Call):
|
|
143
|
+
return _denoted(table, scope_id, node.callee)
|
|
144
|
+
return table.resolve_expression(scope_id, node)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class MembersAnalysis(Analysis[Mapping[SymbolId, Members]]):
|
|
148
|
+
"""The ``Members`` models by class, provided by the engine from the security models;
|
|
149
|
+
none on its own, so every attribute keeps its path."""
|
|
150
|
+
|
|
151
|
+
name: ClassVar[str] = "semantic.members"
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def compute(cls, ctx: AnalysisContext) -> Mapping[SymbolId, Members]:
|
|
155
|
+
return MappingProxyType({})
|
|
76
156
|
|
|
77
157
|
|
|
78
158
|
class SymbolAnalysis(Analysis[SymbolTable]):
|
|
79
159
|
name: ClassVar[str] = "semantic.symbols"
|
|
80
|
-
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({ScopeAnalysis, ImportAnalysis})
|
|
160
|
+
requires: ClassVar[frozenset[AnyAnalysis]] = frozenset({ScopeAnalysis, ImportAnalysis, MembersAnalysis})
|
|
81
161
|
|
|
82
162
|
@classmethod
|
|
83
163
|
def compute(cls, ctx: AnalysisContext) -> SymbolTable:
|
|
84
|
-
return analyze_symbols(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
164
|
+
return analyze_symbols(
|
|
165
|
+
ctx.get(ScopeAnalysis), ctx.get(ImportAnalysis), ctx.module, ctx.get(MembersAnalysis)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
__all__ = [
|
|
170
|
+
"BUILTIN_NAMES",
|
|
171
|
+
"Members",
|
|
172
|
+
"MembersAnalysis",
|
|
173
|
+
"SymbolAnalysis",
|
|
174
|
+
"SymbolId",
|
|
175
|
+
"SymbolTable",
|
|
176
|
+
"analyze_symbols",
|
|
177
|
+
]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Security models and the global multi-kind taint engine (architecture §16, §17)."""
|
|
2
2
|
|
|
3
|
+
from coretrace_python.semantic.symbols import Members
|
|
3
4
|
from coretrace_python.taint.engine import (
|
|
4
5
|
EntryPointAnalysis,
|
|
5
6
|
Taint,
|
|
@@ -9,12 +10,14 @@ from coretrace_python.taint.engine import (
|
|
|
9
10
|
propagate_taint,
|
|
10
11
|
)
|
|
11
12
|
from coretrace_python.taint.models import (
|
|
13
|
+
TEXT_KINDS,
|
|
12
14
|
AuthorizationGuard,
|
|
13
15
|
EntryPoint,
|
|
14
16
|
Model,
|
|
15
17
|
ModelError,
|
|
16
18
|
ModelTable,
|
|
17
19
|
NamedParameter,
|
|
20
|
+
RequestObject,
|
|
18
21
|
RouteRegistrar,
|
|
19
22
|
SafeArgument,
|
|
20
23
|
Sanitizer,
|
|
@@ -32,15 +35,18 @@ from coretrace_python.taint.routes import RegisteredRoutes, Routes, registered_r
|
|
|
32
35
|
from coretrace_python.taint.templates import EscapedTemplates, escaped_templates
|
|
33
36
|
|
|
34
37
|
__all__ = [
|
|
38
|
+
"TEXT_KINDS",
|
|
35
39
|
"AuthorizationGuard",
|
|
36
40
|
"EntryPoint",
|
|
37
41
|
"EntryPointAnalysis",
|
|
38
42
|
"EscapedTemplates",
|
|
43
|
+
"Members",
|
|
39
44
|
"Model",
|
|
40
45
|
"ModelError",
|
|
41
46
|
"ModelTable",
|
|
42
47
|
"NamedParameter",
|
|
43
48
|
"RegisteredRoutes",
|
|
49
|
+
"RequestObject",
|
|
44
50
|
"RouteRegistrar",
|
|
45
51
|
"Routes",
|
|
46
52
|
"SafeArgument",
|
coretrace_python/taint/engine.py
CHANGED
|
@@ -70,10 +70,11 @@ from coretrace_python.ir.model import (
|
|
|
70
70
|
Value,
|
|
71
71
|
)
|
|
72
72
|
from coretrace_python.ir.ssa import SSAAnalysis
|
|
73
|
-
from coretrace_python.semantic.scopes import ScopeAnalysis, ScopeTable
|
|
73
|
+
from coretrace_python.semantic.scopes import ScopeAnalysis, ScopeId, ScopeTable
|
|
74
74
|
from coretrace_python.semantic.symbols import SymbolAnalysis, SymbolId, SymbolTable
|
|
75
75
|
from coretrace_python.source import SourceSpan
|
|
76
76
|
from coretrace_python.taint.models import (
|
|
77
|
+
TEXT_KINDS,
|
|
77
78
|
EntryPoint,
|
|
78
79
|
ModelTable,
|
|
79
80
|
SecurityModelAnalysis,
|
|
@@ -84,6 +85,9 @@ from coretrace_python.taint.models import (
|
|
|
84
85
|
from coretrace_python.taint.routes import RegisteredRoutes, Routes
|
|
85
86
|
from coretrace_python.taint.templates import EscapedTemplates
|
|
86
87
|
|
|
88
|
+
# What only a structure carries, and text does not.
|
|
89
|
+
_STRUCTURED = TaintKind.ALL & ~TEXT_KINDS
|
|
90
|
+
|
|
87
91
|
|
|
88
92
|
@dataclass(frozen=True)
|
|
89
93
|
class Taint:
|
|
@@ -274,19 +278,42 @@ class _TaintProblem(DataflowProblem[State]):
|
|
|
274
278
|
taint = Taint.none()
|
|
275
279
|
symbol = self.symbols.get(instruction.result) if instruction.result else None
|
|
276
280
|
if symbol is not None:
|
|
277
|
-
|
|
278
|
-
if source is not None:
|
|
279
|
-
taint = Taint(source.kinds, frozenset({source}))
|
|
281
|
+
taint = self.covered(symbol)
|
|
280
282
|
if isinstance(instruction, Symbol | Compare):
|
|
281
283
|
return taint
|
|
282
284
|
for operand in instruction.operands():
|
|
283
|
-
|
|
285
|
+
operand_taint = state.get(operand, Taint.none())
|
|
286
|
+
if isinstance(instruction, GetAttr):
|
|
287
|
+
operand_taint = self.attribute(operand_taint, instruction.attribute)
|
|
288
|
+
taint = taint.join(operand_taint)
|
|
284
289
|
if isinstance(instruction, BuildList | BuildTuple | BuildDict | BuildSet):
|
|
285
290
|
# ``[*xs]`` and ``{**d}`` copy the contents of what they unpack.
|
|
286
291
|
for unpacked in instruction.unpacked:
|
|
287
292
|
taint = taint.join(self.deep(unpacked, state))
|
|
288
293
|
return taint
|
|
289
294
|
|
|
295
|
+
def covered(self, symbol: SymbolId) -> Taint:
|
|
296
|
+
"""The taint of the source covering ``symbol``, if any. Below a request source, as
|
|
297
|
+
``self.request.GET`` is, it is the taint of the request attribute read."""
|
|
298
|
+
|
|
299
|
+
source = self.models.source_covering(symbol)
|
|
300
|
+
if source is None:
|
|
301
|
+
return Taint.none()
|
|
302
|
+
taint = Taint(source.kinds, frozenset({source}))
|
|
303
|
+
if symbol == source.symbol:
|
|
304
|
+
return taint
|
|
305
|
+
read = symbol.canonical_name.removeprefix(f"{source.symbol.canonical_name}.").split(".")[0]
|
|
306
|
+
return self.attribute(taint, read)
|
|
307
|
+
|
|
308
|
+
def attribute(self, taint: Taint, name: str) -> Taint:
|
|
309
|
+
"""What attribute ``name`` of a value carrying ``taint`` carries: text when every
|
|
310
|
+
source of the value is a request object listing ``name`` among its text attributes."""
|
|
311
|
+
|
|
312
|
+
requests = [self.models.request_object(source.symbol) for source in taint.sources]
|
|
313
|
+
if taint and all(request is not None and name in request.text for request in requests):
|
|
314
|
+
return taint.without(_STRUCTURED)
|
|
315
|
+
return taint
|
|
316
|
+
|
|
290
317
|
def call(self, call: Call, state: dict[Key, Taint], flows: list[TaintFlow]) -> Taint:
|
|
291
318
|
arguments = tuple(self.deep(a, state) for a in call.arguments)
|
|
292
319
|
keywords = Taint.none()
|
|
@@ -420,11 +447,7 @@ class _TaintProblem(DataflowProblem[State]):
|
|
|
420
447
|
if cleared:
|
|
421
448
|
return everything.without(TaintKind(cleared))
|
|
422
449
|
# A method on a tainted object returns tainted data (``request.args.get``).
|
|
423
|
-
|
|
424
|
-
source = self.models.source_covering(symbol)
|
|
425
|
-
if source is not None:
|
|
426
|
-
return everything.join(Taint(source.kinds, frozenset({source})))
|
|
427
|
-
return everything
|
|
450
|
+
return everything.join(state.get(call.callee, Taint.none())).join(self.covered(symbol))
|
|
428
451
|
|
|
429
452
|
def guarded(self, sink: Sink | None, arguments: Arguments) -> Sink | None:
|
|
430
453
|
"""The sink as this call makes it: an argument declared safe (``yaml.load`` with
|
|
@@ -551,7 +574,12 @@ class _TaintProblem(DataflowProblem[State]):
|
|
|
551
574
|
reaching = taint.kinds & sink.kinds_at(position, keyword)
|
|
552
575
|
if not reaching:
|
|
553
576
|
return
|
|
554
|
-
|
|
577
|
+
# One flow per origin: the parameters of one entry point share its source, with
|
|
578
|
+
# the kinds each can carry (a scalar one no NOSQL); the flow's kinds are the taint's.
|
|
579
|
+
origins: dict[tuple[SymbolId, str], Source] = {}
|
|
580
|
+
for source in sorted(taint.sources, key=lambda s: (str(s.symbol), s.label, s.kinds.value)):
|
|
581
|
+
origins.setdefault((source.symbol, source.label), source)
|
|
582
|
+
for source in origins.values():
|
|
555
583
|
flows.append(
|
|
556
584
|
TaintFlow(
|
|
557
585
|
source,
|
|
@@ -770,17 +798,26 @@ def parameter_sources(
|
|
|
770
798
|
) -> Mapping[int, Source]:
|
|
771
799
|
"""The attacker-controlled parameters of ``function``, by index: every parameter of an
|
|
772
800
|
entry point (``self`` excepted for a method), including one registered elsewhere
|
|
773
|
-
(``routes``), and every parameter annotated with a typed-parameter symbol.
|
|
801
|
+
(``routes``), and every parameter annotated with a typed-parameter symbol. An entry
|
|
802
|
+
point's parameter annotated with scalars is text: FastAPI validates it before the
|
|
803
|
+
handler runs, and a scalar cannot hold a query operator. So are the URL parameters
|
|
804
|
+
a view receives after a request object."""
|
|
774
805
|
|
|
775
806
|
owner = _owner(module, function)
|
|
776
807
|
sources: dict[int, Source] = {}
|
|
808
|
+
scope = scopes.scope_for(function)
|
|
809
|
+
enclosing = scope.parent if scope.parent is not None else scope.id
|
|
777
810
|
entry = function_entry_point(function, module, models, scopes, symbols, instances, routes)
|
|
778
811
|
if entry is not None:
|
|
779
812
|
first = 1 if owner is not None else 0
|
|
813
|
+
request = models.request_object(entry.symbol)
|
|
780
814
|
for index in range(first, len(function.parameters)):
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
815
|
+
annotation = function.parameters[index].annotation
|
|
816
|
+
text = (request is not None and index > first) or (
|
|
817
|
+
annotation is not None and _scalar(annotation, symbols, enclosing)
|
|
818
|
+
)
|
|
819
|
+
kinds = entry.kinds & ~_STRUCTURED if text else entry.kinds
|
|
820
|
+
sources[index] = Source(entry.symbol, entry.label, kinds)
|
|
784
821
|
for index, parameter in enumerate(function.parameters):
|
|
785
822
|
if parameter.annotation is None:
|
|
786
823
|
continue
|
|
@@ -800,6 +837,59 @@ def parameter_sources(
|
|
|
800
837
|
return sources
|
|
801
838
|
|
|
802
839
|
|
|
840
|
+
def _typing(*names: str) -> frozenset[SymbolId]:
|
|
841
|
+
return frozenset(SymbolId(f"python.{module}.{name}") for module in ("typing", "typing_extensions") for name in names)
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
_SCALARS = frozenset(
|
|
845
|
+
SymbolId(f"python.{name}")
|
|
846
|
+
for name in (
|
|
847
|
+
"builtins.str",
|
|
848
|
+
"builtins.int",
|
|
849
|
+
"builtins.float",
|
|
850
|
+
"builtins.bool",
|
|
851
|
+
"builtins.bytes",
|
|
852
|
+
"uuid.UUID",
|
|
853
|
+
"datetime.date",
|
|
854
|
+
"datetime.datetime",
|
|
855
|
+
"datetime.time",
|
|
856
|
+
"datetime.timedelta",
|
|
857
|
+
"decimal.Decimal",
|
|
858
|
+
)
|
|
859
|
+
)
|
|
860
|
+
_CONTAINERS = frozenset(
|
|
861
|
+
SymbolId(f"python.builtins.{name}") for name in ("list", "set", "frozenset", "tuple")
|
|
862
|
+
) | _typing("List", "Set", "FrozenSet", "Tuple", "Sequence")
|
|
863
|
+
_UNIONS = _typing("Optional", "Union")
|
|
864
|
+
_LITERAL = _typing("Literal")
|
|
865
|
+
_ANNOTATED = _typing("Annotated")
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def _scalar(annotation: nodes.Expression, symbols: SymbolTable, scope: ScopeId) -> bool:
|
|
869
|
+
"""Whether a value annotated with ``annotation`` holds scalars only: a scalar class,
|
|
870
|
+
a literal, a container of scalars, a union of them with ``None``, or one of them
|
|
871
|
+
``Annotated`` with metadata. Anything unknown may hold a structure."""
|
|
872
|
+
|
|
873
|
+
if isinstance(annotation, nodes.Constant):
|
|
874
|
+
return annotation.value is None or annotation.value is Ellipsis
|
|
875
|
+
if isinstance(annotation, nodes.BinaryOp):
|
|
876
|
+
return annotation.operator == "bit_or" and all(
|
|
877
|
+
_scalar(side, symbols, scope) for side in (annotation.left, annotation.right)
|
|
878
|
+
)
|
|
879
|
+
if isinstance(annotation, nodes.Subscript):
|
|
880
|
+
origin = symbols.resolve_expression(scope, annotation.value)
|
|
881
|
+
key = annotation.key
|
|
882
|
+
arguments = key.elements if isinstance(key, nodes.Tuple) else (key,)
|
|
883
|
+
if origin in _LITERAL:
|
|
884
|
+
return True
|
|
885
|
+
if origin in _ANNOTATED:
|
|
886
|
+
return _scalar(arguments[0], symbols, scope)
|
|
887
|
+
return (origin in _UNIONS or origin in _CONTAINERS) and all(
|
|
888
|
+
_scalar(argument, symbols, scope) for argument in arguments
|
|
889
|
+
)
|
|
890
|
+
return symbols.resolve_expression(scope, annotation) in _SCALARS
|
|
891
|
+
|
|
892
|
+
|
|
803
893
|
def propagate_taint(
|
|
804
894
|
name: str,
|
|
805
895
|
function: FunctionIR,
|
coretrace_python/taint/models.py
CHANGED
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import hashlib
|
|
12
12
|
import re
|
|
13
|
+
from collections.abc import Mapping
|
|
13
14
|
from dataclasses import dataclass, field
|
|
14
15
|
from enum import Flag, auto
|
|
15
16
|
from types import MappingProxyType
|
|
@@ -17,7 +18,7 @@ from typing import ClassVar
|
|
|
17
18
|
|
|
18
19
|
from coretrace_python.analysis import Analysis, AnalysisContext, MissingInputError
|
|
19
20
|
from coretrace_python.interprocedural import Clearing
|
|
20
|
-
from coretrace_python.semantic.symbols import SymbolId
|
|
21
|
+
from coretrace_python.semantic.symbols import Members, SymbolId
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class TaintKind(Flag):
|
|
@@ -41,6 +42,11 @@ class TaintKind(Flag):
|
|
|
41
42
|
PII = auto()
|
|
42
43
|
|
|
43
44
|
|
|
45
|
+
# What text input carries: a string cannot hold a query operator, only a structure can,
|
|
46
|
+
# so a form field, a header or a URL parameter carries every kind but NOSQL.
|
|
47
|
+
TEXT_KINDS = TaintKind.ALL & ~TaintKind.NOSQL
|
|
48
|
+
|
|
49
|
+
|
|
44
50
|
class ModelError(Exception):
|
|
45
51
|
"""Two models of the same kind claim the same symbol."""
|
|
46
52
|
|
|
@@ -199,6 +205,22 @@ class TemplateRender:
|
|
|
199
205
|
keyword: str = "template_name"
|
|
200
206
|
|
|
201
207
|
|
|
208
|
+
@dataclass(frozen=True)
|
|
209
|
+
class RequestObject:
|
|
210
|
+
"""The inputs from ``symbol`` are request objects; ``symbol`` names an entry point, a
|
|
211
|
+
route registrar, a typed parameter or a source. A view receives the request first
|
|
212
|
+
(after ``self``), then URL parameters, which are text. Reading one of the request's
|
|
213
|
+
``text`` attributes (``GET``, ``headers``) gives text, while its body and files keep the
|
|
214
|
+
kinds of the request: text carries ``TEXT_KINDS``."""
|
|
215
|
+
|
|
216
|
+
symbol: SymbolId
|
|
217
|
+
text: tuple[str, ...] = ()
|
|
218
|
+
|
|
219
|
+
def __post_init__(self) -> None:
|
|
220
|
+
# Sorted: the models' repr is part of the cache key, the same in every process.
|
|
221
|
+
object.__setattr__(self, "text", tuple(sorted(set(self.text))))
|
|
222
|
+
|
|
223
|
+
|
|
202
224
|
@dataclass(frozen=True)
|
|
203
225
|
class AuthorizationGuard:
|
|
204
226
|
"""A decorator, or a condition, that restricts who reaches the code behind it; a
|
|
@@ -221,6 +243,8 @@ Model = (
|
|
|
221
243
|
| SuffixSink
|
|
222
244
|
| SafeArgument
|
|
223
245
|
| TemplateRender
|
|
246
|
+
| Members
|
|
247
|
+
| RequestObject
|
|
224
248
|
)
|
|
225
249
|
|
|
226
250
|
|
|
@@ -238,6 +262,8 @@ class ModelTable:
|
|
|
238
262
|
suffix_sinks: tuple[SuffixSink, ...] = ()
|
|
239
263
|
safe_arguments: tuple[SafeArgument, ...] = ()
|
|
240
264
|
template_renders: tuple[TemplateRender, ...] = ()
|
|
265
|
+
members: tuple[Members, ...] = ()
|
|
266
|
+
request_objects: tuple[RequestObject, ...] = ()
|
|
241
267
|
_by_symbol: dict[type[Model], dict[SymbolId, Model]] = field(
|
|
242
268
|
init=False, repr=False, compare=False
|
|
243
269
|
)
|
|
@@ -254,6 +280,7 @@ class ModelTable:
|
|
|
254
280
|
RouteRegistrar: {m.symbol: m for m in self.route_registrars},
|
|
255
281
|
SafeArgument: {m.symbol: m for m in self.safe_arguments},
|
|
256
282
|
TemplateRender: {m.symbol: m for m in self.template_renders},
|
|
283
|
+
RequestObject: {m.symbol: m for m in self.request_objects},
|
|
257
284
|
}
|
|
258
285
|
object.__setattr__(self, "_by_symbol", MappingProxyType(index))
|
|
259
286
|
|
|
@@ -321,6 +348,8 @@ class ModelTable:
|
|
|
321
348
|
self.suffix_sinks,
|
|
322
349
|
self.safe_arguments,
|
|
323
350
|
self.template_renders,
|
|
351
|
+
self.members,
|
|
352
|
+
self.request_objects,
|
|
324
353
|
)
|
|
325
354
|
|
|
326
355
|
def sanitizer(self, symbol: SymbolId) -> Sanitizer | None:
|
|
@@ -335,6 +364,15 @@ class ModelTable:
|
|
|
335
364
|
found = self._by_symbol[TemplateRender].get(symbol)
|
|
336
365
|
return found if isinstance(found, TemplateRender) else None
|
|
337
366
|
|
|
367
|
+
def request_object(self, symbol: SymbolId) -> RequestObject | None:
|
|
368
|
+
found = self._by_symbol[RequestObject].get(symbol)
|
|
369
|
+
return found if isinstance(found, RequestObject) else None
|
|
370
|
+
|
|
371
|
+
def members_by_class(self) -> Mapping[SymbolId, Members]:
|
|
372
|
+
"""The ``Members`` models by class, as symbol resolution reads them."""
|
|
373
|
+
|
|
374
|
+
return MappingProxyType({m.symbol: m for m in self.members})
|
|
375
|
+
|
|
338
376
|
def clearing(self, escaped: frozenset[str] = frozenset()) -> Clearing:
|
|
339
377
|
"""What calls clear from the data they return: every sanitizer its kinds, every
|
|
340
378
|
template render ``HTML`` when it names one of the ``escaped`` templates."""
|
|
@@ -393,6 +431,8 @@ class SecurityModelRegistry:
|
|
|
393
431
|
suffix_sinks=tuple(m for m in models if isinstance(m, SuffixSink)),
|
|
394
432
|
safe_arguments=tuple(m for m in models if isinstance(m, SafeArgument)),
|
|
395
433
|
template_renders=tuple(m for m in models if isinstance(m, TemplateRender)),
|
|
434
|
+
members=tuple(m for m in models if isinstance(m, Members)),
|
|
435
|
+
request_objects=tuple(m for m in models if isinstance(m, RequestObject)),
|
|
396
436
|
)
|
|
397
437
|
|
|
398
438
|
|
{coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.13.0.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: coretrace-python-analyzer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Static security analysis infrastructure for Python code
|
|
5
5
|
Project-URL: Homepage, https://github.com/CoreTrace/coretrace-python-analyzer
|
|
6
6
|
Project-URL: Repository, https://github.com/CoreTrace/coretrace-python-analyzer
|
{coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.13.0.dist-info}/RECORD
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
coretrace_python/__init__.py,sha256=
|
|
1
|
+
coretrace_python/__init__.py,sha256=4_uvCC4RHPELcdUoMgQ00MwplbVOBvuK0TpVzxBnXII,76
|
|
2
2
|
coretrace_python/__main__.py,sha256=V8DnYtRboISK2VCKm7DGHLREKvqLbqApyWlKBOs26wU,65
|
|
3
3
|
coretrace_python/cache.py,sha256=-pZv-ELm4jMCTXQWSZIobHaT9UHIjFu7IlMkOM0i4LI,13607
|
|
4
4
|
coretrace_python/cli.py,sha256=G5jhb6g6RgSRSjjS7DBgziIjUM1vTFwalJGvFHVKxrg,11225
|
|
5
|
-
coretrace_python/engine.py,sha256
|
|
5
|
+
coretrace_python/engine.py,sha256=nwK8fbcEOT0zKiBlK67aCl7yiQpE00j56LLknXn7Djg,32276
|
|
6
6
|
coretrace_python/abstract/__init__.py,sha256=n5FNkV8Z4LmwG0r06KWdLj4N2CPSgAKwY3896tdmRgM,999
|
|
7
7
|
coretrace_python/abstract/constants.py,sha256=z4-e7IzysxzGi58wFFff1MlEoV8l7AZsB9EL-q6b7vY,9071
|
|
8
8
|
coretrace_python/abstract/heap.py,sha256=OaESXFGC0qzyhAr76xrCu4skeDVzZM4wlenQqfMTkdk,9642
|
|
@@ -19,30 +19,30 @@ coretrace_python/bundled/dependency/sample_advisories/plugin.toml,sha256=cME4pSF
|
|
|
19
19
|
coretrace_python/bundled/dependency/sample_advisories/sample_advisories.py,sha256=jw98GK6hh5RAx05t8QPcUzsFMfGigAL8418vZLprKlc,3710
|
|
20
20
|
coretrace_python/bundled/dependency/vulnerable_dependency/plugin.toml,sha256=4-OnbEb3lYVAoTfuz8mDPH4L9fHOPQ2KW1dDFAbClFk,267
|
|
21
21
|
coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py,sha256=THXqpuffQZ8nuv1b8zQrgkA7eTI1PxhODATMzqhWeyY,3393
|
|
22
|
-
coretrace_python/bundled/models/aiohttp/aiohttp_models.py,sha256=
|
|
22
|
+
coretrace_python/bundled/models/aiohttp/aiohttp_models.py,sha256=X3ZYgbrezHbsP3D9rVt-qM1_Iecu6nT_Yz9H7-XGl4M,3422
|
|
23
23
|
coretrace_python/bundled/models/aiohttp/plugin.toml,sha256=YvU60RUBexYBtIxbXdmUhO83BoijmOe3MeQKwY8QDaM,220
|
|
24
|
-
coretrace_python/bundled/models/bottle/bottle_models.py,sha256=
|
|
24
|
+
coretrace_python/bundled/models/bottle/bottle_models.py,sha256=Y0SUMCNGiGhzcMj0uxBLhCbLFgM7sBc4XBB-C5LEuDw,2200
|
|
25
25
|
coretrace_python/bundled/models/bottle/plugin.toml,sha256=V2Ea1DcocKOWyOWm_KuzNCaPxN7ZOomSnCyo0e7WfXQ,196
|
|
26
|
-
coretrace_python/bundled/models/cli/cli_models.py,sha256=
|
|
26
|
+
coretrace_python/bundled/models/cli/cli_models.py,sha256=jKCCpVsIWsOeG8-Q1P79bU6CENOAaxSQ_HZe6jLwd0c,980
|
|
27
27
|
coretrace_python/bundled/models/cli/plugin.toml,sha256=D2gkrL1lWc6b4ME_C0hjzwZHJX1l_OChRs3_oapam_s,163
|
|
28
28
|
coretrace_python/bundled/models/credentials/credential_models.py,sha256=Mw9ty8PDDyLNPtbPTTm-sIY9XE8gBsi02dPmLeum2kE,1423
|
|
29
29
|
coretrace_python/bundled/models/credentials/plugin.toml,sha256=0ut7Jc7IdcvbsZKPu6ty-z3DBlMg2CUT7JX4y4nMybo,217
|
|
30
30
|
coretrace_python/bundled/models/db_drivers/db_driver_models.py,sha256=x4SAwhcpuG5TLfhmVsuAQ1VV3lAXSNh5YHx7QuFBQyU,2339
|
|
31
31
|
coretrace_python/bundled/models/db_drivers/plugin.toml,sha256=094-QIV92x-69uy2CgMulBXfGvt48qtefLyeW2q3SRs,178
|
|
32
|
-
coretrace_python/bundled/models/django/django_models.py,sha256=
|
|
32
|
+
coretrace_python/bundled/models/django/django_models.py,sha256=Gg_qgQY4oT0CEW8RAFBoxyW2I56FSRJo5eWkZHZrGeQ,7370
|
|
33
33
|
coretrace_python/bundled/models/django/plugin.toml,sha256=_Qa2X_4IViORQkcLvLsAC5vI-2P5CxgG8VvnuNIWcFQ,214
|
|
34
34
|
coretrace_python/bundled/models/fastapi/fastapi_models.py,sha256=K2Edlo1RzYKmAJAIChNVUDc8gorAS48MGEMKNlCIGrI,1251
|
|
35
35
|
coretrace_python/bundled/models/fastapi/plugin.toml,sha256=V_R2WcEc-qkST0tTpL-cRf2FEbjK6Mk727WbJmVpWJM,200
|
|
36
|
-
coretrace_python/bundled/models/flask/flask_models.py,sha256=
|
|
36
|
+
coretrace_python/bundled/models/flask/flask_models.py,sha256=quR3hm4BqyFCNkBByAhaPVgeC-O-W-dnH_UFNFpgj_k,2717
|
|
37
37
|
coretrace_python/bundled/models/flask/plugin.toml,sha256=kI53Xwf5aZKd2n3fEQEbBzNnOXRvuonqEazTOIXPJP4,192
|
|
38
38
|
coretrace_python/bundled/models/http_clients/http_client_models.py,sha256=9XhHWZKq3XWcD0WexucuedAiwxp3fhXxGmo9R6DNtSQ,1954
|
|
39
39
|
coretrace_python/bundled/models/http_clients/plugin.toml,sha256=vq9tWHgy8-m8LiU1StGhm9Lsy7vnj3w90P4AKDaKfc0,216
|
|
40
40
|
coretrace_python/bundled/models/python_stdlib/plugin.toml,sha256=DWqjG68rx91HCJi_VEp6tUs4ujkzlR7Lq7saMvtK2HM,187
|
|
41
|
-
coretrace_python/bundled/models/python_stdlib/python_stdlib.py,sha256=
|
|
41
|
+
coretrace_python/bundled/models/python_stdlib/python_stdlib.py,sha256=IyCxTHjZcd81YA2-JalQE7OvVSZBisDVCo6330my3JY,4873
|
|
42
42
|
coretrace_python/bundled/models/sqlalchemy/plugin.toml,sha256=2gRbglnMK5U2QWnzs2U2pDvmTeWmrlW7XpJ_8dRmIoY,182
|
|
43
43
|
coretrace_python/bundled/models/sqlalchemy/sqlalchemy_models.py,sha256=Gm2qjyXUrjzALy-MigUhPQyyDxYdfWlQKDZLp1C6Jqk,1697
|
|
44
44
|
coretrace_python/bundled/models/tornado/plugin.toml,sha256=u_XjJK2uKBR11zg4yGxsCDHcx2pKEQ1MBvvB6Mm0siA,222
|
|
45
|
-
coretrace_python/bundled/models/tornado/tornado_models.py,sha256=
|
|
45
|
+
coretrace_python/bundled/models/tornado/tornado_models.py,sha256=Z8U46CbDHm7rORSsP26GBhUbe5m-LMf2QNkoBKZxB1M,2651
|
|
46
46
|
coretrace_python/bundled/secrets/config_secrets/config_secrets.py,sha256=JfhWSyVbjlYD9pQYJN6qAc4Rp4IA8PP222akM4KTPU8,1283
|
|
47
47
|
coretrace_python/bundled/secrets/config_secrets/plugin.toml,sha256=dRkA3zQ9wtT2EJyqZ5zGqJ5KYdyIQtQcSvesqrb4M0Q,177
|
|
48
48
|
coretrace_python/bundled/secrets/hardcoded_secrets/hardcoded_secrets.py,sha256=7TD-Nf5S4yfoRBRmJ6Ab6v4Q2zIC2n79FRfNS3I4xos,544
|
|
@@ -53,6 +53,8 @@ coretrace_python/bundled/security/command_injection/command_injection.py,sha256=
|
|
|
53
53
|
coretrace_python/bundled/security/command_injection/plugin.toml,sha256=x9NUpwdVZsTQpaPNX3HoYcrZWk4sOGN-aSp9Bc8jvCA,240
|
|
54
54
|
coretrace_python/bundled/security/insecure_deserialization/insecure_deserialization.py,sha256=-4KIQDaYo-09gSceuNEs3eutmBn2OBuYBTqiUEYOatk,637
|
|
55
55
|
coretrace_python/bundled/security/insecure_deserialization/plugin.toml,sha256=5OTVO9jZN-HBid8v__jYkxU-RnKX974Qqu7JQMFdj54,268
|
|
56
|
+
coretrace_python/bundled/security/nosql_injection/nosql_injection.py,sha256=0MCTZ3DjOf5DQrLgnPPgpa80CYUd5teQ_iau3s_82O0,789
|
|
57
|
+
coretrace_python/bundled/security/nosql_injection/plugin.toml,sha256=FP2qTPtH7ZCiu6vwfgadWlXfVnlKOigXJ43cv9PlorA,232
|
|
56
58
|
coretrace_python/bundled/security/open_redirect/open_redirect.py,sha256=nUjVIgYhVh_2xBs8_1LOfcLDgQ3g3z1E7APqg2sBdpo,562
|
|
57
59
|
coretrace_python/bundled/security/open_redirect/plugin.toml,sha256=8t4KLNG1DIpjjen6TuKxMjz2jv6GDcwVJDm_hJUalCg,224
|
|
58
60
|
coretrace_python/bundled/security/path_traversal/path_traversal.py,sha256=MiTDZBNU6i3WWua1qaxg1swqDy3wlWwBwG6G4ClyDgw,552
|
|
@@ -111,12 +113,12 @@ coretrace_python/hir/__init__.py,sha256=xMpvj-RSp6bd78a_hhkqbe99-snjERkH-_Lv5o9A
|
|
|
111
113
|
coretrace_python/hir/nodes.py,sha256=4BI0f3UhKbnz9ZIxMoKXM7ujav0BmRpD0OXaoblRxIk,12364
|
|
112
114
|
coretrace_python/hir/visitors.py,sha256=JFnX09hK6trLyRcWgZ5ehOG2ywX3VvkqQ1h82nahYNg,885
|
|
113
115
|
coretrace_python/interprocedural/__init__.py,sha256=rBASF3aAvl5CFAmIISu2gID9mqY_iyi-4Hx0GzDVZsg,1300
|
|
114
|
-
coretrace_python/interprocedural/callgraph.py,sha256=
|
|
116
|
+
coretrace_python/interprocedural/callgraph.py,sha256=3phhsJQ4pnpDullBluROSCbuz4AKaZsvycxrvq4UM7c,19898
|
|
115
117
|
coretrace_python/interprocedural/modulegraph.py,sha256=4sB7OKjBYfxtm7aMtHm2jRlqFVUNUsdnLhnv9UZ1BWA,8846
|
|
116
118
|
coretrace_python/interprocedural/summaries.py,sha256=Z_KT3ggH7fgPz1mJL6TBz6V5STka1koIuUBAuYMti7o,26113
|
|
117
119
|
coretrace_python/ir/__init__.py,sha256=ShBPYYqoQFePP-mgBjGezQ2acel0-ReOHF9vCC-G_4Q,232
|
|
118
120
|
coretrace_python/ir/defuse.py,sha256=CZEPnlgntRp4MqwR7kLldd2ZlIlBnr-V9VJAxXxwm0c,2616
|
|
119
|
-
coretrace_python/ir/lowering.py,sha256=
|
|
121
|
+
coretrace_python/ir/lowering.py,sha256=kIVB1HlIW6AMW7-2x9z7wcdhObWj8rjP_eKqOKtgxoM,31442
|
|
120
122
|
coretrace_python/ir/model.py,sha256=i7LvHEnl5pmWYUcSui17ZGS1rdPVavMpnjt7Tog7P4c,11125
|
|
121
123
|
coretrace_python/ir/printer.py,sha256=otBIhZUvB_UfB68gid_FbPcSjBUSmxH3v7vBMZZQVkg,9125
|
|
122
124
|
coretrace_python/ir/ssa.py,sha256=cIYbcv90PI6AOeMrw2ZWp88fy7E2ZO4P3tqjtPHra7g,11116
|
|
@@ -132,22 +134,22 @@ coretrace_python/reporters/json_format.py,sha256=x1XRw6UOOX624LRw2rJFiZDBbIX-LVz
|
|
|
132
134
|
coretrace_python/reporters/report.py,sha256=hEQPDtnEgDF0djXfg74vBM1B0uajZ_AWtNtu3eH7QI4,1777
|
|
133
135
|
coretrace_python/reporters/sarif.py,sha256=0HF47hZ6F2WtrDDb2uICLPqYI0gLRiUgxBX69C5lbnU,3649
|
|
134
136
|
coretrace_python/reporters/text.py,sha256=bHsHkMheyL0AB1iWtxBO5H9S14k1AqJvKkNsFro71xo,1043
|
|
135
|
-
coretrace_python/semantic/__init__.py,sha256=
|
|
137
|
+
coretrace_python/semantic/__init__.py,sha256=tJOvNvMzJ9yjP2bgkLpPO29pl9gMQgICh6XVYJHrovw,464
|
|
136
138
|
coretrace_python/semantic/identity.py,sha256=uQBdqEXrOtYmGIiR9hZu0Pimv52iW1rOOOvIdG0InNY,1472
|
|
137
139
|
coretrace_python/semantic/imports.py,sha256=389MZs8iTUc3CyUHG0VAgWFsypd7VPFKkLHmFz9L-N0,5673
|
|
138
140
|
coretrace_python/semantic/scopes.py,sha256=zg8ClITSAUbOMvLOV5BsfdG1oC4cd0VweUb9S5Jc4js,20241
|
|
139
|
-
coretrace_python/semantic/symbols.py,sha256=
|
|
141
|
+
coretrace_python/semantic/symbols.py,sha256=G0jwsWFo2346Y1R2sztsB8HuZZm_aHfHSJXXeZKPQLo,7354
|
|
140
142
|
coretrace_python/source/__init__.py,sha256=0Y8fvtbkETnNCJR9Zq7RS5TSgF9c22pz3SSk1KdAXF0,301
|
|
141
143
|
coretrace_python/source/manager.py,sha256=JRmMOcWfS1nocbVrdcvfQTqEfIkmnb5JP857TpGywN4,2736
|
|
142
144
|
coretrace_python/source/model.py,sha256=FZnN5mlir3tzbIpWoxnn6YtRi9UcJ_y0sZM60tKr8U4,1727
|
|
143
|
-
coretrace_python/taint/__init__.py,sha256=
|
|
144
|
-
coretrace_python/taint/engine.py,sha256=
|
|
145
|
-
coretrace_python/taint/models.py,sha256=
|
|
145
|
+
coretrace_python/taint/__init__.py,sha256=n0WMjstUkrJZ2QnMgGBlJwqCZ0LHno670XRd__TEcaI,1514
|
|
146
|
+
coretrace_python/taint/engine.py,sha256=rEAbwjR5v294PqBzBlu6f9WiMvVmwV83FqdsWbT4l3c,44648
|
|
147
|
+
coretrace_python/taint/models.py,sha256=8AwPKKdmwAHmE7CxwUbtWn5w7JVkEJ06_Is7xQWTx38,17066
|
|
146
148
|
coretrace_python/taint/routes.py,sha256=UAJKXcw_sM9shf58FfDroxVO9bNSm4qrQdZknwUB0lo,6110
|
|
147
149
|
coretrace_python/taint/templates.py,sha256=8Ojb43VXuWxOVifeXRpi3_2mjZfnvIC7GQ-AW80GUYk,11148
|
|
148
|
-
coretrace_python_analyzer-0.
|
|
149
|
-
coretrace_python_analyzer-0.
|
|
150
|
-
coretrace_python_analyzer-0.
|
|
151
|
-
coretrace_python_analyzer-0.
|
|
152
|
-
coretrace_python_analyzer-0.
|
|
153
|
-
coretrace_python_analyzer-0.
|
|
150
|
+
coretrace_python_analyzer-0.13.0.dist-info/METADATA,sha256=XNQT5nbo6DGzDqYJw9p1sYyciIkdituTpM82-o3z8-8,3217
|
|
151
|
+
coretrace_python_analyzer-0.13.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
152
|
+
coretrace_python_analyzer-0.13.0.dist-info/entry_points.txt,sha256=wdozemiraOkbt65g_Y_zgspuZpoLxJdaCVMJt1Ft3c0,72
|
|
153
|
+
coretrace_python_analyzer-0.13.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
154
|
+
coretrace_python_analyzer-0.13.0.dist-info/licenses/NOTICE,sha256=s3VHj5ngV0ozjW_kZJpg_35ne6JwoBK0ysOjbqVtA0w,113
|
|
155
|
+
coretrace_python_analyzer-0.13.0.dist-info/RECORD,,
|
{coretrace_python_analyzer-0.11.0.dist-info → coretrace_python_analyzer-0.13.0.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|