summit-scanner 0.7.2__tar.gz → 0.7.4__tar.gz

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.
Files changed (80) hide show
  1. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/PKG-INFO +1 -1
  2. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/pyproject.toml +1 -1
  3. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/cli.py +31 -0
  4. summit_scanner-0.7.4/summit/race.py +109 -0
  5. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/core.py +1 -0
  6. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit_scanner.egg-info/PKG-INFO +1 -1
  7. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit_scanner.egg-info/SOURCES.txt +1 -0
  8. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/LICENSE +0 -0
  9. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/README.md +0 -0
  10. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/setup.cfg +0 -0
  11. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/__init__.py +0 -0
  12. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/authorize.py +0 -0
  13. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/captcha.py +0 -0
  14. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/crawl/__init__.py +0 -0
  15. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/crawl/js_endpoints.py +0 -0
  16. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/crawl/spider.py +0 -0
  17. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/cve.py +0 -0
  18. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/fuzz/__init__.py +0 -0
  19. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/fuzz/dirbuster.py +0 -0
  20. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/graphql.py +0 -0
  21. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/http/__init__.py +0 -0
  22. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/http/client.py +0 -0
  23. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/http/models.py +0 -0
  24. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/openapi.py +0 -0
  25. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/paramminer.py +0 -0
  26. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/proxy/__init__.py +0 -0
  27. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/proxy/server.py +0 -0
  28. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/ratelimit.py +0 -0
  29. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/recon.py +0 -0
  30. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/report/__init__.py +0 -0
  31. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/report/writer.py +0 -0
  32. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/__init__.py +0 -0
  33. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/engine.py +0 -0
  34. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/linkcheck.py +0 -0
  35. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/__init__.py +0 -0
  36. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/body_cves.py +0 -0
  37. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/cacheable.py +0 -0
  38. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/cors_reflect.py +0 -0
  39. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/crlf.py +0 -0
  40. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/csrf.py +0 -0
  41. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/directory_listing.py +0 -0
  42. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/enumeration.py +0 -0
  43. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/format_string.py +0 -0
  44. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/headers_grade.py +0 -0
  45. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/host_header.py +0 -0
  46. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/ldap.py +0 -0
  47. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/nosql.py +0 -0
  48. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/open_redirect.py +0 -0
  49. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/os_command.py +0 -0
  50. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/path_traversal.py +0 -0
  51. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/sensitive_files.py +0 -0
  52. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/sqli.py +0 -0
  53. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/sqli_time.py +0 -0
  54. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/ssti.py +0 -0
  55. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/waf_bypass.py +0 -0
  56. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/modules/xss.py +0 -0
  57. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/scan/passive.py +0 -0
  58. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/smart.py +0 -0
  59. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/takeover.py +0 -0
  60. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/tls.py +0 -0
  61. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/toolkit.py +0 -0
  62. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/updates.py +0 -0
  63. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit/waf.py +0 -0
  64. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit_scanner.egg-info/dependency_links.txt +0 -0
  65. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit_scanner.egg-info/entry_points.txt +0 -0
  66. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit_scanner.egg-info/requires.txt +0 -0
  67. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/summit_scanner.egg-info/top_level.txt +0 -0
  68. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/tests/test_fp_guards.py +0 -0
  69. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/tests/test_linkcheck.py +0 -0
  70. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/tests/test_models.py +0 -0
  71. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/tests/test_new_checks.py +0 -0
  72. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/tests/test_passive.py +0 -0
  73. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/tests/test_retries.py +0 -0
  74. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/tests/test_severity.py +0 -0
  75. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/tests/test_tls.py +0 -0
  76. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/tests/test_v05.py +0 -0
  77. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/tests/test_v06.py +0 -0
  78. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/tests/test_v068.py +0 -0
  79. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/tests/test_v069.py +0 -0
  80. {summit_scanner-0.7.2 → summit_scanner-0.7.4}/tests/test_waf.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: summit-scanner
3
- Version: 0.7.2
3
+ Version: 0.7.4
4
4
  Summary: Summit - open-source web security testing platform (proxy, scanner, fuzzer) by Pennacle Security
5
5
  Author: Pennacle Security
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "summit-scanner"
7
- version = "0.7.2"
7
+ version = "0.7.4"
8
8
  description = "Summit - open-source web security testing platform (proxy, scanner, fuzzer) by Pennacle Security"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -13,6 +13,7 @@ from .http.models import HttpRequest
