souleyez 2.43.34__py3-none-any.whl → 3.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.
Potentially problematic release.
This version of souleyez might be problematic. Click here for more details.
- souleyez/__init__.py +1 -1
- souleyez/core/tool_chaining.py +219 -183
- souleyez/docs/README.md +2 -2
- souleyez/handlers/hydra_handler.py +1 -0
- souleyez/handlers/msf_auxiliary_handler.py +1 -0
- souleyez/handlers/nxc_handler.py +47 -1
- souleyez/main.py +1 -1
- souleyez/plugins/http_fingerprint.py +94 -35
- souleyez/plugins/wpscan.py +46 -0
- souleyez/security/validation.py +14 -0
- souleyez/ui/interactive.py +244 -38
- {souleyez-2.43.34.dist-info → souleyez-3.0.0.dist-info}/METADATA +2 -2
- {souleyez-2.43.34.dist-info → souleyez-3.0.0.dist-info}/RECORD +17 -17
- {souleyez-2.43.34.dist-info → souleyez-3.0.0.dist-info}/WHEEL +0 -0
- {souleyez-2.43.34.dist-info → souleyez-3.0.0.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.34.dist-info → souleyez-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.34.dist-info → souleyez-3.0.0.dist-info}/top_level.txt +0 -0
souleyez/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "
|
|
1
|
+
__version__ = "3.0.0"
|
souleyez/core/tool_chaining.py
CHANGED
|
@@ -113,6 +113,96 @@ SERVICE_GROUPS = {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
|
|
116
|
+
def should_test_url_for_sqli(endpoint_url: str) -> bool:
|
|
117
|
+
"""
|
|
118
|
+
Determine if a URL should be tested for SQL injection.
|
|
119
|
+
|
|
120
|
+
This filters out URLs that are unlikely to have injectable parameters,
|
|
121
|
+
while allowing URLs that might have forms or query parameters.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
endpoint_url: The URL to evaluate
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
True if the URL should be tested, False if it should be skipped
|
|
128
|
+
|
|
129
|
+
Examples:
|
|
130
|
+
>>> should_test_url_for_sqli("http://example.com/payroll_app.php")
|
|
131
|
+
True # Dynamic page, might have forms
|
|
132
|
+
>>> should_test_url_for_sqli("http://example.com/index.php")
|
|
133
|
+
False # Common default page, rarely injectable
|
|
134
|
+
>>> should_test_url_for_sqli("http://example.com/search.php?q=test")
|
|
135
|
+
True # Has query parameters
|
|
136
|
+
>>> should_test_url_for_sqli("http://example.com/phpinfo.php")
|
|
137
|
+
False # phpinfo output, not injectable
|
|
138
|
+
>>> should_test_url_for_sqli("http://example.com/cgi-bin/")
|
|
139
|
+
False # Directory, not a script
|
|
140
|
+
>>> should_test_url_for_sqli("http://example.com/")
|
|
141
|
+
False # No path, no params
|
|
142
|
+
"""
|
|
143
|
+
from urllib.parse import urlparse
|
|
144
|
+
|
|
145
|
+
path_lower = endpoint_url.lower()
|
|
146
|
+
has_params = "?" in endpoint_url
|
|
147
|
+
|
|
148
|
+
# URLs with query params should generally be tested - they have injection points
|
|
149
|
+
# But still skip known non-injectable apps even with params
|
|
150
|
+
if has_params:
|
|
151
|
+
# Even with params, skip known non-injectable applications
|
|
152
|
+
hard_skip = [
|
|
153
|
+
"/phpmyadmin/", # phpMyAdmin - DB admin tool, not a target
|
|
154
|
+
"/phpmyadmin?", # phpMyAdmin with params
|
|
155
|
+
]
|
|
156
|
+
if any(pattern in path_lower for pattern in hard_skip):
|
|
157
|
+
return False
|
|
158
|
+
# Skip static files with version/cache-busting params
|
|
159
|
+
# These are not injectable: /jquery.js?v=1.2.3, /style.css?ver=5.0
|
|
160
|
+
if ".js?" in path_lower or ".css?" in path_lower:
|
|
161
|
+
return False
|
|
162
|
+
# Has params and not in hard skip - test it
|
|
163
|
+
return True
|
|
164
|
+
|
|
165
|
+
# No query params - apply stricter filtering
|
|
166
|
+
# Skip known non-injectable paths (only when no params)
|
|
167
|
+
skip_patterns = [
|
|
168
|
+
"/twiki/", # TWiki wiki - not SQLi vulnerable
|
|
169
|
+
"/phpmyadmin/", # phpMyAdmin - DB admin, not SQLi target
|
|
170
|
+
"/phpmyadmin.", # phpMyAdmin CSS/JS files
|
|
171
|
+
"/phpinfo", # phpinfo() output - no injection
|
|
172
|
+
"/cgi-bin/", # Base CGI dir without script - no injection
|
|
173
|
+
"/misc/", # Drupal/CMS static assets directory
|
|
174
|
+
"/modules/", # Drupal modules directory (static files)
|
|
175
|
+
]
|
|
176
|
+
if any(pattern in path_lower for pattern in skip_patterns):
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
# No params - require dynamic extension that might have forms
|
|
180
|
+
dynamic_extensions = (".php", ".asp", ".aspx", ".jsp", ".do", ".action", ".cgi")
|
|
181
|
+
is_dynamic = any(path_lower.endswith(ext) for ext in dynamic_extensions)
|
|
182
|
+
|
|
183
|
+
if not is_dynamic:
|
|
184
|
+
# No params and not a dynamic page - skip
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
# Skip common default/utility pages that rarely have injectable forms
|
|
188
|
+
useless_dynamic_pages = [
|
|
189
|
+
"/index.php", "/index.asp", "/index.aspx", "/index.jsp",
|
|
190
|
+
"/default.php", "/default.asp", "/default.aspx",
|
|
191
|
+
"/home.php", "/home.asp",
|
|
192
|
+
"/info.php", "/test.php",
|
|
193
|
+
]
|
|
194
|
+
if not has_params:
|
|
195
|
+
# Only check this list for pages without params
|
|
196
|
+
try:
|
|
197
|
+
parsed = urlparse(endpoint_url)
|
|
198
|
+
if parsed.path.lower() in useless_dynamic_pages:
|
|
199
|
+
return False
|
|
200
|
+
except Exception:
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
|
|
116
206
|
def classify_os_device(os_string: str, services: list) -> dict:
|
|
117
207
|
"""
|
|
118
208
|
Classify OS and device type from nmap output.
|
|
@@ -1502,23 +1592,15 @@ class ToolChaining:
|
|
|
1502
1592
|
],
|
|
1503
1593
|
description="WordPress detected, scanning for vulnerabilities with WPScan",
|
|
1504
1594
|
),
|
|
1505
|
-
# phpMyAdmin → SQLMap
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
"--batch",
|
|
1515
|
-
"--forms",
|
|
1516
|
-
"--level=2",
|
|
1517
|
-
"--risk=2",
|
|
1518
|
-
"--threads=5",
|
|
1519
|
-
],
|
|
1520
|
-
description="phpMyAdmin found, testing for SQL injection",
|
|
1521
|
-
),
|
|
1595
|
+
# phpMyAdmin → SQLMap - DISABLED
|
|
1596
|
+
# phpMyAdmin is a database management tool with proper authentication.
|
|
1597
|
+
# It doesn't have SQLi in its login form. Use nuclei/searchsploit instead.
|
|
1598
|
+
# ChainRule(
|
|
1599
|
+
# trigger_tool="gobuster",
|
|
1600
|
+
# trigger_condition="finding:phpmyadmin",
|
|
1601
|
+
# target_tool="sqlmap",
|
|
1602
|
+
# ...
|
|
1603
|
+
# ),
|
|
1522
1604
|
# === END WordPress-specific chains ===
|
|
1523
1605
|
# Dalfox - Deep XSS scan if nuclei hints at XSS
|
|
1524
1606
|
ChainRule(
|
|
@@ -1677,9 +1759,11 @@ class ToolChaining:
|
|
|
1677
1759
|
description="Domain member detected, scanning subnet for Domain Controller",
|
|
1678
1760
|
),
|
|
1679
1761
|
# Kerbrute chains - enumerate users via Kerberos when domain discovered
|
|
1762
|
+
# IMPORTANT: Require port 88 (Kerberos) to be open - not just "has:domains"
|
|
1763
|
+
# Samba workgroups report as "domains" but are NOT Active Directory
|
|
1680
1764
|
ChainRule(
|
|
1681
1765
|
trigger_tool="enum4linux",
|
|
1682
|
-
trigger_condition="has:domains",
|
|
1766
|
+
trigger_condition="has:domains & port:88",
|
|
1683
1767
|
target_tool="kerbrute",
|
|
1684
1768
|
priority=6,
|
|
1685
1769
|
args_template=[
|
|
@@ -1690,11 +1774,11 @@ class ToolChaining:
|
|
|
1690
1774
|
"{dc_ip}",
|
|
1691
1775
|
"data/wordlists/ad_users.txt",
|
|
1692
1776
|
],
|
|
1693
|
-
description="
|
|
1777
|
+
description="Active Directory detected (Kerberos port 88 open) - enumerating users",
|
|
1694
1778
|
),
|
|
1695
1779
|
ChainRule(
|
|
1696
1780
|
trigger_tool="crackmapexec",
|
|
1697
|
-
trigger_condition="has:domains",
|
|
1781
|
+
trigger_condition="has:domains & port:88",
|
|
1698
1782
|
target_tool="kerbrute",
|
|
1699
1783
|
priority=6,
|
|
1700
1784
|
args_template=[
|
|
@@ -1705,7 +1789,7 @@ class ToolChaining:
|
|
|
1705
1789
|
"{dc_ip}",
|
|
1706
1790
|
"data/wordlists/ad_users.txt",
|
|
1707
1791
|
],
|
|
1708
|
-
description="
|
|
1792
|
+
description="Active Directory detected (Kerberos port 88 open) - enumerating users",
|
|
1709
1793
|
),
|
|
1710
1794
|
]
|
|
1711
1795
|
)
|
|
@@ -1981,9 +2065,10 @@ class ToolChaining:
|
|
|
1981
2065
|
description="Domain discovered - enumerating all objects to find hidden users",
|
|
1982
2066
|
),
|
|
1983
2067
|
# Kerbrute user enumeration (works even when anonymous LDAP is blocked)
|
|
2068
|
+
# IMPORTANT: Require port 88 (Kerberos) - LDAP alone doesn't mean AD
|
|
1984
2069
|
ChainRule(
|
|
1985
2070
|
trigger_tool="ldapsearch",
|
|
1986
|
-
trigger_condition="has:domains",
|
|
2071
|
+
trigger_condition="has:domains & port:88",
|
|
1987
2072
|
target_tool="kerbrute",
|
|
1988
2073
|
priority=6,
|
|
1989
2074
|
args_template=[
|
|
@@ -1994,7 +2079,7 @@ class ToolChaining:
|
|
|
1994
2079
|
"{dc_ip}",
|
|
1995
2080
|
"data/wordlists/ad_users.txt",
|
|
1996
2081
|
],
|
|
1997
|
-
description="
|
|
2082
|
+
description="Active Directory detected (Kerberos port 88 open) - enumerating users",
|
|
1998
2083
|
),
|
|
1999
2084
|
]
|
|
2000
2085
|
)
|
|
@@ -2175,49 +2260,28 @@ class ToolChaining:
|
|
|
2175
2260
|
)
|
|
2176
2261
|
)
|
|
2177
2262
|
|
|
2178
|
-
# Gobuster discovered PHP files → crawl base URL
|
|
2179
|
-
#
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
"--batch",
|
|
2190
|
-
"--crawl=2",
|
|
2191
|
-
"--risk=2",
|
|
2192
|
-
"--level=3",
|
|
2193
|
-
"--forms",
|
|
2194
|
-
"--threads=5",
|
|
2195
|
-
],
|
|
2196
|
-
description="PHP files discovered, crawling site to find parametrized pages and forms",
|
|
2197
|
-
)
|
|
2198
|
-
)
|
|
2263
|
+
# Gobuster discovered PHP files → crawl base URL - DISABLED
|
|
2264
|
+
# Reason: katana→sqlmap handles this better by targeting specific parametrized URLs.
|
|
2265
|
+
# Crawling the base URL with SQLMap is slow and often wasteful.
|
|
2266
|
+
# self.rules.append(
|
|
2267
|
+
# ChainRule(
|
|
2268
|
+
# trigger_tool="gobuster",
|
|
2269
|
+
# trigger_condition="has:php_files",
|
|
2270
|
+
# target_tool="sqlmap",
|
|
2271
|
+
# ...
|
|
2272
|
+
# )
|
|
2273
|
+
# )
|
|
2199
2274
|
|
|
2200
|
-
# Gobuster discovered ASP/ASPX files → crawl base URL
|
|
2201
|
-
#
|
|
2202
|
-
self.rules.append(
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
"{target}",
|
|
2211
|
-
"--batch",
|
|
2212
|
-
"--crawl=2",
|
|
2213
|
-
"--risk=2",
|
|
2214
|
-
"--level=3",
|
|
2215
|
-
"--forms",
|
|
2216
|
-
"--threads=5",
|
|
2217
|
-
],
|
|
2218
|
-
description="ASP/ASPX files discovered, crawling site to find parametrized pages and forms",
|
|
2219
|
-
)
|
|
2220
|
-
)
|
|
2275
|
+
# Gobuster discovered ASP/ASPX files → crawl base URL - DISABLED
|
|
2276
|
+
# Reason: katana→sqlmap handles this better by targeting specific parametrized URLs.
|
|
2277
|
+
# self.rules.append(
|
|
2278
|
+
# ChainRule(
|
|
2279
|
+
# trigger_tool="gobuster",
|
|
2280
|
+
# trigger_condition="has:asp_files",
|
|
2281
|
+
# target_tool="sqlmap",
|
|
2282
|
+
# ...
|
|
2283
|
+
# )
|
|
2284
|
+
# )
|
|
2221
2285
|
|
|
2222
2286
|
# SMART API DISCOVERY CHAIN
|
|
2223
2287
|
# Replaced broken direct SQLMap rules with intelligent two-step approach
|
|
@@ -2885,37 +2949,44 @@ class ToolChaining:
|
|
|
2885
2949
|
)
|
|
2886
2950
|
|
|
2887
2951
|
# ffuf found parameters → test with SQLMap (skip for LFI scans)
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2952
|
+
# DISABLED: This rule passes {target} (original ffuf target) to sqlmap,
|
|
2953
|
+
# which is useless - we need actual discovered endpoints with parameters.
|
|
2954
|
+
# The smart chain (rule #-1) in auto_chain() handles ffuf → sqlmap properly
|
|
2955
|
+
# by parsing ffuf output and testing discovered endpoints.
|
|
2956
|
+
# self.rules.append(
|
|
2957
|
+
# ChainRule(
|
|
2958
|
+
# trigger_tool="ffuf",
|
|
2959
|
+
# trigger_condition="has:parameters_found & !is:lfi_scan",
|
|
2960
|
+
# target_tool="sqlmap",
|
|
2961
|
+
# priority=9,
|
|
2962
|
+
# args_template=["-u", "{target}", "--batch", "--level=2", "--risk=2"],
|
|
2963
|
+
# description="Parameters discovered, testing for SQL injection",
|
|
2964
|
+
# )
|
|
2965
|
+
# )
|
|
2898
2966
|
|
|
2899
2967
|
# ffuf found parameters → test with Nuclei XSS (skip for LFI scans)
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2968
|
+
# DISABLED: Same issue as above - uses {target} instead of discovered parameters.
|
|
2969
|
+
# Running XSS scans on bare directories like /cgi-bin/ is useless.
|
|
2970
|
+
# Smart chains in auto_chain() handle ffuf → nuclei properly.
|
|
2971
|
+
# self.rules.append(
|
|
2972
|
+
# ChainRule(
|
|
2973
|
+
# trigger_tool="ffuf",
|
|
2974
|
+
# trigger_condition="has:parameters_found & !is:lfi_scan",
|
|
2975
|
+
# target_tool="nuclei",
|
|
2976
|
+
# priority=8,
|
|
2977
|
+
# args_template=[
|
|
2978
|
+
# "-tags",
|
|
2979
|
+
# "xss,rxss",
|
|
2980
|
+
# "-severity",
|
|
2981
|
+
# "critical,high,medium",
|
|
2982
|
+
# "-rate-limit",
|
|
2983
|
+
# "50",
|
|
2984
|
+
# "-c",
|
|
2985
|
+
# "10",
|
|
2986
|
+
# ],
|
|
2987
|
+
# description="Parameters discovered, testing for XSS",
|
|
2988
|
+
# )
|
|
2989
|
+
# )
|
|
2919
2990
|
|
|
2920
2991
|
# Gobuster found API endpoints → parameter fuzzing with ffuf
|
|
2921
2992
|
self.rules.extend(
|
|
@@ -3135,27 +3206,17 @@ class ToolChaining:
|
|
|
3135
3206
|
)
|
|
3136
3207
|
)
|
|
3137
3208
|
|
|
3138
|
-
# Custom PHP → SQLMap standard crawl
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
"--crawl=2",
|
|
3150
|
-
"--forms",
|
|
3151
|
-
"--level=2",
|
|
3152
|
-
"--risk=2",
|
|
3153
|
-
"--smart",
|
|
3154
|
-
"--threads=5",
|
|
3155
|
-
],
|
|
3156
|
-
description="Custom PHP app detected, scan forms for SQL injection",
|
|
3157
|
-
)
|
|
3158
|
-
)
|
|
3209
|
+
# Custom PHP → SQLMap standard crawl - DISABLED
|
|
3210
|
+
# Reason: katana→sqlmap handles this better by targeting specific parametrized URLs.
|
|
3211
|
+
# The "custom_php" category is too broad (default for unrecognized paths).
|
|
3212
|
+
# self.rules.append(
|
|
3213
|
+
# ChainRule(
|
|
3214
|
+
# trigger_tool="gobuster",
|
|
3215
|
+
# trigger_condition="category:custom_php",
|
|
3216
|
+
# target_tool="sqlmap",
|
|
3217
|
+
# ...
|
|
3218
|
+
# )
|
|
3219
|
+
# )
|
|
3159
3220
|
|
|
3160
3221
|
# === END Directory Category Chain Rules ===
|
|
3161
3222
|
|
|
@@ -8544,6 +8605,11 @@ class ToolChaining:
|
|
|
8544
8605
|
|
|
8545
8606
|
# === SQLMap for testable endpoints ===
|
|
8546
8607
|
# Skip SQLMap if this was an LFI fuzz scan - results are LFI payloads, not SQLi targets
|
|
8608
|
+
|
|
8609
|
+
# Use helper function to filter non-injectable URLs
|
|
8610
|
+
if not should_test_url_for_sqli(endpoint_url):
|
|
8611
|
+
continue
|
|
8612
|
+
|
|
8547
8613
|
if (
|
|
8548
8614
|
not is_lfi_scan
|
|
8549
8615
|
and status_code in testable_statuses
|
|
@@ -9190,6 +9256,43 @@ class ToolChaining:
|
|
|
9190
9256
|
):
|
|
9191
9257
|
continue
|
|
9192
9258
|
|
|
9259
|
+
# Skip external URLs - only test URLs on the original target host
|
|
9260
|
+
try:
|
|
9261
|
+
from urllib.parse import urlparse
|
|
9262
|
+
parsed_url = urlparse(url)
|
|
9263
|
+
parsed_target = urlparse(target)
|
|
9264
|
+
if parsed_url.netloc and parsed_target.netloc:
|
|
9265
|
+
if parsed_url.netloc.lower() != parsed_target.netloc.lower():
|
|
9266
|
+
logger.debug(f" Skipping external URL: {url}")
|
|
9267
|
+
continue
|
|
9268
|
+
except Exception:
|
|
9269
|
+
pass
|
|
9270
|
+
|
|
9271
|
+
# Skip non-injectable paths (TWiki, phpMyAdmin, Apache dir params)
|
|
9272
|
+
skip_patterns = [
|
|
9273
|
+
"/twiki/", # TWiki wiki - not SQLi vulnerable
|
|
9274
|
+
"/phpmyadmin/", # phpMyAdmin - DB admin, not SQLi
|
|
9275
|
+
"/phpmyadmin.", # phpMyAdmin CSS/JS files
|
|
9276
|
+
"?c=d", "?c=s", "?c=m", "?c=n", # Apache dir listing sort params
|
|
9277
|
+
"?o=a", "?o=d", # Apache dir listing order params
|
|
9278
|
+
";o=a", ";o=d", # Apache dir listing (semicolon variant)
|
|
9279
|
+
"/misc/", # Drupal/CMS static assets directory
|
|
9280
|
+
"/modules/", # Drupal modules directory (static files)
|
|
9281
|
+
]
|
|
9282
|
+
# Also skip static files with version/cache-busting params
|
|
9283
|
+
# These are not injectable: /jquery.js?v=1.2.3, /style.css?ver=5.0
|
|
9284
|
+
if ".js?" in path_lower or ".css?" in path_lower:
|
|
9285
|
+
logger.debug(f" Skipping static file with cache param: {url}")
|
|
9286
|
+
continue
|
|
9287
|
+
if any(pattern in path_lower for pattern in skip_patterns):
|
|
9288
|
+
logger.debug(f" Skipping non-injectable path: {url}")
|
|
9289
|
+
continue
|
|
9290
|
+
|
|
9291
|
+
# Skip URLs without real parameters (just base URL or path)
|
|
9292
|
+
if "?" not in url and url not in forms_found:
|
|
9293
|
+
logger.debug(f" Skipping URL without parameters: {url}")
|
|
9294
|
+
continue
|
|
9295
|
+
|
|
9193
9296
|
# Determine if this is a form (POST) or URL param (GET)
|
|
9194
9297
|
is_form = url in forms_found
|
|
9195
9298
|
|
|
@@ -10571,74 +10674,7 @@ class ToolChaining:
|
|
|
10571
10674
|
except Exception as e:
|
|
10572
10675
|
logger.debug(f"Evil-WinRM chain check failed: {e}")
|
|
10573
10676
|
|
|
10574
|
-
#
|
|
10575
|
-
if credentials:
|
|
10576
|
-
from souleyez.engine.background import enqueue_job
|
|
10577
|
-
from souleyez.storage.hosts import HostManager
|
|
10578
|
-
from souleyez.log_config import get_logger
|
|
10579
|
-
|
|
10580
|
-
logger = get_logger(__name__)
|
|
10581
|
-
|
|
10582
|
-
try:
|
|
10583
|
-
host_manager = HostManager()
|
|
10584
|
-
target_host = target.split(":")[0] if ":" in target else target
|
|
10585
|
-
|
|
10586
|
-
host = host_manager.get_host_by_ip(engagement_id, target_host)
|
|
10587
|
-
if host:
|
|
10588
|
-
services = host_manager.get_host_services(host["id"])
|
|
10589
|
-
ssh_svc = next(
|
|
10590
|
-
(s for s in services if s.get("port") == 22), None
|
|
10591
|
-
)
|
|
10592
|
-
|
|
10593
|
-
if ssh_svc:
|
|
10594
|
-
# SSH is available - create SSH command execution job
|
|
10595
|
-
for cred in credentials: # Process all credentials
|
|
10596
|
-
username = cred.get("username")
|
|
10597
|
-
password = cred.get("password")
|
|
10598
|
-
|
|
10599
|
-
if username and password:
|
|
10600
|
-
# Check if SSH chain already ran for this user
|
|
10601
|
-
from souleyez.storage.database import Database
|
|
10602
|
-
|
|
10603
|
-
try:
|
|
10604
|
-
db = Database()
|
|
10605
|
-
existing = db.execute(
|
|
10606
|
-
"""SELECT id FROM jobs WHERE engagement_id = ?
|
|
10607
|
-
AND tool = 'nxc' AND args LIKE '%ssh%'
|
|
10608
|
-
AND args LIKE ? AND status != 'killed' LIMIT 1""",
|
|
10609
|
-
(engagement_id, f'%-u", "{username}%'),
|
|
10610
|
-
)
|
|
10611
|
-
if existing:
|
|
10612
|
-
continue
|
|
10613
|
-
except Exception:
|
|
10614
|
-
pass
|
|
10615
|
-
|
|
10616
|
-
# Use nxc (netexec) to test SSH shell access
|
|
10617
|
-
ssh_job_id = enqueue_job(
|
|
10618
|
-
tool="nxc",
|
|
10619
|
-
target=target_host,
|
|
10620
|
-
args=[
|
|
10621
|
-
"ssh",
|
|
10622
|
-
target_host,
|
|
10623
|
-
"-u",
|
|
10624
|
-
username,
|
|
10625
|
-
"-p",
|
|
10626
|
-
password,
|
|
10627
|
-
"-x",
|
|
10628
|
-
"whoami && id && hostname",
|
|
10629
|
-
],
|
|
10630
|
-
label="hydra",
|
|
10631
|
-
engagement_id=engagement_id,
|
|
10632
|
-
parent_id=job.get("id"),
|
|
10633
|
-
reason=f"Auto-triggered by hydra: Testing SSH shell access with {username}",
|
|
10634
|
-
rule_id=-28, # Smart chain: hydra → nxc ssh (shell)
|
|
10635
|
-
)
|
|
10636
|
-
job_ids.append(ssh_job_id)
|
|
10637
|
-
logger.info(
|
|
10638
|
-
f" nxc SSH job #{ssh_job_id} for {username}"
|
|
10639
|
-
)
|
|
10640
|
-
except Exception as e:
|
|
10641
|
-
logger.debug(f"SSH shell chain check failed: {e}")
|
|
10677
|
+
# NOTE: SSH shell chain removed - spawn shell directly from Hydra job via [s] option
|
|
10642
10678
|
|
|
10643
10679
|
return job_ids
|
|
10644
10680
|
|
souleyez/docs/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# SoulEyez Documentation
|
|
2
2
|
|
|
3
|
-
**Version:**
|
|
4
|
-
**Last Updated:** January
|
|
3
|
+
**Version:** 3.0.0
|
|
4
|
+
**Last Updated:** January 29, 2026
|
|
5
5
|
**Organization:** CyberSoul Security
|
|
6
6
|
|
|
7
7
|
Welcome to the SoulEyez documentation! This documentation covers architecture, development, user guides, and operational information for the SoulEyez penetration testing platform.
|
|
@@ -261,6 +261,7 @@ class HydraHandler(BaseToolHandler):
|
|
|
261
261
|
"port": parsed.get("port"),
|
|
262
262
|
"credentials_found": len(parsed.get("credentials", [])),
|
|
263
263
|
"credentials_added": creds_added,
|
|
264
|
+
"credentials": parsed.get("credentials", []), # Include actual creds for smart chains
|
|
264
265
|
"usernames_found": len(parsed.get("usernames", [])),
|
|
265
266
|
"usernames": parsed.get("usernames", []),
|
|
266
267
|
"usernames_added": usernames_added,
|
souleyez/handlers/nxc_handler.py
CHANGED
|
@@ -77,10 +77,51 @@ class NxcHandler(BaseToolHandler):
|
|
|
77
77
|
hostname = banner_match.group(1)
|
|
78
78
|
domain = banner_match.group(2)
|
|
79
79
|
|
|
80
|
-
# Check for Pwn3d
|
|
80
|
+
# Check for Pwn3d (SMB admin access)
|
|
81
81
|
if re.search(self.PWNED_PATTERN, log_content):
|
|
82
82
|
is_pwned = True
|
|
83
83
|
|
|
84
|
+
# Check for SSH Shell access (Linux)
|
|
85
|
+
# Format: [+] leia_organa:help_me_obiwan Linux - Shell access!
|
|
86
|
+
has_shell_access = False
|
|
87
|
+
if "Shell access!" in log_content:
|
|
88
|
+
has_shell_access = True
|
|
89
|
+
# Parse SSH credentials
|
|
90
|
+
ssh_cred_pattern = r"\[\+\]\s+([^:\s]+):(\S+)\s+.*Shell access!"
|
|
91
|
+
for match in re.finditer(ssh_cred_pattern, log_content):
|
|
92
|
+
username = match.group(1)
|
|
93
|
+
password = match.group(2)
|
|
94
|
+
cred = {
|
|
95
|
+
"username": username,
|
|
96
|
+
"password": password,
|
|
97
|
+
"domain": "",
|
|
98
|
+
"service": "ssh",
|
|
99
|
+
"status": "valid",
|
|
100
|
+
}
|
|
101
|
+
credentials.append(cred)
|
|
102
|
+
|
|
103
|
+
# Store in database
|
|
104
|
+
if credentials_manager and host_manager:
|
|
105
|
+
try:
|
|
106
|
+
host = host_manager.get_host_by_ip(engagement_id, target)
|
|
107
|
+
if host:
|
|
108
|
+
credentials_manager.add_credential(
|
|
109
|
+
engagement_id=engagement_id,
|
|
110
|
+
host_id=host["id"],
|
|
111
|
+
username=username,
|
|
112
|
+
password=password,
|
|
113
|
+
service="ssh",
|
|
114
|
+
port=22,
|
|
115
|
+
credential_type="password",
|
|
116
|
+
tool="nxc",
|
|
117
|
+
status="valid",
|
|
118
|
+
)
|
|
119
|
+
logger.warning(
|
|
120
|
+
f"SSH SHELL ACCESS: {username}:{password} on {target}"
|
|
121
|
+
)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.debug(f"Could not store SSH credential: {e}")
|
|
124
|
+
|
|
84
125
|
# Parse valid credentials
|
|
85
126
|
# Format: [+] baby2.vl\Carl.Moore:Carl.Moore
|
|
86
127
|
for match in re.finditer(self.VALID_CRED_PATTERN, log_content):
|
|
@@ -231,6 +272,8 @@ class NxcHandler(BaseToolHandler):
|
|
|
231
272
|
|
|
232
273
|
if credentials:
|
|
233
274
|
status = STATUS_DONE
|
|
275
|
+
elif has_shell_access:
|
|
276
|
+
status = STATUS_DONE # SSH shell access without parsed creds
|
|
234
277
|
elif expired_credentials:
|
|
235
278
|
status = STATUS_WARNING # Expired creds need attention
|
|
236
279
|
elif shares:
|
|
@@ -255,6 +298,8 @@ class NxcHandler(BaseToolHandler):
|
|
|
255
298
|
)
|
|
256
299
|
if is_pwned:
|
|
257
300
|
summary_parts.append("PWNED!")
|
|
301
|
+
if has_shell_access:
|
|
302
|
+
summary_parts.append("SHELL ACCESS!")
|
|
258
303
|
if shares:
|
|
259
304
|
summary_parts.append(
|
|
260
305
|
f"{len(shares)} shares ({len(readable_shares)} readable, {len(writable_shares)} writable)"
|
|
@@ -273,6 +318,7 @@ class NxcHandler(BaseToolHandler):
|
|
|
273
318
|
"credentials": credentials,
|
|
274
319
|
"expired_credentials": expired_credentials,
|
|
275
320
|
"is_pwned": is_pwned,
|
|
321
|
+
"has_shell_access": has_shell_access,
|
|
276
322
|
"summary": summary,
|
|
277
323
|
}
|
|
278
324
|
|
souleyez/main.py
CHANGED
|
@@ -185,7 +185,7 @@ def _check_privileged_tools():
|
|
|
185
185
|
|
|
186
186
|
|
|
187
187
|
@click.group()
|
|
188
|
-
@click.version_option(version="
|
|
188
|
+
@click.version_option(version="3.0.0")
|
|
189
189
|
def cli():
|
|
190
190
|
"""SoulEyez - AI-Powered Pentesting Platform by CyberSoul Security"""
|
|
191
191
|
from souleyez.log_config import init_logging
|