pypproxy 0.1.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.
- pypproxy/__init__.py +0 -0
- pypproxy/api/__init__.py +0 -0
- pypproxy/api/server.py +427 -0
- pypproxy/bulk/__init__.py +0 -0
- pypproxy/bulk/sender.py +97 -0
- pypproxy/cert/__init__.py +0 -0
- pypproxy/cert/ca.py +144 -0
- pypproxy/cert/client_cert.py +65 -0
- pypproxy/codec.py +176 -0
- pypproxy/config/__init__.py +0 -0
- pypproxy/config/config.py +106 -0
- pypproxy/dns/__init__.py +0 -0
- pypproxy/dns/server.py +149 -0
- pypproxy/exporter/__init__.py +0 -0
- pypproxy/exporter/exporter.py +122 -0
- pypproxy/exporter/importer.py +169 -0
- pypproxy/graphql/__init__.py +0 -0
- pypproxy/graphql/detector.py +76 -0
- pypproxy/graphql/introspection.py +217 -0
- pypproxy/graphql/modifier.py +98 -0
- pypproxy/graphql/schema_store.py +33 -0
- pypproxy/intercept/__init__.py +0 -0
- pypproxy/intercept/manager.py +142 -0
- pypproxy/interceptor/__init__.py +0 -0
- pypproxy/interceptor/interceptor.py +172 -0
- pypproxy/proto/__init__.py +0 -0
- pypproxy/proto/grpc.py +48 -0
- pypproxy/proto/mqtt.py +119 -0
- pypproxy/proto/ws.py +120 -0
- pypproxy/proto/ws_intercept.py +117 -0
- pypproxy/proxy/__init__.py +0 -0
- pypproxy/proxy/proxy.py +407 -0
- pypproxy/replay/__init__.py +0 -0
- pypproxy/replay/replay.py +77 -0
- pypproxy/rule/__init__.py +0 -0
- pypproxy/rule/rule.py +198 -0
- pypproxy/scan/__init__.py +0 -0
- pypproxy/scan/scanner.py +296 -0
- pypproxy/script/__init__.py +0 -0
- pypproxy/script/engine.py +49 -0
- pypproxy/security/__init__.py +0 -0
- pypproxy/security/header_checker.py +308 -0
- pypproxy/security/int_overflow.py +193 -0
- pypproxy/security/jwt_checker.py +273 -0
- pypproxy/security/plugin.py +152 -0
- pypproxy/security/randomness.py +165 -0
- pypproxy/store/__init__.py +0 -0
- pypproxy/store/db.py +189 -0
- pypproxy/store/filter_parser.py +181 -0
- pypproxy/store/fts.py +105 -0
- pypproxy/store/models.py +81 -0
- pypproxy/store/scope.py +63 -0
- pypproxy/store/store.py +120 -0
- pypproxy/ui/__init__.py +0 -0
- pypproxy/ui/app.py +386 -0
- pypproxy/ui/bulk_sender_ui.py +125 -0
- pypproxy/ui/cui.py +162 -0
- pypproxy/ui/detail.py +179 -0
- pypproxy/ui/diff_view.py +118 -0
- pypproxy/ui/graphql_tab.py +265 -0
- pypproxy/ui/import_tab.py +136 -0
- pypproxy/ui/intercept_dialog.py +74 -0
- pypproxy/ui/resender.py +140 -0
- pypproxy/ui/scan_tab.py +98 -0
- pypproxy/ui/security_tab.py +356 -0
- pypproxy/ui/settings.py +413 -0
- pypproxy/ui/theme.py +59 -0
- pypproxy-0.1.0.dist-info/METADATA +19 -0
- pypproxy-0.1.0.dist-info/RECORD +72 -0
- pypproxy-0.1.0.dist-info/WHEEL +4 -0
- pypproxy-0.1.0.dist-info/entry_points.txt +2 -0
- pypproxy-0.1.0.dist-info/licenses/LICENSE +21 -0
pypproxy/ui/scan_tab.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from nicegui import ui
|
|
4
|
+
|
|
5
|
+
from pypproxy.store.models import Entry
|
|
6
|
+
from pypproxy.store.store import Store
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_scan_tab(store: Store) -> dict:
|
|
10
|
+
"""Build the Active Scan tab. Returns state with open_entry method."""
|
|
11
|
+
state: dict = {"entry": None}
|
|
12
|
+
|
|
13
|
+
with ui.column().classes("w-full h-full overflow-auto q-pa-md"):
|
|
14
|
+
entry_label = ui.label("No entry selected").classes("text-grey text-caption q-mb-sm")
|
|
15
|
+
|
|
16
|
+
with ui.row().classes("items-center gap-4 q-mb-md"):
|
|
17
|
+
cats = (
|
|
18
|
+
ui.select(
|
|
19
|
+
["xss", "sqli", "cmdi", "ssti", "path_traversal"],
|
|
20
|
+
multiple=True,
|
|
21
|
+
value=["xss", "sqli"],
|
|
22
|
+
label="Categories",
|
|
23
|
+
)
|
|
24
|
+
.props("dense outlined dark")
|
|
25
|
+
.classes("w-72")
|
|
26
|
+
)
|
|
27
|
+
conc = (
|
|
28
|
+
ui.number(label="Concurrency", value=5, min=1, max=20)
|
|
29
|
+
.props("dense outlined dark")
|
|
30
|
+
.classes("w-24")
|
|
31
|
+
)
|
|
32
|
+
run_btn = ui.button("Scan", icon="search").props("color=negative")
|
|
33
|
+
|
|
34
|
+
summary_label = ui.label("").classes("text-caption text-grey q-mb-xs")
|
|
35
|
+
|
|
36
|
+
results_table = (
|
|
37
|
+
ui.table(
|
|
38
|
+
columns=[
|
|
39
|
+
{"name": "param", "label": "Param", "field": "param", "align": "left"},
|
|
40
|
+
{"name": "cat", "label": "Category", "field": "category", "align": "center"},
|
|
41
|
+
{"name": "payload", "label": "Payload", "field": "payload", "align": "left"},
|
|
42
|
+
{
|
|
43
|
+
"name": "status",
|
|
44
|
+
"label": "Status",
|
|
45
|
+
"field": "status_code",
|
|
46
|
+
"align": "center",
|
|
47
|
+
},
|
|
48
|
+
{"name": "ms", "label": "ms", "field": "duration_ms", "align": "right"},
|
|
49
|
+
{"name": "suspicious", "label": "⚠", "field": "suspicious", "align": "center"},
|
|
50
|
+
{"name": "reason", "label": "Reason", "field": "reason", "align": "left"},
|
|
51
|
+
],
|
|
52
|
+
rows=[],
|
|
53
|
+
row_key="payload",
|
|
54
|
+
)
|
|
55
|
+
.classes("w-full")
|
|
56
|
+
.props("dense flat dark")
|
|
57
|
+
)
|
|
58
|
+
results_table.add_slot(
|
|
59
|
+
"body-cell-suspicious",
|
|
60
|
+
"""
|
|
61
|
+
<q-td :props="props">
|
|
62
|
+
<q-badge v-if="props.value" color="negative" label="!" />
|
|
63
|
+
</q-td>
|
|
64
|
+
""",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
async def _scan() -> None:
|
|
68
|
+
from pypproxy.scan.scanner import run_scan
|
|
69
|
+
|
|
70
|
+
entry = state.get("entry")
|
|
71
|
+
if not entry:
|
|
72
|
+
ui.notify("No entry selected", type="warning")
|
|
73
|
+
return
|
|
74
|
+
run_btn.props("loading")
|
|
75
|
+
try:
|
|
76
|
+
results = await run_scan(
|
|
77
|
+
entry,
|
|
78
|
+
categories=list(cats.value) if cats.value else None,
|
|
79
|
+
concurrency=int(conc.value or 5),
|
|
80
|
+
)
|
|
81
|
+
results_table.rows = [r.to_dict() for r in results]
|
|
82
|
+
results_table.update()
|
|
83
|
+
suspicious = sum(1 for r in results if r.suspicious)
|
|
84
|
+
summary_label.text = f"{len(results)} tests — {suspicious} findings"
|
|
85
|
+
if suspicious:
|
|
86
|
+
ui.notify(f"⚠ {suspicious} potential findings!", type="warning")
|
|
87
|
+
else:
|
|
88
|
+
ui.notify(f"Scan complete — {len(results)} tests, no findings", type="positive")
|
|
89
|
+
finally:
|
|
90
|
+
run_btn.props(remove="loading")
|
|
91
|
+
|
|
92
|
+
run_btn.on("click", _scan)
|
|
93
|
+
|
|
94
|
+
def open_entry(entry: Entry) -> None:
|
|
95
|
+
state["entry"] = entry
|
|
96
|
+
entry_label.text = f"#{entry.id} {entry.method} {entry.scheme}://{entry.host}{entry.path}"
|
|
97
|
+
|
|
98
|
+
return {"open_entry": open_entry}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from nicegui import ui
|
|
4
|
+
|
|
5
|
+
from pypproxy.security.header_checker import check_security_headers
|
|
6
|
+
from pypproxy.security.jwt_checker import extract_jwt_from_headers
|
|
7
|
+
from pypproxy.security.jwt_checker import run_checks as jwt_run_checks
|
|
8
|
+
from pypproxy.security.randomness import analyse_token
|
|
9
|
+
from pypproxy.store.models import Entry
|
|
10
|
+
from pypproxy.store.store import Store
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def build_security_tab(store: Store) -> dict:
|
|
14
|
+
"""Build the security tools tab. Returns state with open_entry method."""
|
|
15
|
+
state: dict = {"entry": None}
|
|
16
|
+
|
|
17
|
+
with ui.column().classes("w-full h-full overflow-auto q-pa-md"):
|
|
18
|
+
entry_label = ui.label(
|
|
19
|
+
"No entry selected — right-click a traffic row → Security check"
|
|
20
|
+
).classes("text-grey text-caption q-mb-sm")
|
|
21
|
+
|
|
22
|
+
with ui.tabs().props("dense dark") as sec_tabs:
|
|
23
|
+
jwt_tab = ui.tab("JWT Checker", icon="key")
|
|
24
|
+
header_tab = ui.tab("Security Headers", icon="shield")
|
|
25
|
+
randomness_tab = ui.tab("Token Randomness", icon="casino")
|
|
26
|
+
overflow_tab = ui.tab("Int Overflow", icon="numbers")
|
|
27
|
+
|
|
28
|
+
with ui.tab_panels(sec_tabs, value=jwt_tab).classes("w-full"):
|
|
29
|
+
# --- JWT Checker ---
|
|
30
|
+
with ui.tab_panel(jwt_tab):
|
|
31
|
+
ui.label("JWT Vulnerability Checker").classes("text-subtitle2 q-mb-xs")
|
|
32
|
+
ui.label(
|
|
33
|
+
"Automatically extracts the JWT from the Authorization header and tests common attack vectors."
|
|
34
|
+
).classes("text-caption text-grey q-mb-md")
|
|
35
|
+
|
|
36
|
+
token_input = (
|
|
37
|
+
ui.input(label="JWT Token (auto-filled from entry)")
|
|
38
|
+
.props("outlined dense dark")
|
|
39
|
+
.classes("w-full font-mono text-xs q-mb-sm")
|
|
40
|
+
)
|
|
41
|
+
jwt_run_btn = ui.button("Run JWT Checks", icon="play_arrow").props("color=primary")
|
|
42
|
+
jwt_results_label = ui.label("").classes("text-caption text-grey q-my-xs")
|
|
43
|
+
jwt_table = (
|
|
44
|
+
ui.table(
|
|
45
|
+
columns=[
|
|
46
|
+
{
|
|
47
|
+
"name": "vector",
|
|
48
|
+
"label": "Vector",
|
|
49
|
+
"field": "vector",
|
|
50
|
+
"align": "left",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "status",
|
|
54
|
+
"label": "Status",
|
|
55
|
+
"field": "status_code",
|
|
56
|
+
"align": "center",
|
|
57
|
+
},
|
|
58
|
+
{"name": "ms", "label": "ms", "field": "duration_ms", "align": "right"},
|
|
59
|
+
{
|
|
60
|
+
"name": "suspicious",
|
|
61
|
+
"label": "Suspicious",
|
|
62
|
+
"field": "suspicious",
|
|
63
|
+
"align": "center",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "desc",
|
|
67
|
+
"label": "Description",
|
|
68
|
+
"field": "description",
|
|
69
|
+
"align": "left",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
rows=[],
|
|
73
|
+
row_key="vector",
|
|
74
|
+
)
|
|
75
|
+
.classes("w-full")
|
|
76
|
+
.props("dense flat dark")
|
|
77
|
+
)
|
|
78
|
+
jwt_table.add_slot(
|
|
79
|
+
"body-cell-suspicious",
|
|
80
|
+
"""
|
|
81
|
+
<q-td :props="props">
|
|
82
|
+
<q-badge v-if="props.value" color="negative" label="⚠ YES" />
|
|
83
|
+
<q-badge v-else color="grey" label="no" />
|
|
84
|
+
</q-td>
|
|
85
|
+
""",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
async def _run_jwt() -> None:
|
|
89
|
+
entry = state.get("entry")
|
|
90
|
+
token = token_input.value.strip()
|
|
91
|
+
if not token or not entry:
|
|
92
|
+
ui.notify("No token or entry", type="warning")
|
|
93
|
+
return
|
|
94
|
+
jwt_run_btn.props("loading")
|
|
95
|
+
try:
|
|
96
|
+
results = await jwt_run_checks(
|
|
97
|
+
token=token,
|
|
98
|
+
entry_id=entry.id,
|
|
99
|
+
method=entry.method,
|
|
100
|
+
scheme=entry.scheme,
|
|
101
|
+
host=entry.host,
|
|
102
|
+
path=entry.path,
|
|
103
|
+
query=entry.query,
|
|
104
|
+
headers=entry.req_headers,
|
|
105
|
+
body=entry.req_body,
|
|
106
|
+
)
|
|
107
|
+
jwt_table.rows = [r.to_dict() for r in results]
|
|
108
|
+
jwt_table.update()
|
|
109
|
+
suspicious = sum(1 for r in results if r.suspicious)
|
|
110
|
+
jwt_results_label.text = (
|
|
111
|
+
f"{len(results)} vectors tested — {suspicious} suspicious"
|
|
112
|
+
)
|
|
113
|
+
if suspicious:
|
|
114
|
+
ui.notify(f"⚠ {suspicious} suspicious responses!", type="warning")
|
|
115
|
+
else:
|
|
116
|
+
ui.notify("Done — no obvious vulnerabilities detected", type="positive")
|
|
117
|
+
finally:
|
|
118
|
+
jwt_run_btn.props(remove="loading")
|
|
119
|
+
|
|
120
|
+
jwt_run_btn.on("click", _run_jwt)
|
|
121
|
+
|
|
122
|
+
# --- Security Headers ---
|
|
123
|
+
with ui.tab_panel(header_tab):
|
|
124
|
+
ui.label("Security Header Checker").classes("text-subtitle2 q-mb-xs")
|
|
125
|
+
ui.label(
|
|
126
|
+
"Checks the response headers of the selected entry for common security misconfigurations."
|
|
127
|
+
).classes("text-caption text-grey q-mb-md")
|
|
128
|
+
|
|
129
|
+
hdr_run_btn = ui.button("Check Headers", icon="shield").props("color=primary")
|
|
130
|
+
hdr_results_label = ui.label("").classes("text-caption text-grey q-my-xs")
|
|
131
|
+
hdr_table = (
|
|
132
|
+
ui.table(
|
|
133
|
+
columns=[
|
|
134
|
+
{
|
|
135
|
+
"name": "header",
|
|
136
|
+
"label": "Header",
|
|
137
|
+
"field": "header",
|
|
138
|
+
"align": "left",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"name": "present",
|
|
142
|
+
"label": "Present",
|
|
143
|
+
"field": "present",
|
|
144
|
+
"align": "center",
|
|
145
|
+
},
|
|
146
|
+
{"name": "passed", "label": "OK", "field": "passed", "align": "center"},
|
|
147
|
+
{
|
|
148
|
+
"name": "severity",
|
|
149
|
+
"label": "Severity",
|
|
150
|
+
"field": "severity",
|
|
151
|
+
"align": "center",
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"name": "detail",
|
|
155
|
+
"label": "Detail",
|
|
156
|
+
"field": "detail",
|
|
157
|
+
"align": "left",
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
rows=[],
|
|
161
|
+
row_key="header",
|
|
162
|
+
)
|
|
163
|
+
.classes("w-full")
|
|
164
|
+
.props("dense flat dark")
|
|
165
|
+
)
|
|
166
|
+
hdr_table.add_slot(
|
|
167
|
+
"body-cell-passed",
|
|
168
|
+
"""
|
|
169
|
+
<q-td :props="props">
|
|
170
|
+
<q-badge :color="props.value ? 'positive' : 'negative'" :label="props.value ? '✓' : '✗'" />
|
|
171
|
+
</q-td>
|
|
172
|
+
""",
|
|
173
|
+
)
|
|
174
|
+
hdr_table.add_slot(
|
|
175
|
+
"body-cell-severity",
|
|
176
|
+
"""
|
|
177
|
+
<q-td :props="props">
|
|
178
|
+
<q-badge :color="{'high':'negative','medium':'warning','low':'orange','info':'grey'}[props.value] || 'grey'"
|
|
179
|
+
:label="props.value" />
|
|
180
|
+
</q-td>
|
|
181
|
+
""",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def _run_headers() -> None:
|
|
185
|
+
entry = state.get("entry")
|
|
186
|
+
if not entry or not entry.resp_headers:
|
|
187
|
+
ui.notify("No response headers in selected entry", type="warning")
|
|
188
|
+
return
|
|
189
|
+
results = check_security_headers(entry.resp_headers)
|
|
190
|
+
hdr_table.rows = [r.to_dict() for r in results]
|
|
191
|
+
hdr_table.update()
|
|
192
|
+
fails = sum(1 for r in results if not r.passed)
|
|
193
|
+
hdr_results_label.text = f"{len(results)} headers checked — {fails} issues"
|
|
194
|
+
if fails:
|
|
195
|
+
ui.notify(f"{fails} security header issues found", type="warning")
|
|
196
|
+
else:
|
|
197
|
+
ui.notify("All security headers look good", type="positive")
|
|
198
|
+
|
|
199
|
+
hdr_run_btn.on("click", _run_headers)
|
|
200
|
+
|
|
201
|
+
# --- Token Randomness ---
|
|
202
|
+
with ui.tab_panel(randomness_tab):
|
|
203
|
+
ui.label("Token Randomness Analyser").classes("text-subtitle2 q-mb-xs")
|
|
204
|
+
ui.label(
|
|
205
|
+
"Statistical entropy tests on tokens extracted from the selected entry."
|
|
206
|
+
).classes("text-caption text-grey q-mb-md")
|
|
207
|
+
|
|
208
|
+
rand_token_input = (
|
|
209
|
+
ui.input(label="Token (auto-filled from entry)")
|
|
210
|
+
.props("outlined dense dark")
|
|
211
|
+
.classes("w-full font-mono text-xs q-mb-sm")
|
|
212
|
+
)
|
|
213
|
+
rand_run_btn = ui.button("Analyse", icon="casino").props("color=primary")
|
|
214
|
+
rand_table = (
|
|
215
|
+
ui.table(
|
|
216
|
+
columns=[
|
|
217
|
+
{
|
|
218
|
+
"name": "test",
|
|
219
|
+
"label": "Test",
|
|
220
|
+
"field": "test_name",
|
|
221
|
+
"align": "left",
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"name": "passed",
|
|
225
|
+
"label": "Passed",
|
|
226
|
+
"field": "passed",
|
|
227
|
+
"align": "center",
|
|
228
|
+
},
|
|
229
|
+
{"name": "score", "label": "Score", "field": "score", "align": "right"},
|
|
230
|
+
{
|
|
231
|
+
"name": "detail",
|
|
232
|
+
"label": "Detail",
|
|
233
|
+
"field": "detail",
|
|
234
|
+
"align": "left",
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
rows=[],
|
|
238
|
+
row_key="test_name",
|
|
239
|
+
)
|
|
240
|
+
.classes("w-full")
|
|
241
|
+
.props("dense flat dark")
|
|
242
|
+
)
|
|
243
|
+
rand_table.add_slot(
|
|
244
|
+
"body-cell-passed",
|
|
245
|
+
"""
|
|
246
|
+
<q-td :props="props">
|
|
247
|
+
<q-badge :color="props.value ? 'positive' : 'negative'" :label="props.value ? '✓' : '✗'" />
|
|
248
|
+
</q-td>
|
|
249
|
+
""",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def _run_randomness() -> None:
|
|
253
|
+
token = rand_token_input.value.strip()
|
|
254
|
+
if not token:
|
|
255
|
+
ui.notify("Enter a token", type="warning")
|
|
256
|
+
return
|
|
257
|
+
results = analyse_token(token)
|
|
258
|
+
rand_table.rows = [r.to_dict() for r in results]
|
|
259
|
+
rand_table.update()
|
|
260
|
+
fails = sum(1 for r in results if not r.passed)
|
|
261
|
+
if fails:
|
|
262
|
+
ui.notify(
|
|
263
|
+
f"{fails} randomness tests failed — token may be weak", type="warning"
|
|
264
|
+
)
|
|
265
|
+
else:
|
|
266
|
+
ui.notify("Token appears sufficiently random", type="positive")
|
|
267
|
+
|
|
268
|
+
rand_run_btn.on("click", _run_randomness)
|
|
269
|
+
|
|
270
|
+
# --- Int Overflow ---
|
|
271
|
+
with ui.tab_panel(overflow_tab):
|
|
272
|
+
ui.label("Integer Overflow / Boundary Tester").classes("text-subtitle2 q-mb-xs")
|
|
273
|
+
ui.label(
|
|
274
|
+
"Finds integer parameters in the selected entry and tests boundary values."
|
|
275
|
+
).classes("text-caption text-grey q-mb-md")
|
|
276
|
+
|
|
277
|
+
overflow_run_btn = ui.button("Run Tests", icon="numbers").props("color=primary")
|
|
278
|
+
overflow_label = ui.label("").classes("text-caption text-grey q-my-xs")
|
|
279
|
+
overflow_table = (
|
|
280
|
+
ui.table(
|
|
281
|
+
columns=[
|
|
282
|
+
{"name": "param", "label": "Param", "field": "param", "align": "left"},
|
|
283
|
+
{
|
|
284
|
+
"name": "label",
|
|
285
|
+
"label": "Payload",
|
|
286
|
+
"field": "label",
|
|
287
|
+
"align": "left",
|
|
288
|
+
},
|
|
289
|
+
{"name": "value", "label": "Value", "field": "value", "align": "right"},
|
|
290
|
+
{
|
|
291
|
+
"name": "status",
|
|
292
|
+
"label": "Status",
|
|
293
|
+
"field": "status_code",
|
|
294
|
+
"align": "center",
|
|
295
|
+
},
|
|
296
|
+
{"name": "ms", "label": "ms", "field": "duration_ms", "align": "right"},
|
|
297
|
+
{
|
|
298
|
+
"name": "suspicious",
|
|
299
|
+
"label": "⚠",
|
|
300
|
+
"field": "suspicious",
|
|
301
|
+
"align": "center",
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
rows=[],
|
|
305
|
+
row_key="label",
|
|
306
|
+
)
|
|
307
|
+
.classes("w-full")
|
|
308
|
+
.props("dense flat dark")
|
|
309
|
+
)
|
|
310
|
+
overflow_table.add_slot(
|
|
311
|
+
"body-cell-suspicious",
|
|
312
|
+
"""
|
|
313
|
+
<q-td :props="props">
|
|
314
|
+
<q-badge v-if="props.value" color="negative" label="!" />
|
|
315
|
+
</q-td>
|
|
316
|
+
""",
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
async def _run_overflow() -> None:
|
|
320
|
+
from pypproxy.security.int_overflow import run_checks as overflow_run_checks
|
|
321
|
+
|
|
322
|
+
entry = state.get("entry")
|
|
323
|
+
if not entry:
|
|
324
|
+
ui.notify("No entry selected", type="warning")
|
|
325
|
+
return
|
|
326
|
+
overflow_run_btn.props("loading")
|
|
327
|
+
try:
|
|
328
|
+
results = await overflow_run_checks(entry)
|
|
329
|
+
if not results:
|
|
330
|
+
ui.notify("No integer parameters found in this entry", type="info")
|
|
331
|
+
return
|
|
332
|
+
overflow_table.rows = [r.to_dict() for r in results]
|
|
333
|
+
overflow_table.update()
|
|
334
|
+
suspicious = sum(1 for r in results if r.suspicious)
|
|
335
|
+
overflow_label.text = (
|
|
336
|
+
f"{len(results)} tests — {suspicious} suspicious (5xx responses)"
|
|
337
|
+
)
|
|
338
|
+
if suspicious:
|
|
339
|
+
ui.notify(f"⚠ {suspicious} server errors triggered!", type="warning")
|
|
340
|
+
else:
|
|
341
|
+
ui.notify("Done", type="positive")
|
|
342
|
+
finally:
|
|
343
|
+
overflow_run_btn.props(remove="loading")
|
|
344
|
+
|
|
345
|
+
overflow_run_btn.on("click", _run_overflow)
|
|
346
|
+
|
|
347
|
+
def open_entry(entry: Entry) -> None:
|
|
348
|
+
state["entry"] = entry
|
|
349
|
+
entry_label.text = f"#{entry.id} {entry.method} {entry.scheme}://{entry.host}{entry.path}"
|
|
350
|
+
# Auto-fill JWT token
|
|
351
|
+
token = extract_jwt_from_headers(entry.req_headers)
|
|
352
|
+
if token:
|
|
353
|
+
token_input.value = token
|
|
354
|
+
rand_token_input.value = token
|
|
355
|
+
|
|
356
|
+
return {"open_entry": open_entry}
|