agent-bypass 2.11.5 → 2.11.7
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/README.md +280 -63
- package/SKILL.md +11 -9
- package/assets/banner.svg +91 -0
- package/bin/install.js +1 -0
- package/ck-hunter.md +1 -1
- package/package.json +11 -2
- package/references/mobile-fast-recon.md +102 -0
- package/scripts/argsmini.py +210 -0
- package/scripts/basicauth_brute.py +82 -7
- package/scripts/cvefetch.py +2 -1
- package/scripts/deck.py +2 -1
- package/scripts/depscan.py +2 -1
- package/scripts/dirbrute.py +505 -71
- package/scripts/fastrecon.py +980 -0
- package/scripts/jsrip.py +2 -1
- package/scripts/mutate.py +2 -1
- package/scripts/platf.py +44 -0
- package/scripts/portscan.py +4 -1
- package/scripts/webprobe.py +56 -10
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# 移动环境快速侦察(iSH / Termux)
|
|
2
|
+
|
|
3
|
+
手机端(iOS iSH、Android Termux)执行扫描/爆破的优化方案、实测基准与自动兜底链。
|
|
4
|
+
以下数字为本机 iSH(iOS, Alpine aarch64 模拟环境)实测,2026-09。
|
|
5
|
+
|
|
6
|
+
## 一、实测基准(iSH)
|
|
7
|
+
|
|
8
|
+
| 场景 | 慢办法 | 快办法 | 倍数 |
|
|
9
|
+
|---|---|---|---|
|
|
10
|
+
| 50 端口(静默目标,等超时) | 串行 40.2s | selectors 并发 0.89s | 45x |
|
|
11
|
+
| 500 端口(本地) | - | selectors 0.9s | - |
|
|
12
|
+
| 10 URL HTTP 探测 | 串行 15.2s | curl -Z 并行 2.7s | 5.6x |
|
|
13
|
+
| 目录爆破 183 路径 | py 线程池 106s | curl -Z 引擎 6s | 17.7x |
|
|
14
|
+
| DNS 单域名(本地 resolver) | DoH 1102ms | UDP 直发 5ms | 220x |
|
|
15
|
+
| DNS 30 域名批量 | getent 串行 0.2s | UDP 并发 0.1s | 1.4x |
|
|
16
|
+
|
|
17
|
+
关键坑:
|
|
18
|
+
- 每个请求重建 urllib opener → 触发 getproxies() 读系统代理设置(iSH 下单次约 200ms)→ opener 必须全局缓存。
|
|
19
|
+
- DoH 在移动环境极慢(每域名一次 HTTPS 往返 + 走代理);批量解析用本地 resolver(/etc/resolv.conf,通常 198.18.x.x 代理回环)UDP 直发。
|
|
20
|
+
- 外网 UDP:53 通常被代理环境过滤,UDP 只打本地 resolver。
|
|
21
|
+
|
|
22
|
+
## 二、fastrecon 用法速查
|
|
23
|
+
|
|
24
|
+
三合一:dns(UDP 并发+缓存)/ scan(selectors)/ http(curl -Z)。
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# DNS 批量(UDP 并发 + 本地缓存 + getent 兜底)
|
|
28
|
+
python3 scripts/fastrecon.py dns example.com,github.com
|
|
29
|
+
python3 scripts/fastrecon.py dns subs.txt -w 50
|
|
30
|
+
|
|
31
|
+
# 端口扫描(selectors 并发 + 逐 sock 精确超时)
|
|
32
|
+
python3 scripts/fastrecon.py scan 192.168.1.1 -p top100
|
|
33
|
+
python3 scripts/fastrecon.py scan 10.0.0.0/24 -p 22,80,443 -t 0.6
|
|
34
|
+
python3 scripts/fastrecon.py scan host -p 1-1000 -b # 抓服务横幅
|
|
35
|
+
|
|
36
|
+
# HTTP 批量(curl -Z 并行,10 秒扫完数百 URL)
|
|
37
|
+
python3 scripts/fastrecon.py http urls.txt -b 48
|
|
38
|
+
python3 scripts/fastrecon.py http 'https://a.com,https://b.com' --code
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 三、自动兜底链(快 → 慢)
|
|
42
|
+
|
|
43
|
+
| 能力 | 主路径(默认) | 自动兜底 1 | 自动兜底 2 |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| DNS | UDP 并发直发(5ms/个) | 失败率>60% → getent 并发 | DoH(最后手段,慢) |
|
|
46
|
+
| 端口 | selectors 并发(45x) | selectors 异常 → 线程池 connect | 低并发串行 |
|
|
47
|
+
| HTTP | curl -Z 并行(C 实现) | 无 curl → Python urllib 并发 | 单请求逐个 |
|
|
48
|
+
| 目录爆破 | dirbrute --engine curl(批量+软404过滤) | 无 curl → --engine py 线程池 | - |
|
|
49
|
+
| 凭据爆破 | basicauth curl -Z(URL 内嵌凭据并行) | 无 curl / 需 delay 控制 → py 精细模式 | - |
|
|
50
|
+
|
|
51
|
+
fastrecon 内置自动切换;dirbrute / basicauth_brute 用 --engine auto(默认)自动选择。
|
|
52
|
+
|
|
53
|
+
## 四、平台工具矩阵
|
|
54
|
+
|
|
55
|
+
### iSH(iOS)
|
|
56
|
+
- 可用:Python 纯标准库、curl、busybox、apk(i386 包,nmap 可装但模拟层慢)
|
|
57
|
+
- 推荐:fastrecon 全家桶 + curl -Z
|
|
58
|
+
- 不可行:Go/Rust 原生二进制(x86 模拟架构不匹配)
|
|
59
|
+
|
|
60
|
+
### Termux(Android arm64)
|
|
61
|
+
- 原生工具优先:
|
|
62
|
+
- `pkg install nmap`(C)
|
|
63
|
+
- `pkg install rustscan`(Rust,65k 端口 3 秒级,依赖 nmap)
|
|
64
|
+
- `pkg install masscan`(C,SYN 异步高速;或 anasfanani/masscan-termux-android)
|
|
65
|
+
- `pkg install ffuf`、`pkg install feroxbuster`(内容发现)
|
|
66
|
+
- naabu(Go,ProjectDiscovery release 直接下载 arm64)
|
|
67
|
+
- 无 root:SYN 半开不可用(masscan/nmap -sS 需 root);TCP connect(rustscan/naabu/fastrecon)通用。
|
|
68
|
+
- 兜底:原生工具不可用时回退 fastrecon / dirbrute(Python 版照常可跑)。
|
|
69
|
+
|
|
70
|
+
## 五、性能技巧清单
|
|
71
|
+
|
|
72
|
+
1. 并发模型优先级:selectors(单线程 IO 多路复用)> 线程池 > 串行。iSH 线程切换开销大,selectors 最稳。
|
|
73
|
+
2. 超时管理:非阻塞 connect 超时不会触发 select 事件——必须逐 socket 计时清理,否则整轮扫描被拖到兜底超时。
|
|
74
|
+
3. DNS:批量 UDP 并发 + 本地缓存文件;避免 DoH;getent 兜底。
|
|
75
|
+
4. HTTP:curl -Z(--parallel-max)是 C 级并行,比 Python 快一个数量级;每个 URL 必须配对 -o /dev/null(否则多余 body 污染 stdout)。
|
|
76
|
+
5. urllib:opener 全局缓存(getproxies 系统调用是隐藏杀手)。
|
|
77
|
+
6. 测试方法:单线程测试服务器会制造假象(客户端背锅);并发测试用多线程服务器。
|
|
78
|
+
7. 软404:目标返回"200 假页面"时,先探测随机路径取基线(状态码+大小),过滤假阳性(dirbrute curl 引擎内置此逻辑)。
|
|
79
|
+
8. 大范围扫描:先用 top100 快扫定位存活,再做全端口深扫;CIDR 用 /24 以内切片分次跑。
|
|
80
|
+
|
|
81
|
+
## v2.11.6 提速与自适配(2026-09-28)
|
|
82
|
+
|
|
83
|
+
### iSH import 成本实测与懒加载策略
|
|
84
|
+
- 实测(每次调用固定成本):urllib.request 5s、subprocess 3s、concurrent.futures 2s、argparse 1.8s、json 1.8s、ssl 1s、socket/selectors 各 1s
|
|
85
|
+
- 策略:重模块全部懒加载(首次用才 import);argparse 换 argsmini.py 极简解析(0 成本);curl 调用改 os.popen(免 subprocess 3s);dns 线程池自研(免 cf 2s)
|
|
86
|
+
- 自适配:platf.py 探测 iSH/Termux/RPi/Linux/macOS/Windows → 并发/超时自动调参
|
|
87
|
+
|
|
88
|
+
### 耗时对比(iSH 实测,含 Python 启动)
|
|
89
|
+
| 命令 | 改造前 | v2.11.6 |
|
|
90
|
+
|------|--------|---------|
|
|
91
|
+
| scan | 4-7s | 1s |
|
|
92
|
+
| dns | 7s | 2s |
|
|
93
|
+
| dirbrute | 7-8s | 3s |
|
|
94
|
+
| basicauth | 8s | 5s |
|
|
95
|
+
|
|
96
|
+
### DoT(DNS over TLS)
|
|
97
|
+
- `fastrecon dns example.com --via dot --resolver 1.1.1.1`(RFC 7858:TCP 853+TLS,帧格式同 TCP)
|
|
98
|
+
- `--via auto`:UDP 失败自动切 DoT;论文实测 DoT 优于 DoH、Do53 最快
|
|
99
|
+
- 解析链:UDP →(auto 时)DoT → getent → DoH
|
|
100
|
+
|
|
101
|
+
### 摄像头端口组(-p cam)
|
|
102
|
+
80/81/88/443/554/8554/8000/8080/8081/8899/34567/37777/9527/2000/2020/1935/5000/5001/5540/7070/7447/4520/8787——覆盖 Hikvision/Dahua/XMeye/Axis/Reolink 等相机与 NVR。
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""argparse 极简兼容层(iSH 上省 ~1.8s import 成本,全部工具共用)。
|
|
2
|
+
覆盖子集:位置参数(含 nargs='?')、--opt 值、--opt=value、type、default、
|
|
3
|
+
choices、store_true、action='append'、required、dest、set_defaults、
|
|
4
|
+
subparsers(dest)、parse_args、error、-h。
|
|
5
|
+
不支持:前缀缩写、metavar、互斥组、nargs 其他取值等(全部工具未用)。"""
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _basename(p):
|
|
10
|
+
return p.rsplit('/', 1)[-1] or p
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _NS:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ArgumentParser:
|
|
18
|
+
def __init__(self, prog=None, description=None, **kw):
|
|
19
|
+
self.prog = prog or _basename(sys.argv[0])
|
|
20
|
+
self.description = description or ''
|
|
21
|
+
self._opts = []
|
|
22
|
+
self._sub_dest = None
|
|
23
|
+
self._sub_parsers = {}
|
|
24
|
+
self._defaults = {}
|
|
25
|
+
|
|
26
|
+
def add_argument(self, *names, **kw):
|
|
27
|
+
action = kw.get('action')
|
|
28
|
+
flag = action == 'store_true'
|
|
29
|
+
append = action == 'append'
|
|
30
|
+
optpos = kw.get('nargs') == '?'
|
|
31
|
+
if len(names) == 1 and not names[0].startswith('-'):
|
|
32
|
+
o = dict(dest=kw.get('dest') or names[0], flags=None, pos=True,
|
|
33
|
+
is_flag=False, append=False, required=False, optpos=optpos,
|
|
34
|
+
type=kw.get('type', str), default=kw.get('default'),
|
|
35
|
+
choices=kw.get('choices'), help=kw.get('help', ''))
|
|
36
|
+
else:
|
|
37
|
+
longs = [n for n in names if n.startswith('--')]
|
|
38
|
+
dest = kw.get('dest') or ((longs[0] if longs else names[0])
|
|
39
|
+
.lstrip('-').replace('-', '_'))
|
|
40
|
+
o = dict(dest=dest, flags=list(names), pos=False, is_flag=flag,
|
|
41
|
+
append=append, required=bool(kw.get('required')),
|
|
42
|
+
optpos=False, type=kw.get('type', str),
|
|
43
|
+
default=kw.get('default', [] if append else (False if flag else None)),
|
|
44
|
+
choices=kw.get('choices'), help=kw.get('help', ''))
|
|
45
|
+
self._opts.append(o)
|
|
46
|
+
|
|
47
|
+
def set_defaults(self, **kw):
|
|
48
|
+
self._defaults.update(kw)
|
|
49
|
+
|
|
50
|
+
def add_subparsers(self, dest=None, **kw):
|
|
51
|
+
self._sub_dest = dest or 'cmd'
|
|
52
|
+
holder = self
|
|
53
|
+
|
|
54
|
+
class _Sub:
|
|
55
|
+
def add_parser(self, name, help=''):
|
|
56
|
+
p = ArgumentParser(prog='%s %s' % (holder.prog, name), description=help)
|
|
57
|
+
holder._sub_parsers[name] = p
|
|
58
|
+
return p
|
|
59
|
+
return _Sub()
|
|
60
|
+
|
|
61
|
+
def parse_args(self, argv=None):
|
|
62
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
63
|
+
if self._sub_parsers:
|
|
64
|
+
ns = _NS()
|
|
65
|
+
for k, v in self._defaults.items():
|
|
66
|
+
setattr(ns, k, v)
|
|
67
|
+
if not argv:
|
|
68
|
+
setattr(ns, self._sub_dest, None)
|
|
69
|
+
return ns
|
|
70
|
+
for i, t in enumerate(argv):
|
|
71
|
+
if not t.startswith('-'):
|
|
72
|
+
name, rest = t, argv[:i] + argv[i + 1:]
|
|
73
|
+
break
|
|
74
|
+
else:
|
|
75
|
+
if argv[0] in ('-h', '--help'):
|
|
76
|
+
self.print_help()
|
|
77
|
+
raise SystemExit(0)
|
|
78
|
+
self.error('unrecognized arguments: %s' % argv[0])
|
|
79
|
+
sp = self._sub_parsers.get(name)
|
|
80
|
+
if sp is None:
|
|
81
|
+
self.error('无效命令: %s(可选: %s)' % (name, ', '.join(sorted(self._sub_parsers))))
|
|
82
|
+
ns = sp.parse_args(rest)
|
|
83
|
+
setattr(ns, self._sub_dest, name)
|
|
84
|
+
return ns
|
|
85
|
+
ns = _NS()
|
|
86
|
+
for o in self._opts:
|
|
87
|
+
setattr(ns, o['dest'], o['default'])
|
|
88
|
+
for k, v in self._defaults.items():
|
|
89
|
+
setattr(ns, k, v)
|
|
90
|
+
pos_order = [o for o in self._opts if o['pos']]
|
|
91
|
+
pos_idx = 0
|
|
92
|
+
i = 0
|
|
93
|
+
while i < len(argv):
|
|
94
|
+
t = argv[i]
|
|
95
|
+
if t in ('-h', '--help'):
|
|
96
|
+
self.print_help()
|
|
97
|
+
raise SystemExit(0)
|
|
98
|
+
if t.startswith('-') and t != '-':
|
|
99
|
+
tkey, teq, tval = t.partition('=')
|
|
100
|
+
o = self._find(tkey if teq else t)
|
|
101
|
+
if o is None:
|
|
102
|
+
self.error('unrecognized arguments: %s' % t)
|
|
103
|
+
if o['is_flag']:
|
|
104
|
+
setattr(ns, o['dest'], True)
|
|
105
|
+
i += 1
|
|
106
|
+
elif teq:
|
|
107
|
+
self._setval(ns, o, self._conv(o, tval, tkey))
|
|
108
|
+
i += 1
|
|
109
|
+
else:
|
|
110
|
+
if i + 1 >= len(argv):
|
|
111
|
+
self.error('argument %s: expected one argument' % t)
|
|
112
|
+
self._setval(ns, o, self._conv(o, argv[i + 1], t))
|
|
113
|
+
i += 2
|
|
114
|
+
else:
|
|
115
|
+
if pos_idx >= len(pos_order):
|
|
116
|
+
self.error('unrecognized arguments: %s' % t)
|
|
117
|
+
o = pos_order[pos_idx]
|
|
118
|
+
setattr(ns, o['dest'], self._conv(o, t, o['dest']))
|
|
119
|
+
pos_idx += 1
|
|
120
|
+
i += 1
|
|
121
|
+
for o in pos_order[pos_idx:]:
|
|
122
|
+
if o['default'] is None and not o['optpos']:
|
|
123
|
+
self.error('the following arguments are required: %s' % o['dest'])
|
|
124
|
+
for o in self._opts:
|
|
125
|
+
if o['required'] and getattr(ns, o['dest']) is None:
|
|
126
|
+
fl = ', '.join(o['flags'] or [o['dest']])
|
|
127
|
+
self.error('the following arguments are required: %s' % fl)
|
|
128
|
+
return ns
|
|
129
|
+
|
|
130
|
+
def _setval(self, ns, o, v):
|
|
131
|
+
if o['append']:
|
|
132
|
+
cur = getattr(ns, o['dest'])
|
|
133
|
+
if not isinstance(cur, list):
|
|
134
|
+
cur = []
|
|
135
|
+
setattr(ns, o['dest'], cur)
|
|
136
|
+
cur.append(v)
|
|
137
|
+
else:
|
|
138
|
+
setattr(ns, o['dest'], v)
|
|
139
|
+
|
|
140
|
+
def _find(self, flag):
|
|
141
|
+
for o in self._opts:
|
|
142
|
+
if o['flags'] and flag in o['flags']:
|
|
143
|
+
return o
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
def _conv(self, o, raw, shown):
|
|
147
|
+
if o['type'] is not str:
|
|
148
|
+
try:
|
|
149
|
+
v = o['type'](raw)
|
|
150
|
+
except (ValueError, TypeError):
|
|
151
|
+
self.error('argument %s: invalid %s value: %r' % (shown, o['type'].__name__, raw))
|
|
152
|
+
return None
|
|
153
|
+
else:
|
|
154
|
+
v = raw
|
|
155
|
+
if o['choices'] is not None and v not in o['choices']:
|
|
156
|
+
self.error('argument %s: invalid choice: %r (choose from %s)' % (
|
|
157
|
+
shown, v, ', '.join(map(repr, o['choices']))))
|
|
158
|
+
return v
|
|
159
|
+
|
|
160
|
+
def print_help(self, out=None):
|
|
161
|
+
out = out if out is not None else sys.stdout
|
|
162
|
+
w = out.write
|
|
163
|
+
w('usage: %s [-h] [options]\n' % self.prog)
|
|
164
|
+
if self.description:
|
|
165
|
+
w('\n%s\n' % self.description)
|
|
166
|
+
w('\noptions:\n')
|
|
167
|
+
for o in self._opts:
|
|
168
|
+
if o['flags']:
|
|
169
|
+
meta = ' {%s}' % ', '.join(map(str, o['choices'])) if o['choices'] else ''
|
|
170
|
+
req = ' (required)' if o.get('required') else ''
|
|
171
|
+
w(' %s%s%s %s\n' % (', '.join(o['flags']), meta, req, o['help']))
|
|
172
|
+
else:
|
|
173
|
+
w(' %s %s\n' % (o['dest'], o['help']))
|
|
174
|
+
if self._sub_parsers:
|
|
175
|
+
w('\ncommands:\n')
|
|
176
|
+
for n, p in sorted(self._sub_parsers.items()):
|
|
177
|
+
w(' %s %s\n' % (n, p.description))
|
|
178
|
+
w(' -h, --help show this help message and exit\n')
|
|
179
|
+
|
|
180
|
+
def error(self, message):
|
|
181
|
+
sys.stderr.write('%s: error: %s\n' % (self.prog, message))
|
|
182
|
+
raise SystemExit(2)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if __name__ == '__main__':
|
|
186
|
+
ap = ArgumentParser(prog='amtest', description='argsmini 自测')
|
|
187
|
+
sub = ap.add_subparsers(dest='cmd')
|
|
188
|
+
d = sub.add_parser('dns', help='dns test')
|
|
189
|
+
d.add_argument('target', help='域名')
|
|
190
|
+
d.add_argument('-w', '--workers', type=int, default=8, help='并发')
|
|
191
|
+
d.add_argument('--dot', action='store_true', help='DoT')
|
|
192
|
+
d.add_argument('--via', choices=['udp', 'dot'], default='udp', help='通道')
|
|
193
|
+
a = sub.add_parser('add', help='add test')
|
|
194
|
+
a.add_argument('--type', required=True, choices=['a', 'b'], help='类型')
|
|
195
|
+
a.add_argument('--lib', action='append', default=[], help='重复项')
|
|
196
|
+
a.add_argument('--from', dest='source', default='', help='dest 改名')
|
|
197
|
+
a.add_argument('tgt', nargs='?', help='可选位置')
|
|
198
|
+
a.set_defaults(func='F')
|
|
199
|
+
ns = ap.parse_args(['add', '--type=a', '--lib', 'x', '--lib=y', '--from', 'src'])
|
|
200
|
+
assert (ns.cmd, ns.type, ns.lib, ns.source, ns.tgt, ns.func) == \
|
|
201
|
+
('add', 'a', ['x', 'y'], 'src', None, 'F'), vars(ns)
|
|
202
|
+
ns2 = ap.parse_args(['dns', 'example.com', '-w', '4', '--dot'])
|
|
203
|
+
assert (ns2.cmd, ns2.target, ns2.workers, ns2.dot, ns2.via) == \
|
|
204
|
+
('dns', 'example.com', 4, True, 'udp'), vars(ns2)
|
|
205
|
+
try:
|
|
206
|
+
ap.parse_args(['add'])
|
|
207
|
+
raise AssertionError('required 未触发')
|
|
208
|
+
except SystemExit as e:
|
|
209
|
+
assert e.code == 2
|
|
210
|
+
print('argsmini self-test OK')
|
|
@@ -4,23 +4,94 @@
|
|
|
4
4
|
HTTP Basic Auth 爆破(低并发 + 退避,避免触发 WAF/封 IP)
|
|
5
5
|
用法: python3 basicauth_brute.py <url> <userlist> <passlist> [--conc 4] [--delay 0.15]
|
|
6
6
|
"""
|
|
7
|
-
import
|
|
7
|
+
import os, sys, base64, time, json, shutil, re
|
|
8
|
+
import argsmini as argparse
|
|
9
|
+
|
|
10
|
+
# ---- 懒加载/popen 优化:ssl/urllib/concurrent import 各耗 1-5s(iSH),仅首次用时载入 ----
|
|
11
|
+
_ssl_m = None
|
|
12
|
+
def _get_ssl():
|
|
13
|
+
global _ssl_m
|
|
14
|
+
if _ssl_m is None: import ssl as _s; _ssl_m = _s
|
|
15
|
+
return _ssl_m
|
|
16
|
+
_url_m = None
|
|
17
|
+
def _get_urllib():
|
|
18
|
+
global _url_m
|
|
19
|
+
if _url_m is None: import urllib.request as _r; import urllib.error as _e; _url_m = (_r, _e)
|
|
20
|
+
return _url_m
|
|
21
|
+
_cf_m = None
|
|
22
|
+
def _get_cf():
|
|
23
|
+
global _cf_m
|
|
24
|
+
if _cf_m is None: import concurrent.futures as _c; _cf_m = _c
|
|
25
|
+
return _cf_m
|
|
26
|
+
def _quote(s):
|
|
27
|
+
import urllib.parse as _p
|
|
28
|
+
return _p.quote(s, safe='')
|
|
29
|
+
def _unquote(s):
|
|
30
|
+
import urllib.parse as _p
|
|
31
|
+
return _p.unquote(s)
|
|
32
|
+
_SAFE = frozenset("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@%_+=:,./-")
|
|
33
|
+
def _shq(s):
|
|
34
|
+
if s and all(c in _SAFE for c in s): return s
|
|
35
|
+
return "'%s'" % s.replace("'", "'" + chr(92) + "'" + "'")
|
|
36
|
+
def _run_curl(cargs):
|
|
37
|
+
p = os.popen(" ".join(_shq(a) for a in cargs) + " 2>/dev/null")
|
|
38
|
+
out = p.read(); p.close(); return out
|
|
39
|
+
_CTX = None
|
|
40
|
+
def _ctx():
|
|
41
|
+
global _CTX
|
|
42
|
+
if _CTX is None:
|
|
43
|
+
_s = _get_ssl()
|
|
44
|
+
c = _s.SSLContext(_s.PROTOCOL_TLS_CLIENT)
|
|
45
|
+
c.check_hostname = False
|
|
46
|
+
c.verify_mode = _s.CERT_NONE
|
|
47
|
+
_CTX = c
|
|
48
|
+
return _CTX
|
|
8
49
|
|
|
9
50
|
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36"
|
|
10
|
-
CTX = ssl.create_default_context(); CTX.check_hostname = False; CTX.verify_mode = ssl.CERT_NONE
|
|
11
51
|
|
|
12
52
|
def try_one(url, user, pwd, delay):
|
|
53
|
+
_r, _e = _get_urllib()
|
|
13
54
|
if delay: time.sleep(delay)
|
|
14
55
|
tok = base64.b64encode(f"{user}:{pwd}".encode()).decode()
|
|
15
|
-
r =
|
|
56
|
+
r = _r.Request(url, headers={"User-Agent": UA, "Authorization": f"Basic {tok}"})
|
|
16
57
|
try:
|
|
17
|
-
with
|
|
58
|
+
with _r.urlopen(r, timeout=10, context=_ctx()) as resp:
|
|
18
59
|
return (user, pwd, resp.status, resp.read(200))
|
|
19
|
-
except
|
|
60
|
+
except _e.HTTPError as e:
|
|
20
61
|
return (user, pwd, e.code, b"")
|
|
21
62
|
except Exception as e:
|
|
22
63
|
return (user, pwd, 0, str(e).encode()[:100])
|
|
23
64
|
|
|
65
|
+
def brute_curl(url, users, passes, batch, timeout):
|
|
66
|
+
"""快速引擎:curl -Z 批量(URL 内嵌凭据,状态码区分)"""
|
|
67
|
+
pairs = [(u, p) for u in users for p in passes]
|
|
68
|
+
scheme, _, rest = url.partition('://')
|
|
69
|
+
hits = 0
|
|
70
|
+
for i in range(0, len(pairs), batch):
|
|
71
|
+
chunk = pairs[i:i + batch]
|
|
72
|
+
cargs = ['curl', '-sZ', '--parallel-max', str(batch), '-m', str(timeout),
|
|
73
|
+
'-w', '%{http_code} %{url_effective}\n']
|
|
74
|
+
for u, p in chunk:
|
|
75
|
+
cargs += ['-o', '/dev/null', f"{scheme}://{_quote(u)}:{_quote(p)}@{rest}"]
|
|
76
|
+
try:
|
|
77
|
+
out = _run_curl(cargs)
|
|
78
|
+
except OSError:
|
|
79
|
+
continue
|
|
80
|
+
for line in out.splitlines():
|
|
81
|
+
code_s, _, eff = line.partition(' ')
|
|
82
|
+
code = int(code_s) if code_s.isdigit() else 0
|
|
83
|
+
m = re.match(r'^[a-zA-Z]+://([^@]+)@', eff)
|
|
84
|
+
cred = _unquote(m.group(1)) if m else ''
|
|
85
|
+
u, _, p = cred.partition(':')
|
|
86
|
+
if code in (200, 301, 302):
|
|
87
|
+
hits += 1
|
|
88
|
+
print(json.dumps({"HIT": True, "user": u, "pass": p, "status": code}))
|
|
89
|
+
elif code not in (401, 0):
|
|
90
|
+
print(json.dumps({"odd": True, "user": u, "pass": p, "status": code}))
|
|
91
|
+
print(f"[*] {min(i + batch, len(pairs))}/{len(pairs)} done", file=sys.stderr)
|
|
92
|
+
print(json.dumps({"done": True, "attempts": len(pairs), "hits": hits}))
|
|
93
|
+
|
|
94
|
+
|
|
24
95
|
def main():
|
|
25
96
|
ap = argparse.ArgumentParser()
|
|
26
97
|
ap.add_argument("url"); ap.add_argument("users"); ap.add_argument("passes")
|
|
@@ -29,10 +100,14 @@ def main():
|
|
|
29
100
|
users = [l.strip() for l in open(a.users) if l.strip()]
|
|
30
101
|
passes = [l.strip() for l in open(a.passes) if l.strip()]
|
|
31
102
|
print(f"[*] {len(users)} users x {len(passes)} passes = {len(users)*len(passes)} attempts", file=sys.stderr)
|
|
103
|
+
if (not a.delay or a.delay <= 0) and shutil.which("curl"):
|
|
104
|
+
print("[*] 快速模式: curl -Z 批量(无 curl 或缺 delay 控制需求时自动改用线程池)", file=sys.stderr)
|
|
105
|
+
brute_curl(a.url, users, passes, 48, 10)
|
|
106
|
+
return
|
|
32
107
|
n = 0
|
|
33
|
-
with
|
|
108
|
+
with _get_cf().ThreadPoolExecutor(max_workers=a.conc) as ex:
|
|
34
109
|
futs = [ex.submit(try_one, a.url, u, p, a.delay) for u in users for p in passes]
|
|
35
|
-
for f in
|
|
110
|
+
for f in _get_cf().as_completed(futs):
|
|
36
111
|
u, p, st, body = f.result()
|
|
37
112
|
n += 1
|
|
38
113
|
if st in (200, 302, 301):
|
package/scripts/cvefetch.py
CHANGED
package/scripts/deck.py
CHANGED
package/scripts/depscan.py
CHANGED
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
# 只提取不查 CVE
|
|
21
21
|
python3 depscan.py https://target.com --no-cve
|
|
22
22
|
"""
|
|
23
|
-
import
|
|
23
|
+
import argsmini as argparse
|
|
24
|
+
import json, re, ssl, sys, urllib.parse
|
|
24
25
|
import urllib.request, urllib.error
|
|
25
26
|
|
|
26
27
|
CTX = ssl.create_default_context()
|