pentool 1.0.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.
Files changed (169) hide show
  1. pentool/__init__.py +4 -0
  2. pentool/__main__.py +17 -0
  3. pentool/api/__init__.py +45 -0
  4. pentool/api/base_api.py +52 -0
  5. pentool/api/comparer_api.py +23 -0
  6. pentool/api/decoder_api.py +28 -0
  7. pentool/api/intruder_api.py +209 -0
  8. pentool/api/proxy_api.py +319 -0
  9. pentool/api/repeater_api.py +118 -0
  10. pentool/api/scanner_api.py +336 -0
  11. pentool/api/sequencer_api.py +21 -0
  12. pentool/api/spider_api.py +126 -0
  13. pentool/api/target_api.py +121 -0
  14. pentool/cli/__init__.py +1 -0
  15. pentool/cli/main.py +100 -0
  16. pentool/cli/project.py +90 -0
  17. pentool/cli/proxy.py +185 -0
  18. pentool/cli/scan.py +118 -0
  19. pentool/core/__init__.py +1 -0
  20. pentool/core/config.py +184 -0
  21. pentool/core/crash_reporter.py +75 -0
  22. pentool/core/database.py +139 -0
  23. pentool/core/event_bus.py +232 -0
  24. pentool/core/events.py +164 -0
  25. pentool/core/features.py +410 -0
  26. pentool/core/license.py +294 -0
  27. pentool/core/logging.py +59 -0
  28. pentool/core/plugin_manager.py +332 -0
  29. pentool/core/project.py +37 -0
  30. pentool/core/storage_interface.py +311 -0
  31. pentool/core/updater.py +96 -0
  32. pentool/core/utils.py +61 -0
  33. pentool/modules/__init__.py +1 -0
  34. pentool/modules/comparer.py +177 -0
  35. pentool/modules/decoder.py +281 -0
  36. pentool/modules/intruder.py +432 -0
  37. pentool/modules/intruder_turbo.py +190 -0
  38. pentool/modules/match_replace.py +146 -0
  39. pentool/modules/proxy.py +803 -0
  40. pentool/modules/repeater.py +237 -0
  41. pentool/modules/scanner/__init__.py +7 -0
  42. pentool/modules/scanner/ai_analyzer.py +230 -0
  43. pentool/modules/scanner/base.py +173 -0
  44. pentool/modules/scanner/baseline.py +101 -0
  45. pentool/modules/scanner/checks/__init__.py +47 -0
  46. pentool/modules/scanner/checks/broken_auth.py +231 -0
  47. pentool/modules/scanner/checks/cors.py +209 -0
  48. pentool/modules/scanner/checks/dom_xss.py +114 -0
  49. pentool/modules/scanner/checks/graphql.py +110 -0
  50. pentool/modules/scanner/checks/header_injection.py +262 -0
  51. pentool/modules/scanner/checks/headers.py +75 -0
  52. pentool/modules/scanner/checks/helpers.py +358 -0
  53. pentool/modules/scanner/checks/info_leak.py +68 -0
  54. pentool/modules/scanner/checks/jwt_none.py +238 -0
  55. pentool/modules/scanner/checks/lfi.py +186 -0
  56. pentool/modules/scanner/checks/nosql_injection.py +92 -0
  57. pentool/modules/scanner/checks/oauth.py +123 -0
  58. pentool/modules/scanner/checks/open_redirect.py +168 -0
  59. pentool/modules/scanner/checks/path_traversal.py +229 -0
  60. pentool/modules/scanner/checks/prototype_pollution.py +88 -0
  61. pentool/modules/scanner/checks/rce.py +338 -0
  62. pentool/modules/scanner/checks/sensitive_data.py +74 -0
  63. pentool/modules/scanner/checks/sqli.py +546 -0
  64. pentool/modules/scanner/checks/ssrf.py +273 -0
  65. pentool/modules/scanner/checks/ssti.py +353 -0
  66. pentool/modules/scanner/checks/xss.py +622 -0
  67. pentool/modules/scanner/checks/xxe.py +240 -0
  68. pentool/modules/scanner/engine.py +407 -0
  69. pentool/modules/scanner/fingerprint.py +169 -0
  70. pentool/modules/scanner/helpers.py +94 -0
  71. pentool/modules/scanner/mutator.py +378 -0
  72. pentool/modules/scanner/oob.py +104 -0
  73. pentool/modules/scanner/passive.py +114 -0
  74. pentool/modules/scanner/payloads.py +224 -0
  75. pentool/modules/scanner/report.py +117 -0
  76. pentool/modules/sequencer.py +500 -0
  77. pentool/modules/spider.py +702 -0
  78. pentool/modules/target.py +190 -0
  79. pentool/modules/websocket_handler.py +259 -0
  80. pentool/plugins/__init__.py +7 -0
  81. pentool/plugins/builtin/__init__.py +1 -0
  82. pentool/plugins/builtin/payloads_pro.py +404 -0
  83. pentool/plugins/builtin/reports_pro.py +450 -0
  84. pentool/plugins/builtin/scanner_pro.py +308 -0
  85. pentool/plugins/example_plugin.py +75 -0
  86. pentool/project_to_txt.py +67 -0
  87. pentool/services/__init__.py +1 -0
  88. pentool/services/base_service.py +64 -0
  89. pentool/services/intruder_service.py +108 -0
  90. pentool/services/proxy_service.py +232 -0
  91. pentool/services/repeater_service.py +111 -0
  92. pentool/services/scan_service.py +340 -0
  93. pentool/storage/__init__.py +0 -0
  94. pentool/storage/http_storage.py +522 -0
  95. pentool/storage/large_body_handler.py +54 -0
  96. pentool/storage/lru_cache.py +49 -0
  97. pentool/t.py +213 -0
  98. pentool/tui/__init__.py +1 -0
  99. pentool/tui/app.py +1438 -0
  100. pentool/tui/constants.py +79 -0
  101. pentool/tui/dialogs/__init__.py +0 -0
  102. pentool/tui/dialogs/cert_dialog.py +81 -0
  103. pentool/tui/dialogs/file_selector.py +227 -0
  104. pentool/tui/dialogs/load_from_proxy.py +125 -0
  105. pentool/tui/dialogs/match_replace_dialog.py +229 -0
  106. pentool/tui/dialogs/project_dialog.py +82 -0
  107. pentool/tui/dialogs/scope_dialog.py +225 -0
  108. pentool/tui/messages.py +121 -0
  109. pentool/tui/mixins/__init__.py +0 -0
  110. pentool/tui/mixins/app_mixin.py +61 -0
  111. pentool/tui/mixins/dialog_cancel.py +25 -0
  112. pentool/tui/mixins/request_context_menu.py +285 -0
  113. pentool/tui/mixins/tab_rename.py +101 -0
  114. pentool/tui/screens/__init__.py +31 -0
  115. pentool/tui/screens/comparer/__init__.py +3 -0
  116. pentool/tui/screens/comparer/screen.py +233 -0
  117. pentool/tui/screens/dashboard/__init__.py +2 -0
  118. pentool/tui/screens/dashboard/live_dashboard.py +741 -0
  119. pentool/tui/screens/dashboard/screen.py +820 -0
  120. pentool/tui/screens/decoder/__init__.py +3 -0
  121. pentool/tui/screens/decoder/screen.py +311 -0
  122. pentool/tui/screens/extensions/__init__.py +3 -0
  123. pentool/tui/screens/extensions/screen.py +24 -0
  124. pentool/tui/screens/intruder/__init__.py +3 -0
  125. pentool/tui/screens/intruder/screen.py +1362 -0
  126. pentool/tui/screens/proxy/__init__.py +3 -0
  127. pentool/tui/screens/proxy/screen.py +1664 -0
  128. pentool/tui/screens/repeater/__init__.py +3 -0
  129. pentool/tui/screens/repeater/screen.py +646 -0
  130. pentool/tui/screens/scanner/__init__.py +3 -0
  131. pentool/tui/screens/scanner/screen.py +1984 -0
  132. pentool/tui/screens/sequencer/__init__.py +3 -0
  133. pentool/tui/screens/sequencer/screen.py +520 -0
  134. pentool/tui/screens/settings/__init__.py +5 -0
  135. pentool/tui/screens/settings/hotkeys.py +134 -0
  136. pentool/tui/screens/settings/screen.py +543 -0
  137. pentool/tui/screens/spider/__init__.py +3 -0
  138. pentool/tui/screens/spider/screen.py +385 -0
  139. pentool/tui/screens/target/__init__.py +4 -0
  140. pentool/tui/screens/target/screen.py +361 -0
  141. pentool/tui/screens/terminal/__init__.py +5 -0
  142. pentool/tui/screens/terminal/screen.py +181 -0
  143. pentool/tui/widgets/__init__.py +1 -0
  144. pentool/tui/widgets/context_menu.py +148 -0
  145. pentool/tui/widgets/filter_bar.py +206 -0
  146. pentool/tui/widgets/inspector_panel.py +112 -0
  147. pentool/tui/widgets/menu.py +86 -0
  148. pentool/tui/widgets/menu_bar.py +238 -0
  149. pentool/tui/widgets/module_tabs.py +73 -0
  150. pentool/tui/widgets/payload_drop_zone.py +66 -0
  151. pentool/tui/widgets/request_editor.py +504 -0
  152. pentool/tui/widgets/resize_handle.py +116 -0
  153. pentool/tui/widgets/search_bar.py +113 -0
  154. pentool/tui/widgets/statusbar.py +99 -0
  155. pentool/tui/widgets/toolbar_button.py +76 -0
  156. pentool/utils/__init__.py +1 -0
  157. pentool/utils/cert.py +361 -0
  158. pentool/utils/coder.py +170 -0
  159. pentool/utils/copy_as.py +251 -0
  160. pentool/utils/diff.py +91 -0
  161. pentool/utils/http_client.py +175 -0
  162. pentool/utils/parser.py +227 -0
  163. pentool/utils/terminal_check.py +32 -0
  164. pentool-1.0.0.dist-info/METADATA +155 -0
  165. pentool-1.0.0.dist-info/RECORD +169 -0
  166. pentool-1.0.0.dist-info/WHEEL +5 -0
  167. pentool-1.0.0.dist-info/entry_points.txt +2 -0
  168. pentool-1.0.0.dist-info/licenses/LICENSE +34 -0
  169. pentool-1.0.0.dist-info/top_level.txt +1 -0
