souleyez 2.43.26__py3-none-any.whl → 2.43.34__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.
- souleyez/__init__.py +1 -2
- souleyez/ai/__init__.py +21 -15
- souleyez/ai/action_mapper.py +249 -150
- souleyez/ai/chain_advisor.py +116 -100
- souleyez/ai/claude_provider.py +29 -28
- souleyez/ai/context_builder.py +80 -62
- souleyez/ai/executor.py +158 -117
- souleyez/ai/feedback_handler.py +136 -121
- souleyez/ai/llm_factory.py +27 -20
- souleyez/ai/llm_provider.py +4 -2
- souleyez/ai/ollama_provider.py +6 -9
- souleyez/ai/ollama_service.py +44 -37
- souleyez/ai/path_scorer.py +91 -76
- souleyez/ai/recommender.py +176 -144
- souleyez/ai/report_context.py +74 -73
- souleyez/ai/report_service.py +84 -66
- souleyez/ai/result_parser.py +222 -229
- souleyez/ai/safety.py +67 -44
- souleyez/auth/__init__.py +23 -22
- souleyez/auth/audit.py +36 -26
- souleyez/auth/engagement_access.py +65 -48
- souleyez/auth/permissions.py +14 -3
- souleyez/auth/session_manager.py +54 -37
- souleyez/auth/user_manager.py +109 -64
- souleyez/commands/audit.py +40 -43
- souleyez/commands/auth.py +35 -15
- souleyez/commands/deliverables.py +55 -50
- souleyez/commands/engagement.py +47 -28
- souleyez/commands/license.py +32 -23
- souleyez/commands/screenshots.py +36 -32
- souleyez/commands/user.py +82 -36
- souleyez/config.py +52 -44
- souleyez/core/credential_tester.py +87 -81
- souleyez/core/cve_mappings.py +179 -192
- souleyez/core/cve_matcher.py +162 -148
- souleyez/core/msf_auto_mapper.py +100 -83
- souleyez/core/msf_chain_engine.py +294 -256
- souleyez/core/msf_database.py +153 -70
- souleyez/core/msf_integration.py +679 -673
- souleyez/core/msf_rpc_client.py +40 -42
- souleyez/core/msf_rpc_manager.py +77 -79
- souleyez/core/msf_sync_manager.py +241 -181
- souleyez/core/network_utils.py +22 -15
- souleyez/core/parser_handler.py +34 -25
- souleyez/core/pending_chains.py +114 -63
- souleyez/core/templates.py +158 -107
- souleyez/core/tool_chaining.py +9526 -2879
- souleyez/core/version_utils.py +79 -94
- souleyez/core/vuln_correlation.py +136 -89
- souleyez/core/web_utils.py +33 -32
- souleyez/data/wordlists/ad_users.txt +378 -0
- souleyez/data/wordlists/api_endpoints_large.txt +769 -0
- souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
- souleyez/data/wordlists/lfi_payloads.txt +82 -0
- souleyez/data/wordlists/passwords_brute.txt +1548 -0
- souleyez/data/wordlists/passwords_crack.txt +2479 -0
- souleyez/data/wordlists/passwords_spray.txt +386 -0
- souleyez/data/wordlists/subdomains_large.txt +5057 -0
- souleyez/data/wordlists/usernames_common.txt +694 -0
- souleyez/data/wordlists/web_dirs_large.txt +4769 -0
- souleyez/detection/__init__.py +1 -1
- souleyez/detection/attack_signatures.py +12 -17
- souleyez/detection/mitre_mappings.py +61 -55
- souleyez/detection/validator.py +97 -86
- souleyez/devtools.py +23 -10
- souleyez/docs/README.md +4 -4
- souleyez/docs/api-reference/cli-commands.md +2 -2
- souleyez/docs/developer-guide/adding-new-tools.md +562 -0
- souleyez/docs/user-guide/auto-chaining.md +30 -8
- souleyez/docs/user-guide/getting-started.md +1 -1
- souleyez/docs/user-guide/installation.md +26 -3
- souleyez/docs/user-guide/metasploit-integration.md +2 -2
- souleyez/docs/user-guide/rbac.md +1 -1
- souleyez/docs/user-guide/scope-management.md +1 -1
- souleyez/docs/user-guide/siem-integration.md +1 -1
- souleyez/docs/user-guide/tools-reference.md +1 -8
- souleyez/docs/user-guide/worker-management.md +1 -1
- souleyez/engine/background.py +1239 -535
- souleyez/engine/base.py +4 -1
- souleyez/engine/job_status.py +17 -49
- souleyez/engine/log_sanitizer.py +103 -77
- souleyez/engine/manager.py +38 -7
- souleyez/engine/result_handler.py +2200 -1550
- souleyez/engine/worker_manager.py +50 -41
- souleyez/export/evidence_bundle.py +72 -62
- souleyez/feature_flags/features.py +16 -20
- souleyez/feature_flags.py +5 -9
- souleyez/handlers/__init__.py +11 -0
- souleyez/handlers/base.py +188 -0
- souleyez/handlers/bash_handler.py +277 -0
- souleyez/handlers/bloodhound_handler.py +243 -0
- souleyez/handlers/certipy_handler.py +311 -0
- souleyez/handlers/crackmapexec_handler.py +486 -0
- souleyez/handlers/dnsrecon_handler.py +344 -0
- souleyez/handlers/enum4linux_handler.py +400 -0
- souleyez/handlers/evil_winrm_handler.py +493 -0
- souleyez/handlers/ffuf_handler.py +815 -0
- souleyez/handlers/gobuster_handler.py +1114 -0
- souleyez/handlers/gpp_extract_handler.py +334 -0
- souleyez/handlers/hashcat_handler.py +444 -0
- souleyez/handlers/hydra_handler.py +563 -0
- souleyez/handlers/impacket_getuserspns_handler.py +343 -0
- souleyez/handlers/impacket_psexec_handler.py +222 -0
- souleyez/handlers/impacket_secretsdump_handler.py +426 -0
- souleyez/handlers/john_handler.py +286 -0
- souleyez/handlers/katana_handler.py +425 -0
- souleyez/handlers/kerbrute_handler.py +298 -0
- souleyez/handlers/ldapsearch_handler.py +636 -0
- souleyez/handlers/lfi_extract_handler.py +464 -0
- souleyez/handlers/msf_auxiliary_handler.py +408 -0
- souleyez/handlers/msf_exploit_handler.py +380 -0
- souleyez/handlers/nikto_handler.py +413 -0
- souleyez/handlers/nmap_handler.py +821 -0
- souleyez/handlers/nuclei_handler.py +359 -0
- souleyez/handlers/nxc_handler.py +371 -0
- souleyez/handlers/rdp_sec_check_handler.py +353 -0
- souleyez/handlers/registry.py +292 -0
- souleyez/handlers/responder_handler.py +232 -0
- souleyez/handlers/service_explorer_handler.py +434 -0
- souleyez/handlers/smbclient_handler.py +344 -0
- souleyez/handlers/smbmap_handler.py +510 -0
- souleyez/handlers/smbpasswd_handler.py +296 -0
- souleyez/handlers/sqlmap_handler.py +1116 -0
- souleyez/handlers/theharvester_handler.py +601 -0
- souleyez/handlers/web_login_test_handler.py +327 -0
- souleyez/handlers/whois_handler.py +277 -0
- souleyez/handlers/wpscan_handler.py +554 -0
- souleyez/history.py +32 -16
- souleyez/importers/msf_importer.py +106 -75
- souleyez/importers/smart_importer.py +208 -147
- souleyez/integrations/siem/__init__.py +10 -10
- souleyez/integrations/siem/base.py +17 -18
- souleyez/integrations/siem/elastic.py +108 -122
- souleyez/integrations/siem/factory.py +207 -80
- souleyez/integrations/siem/googlesecops.py +146 -154
- souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
- souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
- souleyez/integrations/siem/sentinel.py +107 -109
- souleyez/integrations/siem/splunk.py +246 -212
- souleyez/integrations/siem/wazuh.py +65 -71
- souleyez/integrations/wazuh/__init__.py +5 -5
- souleyez/integrations/wazuh/client.py +70 -93
- souleyez/integrations/wazuh/config.py +85 -57
- souleyez/integrations/wazuh/host_mapper.py +28 -36
- souleyez/integrations/wazuh/sync.py +78 -68
- souleyez/intelligence/__init__.py +4 -5
- souleyez/intelligence/correlation_analyzer.py +309 -295
- souleyez/intelligence/exploit_knowledge.py +661 -623
- souleyez/intelligence/exploit_suggestions.py +159 -139
- souleyez/intelligence/gap_analyzer.py +132 -97
- souleyez/intelligence/gap_detector.py +251 -214
- souleyez/intelligence/sensitive_tables.py +266 -129
- souleyez/intelligence/service_parser.py +137 -123
- souleyez/intelligence/surface_analyzer.py +407 -268
- souleyez/intelligence/target_parser.py +159 -162
- souleyez/licensing/__init__.py +6 -6
- souleyez/licensing/validator.py +17 -19
- souleyez/log_config.py +79 -54
- souleyez/main.py +1505 -687
- souleyez/migrations/fix_job_counter.py +16 -14
- souleyez/parsers/bloodhound_parser.py +41 -39
- souleyez/parsers/crackmapexec_parser.py +178 -111
- souleyez/parsers/dalfox_parser.py +72 -77
- souleyez/parsers/dnsrecon_parser.py +103 -91
- souleyez/parsers/enum4linux_parser.py +183 -153
- souleyez/parsers/ffuf_parser.py +29 -25
- souleyez/parsers/gobuster_parser.py +301 -41
- souleyez/parsers/hashcat_parser.py +324 -79
- souleyez/parsers/http_fingerprint_parser.py +350 -103
- souleyez/parsers/hydra_parser.py +131 -111
- souleyez/parsers/impacket_parser.py +231 -178
- souleyez/parsers/john_parser.py +98 -86
- souleyez/parsers/katana_parser.py +316 -0
- souleyez/parsers/msf_parser.py +943 -498
- souleyez/parsers/nikto_parser.py +346 -65
- souleyez/parsers/nmap_parser.py +262 -174
- souleyez/parsers/nuclei_parser.py +40 -44
- souleyez/parsers/responder_parser.py +26 -26
- souleyez/parsers/searchsploit_parser.py +74 -74
- souleyez/parsers/service_explorer_parser.py +279 -0
- souleyez/parsers/smbmap_parser.py +180 -124
- souleyez/parsers/sqlmap_parser.py +434 -308
- souleyez/parsers/theharvester_parser.py +75 -57
- souleyez/parsers/whois_parser.py +135 -94
- souleyez/parsers/wpscan_parser.py +278 -190
- souleyez/plugins/afp.py +44 -36
- souleyez/plugins/afp_brute.py +114 -46
- souleyez/plugins/ard.py +48 -37
- souleyez/plugins/bloodhound.py +95 -61
- souleyez/plugins/certipy.py +303 -0
- souleyez/plugins/crackmapexec.py +186 -85
- souleyez/plugins/dalfox.py +120 -59
- souleyez/plugins/dns_hijack.py +146 -41
- souleyez/plugins/dnsrecon.py +97 -61
- souleyez/plugins/enum4linux.py +91 -66
- souleyez/plugins/evil_winrm.py +291 -0
- souleyez/plugins/ffuf.py +166 -90
- souleyez/plugins/firmware_extract.py +133 -29
- souleyez/plugins/gobuster.py +387 -190
- souleyez/plugins/gpp_extract.py +393 -0
- souleyez/plugins/hashcat.py +100 -73
- souleyez/plugins/http_fingerprint.py +854 -267
- souleyez/plugins/hydra.py +566 -200
- souleyez/plugins/impacket_getnpusers.py +117 -69
- souleyez/plugins/impacket_psexec.py +84 -64
- souleyez/plugins/impacket_secretsdump.py +103 -69
- souleyez/plugins/impacket_smbclient.py +89 -75
- souleyez/plugins/john.py +86 -69
- souleyez/plugins/katana.py +313 -0
- souleyez/plugins/kerbrute.py +237 -0
- souleyez/plugins/lfi_extract.py +541 -0
- souleyez/plugins/macos_ssh.py +117 -48
- souleyez/plugins/mdns.py +35 -30
- souleyez/plugins/msf_auxiliary.py +253 -130
- souleyez/plugins/msf_exploit.py +239 -161
- souleyez/plugins/nikto.py +134 -78
- souleyez/plugins/nmap.py +275 -91
- souleyez/plugins/nuclei.py +180 -89
- souleyez/plugins/nxc.py +285 -0
- souleyez/plugins/plugin_base.py +35 -36
- souleyez/plugins/plugin_template.py +13 -5
- souleyez/plugins/rdp_sec_check.py +130 -0
- souleyez/plugins/responder.py +112 -71
- souleyez/plugins/router_http_brute.py +76 -65
- souleyez/plugins/router_ssh_brute.py +118 -41
- souleyez/plugins/router_telnet_brute.py +124 -42
- souleyez/plugins/routersploit.py +91 -59
- souleyez/plugins/routersploit_exploit.py +77 -55
- souleyez/plugins/searchsploit.py +91 -77
- souleyez/plugins/service_explorer.py +1160 -0
- souleyez/plugins/smbmap.py +122 -72
- souleyez/plugins/smbpasswd.py +215 -0
- souleyez/plugins/sqlmap.py +301 -113
- souleyez/plugins/theharvester.py +127 -75
- souleyez/plugins/tr069.py +79 -57
- souleyez/plugins/upnp.py +65 -47
- souleyez/plugins/upnp_abuse.py +73 -55
- souleyez/plugins/vnc_access.py +129 -42
- souleyez/plugins/vnc_brute.py +109 -38
- souleyez/plugins/web_login_test.py +417 -0
- souleyez/plugins/whois.py +77 -58
- souleyez/plugins/wpscan.py +173 -69
- souleyez/reporting/__init__.py +2 -1
- souleyez/reporting/attack_chain.py +411 -346
- souleyez/reporting/charts.py +436 -501
- souleyez/reporting/compliance_mappings.py +334 -201
- souleyez/reporting/detection_report.py +126 -125
- souleyez/reporting/formatters.py +828 -591
- souleyez/reporting/generator.py +386 -302
- souleyez/reporting/metrics.py +72 -75
- souleyez/scanner.py +35 -29
- souleyez/security/__init__.py +37 -11
- souleyez/security/scope_validator.py +175 -106
- souleyez/security/validation.py +223 -149
- souleyez/security.py +22 -6
- souleyez/storage/credentials.py +247 -186
- souleyez/storage/crypto.py +296 -129
- souleyez/storage/database.py +73 -50
- souleyez/storage/db.py +58 -36
- souleyez/storage/deliverable_evidence.py +177 -128
- souleyez/storage/deliverable_exporter.py +282 -246
- souleyez/storage/deliverable_templates.py +134 -116
- souleyez/storage/deliverables.py +135 -130
- souleyez/storage/engagements.py +109 -56
- souleyez/storage/evidence.py +181 -152
- souleyez/storage/execution_log.py +31 -17
- souleyez/storage/exploit_attempts.py +93 -57
- souleyez/storage/exploits.py +67 -36
- souleyez/storage/findings.py +48 -61
- souleyez/storage/hosts.py +176 -144
- souleyez/storage/migrate_to_engagements.py +43 -19
- souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
- souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
- souleyez/storage/migrations/_003_add_execution_log.py +14 -8
- souleyez/storage/migrations/_005_screenshots.py +13 -5
- souleyez/storage/migrations/_006_deliverables.py +13 -5
- souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
- souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
- souleyez/storage/migrations/_010_evidence_linking.py +17 -10
- souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
- souleyez/storage/migrations/_012_team_collaboration.py +34 -21
- souleyez/storage/migrations/_013_add_host_tags.py +12 -6
- souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
- souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
- souleyez/storage/migrations/_016_add_domain_field.py +10 -4
- souleyez/storage/migrations/_017_msf_sessions.py +16 -8
- souleyez/storage/migrations/_018_add_osint_target.py +10 -6
- souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
- souleyez/storage/migrations/_020_add_rbac.py +36 -15
- souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
- souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
- souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
- souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
- souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
- souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
- souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
- souleyez/storage/migrations/__init__.py +26 -26
- souleyez/storage/migrations/migration_manager.py +19 -19
- souleyez/storage/msf_sessions.py +100 -65
- souleyez/storage/osint.py +17 -24
- souleyez/storage/recommendation_engine.py +269 -235
- souleyez/storage/screenshots.py +33 -32
- souleyez/storage/smb_shares.py +136 -92
- souleyez/storage/sqlmap_data.py +183 -128
- souleyez/storage/team_collaboration.py +135 -141
- souleyez/storage/timeline_tracker.py +122 -94
- souleyez/storage/wazuh_vulns.py +64 -66
- souleyez/storage/web_paths.py +33 -37
- souleyez/testing/credential_tester.py +221 -205
- souleyez/ui/__init__.py +1 -1
- souleyez/ui/ai_quotes.py +12 -12
- souleyez/ui/attack_surface.py +2439 -1516
- souleyez/ui/chain_rules_view.py +914 -382
- souleyez/ui/correlation_view.py +312 -230
- souleyez/ui/dashboard.py +2382 -1130
- souleyez/ui/deliverables_view.py +148 -62
- souleyez/ui/design_system.py +13 -13
- souleyez/ui/errors.py +49 -49
- souleyez/ui/evidence_linking_view.py +284 -179
- souleyez/ui/evidence_vault.py +393 -285
- souleyez/ui/exploit_suggestions_view.py +555 -349
- souleyez/ui/export_view.py +100 -66
- souleyez/ui/gap_analysis_view.py +315 -171
- souleyez/ui/help_system.py +105 -97
- souleyez/ui/intelligence_view.py +436 -293
- souleyez/ui/interactive.py +23434 -10286
- souleyez/ui/interactive_selector.py +75 -68
- souleyez/ui/log_formatter.py +47 -39
- souleyez/ui/menu_components.py +22 -13
- souleyez/ui/msf_auxiliary_menu.py +184 -133
- souleyez/ui/pending_chains_view.py +336 -172
- souleyez/ui/progress_indicators.py +5 -3
- souleyez/ui/recommendations_view.py +195 -137
- souleyez/ui/rule_builder.py +343 -225
- souleyez/ui/setup_wizard.py +678 -284
- souleyez/ui/shortcuts.py +217 -165
- souleyez/ui/splunk_gap_analysis_view.py +452 -270
- souleyez/ui/splunk_vulns_view.py +139 -86
- souleyez/ui/team_dashboard.py +498 -335
- souleyez/ui/template_selector.py +196 -105
- souleyez/ui/terminal.py +6 -6
- souleyez/ui/timeline_view.py +198 -127
- souleyez/ui/tool_setup.py +264 -164
- souleyez/ui/tutorial.py +202 -72
- souleyez/ui/tutorial_state.py +40 -40
- souleyez/ui/wazuh_vulns_view.py +235 -141
- souleyez/ui/wordlist_browser.py +260 -107
- souleyez/ui.py +464 -312
- souleyez/utils/tool_checker.py +427 -367
- souleyez/utils.py +33 -29
- souleyez/wordlists.py +134 -167
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
- souleyez-2.43.34.dist-info/RECORD +443 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
- souleyez-2.43.26.dist-info/RECORD +0 -379
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
souleyez/plugins/john.py
CHANGED
|
@@ -41,138 +41,158 @@ HELP = {
|
|
|
41
41
|
{
|
|
42
42
|
"name": "Quick Dictionary (Top 1000)",
|
|
43
43
|
"description": "Fast dictionary attack with top 1000 passwords",
|
|
44
|
-
"args": "--wordlist=data/wordlists/top100.txt"
|
|
44
|
+
"args": "--wordlist=data/wordlists/top100.txt",
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"name": "Dictionary Attack (RockYou)",
|
|
48
48
|
"description": "Full dictionary attack with rockyou.txt",
|
|
49
|
-
"args": "--wordlist=data/wordlists/top100.txt"
|
|
50
|
-
}
|
|
49
|
+
"args": "--wordlist=data/wordlists/top100.txt",
|
|
50
|
+
},
|
|
51
51
|
],
|
|
52
52
|
"formats": [
|
|
53
53
|
{
|
|
54
54
|
"name": "MD5 Crypt (Linux Shadow)",
|
|
55
55
|
"description": "Linux /etc/shadow MD5 hashes",
|
|
56
|
-
"args": "--format=md5crypt"
|
|
56
|
+
"args": "--format=md5crypt",
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
"name": "SHA-512 Crypt (Modern Linux)",
|
|
60
60
|
"description": "Modern Linux /etc/shadow SHA-512 hashes",
|
|
61
|
-
"args": "--format=sha512crypt"
|
|
61
|
+
"args": "--format=sha512crypt",
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
"name": "NTLM (Windows)",
|
|
65
65
|
"description": "Windows NTLM password hashes",
|
|
66
|
-
"args": "--format=NT"
|
|
67
|
-
}
|
|
66
|
+
"args": "--format=NT",
|
|
67
|
+
},
|
|
68
68
|
],
|
|
69
69
|
"advanced": [
|
|
70
70
|
{
|
|
71
71
|
"name": "Incremental Brute Force",
|
|
72
72
|
"description": "Try all possible combinations (slow but thorough)",
|
|
73
|
-
"args": "--incremental"
|
|
73
|
+
"args": "--incremental",
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
"name": "Single Crack Mode",
|
|
77
77
|
"description": "Use usernames and GECOS info to generate passwords",
|
|
78
|
-
"args": "--single"
|
|
78
|
+
"args": "--single",
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
81
|
"name": "Rules with Wordlist",
|
|
82
82
|
"description": "Apply mutation rules to wordlist",
|
|
83
|
-
"args": "--wordlist=data/wordlists/top100.txt --rules"
|
|
84
|
-
}
|
|
85
|
-
]
|
|
83
|
+
"args": "--wordlist=data/wordlists/top100.txt --rules",
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
86
|
},
|
|
87
87
|
"help_sections": [
|
|
88
88
|
{
|
|
89
89
|
"title": "What is John the Ripper?",
|
|
90
90
|
"color": "cyan",
|
|
91
91
|
"content": [
|
|
92
|
-
{
|
|
93
|
-
|
|
94
|
-
"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
"
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
{
|
|
93
|
+
"title": "Overview",
|
|
94
|
+
"desc": "John the Ripper is the legendary password cracker, perfect for Linux/Unix shadow files with automatic hash format detection.",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"title": "Use Cases",
|
|
98
|
+
"desc": "Simpler than hashcat for basic cracking",
|
|
99
|
+
"tips": [
|
|
100
|
+
"Crack Linux /etc/shadow files (use unshadow first)",
|
|
101
|
+
"Auto-detect hash formats",
|
|
102
|
+
"Windows LM/NTLM hashes",
|
|
103
|
+
"ZIP/RAR password-protected archives",
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
],
|
|
100
107
|
},
|
|
101
108
|
{
|
|
102
109
|
"title": "How to Use",
|
|
103
110
|
"color": "green",
|
|
104
111
|
"content": [
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
"
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
"
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
{
|
|
113
|
+
"title": "Basic Workflow",
|
|
114
|
+
"desc": "1. Prepare hashes (unshadow passwd shadow > hashes)\n 2. Run john with wordlist or incremental mode\n 3. Use --show to see cracked passwords\n 4. Results saved in ~/.john/john.pot",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"title": "Attack Modes",
|
|
118
|
+
"desc": "Different cracking strategies",
|
|
119
|
+
"tips": [
|
|
120
|
+
"Dictionary: john --wordlist=wordlist.txt hashes",
|
|
121
|
+
"Incremental: john --incremental hashes (brute-force)",
|
|
122
|
+
"Single: john --single hashes (uses usernames)",
|
|
123
|
+
"Rules: john --rules --wordlist=wordlist.txt hashes",
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
],
|
|
113
127
|
},
|
|
114
128
|
{
|
|
115
129
|
"title": "Tips & Best Practices",
|
|
116
130
|
"color": "yellow",
|
|
117
131
|
"content": [
|
|
118
|
-
(
|
|
119
|
-
"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
"
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
(
|
|
133
|
+
"Best Practices:",
|
|
134
|
+
[
|
|
135
|
+
"Use unshadow to combine passwd and shadow files",
|
|
136
|
+
"John auto-detects most hash formats",
|
|
137
|
+
"Check progress with --show",
|
|
138
|
+
"Cracked passwords in ~/.john/john.pot",
|
|
139
|
+
"Use --format to force specific hash type",
|
|
140
|
+
],
|
|
141
|
+
),
|
|
142
|
+
(
|
|
143
|
+
"Common Issues:",
|
|
144
|
+
[
|
|
145
|
+
"No hash format: Use --format=<type> to specify",
|
|
146
|
+
"Slow progress: Try different attack mode or wordlist",
|
|
147
|
+
"Can't find john.pot: Check ~/.john/ directory",
|
|
148
|
+
"Unshadow needed: Combine passwd and shadow first",
|
|
149
|
+
],
|
|
150
|
+
),
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
],
|
|
134
154
|
}
|
|
135
155
|
|
|
136
156
|
|
|
137
157
|
class JohnPlugin(PluginBase):
|
|
138
158
|
"""John the Ripper password cracking plugin."""
|
|
139
|
-
|
|
159
|
+
|
|
140
160
|
name = "john"
|
|
141
161
|
tool = "john"
|
|
142
162
|
category = "credential_access"
|
|
143
163
|
HELP = HELP
|
|
144
164
|
|
|
145
|
-
|
|
146
|
-
|
|
165
|
+
def build_command(
|
|
166
|
+
self, target: str, args: List[str] = None, label: str = "", log_path: str = None
|
|
167
|
+
):
|
|
147
168
|
"""Build command for background execution with PID tracking."""
|
|
148
169
|
if not target:
|
|
149
170
|
if log_path:
|
|
150
|
-
with open(log_path,
|
|
171
|
+
with open(log_path, "w") as f:
|
|
151
172
|
f.write("ERROR: Hash file path is required\n")
|
|
152
173
|
return None
|
|
153
|
-
|
|
174
|
+
|
|
154
175
|
# Validate hash file exists
|
|
155
176
|
if not os.path.isfile(target):
|
|
156
177
|
if log_path:
|
|
157
|
-
with open(log_path,
|
|
178
|
+
with open(log_path, "w") as f:
|
|
158
179
|
f.write(f"ERROR: Hash file not found: {target}\n")
|
|
159
180
|
return None
|
|
160
|
-
|
|
181
|
+
|
|
161
182
|
args = args or []
|
|
162
|
-
|
|
183
|
+
|
|
163
184
|
# John syntax: john [options] hashfile
|
|
164
185
|
args_list = args if isinstance(args, list) else args.split()
|
|
165
186
|
cmd = ["john"] + args_list + [target]
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
'cmd': cmd,
|
|
169
|
-
'timeout': 7200
|
|
170
|
-
}
|
|
171
187
|
|
|
172
|
-
|
|
188
|
+
return {"cmd": cmd, "timeout": 7200}
|
|
189
|
+
|
|
190
|
+
def run(
|
|
191
|
+
self, target: str, args: List[str] = None, label: str = "", log_path: str = None
|
|
192
|
+
) -> int:
|
|
173
193
|
"""
|
|
174
194
|
Execute john and write output to log_path.
|
|
175
|
-
|
|
195
|
+
|
|
176
196
|
Note: 'target' is used as the hash file path for this plugin.
|
|
177
197
|
"""
|
|
178
198
|
if not target:
|
|
@@ -181,7 +201,7 @@ class JohnPlugin(PluginBase):
|
|
|
181
201
|
# Validate hash file exists
|
|
182
202
|
if not os.path.isfile(target):
|
|
183
203
|
if log_path:
|
|
184
|
-
with open(log_path,
|
|
204
|
+
with open(log_path, "w") as f:
|
|
185
205
|
f.write(f"ERROR: Hash file not found: {target}\n")
|
|
186
206
|
return 1
|
|
187
207
|
|
|
@@ -193,7 +213,7 @@ class JohnPlugin(PluginBase):
|
|
|
193
213
|
cmd = ["john"] + args_list + [target]
|
|
194
214
|
|
|
195
215
|
if log_path:
|
|
196
|
-
with open(log_path,
|
|
216
|
+
with open(log_path, "w") as f:
|
|
197
217
|
f.write(f"# John the Ripper password cracking\n")
|
|
198
218
|
f.write(f"# Hash file: {target}\n")
|
|
199
219
|
f.write(f"# Command: {' '.join(cmd)}\n")
|
|
@@ -201,18 +221,15 @@ class JohnPlugin(PluginBase):
|
|
|
201
221
|
|
|
202
222
|
try:
|
|
203
223
|
result = subprocess.run(
|
|
204
|
-
cmd,
|
|
205
|
-
capture_output=True,
|
|
206
|
-
text=True,
|
|
207
|
-
timeout=3600 # 1 hour timeout
|
|
224
|
+
cmd, capture_output=True, text=True, timeout=3600 # 1 hour timeout
|
|
208
225
|
)
|
|
209
226
|
|
|
210
227
|
if log_path:
|
|
211
|
-
with open(log_path,
|
|
228
|
+
with open(log_path, "a") as f:
|
|
212
229
|
f.write(result.stdout)
|
|
213
230
|
if result.stderr:
|
|
214
231
|
f.write(f"\n\n# Errors:\n{result.stderr}\n")
|
|
215
|
-
|
|
232
|
+
|
|
216
233
|
# Add helper to show cracked passwords
|
|
217
234
|
f.write(f"\n\n# To view cracked passwords, run:\n")
|
|
218
235
|
f.write(f"# john --show {target}\n")
|
|
@@ -221,12 +238,12 @@ class JohnPlugin(PluginBase):
|
|
|
221
238
|
|
|
222
239
|
except subprocess.TimeoutExpired:
|
|
223
240
|
if log_path:
|
|
224
|
-
with open(log_path,
|
|
241
|
+
with open(log_path, "a") as f:
|
|
225
242
|
f.write("\n\n# ERROR: Command timed out after 1 hour\n")
|
|
226
243
|
return 124
|
|
227
244
|
except Exception as e:
|
|
228
245
|
if log_path:
|
|
229
|
-
with open(log_path,
|
|
246
|
+
with open(log_path, "a") as f:
|
|
230
247
|
f.write(f"\n\n# ERROR: {str(e)}\n")
|
|
231
248
|
return 1
|
|
232
249
|
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
souleyez.plugins.katana - Web crawling and spidering for parameter discovery
|
|
4
|
+
|
|
5
|
+
Katana is a next-generation crawling and spidering framework from ProjectDiscovery.
|
|
6
|
+
It discovers endpoints, parameters, forms, and JavaScript-rendered routes.
|
|
7
|
+
"""
|
|
8
|
+
import subprocess
|
|
9
|
+
import shutil
|
|
10
|
+
from typing import List, Optional, Dict, Any
|
|
11
|
+
|
|
12
|
+
from .plugin_base import PluginBase
|
|
13
|
+
from souleyez.security.validation import validate_url, ValidationError
|
|
14
|
+
|
|
15
|
+
HELP = {
|
|
16
|
+
"name": "Katana - Web Crawler & Spider",
|
|
17
|
+
"description": (
|
|
18
|
+
"Katana is a fast and configurable web crawler designed for reconnaissance.\n\n"
|
|
19
|
+
"It discovers hidden endpoints, query parameters, forms, and JavaScript-rendered routes "
|
|
20
|
+
"that static tools like gobuster miss. This is essential for finding actual attack surface "
|
|
21
|
+
"before running injection tests with SQLMap.\n\n"
|
|
22
|
+
"Key features:\n"
|
|
23
|
+
"- Headless browser mode for JavaScript-heavy SPAs (React, Angular, Vue)\n"
|
|
24
|
+
"- Automatic parameter extraction from discovered URLs\n"
|
|
25
|
+
"- Form discovery (POST endpoints)\n"
|
|
26
|
+
"- JavaScript parsing for hidden API routes\n"
|
|
27
|
+
"- Configurable crawl depth and scope\n\n"
|
|
28
|
+
"Quick tips:\n"
|
|
29
|
+
"- Default headless mode works best for modern web apps\n"
|
|
30
|
+
"- Use -d to control crawl depth (default: 3)\n"
|
|
31
|
+
"- Results chain automatically to SQLMap for injection testing\n"
|
|
32
|
+
),
|
|
33
|
+
"usage": "souleyez jobs enqueue katana <target>",
|
|
34
|
+
"examples": [
|
|
35
|
+
"souleyez jobs enqueue katana http://example.com",
|
|
36
|
+
'souleyez jobs enqueue katana http://example.com --args "-d 5"',
|
|
37
|
+
'souleyez jobs enqueue katana http://example.com --args "-no-headless"',
|
|
38
|
+
],
|
|
39
|
+
"flags": [
|
|
40
|
+
["-d <depth>", "Maximum crawl depth (default: 3)"],
|
|
41
|
+
["-headless", "Enable headless browser (default: ON)"],
|
|
42
|
+
["-no-headless", "Disable headless browser for faster scanning"],
|
|
43
|
+
["-jc", "Enable JavaScript crawling"],
|
|
44
|
+
["-timeout <sec>", "Request timeout in seconds (default: 10)"],
|
|
45
|
+
["-c <num>", "Concurrency level (default: 10)"],
|
|
46
|
+
["-rl <num>", "Rate limit (requests per second)"],
|
|
47
|
+
["-scope <regex>", "Regex to filter in-scope URLs"],
|
|
48
|
+
["-silent", "Suppress banner and info output"],
|
|
49
|
+
],
|
|
50
|
+
"preset_categories": {
|
|
51
|
+
"crawl_modes": [
|
|
52
|
+
{
|
|
53
|
+
"name": "Standard Crawl",
|
|
54
|
+
"args": ["-d", "3", "-headless", "-jc"],
|
|
55
|
+
"desc": "Default crawl with JavaScript support (recommended)",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "Deep Crawl",
|
|
59
|
+
"args": ["-d", "5", "-headless", "-jc"],
|
|
60
|
+
"desc": "Deeper crawl for complex applications",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "Fast Crawl",
|
|
64
|
+
"args": ["-d", "2", "-no-headless"],
|
|
65
|
+
"desc": "Quick scan without headless browser",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "Aggressive",
|
|
69
|
+
"args": ["-d", "5", "-headless", "-jc", "-c", "20"],
|
|
70
|
+
"desc": "Deep and concurrent crawl",
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
"scope_control": [
|
|
74
|
+
{
|
|
75
|
+
"name": "Same Domain Only",
|
|
76
|
+
"args": ["-headless", "-jc", "-fs", "dn"],
|
|
77
|
+
"desc": "Stay within the same domain",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "Same Host Only",
|
|
81
|
+
"args": ["-headless", "-jc", "-fs", "sdn"],
|
|
82
|
+
"desc": "Stay on exact subdomain",
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
"presets": [
|
|
87
|
+
{
|
|
88
|
+
"name": "Standard Crawl",
|
|
89
|
+
"args": ["-d", "3", "-headless", "-jc"],
|
|
90
|
+
"desc": "Default crawl with JavaScript support",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "Deep Crawl",
|
|
94
|
+
"args": ["-d", "5", "-headless", "-jc"],
|
|
95
|
+
"desc": "Thorough crawl for complex apps",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "Fast (No JS)",
|
|
99
|
+
"args": ["-d", "2", "-no-headless"],
|
|
100
|
+
"desc": "Quick crawl without browser",
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
"help_sections": [
|
|
104
|
+
{
|
|
105
|
+
"title": "How It Fits In The Chain",
|
|
106
|
+
"color": "cyan",
|
|
107
|
+
"content": [
|
|
108
|
+
{
|
|
109
|
+
"title": "Discovery Flow",
|
|
110
|
+
"desc": (
|
|
111
|
+
"1. Gobuster/FFUF find paths (/api, /admin, etc.)\n"
|
|
112
|
+
" 2. Katana crawls those paths to find parameters\n"
|
|
113
|
+
" 3. SQLMap tests the parameters for injection\n"
|
|
114
|
+
" 4. Nuclei scans crawled URLs for other vulns"
|
|
115
|
+
),
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
}
|
|
119
|
+
],
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class KatanaPlugin(PluginBase):
|
|
124
|
+
name = "Katana"
|
|
125
|
+
tool = "katana"
|
|
126
|
+
category = "vulnerability_analysis"
|
|
127
|
+
HELP = HELP
|
|
128
|
+
|
|
129
|
+
def check_tool_available(self) -> tuple:
|
|
130
|
+
"""
|
|
131
|
+
Check if katana and chromium are installed.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
(is_available: bool, error_message: str or None)
|
|
135
|
+
"""
|
|
136
|
+
# Check katana
|
|
137
|
+
if not shutil.which("katana"):
|
|
138
|
+
return (False, "Katana not found. Install with: sudo apt install katana")
|
|
139
|
+
|
|
140
|
+
# Check chromium for headless mode
|
|
141
|
+
chromium_binaries = ["chromium", "chromium-browser", "google-chrome", "chrome"]
|
|
142
|
+
chromium_found = any(shutil.which(b) for b in chromium_binaries)
|
|
143
|
+
|
|
144
|
+
if not chromium_found:
|
|
145
|
+
return (
|
|
146
|
+
False,
|
|
147
|
+
"Chromium not found (required for headless mode). Install with: sudo apt install chromium",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return (True, None)
|
|
151
|
+
|
|
152
|
+
def _normalize_target(self, target: str, log_path: str = None) -> Optional[str]:
|
|
153
|
+
"""
|
|
154
|
+
Normalize target URL for Katana.
|
|
155
|
+
|
|
156
|
+
Katana requires a full URL - bare IPs/domains get http:// prepended.
|
|
157
|
+
"""
|
|
158
|
+
import re
|
|
159
|
+
|
|
160
|
+
# Already a URL - validate and return
|
|
161
|
+
if target.startswith(("http://", "https://")):
|
|
162
|
+
try:
|
|
163
|
+
return validate_url(target)
|
|
164
|
+
except ValidationError as e:
|
|
165
|
+
if log_path:
|
|
166
|
+
with open(log_path, "w") as f:
|
|
167
|
+
f.write(f"ERROR: Invalid URL: {e}\n")
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
# Bare IP or domain - prepend http://
|
|
171
|
+
ip_pattern = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?$"
|
|
172
|
+
domain_pattern = r"^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$"
|
|
173
|
+
|
|
174
|
+
if re.match(ip_pattern, target) or re.match(domain_pattern, target):
|
|
175
|
+
if log_path:
|
|
176
|
+
with open(log_path, "a") as f:
|
|
177
|
+
f.write(
|
|
178
|
+
f"NOTE: Converting bare target '{target}' to 'http://{target}'\n"
|
|
179
|
+
)
|
|
180
|
+
return f"http://{target}"
|
|
181
|
+
|
|
182
|
+
return target
|
|
183
|
+
|
|
184
|
+
def build_command(
|
|
185
|
+
self, target: str, args: List[str] = None, label: str = "", log_path: str = None
|
|
186
|
+
) -> Optional[Dict[str, Any]]:
|
|
187
|
+
"""
|
|
188
|
+
Build katana command for background execution.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
target: Target URL to crawl
|
|
192
|
+
args: Additional command line arguments
|
|
193
|
+
label: Job label
|
|
194
|
+
log_path: Path to write output
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Dict with 'cmd' and 'timeout' keys, or None on error
|
|
198
|
+
"""
|
|
199
|
+
args = args or []
|
|
200
|
+
|
|
201
|
+
# Normalize target
|
|
202
|
+
target = self._normalize_target(target, log_path)
|
|
203
|
+
if target is None:
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
# Replace <target> placeholder in args
|
|
207
|
+
args = [arg.replace("<target>", target) for arg in args]
|
|
208
|
+
|
|
209
|
+
# Build base command
|
|
210
|
+
cmd = ["katana", "-u", target]
|
|
211
|
+
|
|
212
|
+
# Force JSONL output for parsing
|
|
213
|
+
if "-jsonl" not in args and "-json" not in args:
|
|
214
|
+
cmd.append("-jsonl")
|
|
215
|
+
|
|
216
|
+
# Output to log file
|
|
217
|
+
if log_path and "-o" not in args:
|
|
218
|
+
cmd.extend(["-o", log_path])
|
|
219
|
+
|
|
220
|
+
# Add user args
|
|
221
|
+
cmd.extend(args)
|
|
222
|
+
|
|
223
|
+
# Set defaults if not specified
|
|
224
|
+
# Headless mode by default (unless -no-headless specified)
|
|
225
|
+
if "-headless" not in args and "-no-headless" not in args:
|
|
226
|
+
cmd.append("-headless")
|
|
227
|
+
|
|
228
|
+
# JavaScript crawling by default
|
|
229
|
+
if "-jc" not in args:
|
|
230
|
+
cmd.append("-jc")
|
|
231
|
+
|
|
232
|
+
# Default crawl depth
|
|
233
|
+
if "-d" not in args and "-depth" not in args:
|
|
234
|
+
cmd.extend(["-d", "3"])
|
|
235
|
+
|
|
236
|
+
# Request timeout
|
|
237
|
+
if "-timeout" not in args:
|
|
238
|
+
cmd.extend(["-timeout", "10"])
|
|
239
|
+
|
|
240
|
+
# Silent mode to reduce noise
|
|
241
|
+
if "-silent" not in args:
|
|
242
|
+
cmd.append("-silent")
|
|
243
|
+
|
|
244
|
+
return {"cmd": cmd, "timeout": 1800} # 30 minutes for crawling
|
|
245
|
+
|
|
246
|
+
def run(
|
|
247
|
+
self, target: str, args: List[str] = None, label: str = "", log_path: str = None
|
|
248
|
+
) -> int:
|
|
249
|
+
"""
|
|
250
|
+
Execute katana crawl synchronously.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
target: Target URL
|
|
254
|
+
args: Command line arguments
|
|
255
|
+
label: Job label
|
|
256
|
+
log_path: Output file path
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Exit code (0 = success)
|
|
260
|
+
"""
|
|
261
|
+
cmd_spec = self.build_command(target, args, label, log_path)
|
|
262
|
+
if cmd_spec is None:
|
|
263
|
+
return 1
|
|
264
|
+
|
|
265
|
+
cmd = cmd_spec["cmd"]
|
|
266
|
+
timeout = cmd_spec.get("timeout", 1800)
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
if log_path:
|
|
270
|
+
# Write metadata header
|
|
271
|
+
with open(log_path, "w") as f:
|
|
272
|
+
f.write(f"=== Plugin: Katana ===\n")
|
|
273
|
+
f.write(f"Target: {target}\n")
|
|
274
|
+
f.write(f"Args: {args}\n")
|
|
275
|
+
f.write(f"Label: {label}\n")
|
|
276
|
+
f.write(f"Command: {' '.join(cmd)}\n\n")
|
|
277
|
+
|
|
278
|
+
# Run katana
|
|
279
|
+
proc = subprocess.run(
|
|
280
|
+
cmd, capture_output=True, timeout=timeout, check=False
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# Append completion marker
|
|
284
|
+
with open(log_path, "a") as f:
|
|
285
|
+
f.write(f"\n=== Completed ===\n")
|
|
286
|
+
f.write(f"Exit Code: {proc.returncode}\n")
|
|
287
|
+
if proc.stderr:
|
|
288
|
+
f.write(
|
|
289
|
+
f"Stderr: {proc.stderr.decode('utf-8', errors='replace')}\n"
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
return proc.returncode
|
|
293
|
+
else:
|
|
294
|
+
proc = subprocess.run(
|
|
295
|
+
cmd, capture_output=True, timeout=timeout, check=False
|
|
296
|
+
)
|
|
297
|
+
return proc.returncode
|
|
298
|
+
|
|
299
|
+
except subprocess.TimeoutExpired:
|
|
300
|
+
if log_path:
|
|
301
|
+
with open(log_path, "a") as f:
|
|
302
|
+
f.write(f"\nERROR: Crawl timed out after {timeout} seconds\n")
|
|
303
|
+
return 124
|
|
304
|
+
|
|
305
|
+
except Exception as e:
|
|
306
|
+
if log_path:
|
|
307
|
+
with open(log_path, "a") as f:
|
|
308
|
+
f.write(f"\nERROR: {str(e)}\n")
|
|
309
|
+
return 1
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
# Export plugin instance for auto-discovery
|
|
313
|
+
plugin = KatanaPlugin()
|