13
13
  from .openapi import load_spec, spec_pages
14
14
  from .paramminer import mine
15
15
  from .proxy.server import ProxyServer
16
+ from .race import findings_from as race_findings_from, run_race_test
16
17
  from .recon import missing_tools, render_report, run_recon
17
18
  from .report.writer import write_console, write_html, write_json
18
19
  from .scan.core import SEVERITIES, filter_by_min_severity, filter_by_severities
@@ -108,6 +109,15 @@ def build_parser() -> argparse.ArgumentParser:
108
109
  tls_p.add_argument("--timeout", type=float, default=10.0)
109
110
  tls_p.add_argument("--json", dest="json_path", help="write findings as JSON")
110
111
 
112
+ race_p = sub.add_parser("race", help="detect race conditions via concurrent identical requests")
113
+ race_p.add_argument("url", help="target URL that performs a state change")
114
+ race_p.add_argument("-n", "--concurrency", type=int, default=10, help="number of concurrent requests (default 10)")
115
+ race_p.add_argument("--method", default="GET", help="HTTP method (default GET)")
116
+ race_p.add_argument("--data", default=None, help="request body (for POST)")
117
+ race_p.add_argument("--cookie", default=None, help="Cookie header value")
118
+ race_p.add_argument("--timeout", type=float, default=10.0)
119
+ race_p.add_argument("--json", dest="json_path", help="write findings as JSON")
120
+
111
121
  return parser
112
122
 
113
123
 
@@ -310,6 +320,25 @@ def _cmd_tls(args: argparse.Namespace) -> int:
310
320
  return 0
311
321
 
312
322
 
323
+ def _cmd_race(args: argparse.Namespace) -> int:
324
+ print(BANNER.format(version=__version__))
325
+ client = HttpClient(timeout=args.timeout)
326
+ headers = {"Cookie": args.cookie} if args.cookie else {}
327
+ print(f"[*] testing {args.url} with {args.concurrency} concurrent {args.method} requests ...")
328
+ result = run_race_test(
329
+ client, args.url, concurrency=args.concurrency, method=args.method,
330
+ body=args.data.encode() if args.data else None, headers=headers,
331
+ )
332
+ print(f"[*] baseline: HTTP {result.baseline.status}/{len(result.baseline.body)}B")
333
+ print(f"[*] concurrent: {len(result.concurrent)} responses, {result.unique_responses} unique")
334
+ findings = race_findings_from([result])
335
+ print(write_console(findings, args.url))
336
+ if args.json_path:
337
+ write_json(findings, args.json_path)
338
+ print(f"[*] JSON report written to {args.json_path}")
339
+ return 0
340
+
341
+
313
342
  def _cmd_encode(args: argparse.Namespace) -> int:
314
343
  try:
315
344
  print(encode_text(args.value, args.encoding))
@@ -354,6 +383,8 @@ def main(argv: list[str] | None = None) -> int:
354
383
  return _cmd_recon(args)
355
384
  if args.command == "tls":
356
385
  return _cmd_tls(args)
386
+ if args.command == "race":
387
+ return _cmd_race(args)
357
388
  if args.command == "encode":
358
389
  return _cmd_encode(args)
359
390
  if args.command == "decode":
