coretrace-python-analyzer 0.8.0__py3-none-any.whl → 0.9.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/dependency/__init__.py +2 -0
- coretrace_python/dependency/advisories.py +36 -1
- coretrace_python/dependency/correlation.py +4 -1
- coretrace_python/dependency/graph.py +34 -1
- coretrace_python/taint/engine.py +17 -2
- {coretrace_python_analyzer-0.8.0.dist-info → coretrace_python_analyzer-0.9.0.dist-info}/METADATA +1 -1
- {coretrace_python_analyzer-0.8.0.dist-info → coretrace_python_analyzer-0.9.0.dist-info}/RECORD +12 -12
- {coretrace_python_analyzer-0.8.0.dist-info → coretrace_python_analyzer-0.9.0.dist-info}/WHEEL +0 -0
- {coretrace_python_analyzer-0.8.0.dist-info → coretrace_python_analyzer-0.9.0.dist-info}/entry_points.txt +0 -0
- {coretrace_python_analyzer-0.8.0.dist-info → coretrace_python_analyzer-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {coretrace_python_analyzer-0.8.0.dist-info → coretrace_python_analyzer-0.9.0.dist-info}/licenses/NOTICE +0 -0
coretrace_python/__init__.py
CHANGED
|
@@ -12,6 +12,7 @@ from coretrace_python.dependency.graph import (
|
|
|
12
12
|
DEPENDENCY_FILES,
|
|
13
13
|
Advisory,
|
|
14
14
|
AdvisoryEntryPoint,
|
|
15
|
+
AttackerArgument,
|
|
15
16
|
Condition,
|
|
16
17
|
DependencyAnalysis,
|
|
17
18
|
DependencyGraph,
|
|
@@ -31,6 +32,7 @@ __all__ = [
|
|
|
31
32
|
"Advisory",
|
|
32
33
|
"AdvisoryEntryPoint",
|
|
33
34
|
"AdvisoryFileError",
|
|
35
|
+
"AttackerArgument",
|
|
34
36
|
"Condition",
|
|
35
37
|
"DependencyAnalysis",
|
|
36
38
|
"DependencyGraph",
|
|
@@ -17,7 +17,13 @@ from collections.abc import Iterable, Iterator, Mapping
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
from typing import Any
|
|
19
19
|
|
|
20
|
-
from coretrace_python.dependency.graph import
|
|
20
|
+
from coretrace_python.dependency.graph import (
|
|
21
|
+
Advisory,
|
|
22
|
+
AdvisoryEntryPoint,
|
|
23
|
+
AttackerArgument,
|
|
24
|
+
Condition,
|
|
25
|
+
normalize,
|
|
26
|
+
)
|
|
21
27
|
from coretrace_python.findings import Severity
|
|
22
28
|
from coretrace_python.semantic.symbols import SymbolId
|
|
23
29
|
|
|
@@ -155,6 +161,19 @@ def _entry_point_entry(entry_point: AdvisoryEntryPoint) -> dict[str, Any]:
|
|
|
155
161
|
}
|
|
156
162
|
if entry_point.read:
|
|
157
163
|
entry["read"] = True
|
|
164
|
+
if entry_point.attacker_arguments:
|
|
165
|
+
entry["attacker_arguments"] = [_attacker_argument_entry(a) for a in entry_point.attacker_arguments]
|
|
166
|
+
return entry
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _attacker_argument_entry(argument: AttackerArgument) -> dict[str, Any]:
|
|
170
|
+
entry: dict[str, Any] = {}
|
|
171
|
+
if argument.argument is not None:
|
|
172
|
+
entry["argument"] = argument.argument
|
|
173
|
+
if argument.position is not None:
|
|
174
|
+
entry["position"] = argument.position
|
|
175
|
+
if argument.variadic:
|
|
176
|
+
entry["variadic"] = True
|
|
158
177
|
return entry
|
|
159
178
|
|
|
160
179
|
|
|
@@ -206,9 +225,25 @@ def _entry_point(entry: Mapping[str, Any]) -> AdvisoryEntryPoint:
|
|
|
206
225
|
str(entry["justification"]),
|
|
207
226
|
tuple(_condition(c) for c in entry.get("conditions") or []),
|
|
208
227
|
read,
|
|
228
|
+
tuple(_attacker_argument(a) for a in entry.get("attacker_arguments") or []),
|
|
209
229
|
)
|
|
210
230
|
|
|
211
231
|
|
|
232
|
+
def _attacker_argument(entry: Mapping[str, Any]) -> AttackerArgument:
|
|
233
|
+
argument, position, variadic = entry.get("argument"), entry.get("position"), entry.get("variadic", False)
|
|
234
|
+
if argument is not None and not isinstance(argument, str):
|
|
235
|
+
raise TypeError(f"attacker argument name must be a string, got {argument!r}")
|
|
236
|
+
if position is not None and (not isinstance(position, int) or isinstance(position, bool) or position < 0):
|
|
237
|
+
raise TypeError(f"attacker argument position must be a non-negative integer, got {position!r}")
|
|
238
|
+
if not isinstance(variadic, bool):
|
|
239
|
+
raise TypeError(f"attacker argument variadic must be true or false, got {variadic!r}")
|
|
240
|
+
if argument is None and position is None:
|
|
241
|
+
raise ValueError("an attacker argument needs a name, a position or both")
|
|
242
|
+
if variadic and position is None:
|
|
243
|
+
raise ValueError("a variadic attacker argument needs the position it starts at")
|
|
244
|
+
return AttackerArgument(argument, position, variadic)
|
|
245
|
+
|
|
246
|
+
|
|
212
247
|
def _condition(entry: Mapping[str, Any]) -> Condition:
|
|
213
248
|
position = entry.get("position")
|
|
214
249
|
if position is not None and (not isinstance(position, int) or isinstance(position, bool) or position < 0):
|
|
@@ -133,7 +133,10 @@ def correlate(
|
|
|
133
133
|
continue
|
|
134
134
|
hotspot = verdict is not None and verdict.status is Status.HOTSPOT
|
|
135
135
|
for advisory in affected.get(flow.sink.symbol, ()):
|
|
136
|
-
|
|
136
|
+
entry = advisory.entry_point(flow.sink.symbol)
|
|
137
|
+
if entry is not None and not any(entry.exploitable_through(p, k) for p, k in flow.passed_as):
|
|
138
|
+
continue
|
|
139
|
+
check = check_conditions(entry, flow.sink_arguments)
|
|
137
140
|
if check.contradicted is None:
|
|
138
141
|
findings.append(_exploitable(function, flow, advisory, verdict, hotspot, check))
|
|
139
142
|
return tuple(findings)
|
|
@@ -166,6 +166,31 @@ class Condition:
|
|
|
166
166
|
return self.kind == "argument"
|
|
167
167
|
|
|
168
168
|
|
|
169
|
+
@dataclass(frozen=True)
|
|
170
|
+
class AttackerArgument:
|
|
171
|
+
"""An argument of an entry point that must carry the attacker's input: passed by
|
|
172
|
+
keyword ``argument``, or at ``position`` (from 0, a method's receiver excluded) when it
|
|
173
|
+
may be passed positionally; a ``variadic`` one, such as ``*pathnames``, also takes every
|
|
174
|
+
later position."""
|
|
175
|
+
|
|
176
|
+
argument: str | None = None
|
|
177
|
+
position: int | None = None
|
|
178
|
+
variadic: bool = False
|
|
179
|
+
|
|
180
|
+
def receives(self, position: int | None, keyword: str | None) -> bool:
|
|
181
|
+
"""Whether the argument passed at ``position`` or as ``keyword`` is this one. An
|
|
182
|
+
argument unpacked with ``*`` or ``**`` has neither and may fill this one: like an
|
|
183
|
+
argument condition it leaves undecided, it does not rule the call out, so it is."""
|
|
184
|
+
|
|
185
|
+
if keyword is not None:
|
|
186
|
+
return keyword == self.argument
|
|
187
|
+
if position is None:
|
|
188
|
+
return True
|
|
189
|
+
if self.position is None:
|
|
190
|
+
return False
|
|
191
|
+
return position == self.position or (self.variadic and position > self.position)
|
|
192
|
+
|
|
193
|
+
|
|
169
194
|
@dataclass(frozen=True)
|
|
170
195
|
class AdvisoryEntryPoint:
|
|
171
196
|
"""A public API through which a project reaches an affected symbol, justified by the
|
|
@@ -173,12 +198,20 @@ class AdvisoryEntryPoint:
|
|
|
173
198
|
|
|
174
199
|
An entry point is reached by a call, or — when ``read`` is set, for an attribute whose
|
|
175
200
|
getter runs the affected code, such as a lazily parsed request body — by any read of
|
|
176
|
-
it or of an attribute of it.
|
|
201
|
+
it or of an attribute of it. When it names ``attacker_arguments``, attacker input
|
|
202
|
+
exploits it only through one of them; otherwise through any argument."""
|
|
177
203
|
|
|
178
204
|
symbol: SymbolId
|
|
179
205
|
justification: str
|
|
180
206
|
conditions: tuple[Condition, ...] = ()
|
|
181
207
|
read: bool = False
|
|
208
|
+
attacker_arguments: tuple[AttackerArgument, ...] = ()
|
|
209
|
+
|
|
210
|
+
def exploitable_through(self, position: int | None, keyword: str | None) -> bool:
|
|
211
|
+
"""Whether attacker input in the argument at ``position`` or ``keyword`` can
|
|
212
|
+
exploit the vulnerability."""
|
|
213
|
+
|
|
214
|
+
return not self.attacker_arguments or any(a.receives(position, keyword) for a in self.attacker_arguments)
|
|
182
215
|
|
|
183
216
|
|
|
184
217
|
DIRECT = "affected symbol, changed by the fix"
|
coretrace_python/taint/engine.py
CHANGED
|
@@ -9,7 +9,7 @@ argument reaching a sink whose kinds it still carries is reported as a ``TaintFl
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
-
from collections.abc import Callable, Mapping
|
|
12
|
+
from collections.abc import Callable, Iterable, Mapping
|
|
13
13
|
from dataclasses import dataclass, replace
|
|
14
14
|
from types import MappingProxyType
|
|
15
15
|
from typing import ClassVar
|
|
@@ -119,6 +119,9 @@ class TaintFlow:
|
|
|
119
119
|
sink_location: SourceSpan | None = None
|
|
120
120
|
# What the arguments of the sink call denote, to decide the conditions on them.
|
|
121
121
|
sink_arguments: Arguments | None = None
|
|
122
|
+
# How the tainted value is passed to the sink call: at a position, by a keyword, or
|
|
123
|
+
# (None, None) unpacked with ``*`` or ``**``; ``f(x, key=x)`` passes it twice.
|
|
124
|
+
passed_as: frozenset[tuple[int | None, str | None]] = frozenset()
|
|
122
125
|
|
|
123
126
|
|
|
124
127
|
Key = Value | HeapLocation
|
|
@@ -559,6 +562,7 @@ class _TaintProblem(DataflowProblem[State]):
|
|
|
559
562
|
through,
|
|
560
563
|
call.location if sink_location is None else sink_location,
|
|
561
564
|
arguments,
|
|
565
|
+
frozenset({(position, keyword)}),
|
|
562
566
|
)
|
|
563
567
|
)
|
|
564
568
|
|
|
@@ -819,7 +823,18 @@ def propagate_taint(
|
|
|
819
823
|
state, found = problem.evaluate(block, solution.incoming(block.id))
|
|
820
824
|
taints.update(state)
|
|
821
825
|
flows.extend(found)
|
|
822
|
-
return TaintFacts(taints,
|
|
826
|
+
return TaintFacts(taints, _merged(flows))
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def _merged(flows: Iterable[TaintFlow]) -> tuple[TaintFlow, ...]:
|
|
830
|
+
"""One flow per source, sink, kinds and tainted value, however many arguments of the
|
|
831
|
+
sink call it is passed as."""
|
|
832
|
+
|
|
833
|
+
passed: dict[TaintFlow, frozenset[tuple[int | None, str | None]]] = {}
|
|
834
|
+
for flow in flows:
|
|
835
|
+
key = replace(flow, passed_as=frozenset())
|
|
836
|
+
passed[key] = passed.get(key, frozenset()) | flow.passed_as
|
|
837
|
+
return tuple(replace(flow, passed_as=ways) for flow, ways in passed.items())
|
|
823
838
|
|
|
824
839
|
|
|
825
840
|
class TaintAnalysis(FunctionAnalysis[TaintFacts]):
|
{coretrace_python_analyzer-0.8.0.dist-info → coretrace_python_analyzer-0.9.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.9.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.8.0.dist-info → coretrace_python_analyzer-0.9.0.dist-info}/RECORD
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
coretrace_python/__init__.py,sha256=
|
|
1
|
+
coretrace_python/__init__.py,sha256=JoQcl29Ph_JPstj_FTAA1vDAxIylnVKy9661tvKlYIo,75
|
|
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=86rTb3Kcezwk9DBTRg-G3G38ypx8zH4lZbCR467xvpE,11008
|
|
@@ -88,10 +88,10 @@ coretrace_python/cfg/model.py,sha256=6EJiXFAGrsMgCdyxMJ6TClY8tQhJF6Hq2bX802Gc0yg
|
|
|
88
88
|
coretrace_python/dataflow/__init__.py,sha256=N5uY8VTNjEk6-ARCgDkxr6uGqQIz2NbF-U9sC_v_lfA,547
|
|
89
89
|
coretrace_python/dataflow/lattice.py,sha256=HgYGVcKVUlexvEc0yT47aQCudLxU_SWLpHhr17igkiE,1929
|
|
90
90
|
coretrace_python/dataflow/solver.py,sha256=iDsW0wujhM2DXplu2JMKC46BRsroXlNETHPEoikPeG4,3475
|
|
91
|
-
coretrace_python/dependency/__init__.py,sha256=
|
|
92
|
-
coretrace_python/dependency/advisories.py,sha256=
|
|
93
|
-
coretrace_python/dependency/correlation.py,sha256=
|
|
94
|
-
coretrace_python/dependency/graph.py,sha256=
|
|
91
|
+
coretrace_python/dependency/__init__.py,sha256=BxkxnNwIEpAYtwbshCQovG-YXJ80QbwyzkmyJdbgsVg,1184
|
|
92
|
+
coretrace_python/dependency/advisories.py,sha256=zti7MrojcfxfYWJuGpyLXXJU3pffpT4SJnQUa_g34Lw,10274
|
|
93
|
+
coretrace_python/dependency/correlation.py,sha256=GLtrMn5yYr4NcKYNYZTqIFpwoXOrgsqI9t6lggXxFbg,7388
|
|
94
|
+
coretrace_python/dependency/graph.py,sha256=HtvKeWWG9cy9TzJQ7lYMGPfHjPBxoSVgwzoj71SegaE,18445
|
|
95
95
|
coretrace_python/dependency/policy.py,sha256=bsxZXL9xC0jnAzkzDh6ncnW8oKLG2H13jYmXF_q99Ng,2454
|
|
96
96
|
coretrace_python/dependency/sbom.py,sha256=h4K3Gb9O_D6uZyfpwpC5rPcNjH_Ib-3uvFxix6OKlfs,2520
|
|
97
97
|
coretrace_python/dependency/vex.py,sha256=ZPASwL6Var269WifrU7g1NRlDf-jxKKs8LzMo8dUkxI,7237
|
|
@@ -138,13 +138,13 @@ coretrace_python/source/__init__.py,sha256=0Y8fvtbkETnNCJR9Zq7RS5TSgF9c22pz3SSk1
|
|
|
138
138
|
coretrace_python/source/manager.py,sha256=JRmMOcWfS1nocbVrdcvfQTqEfIkmnb5JP857TpGywN4,2736
|
|
139
139
|
coretrace_python/source/model.py,sha256=FZnN5mlir3tzbIpWoxnn6YtRi9UcJ_y0sZM60tKr8U4,1727
|
|
140
140
|
coretrace_python/taint/__init__.py,sha256=kfqCEXE5wKFLwz2XrT42_yEv9pW4vPfgg5yYBVVSr2I,1371
|
|
141
|
-
coretrace_python/taint/engine.py,sha256=
|
|
141
|
+
coretrace_python/taint/engine.py,sha256=_uVUc2m29aljN_YR2iCQtEdvZFc-LJ5CDWiZF1Tz3WU,40524
|
|
142
142
|
coretrace_python/taint/models.py,sha256=B84caPxmQvTdB9H_80nZOqcFlpNd9ofpKI2YL5Uc0UA,15269
|
|
143
143
|
coretrace_python/taint/routes.py,sha256=UAJKXcw_sM9shf58FfDroxVO9bNSm4qrQdZknwUB0lo,6110
|
|
144
144
|
coretrace_python/taint/templates.py,sha256=8Ojb43VXuWxOVifeXRpi3_2mjZfnvIC7GQ-AW80GUYk,11148
|
|
145
|
-
coretrace_python_analyzer-0.
|
|
146
|
-
coretrace_python_analyzer-0.
|
|
147
|
-
coretrace_python_analyzer-0.
|
|
148
|
-
coretrace_python_analyzer-0.
|
|
149
|
-
coretrace_python_analyzer-0.
|
|
150
|
-
coretrace_python_analyzer-0.
|
|
145
|
+
coretrace_python_analyzer-0.9.0.dist-info/METADATA,sha256=Yn4OctM3x0MgjM30b8fPuLrCeFRLIBCe4RZROB1eGR0,3216
|
|
146
|
+
coretrace_python_analyzer-0.9.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
147
|
+
coretrace_python_analyzer-0.9.0.dist-info/entry_points.txt,sha256=wdozemiraOkbt65g_Y_zgspuZpoLxJdaCVMJt1Ft3c0,72
|
|
148
|
+
coretrace_python_analyzer-0.9.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
149
|
+
coretrace_python_analyzer-0.9.0.dist-info/licenses/NOTICE,sha256=s3VHj5ngV0ozjW_kZJpg_35ne6JwoBK0ysOjbqVtA0w,113
|
|
150
|
+
coretrace_python_analyzer-0.9.0.dist-info/RECORD,,
|
{coretrace_python_analyzer-0.8.0.dist-info → coretrace_python_analyzer-0.9.0.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|