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.
- pentool/__init__.py +4 -0
- pentool/__main__.py +17 -0
- pentool/api/__init__.py +45 -0
- pentool/api/base_api.py +52 -0
- pentool/api/comparer_api.py +23 -0
- pentool/api/decoder_api.py +28 -0
- pentool/api/intruder_api.py +209 -0
- pentool/api/proxy_api.py +319 -0
- pentool/api/repeater_api.py +118 -0
- pentool/api/scanner_api.py +336 -0
- pentool/api/sequencer_api.py +21 -0
- pentool/api/spider_api.py +126 -0
- pentool/api/target_api.py +121 -0
- pentool/cli/__init__.py +1 -0
- pentool/cli/main.py +100 -0
- pentool/cli/project.py +90 -0
- pentool/cli/proxy.py +185 -0
- pentool/cli/scan.py +118 -0
- pentool/core/__init__.py +1 -0
- pentool/core/config.py +184 -0
- pentool/core/crash_reporter.py +75 -0
- pentool/core/database.py +139 -0
- pentool/core/event_bus.py +232 -0
- pentool/core/events.py +164 -0
- pentool/core/features.py +410 -0
- pentool/core/license.py +294 -0
- pentool/core/logging.py +59 -0
- pentool/core/plugin_manager.py +332 -0
- pentool/core/project.py +37 -0
- pentool/core/storage_interface.py +311 -0
- pentool/core/updater.py +96 -0
- pentool/core/utils.py +61 -0
- pentool/modules/__init__.py +1 -0
- pentool/modules/comparer.py +177 -0
- pentool/modules/decoder.py +281 -0
- pentool/modules/intruder.py +432 -0
- pentool/modules/intruder_turbo.py +190 -0
- pentool/modules/match_replace.py +146 -0
- pentool/modules/proxy.py +803 -0
- pentool/modules/repeater.py +237 -0
- pentool/modules/scanner/__init__.py +7 -0
- pentool/modules/scanner/ai_analyzer.py +230 -0
- pentool/modules/scanner/base.py +173 -0
- pentool/modules/scanner/baseline.py +101 -0
- pentool/modules/scanner/checks/__init__.py +47 -0
- pentool/modules/scanner/checks/broken_auth.py +231 -0
- pentool/modules/scanner/checks/cors.py +209 -0
- pentool/modules/scanner/checks/dom_xss.py +114 -0
- pentool/modules/scanner/checks/graphql.py +110 -0
- pentool/modules/scanner/checks/header_injection.py +262 -0
- pentool/modules/scanner/checks/headers.py +75 -0
- pentool/modules/scanner/checks/helpers.py +358 -0
- pentool/modules/scanner/checks/info_leak.py +68 -0
- pentool/modules/scanner/checks/jwt_none.py +238 -0
- pentool/modules/scanner/checks/lfi.py +186 -0
- pentool/modules/scanner/checks/nosql_injection.py +92 -0
- pentool/modules/scanner/checks/oauth.py +123 -0
- pentool/modules/scanner/checks/open_redirect.py +168 -0
- pentool/modules/scanner/checks/path_traversal.py +229 -0
- pentool/modules/scanner/checks/prototype_pollution.py +88 -0
- pentool/modules/scanner/checks/rce.py +338 -0
- pentool/modules/scanner/checks/sensitive_data.py +74 -0
- pentool/modules/scanner/checks/sqli.py +546 -0
- pentool/modules/scanner/checks/ssrf.py +273 -0
- pentool/modules/scanner/checks/ssti.py +353 -0
- pentool/modules/scanner/checks/xss.py +622 -0
- pentool/modules/scanner/checks/xxe.py +240 -0
- pentool/modules/scanner/engine.py +407 -0
- pentool/modules/scanner/fingerprint.py +169 -0
- pentool/modules/scanner/helpers.py +94 -0
- pentool/modules/scanner/mutator.py +378 -0
- pentool/modules/scanner/oob.py +104 -0
- pentool/modules/scanner/passive.py +114 -0
- pentool/modules/scanner/payloads.py +224 -0
- pentool/modules/scanner/report.py +117 -0
- pentool/modules/sequencer.py +500 -0
- pentool/modules/spider.py +702 -0
- pentool/modules/target.py +190 -0
- pentool/modules/websocket_handler.py +259 -0
- pentool/plugins/__init__.py +7 -0
- pentool/plugins/builtin/__init__.py +1 -0
- pentool/plugins/builtin/payloads_pro.py +404 -0
- pentool/plugins/builtin/reports_pro.py +450 -0
- pentool/plugins/builtin/scanner_pro.py +308 -0
- pentool/plugins/example_plugin.py +75 -0
- pentool/project_to_txt.py +67 -0
- pentool/services/__init__.py +1 -0
- pentool/services/base_service.py +64 -0
- pentool/services/intruder_service.py +108 -0
- pentool/services/proxy_service.py +232 -0
- pentool/services/repeater_service.py +111 -0
- pentool/services/scan_service.py +340 -0
- pentool/storage/__init__.py +0 -0
- pentool/storage/http_storage.py +522 -0
- pentool/storage/large_body_handler.py +54 -0
- pentool/storage/lru_cache.py +49 -0
- pentool/t.py +213 -0
- pentool/tui/__init__.py +1 -0
- pentool/tui/app.py +1438 -0
- pentool/tui/constants.py +79 -0
- pentool/tui/dialogs/__init__.py +0 -0
- pentool/tui/dialogs/cert_dialog.py +81 -0
- pentool/tui/dialogs/file_selector.py +227 -0
- pentool/tui/dialogs/load_from_proxy.py +125 -0
- pentool/tui/dialogs/match_replace_dialog.py +229 -0
- pentool/tui/dialogs/project_dialog.py +82 -0
- pentool/tui/dialogs/scope_dialog.py +225 -0
- pentool/tui/messages.py +121 -0
- pentool/tui/mixins/__init__.py +0 -0
- pentool/tui/mixins/app_mixin.py +61 -0
- pentool/tui/mixins/dialog_cancel.py +25 -0
- pentool/tui/mixins/request_context_menu.py +285 -0
- pentool/tui/mixins/tab_rename.py +101 -0
- pentool/tui/screens/__init__.py +31 -0
- pentool/tui/screens/comparer/__init__.py +3 -0
- pentool/tui/screens/comparer/screen.py +233 -0
- pentool/tui/screens/dashboard/__init__.py +2 -0
- pentool/tui/screens/dashboard/live_dashboard.py +741 -0
- pentool/tui/screens/dashboard/screen.py +820 -0
- pentool/tui/screens/decoder/__init__.py +3 -0
- pentool/tui/screens/decoder/screen.py +311 -0
- pentool/tui/screens/extensions/__init__.py +3 -0
- pentool/tui/screens/extensions/screen.py +24 -0
- pentool/tui/screens/intruder/__init__.py +3 -0
- pentool/tui/screens/intruder/screen.py +1362 -0
- pentool/tui/screens/proxy/__init__.py +3 -0
- pentool/tui/screens/proxy/screen.py +1664 -0
- pentool/tui/screens/repeater/__init__.py +3 -0
- pentool/tui/screens/repeater/screen.py +646 -0
- pentool/tui/screens/scanner/__init__.py +3 -0
- pentool/tui/screens/scanner/screen.py +1984 -0
- pentool/tui/screens/sequencer/__init__.py +3 -0
- pentool/tui/screens/sequencer/screen.py +520 -0
- pentool/tui/screens/settings/__init__.py +5 -0
- pentool/tui/screens/settings/hotkeys.py +134 -0
- pentool/tui/screens/settings/screen.py +543 -0
- pentool/tui/screens/spider/__init__.py +3 -0
- pentool/tui/screens/spider/screen.py +385 -0
- pentool/tui/screens/target/__init__.py +4 -0
- pentool/tui/screens/target/screen.py +361 -0
- pentool/tui/screens/terminal/__init__.py +5 -0
- pentool/tui/screens/terminal/screen.py +181 -0
- pentool/tui/widgets/__init__.py +1 -0
- pentool/tui/widgets/context_menu.py +148 -0
- pentool/tui/widgets/filter_bar.py +206 -0
- pentool/tui/widgets/inspector_panel.py +112 -0
- pentool/tui/widgets/menu.py +86 -0
- pentool/tui/widgets/menu_bar.py +238 -0
- pentool/tui/widgets/module_tabs.py +73 -0
- pentool/tui/widgets/payload_drop_zone.py +66 -0
- pentool/tui/widgets/request_editor.py +504 -0
- pentool/tui/widgets/resize_handle.py +116 -0
- pentool/tui/widgets/search_bar.py +113 -0
- pentool/tui/widgets/statusbar.py +99 -0
- pentool/tui/widgets/toolbar_button.py +76 -0
- pentool/utils/__init__.py +1 -0
- pentool/utils/cert.py +361 -0
- pentool/utils/coder.py +170 -0
- pentool/utils/copy_as.py +251 -0
- pentool/utils/diff.py +91 -0
- pentool/utils/http_client.py +175 -0
- pentool/utils/parser.py +227 -0
- pentool/utils/terminal_check.py +32 -0
- pentool-1.0.0.dist-info/METADATA +155 -0
- pentool-1.0.0.dist-info/RECORD +169 -0
- pentool-1.0.0.dist-info/WHEEL +5 -0
- pentool-1.0.0.dist-info/entry_points.txt +2 -0
- pentool-1.0.0.dist-info/licenses/LICENSE +34 -0
- pentool-1.0.0.dist-info/top_level.txt +1 -0
pentool/__init__.py
ADDED
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()
|
pentool/api/__init__.py
ADDED
|
@@ -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
|
+
]
|
pentool/api/base_api.py
ADDED
|
@@ -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
|