-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathasset_common.py
More file actions
345 lines (283 loc) · 9.72 KB
/
Copy pathasset_common.py
File metadata and controls
345 lines (283 loc) · 9.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
import asyncio
import faulthandler
import hashlib
import ipaddress
import json
import os
import re
import sys
import threading
import traceback
from datetime import datetime
RE_AST = r"((?:https?://|)\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?::\d+)?\b)"
RE_IP = r"\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?::\d+)?\b"
RE_DOM = r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}"
CONFIG_FILE = "config.json"
WORKSPACE_DIR = "workspace"
GLOBAL_LOG_FILE = "AssetCommander.log"
CRASH_DUMP_FILE = "AssetCommander-crash.log"
RESULT_FIELDS = ["url", "site", "host", "code", "len", "title", "conf", "remark"]
LOW_RISK_UI_LIMIT = 800
SCAN_PROGRESS_FILE = "scan_progress.json"
FAIL_SAMPLE_FILE = "fail_samples.log"
PROJECT_COPY_FILES = [
"ips.txt",
"domains.txt",
"settings.json",
"dict_config.json",
"domain_to_ip.json",
"hunter.json",
"fission.json",
"reverse_ip.json",
]
_TASK_FINGERPRINT_RE = re.compile(r"^[0-9a-f]{32}$")
_CRASH_DUMP_HANDLE = None
def _patch_windows_proactor_reset_noise():
if sys.platform != "win32":
return
import asyncio.proactor_events
transport_cls = asyncio.proactor_events._ProactorBasePipeTransport
if getattr(transport_cls, "_assetcommander_reset_patch", False):
return
original = transport_cls._call_connection_lost
def _silence_10054(self, exc):
try:
original(self, exc)
except (ConnectionResetError, ConnectionAbortedError, BrokenPipeError):
pass
except OSError as err:
if getattr(err, "winerror", None) in (10053, 10054):
return
raise
transport_cls._call_connection_lost = _silence_10054
transport_cls._assetcommander_reset_patch = True
_patch_windows_proactor_reset_noise()
HTTP_STATUS = {
100: "Continue(继续)",
101: "Switching Protocols(切换协议)",
200: "OK(请求成功)",
201: "Created(已创建)",
202: "Accepted(已接受)",
203: "Non-Authoritative(非权威信息)",
204: "No Content(无内容)",
205: "Reset Content(重置内容)",
206: "Partial Content(部分内容)",
300: "Multiple Choices(多种选择)",
301: "Moved Permanently(永久重定向)",
302: "Found(临时重定向)",
303: "See Other(查看其他位置)",
304: "Not Modified(未修改)",
305: "Use Proxy(使用代理)",
307: "Temporary Redirect(临时重定向)",
400: "Bad Request(错误请求)",
401: "Unauthorized(未授权)",
402: "Payment Required(保留)",
403: "Forbidden(禁止访问)",
404: "Not Found(未找到)",
405: "Method Not Allowed(方法不允许)",
406: "Not Acceptable(不可接受)",
407: "Proxy Auth Required(需要代理认证)",
408: "Request Timeout(请求超时)",
409: "Conflict(冲突)",
410: "Gone(资源已删除)",
411: "Length Required(需要长度)",
412: "Precondition Failed(前置条件失败)",
413: "Entity Too Large(实体过大)",
414: "URI Too Large(URI 过长)",
415: "Unsupported Media Type(不支持的媒体类型)",
416: "Range Not Satisfiable(范围无效)",
417: "Expectation Failed(预期失败)",
418: "I'm a teapot(彩蛋状态码)",
500: "Internal Server Error(服务器内部错误)",
501: "Not Implemented(未实现)",
502: "Bad Gateway(网关错误)",
503: "Service Unavailable(服务不可用)",
504: "Gateway Timeout(网关超时)",
505: "HTTP Version Not Supported(HTTP 版本不支持)",
}
def write_global_log(level, msg):
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
try:
with open(GLOBAL_LOG_FILE, "a", encoding="utf-8") as handle:
handle.write(f"[{timestamp}] [{level}] {msg}\n")
except Exception:
pass
def task_fingerprint(value):
raw = str(value or "").strip().lower()
if not raw:
return ""
return hashlib.blake2b(
raw.encode("utf-8", errors="ignore"),
digest_size=16,
).hexdigest()
def normalize_task_record(value):
raw = str(value or "").strip().lower()
if not raw:
return ""
if _TASK_FINGERPRINT_RE.fullmatch(raw):
return raw
return task_fingerprint(raw)
def install_global_crash_handlers():
global _CRASH_DUMP_HANDLE
if getattr(install_global_crash_handlers, "_installed", False):
return
def _handle_unhandled_exception(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt):
return sys.__excepthook__(exc_type, exc_value, exc_traceback)
formatted = "".join(
traceback.format_exception(exc_type, exc_value, exc_traceback)
).rstrip()
write_global_log("FATAL", f"未捕获异常:\n{formatted}")
try:
sys.__excepthook__(exc_type, exc_value, exc_traceback)
except Exception:
pass
sys.excepthook = _handle_unhandled_exception
if hasattr(threading, "excepthook"):
def _thread_excepthook(args):
_handle_unhandled_exception(
args.exc_type,
args.exc_value,
args.exc_traceback,
)
threading.excepthook = _thread_excepthook
try:
_CRASH_DUMP_HANDLE = open(CRASH_DUMP_FILE, "a", encoding="utf-8", buffering=1)
faulthandler.enable(_CRASH_DUMP_HANDLE, all_threads=True)
except Exception:
pass
install_global_crash_handlers._installed = True
def get_base_config():
default = {
"fscan_cmd": "fscan.exe -h {target} -p 80,443,8080,8443 -m web -np -t 100",
"oneforall_cmd": "python oneforall.py --target {target} run",
}
if not os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, "w", encoding="utf-8") as handle:
json.dump(default, handle, indent=4, ensure_ascii=False)
return default
try:
with open(CONFIG_FILE, "r", encoding="utf-8") as handle:
current_config = json.load(handle)
except Exception:
current_config = {}
needs_update = False
for key, value in default.items():
if key not in current_config:
current_config[key] = value
needs_update = True
if needs_update:
with open(CONFIG_FILE, "w", encoding="utf-8") as handle:
json.dump(current_config, handle, indent=4, ensure_ascii=False)
return current_config
def extract_title(html_text):
match = re.search(r"<title>(.*?)</title>", html_text, re.I | re.S)
if match:
return match.group(1).strip().replace("\n", "").replace("\r", "")
return "N/A"
def split_token_candidates(raw_text):
return [
token.strip()
for token in re.split(r"[\s,;,;]+", str(raw_text or ""))
if token.strip()
]
def normalize_ip_value(value):
raw = str(value or "").strip()
if not raw:
return ""
candidate = raw
if "://" in candidate:
candidate = candidate.split("://", 1)[1]
candidate = candidate.split("/", 1)[0].strip("[]")
if ":" in candidate:
host, _, port = candidate.partition(":")
if port.isdigit():
candidate = host
try:
return str(ipaddress.ip_address(candidate))
except ValueError:
return ""
def normalize_ip_target(value):
raw = str(value or "").strip()
if not raw:
return ""
candidate = raw
if "://" in candidate:
candidate = candidate.split("://", 1)[1]
candidate = candidate.split("/", 1)[0].strip().strip("[]")
host = candidate
port = ""
if ":" in candidate:
maybe_host, _, maybe_port = candidate.partition(":")
if maybe_port.isdigit():
host = maybe_host
port = maybe_port
normalized_host = normalize_ip_value(host)
if not normalized_host:
return ""
if port:
return f"{normalized_host}:{port}"
return normalized_host
def collect_unique_ips(raw_values):
seen = set()
items = []
for raw_value in raw_values or []:
for token in split_token_candidates(raw_value):
normalized = normalize_ip_target(token)
if normalized and normalized not in seen:
seen.add(normalized)
items.append(normalized)
return items
def normalize_host_value(value):
raw = str(value or "").strip().lower()
if not raw:
return ""
if "://" in raw:
raw = raw.split("://", 1)[1]
raw = raw.split("/", 1)[0].strip().strip("[]")
return raw
def collect_unique_hosts(raw_values):
seen = set()
items = []
for raw_value in raw_values or []:
for token in split_token_candidates(raw_value):
normalized = normalize_host_value(token)
if normalized and normalized not in seen:
seen.add(normalized)
items.append(normalized)
return items
def derive_site_label(url, host):
clean_url = str(url or "").strip()
clean_host = normalize_host_value(host)
if not clean_host and clean_url:
target = clean_url.split("://", 1)[-1].split("/", 1)[0]
clean_host = normalize_host_value(target)
if not clean_host:
return clean_url
scheme = ""
if "://" in clean_url:
scheme = clean_url.split("://", 1)[0].strip().lower()
if scheme:
return f"{scheme}://{clean_host}"
return clean_host
__all__ = [
"CRASH_DUMP_FILE",
"CONFIG_FILE",
"FAIL_SAMPLE_FILE",
"GLOBAL_LOG_FILE",
"HTTP_STATUS",
"install_global_crash_handlers",
"LOW_RISK_UI_LIMIT",
"normalize_task_record",
"PROJECT_COPY_FILES",
"RE_AST",
"RE_DOM",
"RE_IP",
"RESULT_FIELDS",
"SCAN_PROGRESS_FILE",
"WORKSPACE_DIR",
"extract_title",
"get_base_config",
"task_fingerprint",
"write_global_log",
]