pentool/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ """Pentool — консольный аналог Burp Suite с TUI на Textual."""
2
+
3
+ __version__ = "0.1.0"
4
+ __author__ = "pentool"
pentool/__main__.py ADDED
@@ -0,0 +1,17 @@
1
+ """Точка входа: без аргументов — TUI, с аргументами — CLI."""
2
+
3
+ import sys
4
+
5
+
6
+ def main() -> None:
7
+ """Запустить TUI или CLI в зависимости от аргументов."""
8
+ if len(sys.argv) > 1:
9
+ from pentool.cli.main import cli
10
+ cli()
11
+ else:
12
+ from pentool.tui.app import PentoolApp
13
+ PentoolApp().run()
14
+
15
+
16
+ if __name__ == "__main__":
17
+ main()
@@ -0,0 +1,45 @@
1
+ """API-слой pentool — публичный интерфейс между modules/ и TUI/CLI.
2
+
3
+ TUI и CLI импортируют только из pentool.api, не из pentool.modules напрямую.
4
+ Это позволяет независимо менять логику и отображение.
5
+ """
6
+
7
+ from pentool.api.proxy_api import ProxyAPI, InterceptedRequest, MatchReplaceRule
8
+ from pentool.api.repeater_api import RepeaterAPI
9
+ from pentool.api.scanner_api import ScannerAPI
10
+ from pentool.api.intruder_api import IntruderAPI, IntruderConfig, IntruderAttack
11
+ from pentool.api.spider_api import SpiderAPI
12
+ from pentool.api.target_api import TargetAPI
13
+ # Decoder/Comparer/Sequencer — функциональные API без класса-обёртки
14
+ from pentool.api.decoder_api import ( # noqa: F401
15
+ OPERATIONS, OP_LABELS, DecoderChain,
16
+ decode_op, decode_smart, encode_op, run_chain,
17
+ )
18
+ from pentool.api.comparer_api import ( # noqa: F401
19
+ compare, compare_lines, CompareStats, DiffLine, DiffResult,
20
+ )
21
+ from pentool.api.sequencer_api import ( # noqa: F401
22
+ Sequencer, SequencerReport, token_entropy, charset_size,
23
+ )
24
+
25
+ __all__ = [
26
+ # Proxy
27
+ "ProxyAPI", "InterceptedRequest", "MatchReplaceRule",
28
+ # Repeater
29
+ "RepeaterAPI",
30
+ # Scanner
31
+ "ScannerAPI",
32
+ # Intruder
33
+ "IntruderAPI", "IntruderConfig", "IntruderAttack",
34
+ # Spider
35
+ "SpiderAPI",
36
+ # Target
37
+ "TargetAPI",
38
+ # Decoder
39
+ "OPERATIONS", "OP_LABELS", "DecoderChain",
40
+ "decode_op", "decode_smart", "encode_op", "run_chain",
41
+ # Comparer
42
+ "compare", "compare_lines", "CompareStats", "DiffLine", "DiffResult",
43
+ # Sequencer
44
+ "Sequencer", "SequencerReport", "token_entropy", "charset_size",
45
+ ]
@@ -0,0 +1,52 @@
1
+ """ExportableAPI — base mixin for API classes supporting project persistence.
2
+
3
+ All API classes that implement export_project_data/import_project_data should
4
+ inherit from this mixin for type consistency and documentation.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from abc import ABC, abstractmethod
10
+
11
+
12
+ class ExportableAPI(ABC):
13
+ """Mixin for API classes that support project data export/import.
14
+
15
+ Subclasses must implement export_project_data and import_project_data.
16
+ These methods are called by core.project.save_project/load_project.
17
+
18
+ Usage::
19
+
20
+ class MyAPI(ExportableAPI):
21
+ def export_project_data(self) -> dict:
22
+ return {"my_data": [...]}
23
+
24
+ def import_project_data(self, data: dict) -> int:
25
+ items = data.get("my_data", [])
26
+ # restore items
27
+ return len(items)
28
+ """
29
+
30
+ @abstractmethod
31
+ def export_project_data(self) -> dict:
32
+ """Export module state to a serializable dict.
33
+
34
+ Returns:
35
+ dict: Module-specific data structure (e.g., {"results": [...]}).
36
+ Must be JSON-serializable (no datetime, use .isoformat()).
37
+ """
38
+ raise NotImplementedError
39
+
40
+ @abstractmethod
41
+ def import_project_data(self, data: dict) -> int | tuple[int, str]:
42
+ """Import module state from a loaded project dict.
43
+
44
+ Args:
45
+ data: The module's block from project.json (e.g., data["intruder"]).
46
+
47
+ Returns:
48
+ int: Number of items loaded successfully.
49
+ OR
50
+ tuple[int, str]: (count, error_message) — empty string if OK.
51
+ """
52
+ raise NotImplementedError
@@ -0,0 +1,23 @@
1
+ """ComparerAPI — публичный интерфейс Comparer для TUI.
2
+
3
+ TUI-экраны импортируют только этот модуль,
4
+ не modules/comparer.py напрямую.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pentool.modules.comparer import ( # noqa: F401
10
+ compare,
11
+ compare_lines,
12
+ CompareStats,
13
+ DiffLine,
14
+ DiffResult,
15
+ )
16
+
17
+ __all__ = [
18
+ "compare",
19
+ "compare_lines",
20
+ "CompareStats",
21
+ "DiffLine",
22
+ "DiffResult",
23
+ ]
@@ -0,0 +1,28 @@
1
+ """DecoderAPI — публичный интерфейс Decoder/Encoder для TUI.
2
+
3
+ TUI-экраны импортируют только этот модуль,
4
+ не modules/decoder.py напрямую.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ # Реэкспорт всего публичного из modules/decoder
10
+ from pentool.modules.decoder import ( # noqa: F401
11
+ OPERATIONS,
12
+ OP_LABELS,
13
+ DecoderChain,
14
+ decode_op,
15
+ decode_smart,
16
+ encode_op,
17
+ run_chain,
18
+ )
19
+
20
+ __all__ = [
21
+ "OPERATIONS",
22
+ "OP_LABELS",
23
+ "DecoderChain",
24
+ "decode_op",
25
+ "decode_smart",
26
+ "encode_op",
27
+ "run_chain",
28
+ ]
@@ -0,0 +1,209 @@
1
+ """Публичный API Intruder-модуля для TUI и CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+
7
+ from pentool.modules.intruder import (
8
+ AttackType,
9
+ IntruderAttack,
10
+ IntruderConfig,
11
+ IntruderResult,
12
+ count_markers,
13
+ extract_marker_defaults,
14
+ generate_char_payloads,
15
+ generate_numeric_payloads,
16
+ load_payloads_from_file,
17
+ process_payload,
18
+ )
19
+
20
+ __all__ = [
21
+ "IntruderAPI",
22
+ "AttackType",
23
+ "IntruderAttack",
24
+ "IntruderConfig",
25
+ "IntruderResult",
26
+ "count_markers",
27
+ "extract_marker_defaults",
28
+ "generate_char_payloads",
29
+ "generate_numeric_payloads",
30
+ "load_payloads_from_file",
31
+ "process_payload",
32
+ ]
33
+
34
+
35
+ class IntruderAPI:
36
+ """Публичный интерфейс Intruder-модуля.
37
+
38
+ Тонкая обёртка над IntruderAttack.
39
+ Не знает о Textual.
40
+ """
41
+
42
+ def __init__(self, db_path: str | None = None) -> None:
43
+ self._db_path = db_path
44
+ self._attack: IntruderAttack | None = None
45
+
46
+ async def start_attack(
47
+ self,
48
+ config: IntruderConfig,
49
+ on_result=None,
50
+ on_progress=None,
51
+ turbo_mode: bool = False,
52
+ ) -> str:
53
+ """Запустить атаку. Возвращает attack_id.
54
+
55
+ Args:
56
+ config: Конфиг атаки.
57
+ on_result: Callback(IntruderResult) — вызывается на каждый результат.
58
+ on_progress: Callback(done, total) — вызывается при обновлении прогресса.
59
+ turbo_mode: Использовать Turbo Mode (HTTP Keep-Alive, connection pooling).
60
+
61
+ Returns:
62
+ str: attack_id (UUID).
63
+ """
64
+ if turbo_mode:
65
+ # Turbo Mode: connection pooling + Keep-Alive
66
+ from pentool.modules.intruder_turbo import TurboIntruderAttack
67
+ self._attack = TurboIntruderAttack(config)
68
+ else:
69
+ # Обычный режим
70
+ self._attack = IntruderAttack(config, db_path=self._db_path)
71
+
72
+ _on_result = on_result if on_result else lambda r: None
73
+ _on_progress = on_progress if on_progress else lambda d, t: None
74
+
75
+ import asyncio
76
+ asyncio.create_task(self._attack.run(_on_result, _on_progress))
77
+ return self._attack.attack_id if hasattr(self._attack, 'attack_id') else "turbo"
78
+
79
+ async def pause(self) -> None:
80
+ """Поставить атаку на паузу."""
81
+ if self._attack:
82
+ await self._attack.pause()
83
+
84
+ async def resume(self) -> None:
85
+ """Возобновить атаку после паузы."""
86
+ if self._attack:
87
+ await self._attack.resume()
88
+
89
+ async def stop(self) -> None:
90
+ """Остановить атаку."""
91
+ if self._attack:
92
+ await self._attack.stop()
93
+
94
+ def get_results(self) -> list[IntruderResult]:
95
+ """Получить список результатов текущей атаки."""
96
+ if self._attack:
97
+ # Turbo mode использует get_results(), обычный - results property
98
+ if hasattr(self._attack, 'get_results'):
99
+ return self._attack.get_results()
100
+ return self._attack.results
101
+ return []
102
+
103
+ def get_progress(self) -> tuple[int, int]:
104
+ """Вернуть (done, total) текущей атаки."""
105
+ if self._attack:
106
+ return self._attack.progress
107
+ return (0, 0)
108
+
109
+ @property
110
+ def is_running(self) -> bool:
111
+ """Возвращает True если атака сейчас выполняется."""
112
+ return bool(self._attack and self._attack.is_running)
113
+
114
+ async def load_payloads(self, path: str) -> list[str]:
115
+ """Загрузить payload'ы из файла."""
116
+ return load_payloads_from_file(path)
117
+
118
+ async def generate_numeric(self, start: int, end: int, step: int = 1) -> list[str]:
119
+ """Сгенерировать числовые payload'ы."""
120
+ return generate_numeric_payloads(start, end, step)
121
+
122
+ async def generate_chars(
123
+ self, charset: str, min_len: int, max_len: int
124
+ ) -> list[str]:
125
+ """Сгенерировать символьные payload'ы."""
126
+ return generate_char_payloads(charset, min_len, max_len)
127
+
128
+ def export_csv(self, path: str) -> None:
129
+ """Экспортировать результаты в CSV."""
130
+ if self._attack:
131
+ self._attack.export_csv(path)
132
+
133
+ # ── Project persistence ────────────────────────────────────────────────────
134
+
135
+ def export_project_data(self) -> dict:
136
+ """Экспортировать результаты Intruder в сериализуемый dict.
137
+
138
+ Returns:
139
+ dict с ключом "results" — список IntruderResult в виде dict.
140
+ """
141
+ results = self.get_results()
142
+ return {
143
+ "results": [
144
+ {
145
+ "id": r.id,
146
+ "attack_id": r.attack_id,
147
+ "request_number": r.request_number,
148
+ "payload_values": r.payload_values,
149
+ "request_raw": r.request_raw,
150
+ "response_status": r.response_status,
151
+ "response_length": r.response_length,
152
+ "response_time_ms": r.response_time_ms,
153
+ "error": r.error,
154
+ "timestamp": r.timestamp.isoformat(),
155
+ }
156
+ for r in results
157
+ ]
158
+ }
159
+
160
+ def import_project_data(self, data: dict) -> int:
161
+ """Импортировать результаты Intruder из загруженного dict проекта.
162
+
163
+ Args:
164
+ data: Блок "intruder" из project.json ({"results": [...]}).
165
+
166
+ Returns:
167
+ Количество загруженных результатов.
168
+ """
169
+ from datetime import datetime, timezone
170
+ from pentool.modules.intruder import IntruderResult
171
+
172
+ results_data = data.get("results", [])
173
+ # Создаём фиктивный attack для хранения результатов
174
+ # без реального запуска атаки
175
+ if self._attack is None:
176
+ # Ленивая инициализация — просто храним в атрибуте
177
+ self._restored_results: list[IntruderResult] = []
178
+ else:
179
+ self._restored_results = []
180
+
181
+ loaded = 0
182
+ for rd in results_data:
183
+ try:
184
+ ts_raw = rd.get("timestamp", "")
185
+ try:
186
+ ts = datetime.fromisoformat(ts_raw)
187
+ except Exception:
188
+ ts = datetime.now(timezone.utc)
189
+ result = IntruderResult(
190
+ id=rd.get("id", ""),
191
+ attack_id=rd.get("attack_id", ""),
192
+ request_number=rd.get("request_number", 0),
193
+ payload_values=rd.get("payload_values", []),
194
+ request_raw=rd.get("request_raw", ""),
195
+ response_status=rd.get("response_status"),
196
+ response_length=rd.get("response_length"),
197
+ response_time_ms=rd.get("response_time_ms"),
198
+ error=rd.get("error"),
199
+ timestamp=ts,
200
+ )
201
+ if hasattr(self, "_restored_results"):
202
+ self._restored_results.append(result)
203
+ loaded += 1
204
+ except Exception as exc:
205
+ from pentool.core.logging import get_logger
206
+ get_logger(__name__).warning(
207
+ "IntruderAPI.import_project_data: skip result: %s", exc
208
+ )
209
+ return loaded