@@ -0,0 +1,109 @@
1
+ """Race condition detection - concurrent state manipulation.
2
+
3
+ Race conditions exist when concurrent requests produce a state that
4
+ sequential requests cannot. The classic examples: double-spending a
5
+ gift card, withdrawing more than the balance, creating duplicate
6
+ accounts, bypassing rate limits.
7
+
8
+ Detection strategy:
9
+
10
+ 1. send one request sequentially to establish the baseline response
11
+ 2. send N identical requests concurrently
12
+ 3. compare: if concurrent responses differ from the baseline in a
13
+ meaningful way (different body, different status, different side
14
+ effects visible in the response), the server has a concurrency bug
15
+
16
+ This is always `potential` - only a human who understands the business
17
+ logic can confirm that the differing responses represent an exploit
18
+ rather than expected variation.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from concurrent.futures import ThreadPoolExecutor, as_completed
24
+ from dataclasses import dataclass
25
+
26
+ from ...http.client import HttpClient
27
+ from ...http.models import HttpRequest, HttpResponse
28
+ from ..scan.core import Finding
29
+
30
+
31
+ @dataclass
32
+ class RaceResult:
33
+ url: str
34
+ baseline: HttpResponse
35
+ concurrent: list[HttpResponse]
36
+ unique_responses: int
37
+ verdict: str # no-race | possible-race
38
+
39
+
40
+ def _fingerprint(response: HttpResponse) -> tuple:
41
+ return (response.status, len(response.body))
42
+
43
+
44
+ def run_race_test(
45
+ client: HttpClient,
46
+ url: str,
47
+ concurrency: int = 10,
48
+ method: str = "GET",
49
+ body: bytes | None = None,
50
+ headers: dict[str, str] | None = None,
51
+ ) -> RaceResult:
52
+ """Send one sequential request, then N concurrent identical requests."""
53
+ request = HttpRequest(method=method, url=url, headers=headers or {}, body=body)
54
+
55
+ baseline = client.send(request)
56
+ base_fp = _fingerprint(baseline)
57
+
58
+ concurrent_responses: list[HttpResponse] = []
59
+ with ThreadPoolExecutor(max_workers=concurrency) as pool:
60
+ futures = [pool.submit(client.send, request) for _ in range(concurrency)]
61
+ for future in as_completed(futures):
62
+ concurrent_responses.append(future.result())
63
+
64
+ unique_fps = {_fingerprint(r) for r in concurrent_responses}
65
+ all_fps = unique_fps | {base_fp}
66
+
67
+ if len(all_fps) == 1:
68
+ verdict = "no-race"
69
+ elif len(unique_fps) > 1 or (len(all_fps) > 1 and base_fp not in unique_fps):
70
+ verdict = "possible-race"
71
+ else:
72
+ verdict = "possible-race"
73
+
74
+ return RaceResult(
75
+ url=url,
76
+ baseline=baseline,
77
+ concurrent=concurrent_responses,
78
+ unique_responses=len(unique_fps),
79
+ verdict=verdict,
80
+ )
81
+
82
+
83
+ def findings_from(results: list[RaceResult]) -> list[Finding]:
84
+ findings: list[Finding] = []
85
+ for result in results:
86
+ if result.verdict != "possible-race":
87
+ continue
88
+ statuses = sorted({r.status for r in result.concurrent})
89
+ sizes = sorted({len(r.body) for r in result.concurrent})
90
+ findings.append(
91
+ Finding(
92
+ check_id="race-condition",
93
+ title="Race condition (concurrent requests produce different responses)",
94
+ severity="high",
95
+ url=result.url,
96
+ confidence="potential",
97
+ detail=(
98
+ f"{len(result.concurrent)} concurrent requests produced "
99
+ f"{result.unique_responses} unique response(s) that differ from the "
100
+ f"sequential baseline (HTTP {result.baseline.status}/{len(result.baseline.body)}B). "
101
+ "If this endpoint performs a state change (balance transfer, voucher redemption, "
102
+ "rate limit), concurrent requests may bypass the check. Verify manually by "
103
+ "inspecting server-side state after the race."
104
+ ),
105
+ evidence=f"statuses: {statuses}, sizes: {sizes}, unique: {result.unique_responses}",
106
+ remediation="Use atomic operations, database-level locks, or idempotency keys for state-changing endpoints.",
107
+ )
108
+ )
109
+ return findings
@@ -68,6 +68,7 @@ CWE_BY_CHECK_ID = {
68
68
  "nosql-injection": "CWE-943",
69
69
  "ldap-injection": "CWE-90",
70
70
  "format-string": "CWE-134",
71
+ "race-condition": "CWE-362",
71
72
  }
72
73
 
73
74
  # Security impact per check: what an attacker concretely achieves, not
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: summit-scanner
3
- Version: 0.7.2
3
+ Version: 0.7.4
4
4
  Summary: Summit - open-source web security testing platform (proxy, scanner, fuzzer) by Pennacle Security
5
5
  Author: Pennacle Security
6
6
  License: MIT
@@ -9,6 +9,7 @@ summit/cve.py
9
9
  summit/graphql.py
10
10
  summit/openapi.py
11
11
  summit/paramminer.py
12
+ summit/race.py
12
13
  summit/ratelimit.py
13
14
  summit/recon.py
14
15
  summit/smart.py
File without changes
File without changes
File without changes