第七章:哈希函数与攻击
7.1哈希函数基础
哈希函数是将任意长度的输入映射为固定长度输出(称为哈希值、摘要或指纹)的函数。
安全哈希函数应满足的性质:
- 确定性:相同输入总是产生相同输出
- 抗原像性(Preimage Resistance):给定哈希值h,难以找到m使得H(m) = h
- 抗第二原像性(Second Preimage Resistance):给定m1,难以找到m2≠m1使得H(m1) = H(m2)
- 抗碰撞性(Collision Resistance):难以找到任意两个不同的m1、m2使得H(m1) = H(m2)
- 雪崩效应:输入的微小变化导致输出的巨大变化
算法 | 输出长度 | 安全性 | 备注 |
MD5 | 128位 | 已不安全 | 已发现碰撞,不应用于安全场景 |
SHA-1 | 160位 | 已不安全 | 2017年Google实现了碰撞(SHAttered) |
SHA-256 | 256位 | 安全 | SHA-2家族,目前广泛使用 |
SHA-3 | 可变 | 安全 | 基于Keccak海绵结构,非Merkle-Damgård |
7.2哈希碰撞攻击
生日攻击:基于生日悖论,对于n位哈希值,期望在约2^(n/2)次尝试后找到碰撞。例如MD5(128位)理论上需要约2^64次运算。
MD5碰撞:使用fastcoll工具可以在几秒内生成两个具有相同MD5值的不同文件。CTF中常见考法:提交两个不同的文件使其MD5相同。
SHA-1碰撞(SHAttered):Google团队在2017年展示了首个实际SHA-1碰撞,构造了两个不同但SHA-1相同的PDF文件。
7.3哈希长度扩展攻击(Length Extension Attack)
适用算法:MD5、SHA-1、SHA-256等基于Merkle-Damgård结构的哈希函数。不适用于SHA-3(海绵结构)和HMAC。
攻击原理:
Merkle-Damgård结构的哈希函数按块迭代处理输入。H(m)的结果就是处理完所有块后的内部状态。如果知道H(secret || message)和message的长度,攻击者可以:
- 将H(secret || message)作为新的内部状态
- 从这个状态继续"追加"数据
- 在不知道secret的情况下计算H(secret || message || padding || extension)
攻击条件:
- 已知H(secret || message)的值
- 已知message的内容
- 已知或可猜测secret的长度
- 使用基于Merkle-Damgård结构的哈希函数
攻击工具:HashPump, hash_extender
import hashpumpy import hashlib from urllib.parse import quote class HashLengthExtender: """ CTF 专用哈希长度扩展攻击工具类 支持:MD5, SHA1, SHA256, SHA512 """ def __init__(self, algorithm='md5'): self.algorithm = algorithm # 映射算法名称到 hashpumpy 支持的格式 self.algo_map = { 'md5': 'md5', 'sha1': 'sha1', 'sha256': 'sha256', 'sha512': 'sha512' } def attack(self, original_hash_hex, original_data_bytes, append_data_bytes, secret_length): """ 执行长度扩展攻击 参数: original_hash_hex: 原始哈希值 (Hex字符串) original_data_bytes: 原始数据 (bytes) append_data_bytes: 要追加的数据 (bytes) secret_length: 密钥长度 (int) 返回: (new_hash_hex, new_data_bytes): 新哈希值和新数据 """ algo = self.algo_map.get(self.algorithm.lower()) if not algo: raise ValueError(f"不支持的算法: {self.algorithm}") # 调用 hashpumpy # 注意:hashpumpy.hashpump 返回的是 (new_hash_hex, new_data_bytes) new_hash, new_data = hashpumpy.hashpump( original_hash_hex, original_data_bytes, append_data_bytes, secret_length, algo ) return new_hash, new_data def url_encode_payload(self, data_bytes): """ 将 payload 转换为 URL 编码格式 (Web 题常用) """ # safe='' 表示所有非字母数字字符都编码 return quote(data_bytes, safe='') # ========================================== # 使用示例 # ========================================== if __name__ == "__main__": # --- 场景配置 --- # 假设服务端逻辑:md5(SECRET + "user=guest") SECRET = b"supersecretkey16" # 实际攻击中不知道这个,只知道长度 ORIGINAL_DATA = b"user=guest" APPEND_DATA = b"&role=admin" # 我们想追加的内容 SECRET_LENGTH = 16 # 1. 模拟获取原始哈希 (攻击者已知) original_hash = hashlib.md5(SECRET + ORIGINAL_DATA).hexdigest() print(f"--- 场景: MD5 长度扩展攻击 ---") print(f"原始数据: {ORIGINAL_DATA}") print(f"原始哈希: {original_hash}") print() # --- 执行攻击 --- extender = HashLengthExtender(algorithm='md5') try: new_hash, new_data = extender.attack( original_hash, ORIGINAL_DATA, APPEND_DATA, SECRET_LENGTH ) print(f"--- 攻击结果 ---") print(f"新哈希 (伪造签名): {new_hash}") print(f"新数据 (原始字节): {new_data}") # Web 题中通常需要 URL 编码发送 url_payload = extender.url_encode_payload(new_data) print(f"新数据 (URL编码): {url_payload}") print() # --- 本地验证 --- # 模拟服务端接收数据并验证 # 服务端逻辑:md5(SECRET + received_data) verify_hash = hashlib.md5(SECRET + new_data).hexdigest() print(f"--- 验证 ---") print(f"服务端计算哈希: {verify_hash}") if verify_hash == new_hash: print("✅ 攻击成功!哈希值匹配。") print(f"🚀 构造的最终 URL: ?data={url_payload}&sign={new_hash}") else: print("❌ 攻击失败,哈希值不匹配。") except Exception as e: print(f"发生错误: {e}")7.4彩虹表与字典攻击
原理:预计算大量常见密码的哈希值,建立"密码→哈希"的查找表。遇到目标哈希时直接查表。
在线查询服务:
- cmd5.com:中文最大的哈希查询网站
- crackstation.net:支持多种哈希算法的在线查询
hashcat基本用法:
#!/bin/bash # ============================================================================== # Hashcat 实战命令库 # 版本: 2026.04 # 描述: 整理常用的 Hashcat 攻击模式、掩码定义及性能参数 # ============================================================================== # --- 基础变量定义 --- HASH_FILE="hash.txt" # 包含哈希值的文件 WORDLIST="rockyou.txt" # 字典文件 OUTPUT_FILE="found.txt" # 破解结果输出文件 RULE_FILE="best64.rule" # 规则文件 (位于 hashcat/rules/ 目录下) # ============================================================================== # 1. 基础信息与环境检查 # ============================================================================== # 查看版本信息 hashcat --version # 查看支持的哈希类型 (查找特定算法,如 MD5) hashcat --help | grep MD5 # 基准测试 (测试当前硬件的破解速度,非常重要!) hashcat -b # 查看设备信息 (OpenCL/CUDA 设备) hashcat -I # ============================================================================== # 2. 常见哈希类型 (-m 参数速查) # ============================================================================== # 0 = MD5 # 100 = SHA1 # 1400 = SHA256 # 1000 = NTLM (Windows 密码) # 3200 = Bcrypt # 11600 = 7-Zip (压缩包) # 17220 = ZIP (压缩包) # 22000 = WPA/WPA2 (WiFi) # ============================================================================== # 3. 攻击模式详解 (-a 参数) # ============================================================================== # --- 模式 0: 字典攻击 (直攻) --- # 最基础的模式,逐行尝试字典中的密码 echo ">>> 模式 0: 字典攻击" # hashcat -m 0 -a 0 $HASH_FILE $WORDLIST -o $OUTPUT_FILE # --- 模式 1: 组合攻击 --- # 将两个字典进行笛卡尔积组合 (例如: dict1 中的 "admin" + dict2 中的 "123" = "admin123") echo ">>> 模式 1: 组合攻击" # hashcat -m 0 -a 1 $HASH_FILE dict1.txt dict2.txt -o $OUTPUT_FILE # --- 模式 3: 掩码暴力攻击 --- # 适用于知道密码格式的情况 (如: 8位数字, 6位小写字母等) echo ">>> 模式 3: 掩码攻击" # 示例 A: 6位纯数字 (如验证码, 手机锁屏) # hashcat -m 0 -a 3 $HASH_FILE ?d?d?d?d?d?d # 示例 B: 8位字母数字混合 (不区分大小写) # hashcat -m 0 -a 3 $HASH_FILE ?h?h?h?h?h?h?h?h # 示例 C: 8位任意字符 (包含特殊符号,速度慢) # hashcat -m 0 -a 3 $HASH_FILE ?a?a?a?a?a?a?a?a # 示例 D: 自定义字符集 (例如只包含 a,b,c,1,2,3) # hashcat -m 0 -a 3 $HASH_FILE -1 abc123 ?1?1?1?1?1 # --- 模式 6: 混合字典+掩码 (字典在左) --- # 在字典单词后追加字符 (例如: password + 2位数字) echo ">>> 模式 6: 混合攻击 (字典+掩码)" # hashcat -m 0 -a 6 $HASH_FILE $WORDLIST ?d?d # --- 模式 7: 混合掩码+字典 (掩码在左) --- # 在字典单词前添加字符 (例如: 2位数字 + password) echo ">>> 模式 7: 混合攻击 (掩码+字典)" # hashcat -m 0 -a 7 $HASH_FILE ?d?d $WORDLIST # --- 模式 0 + 规则攻击 --- # 使用规则引擎变换字典单词 (如: 首字母大写, 末尾加数字) echo ">>> 模式 0 + 规则: 智能变换" # hashcat -m 0 -a 0 $HASH_FILE $WORDLIST -r $RULE_FILE # ============================================================================== # 4. 常用性能与优化参数 # ============================================================================== # -O : 开启优化内核 (推荐,能显著提升速度,但限制密码最大长度) # -w 3 : 设置工作负载 (1-4, 3为高性能,4为疯狂模式,注意散热) # --force : 强制运行 (忽略警告,如没有GPU或权限问题时) # --show : 显示已破解的密码 # --remove : 破解成功后从哈希文件中删除该行 (节省时间) # -i : 开启增量模式 (配合掩码攻击,自动从短到长尝试) # 示例: 优化的掩码攻击命令 # hashcat -m 0 -a 3加盐(Salt)防御:在密码哈希前加入随机字符串(salt),使相同密码产生不同的哈希值,使彩虹表攻击失效。
7.5实战案例:哈希长度扩展攻击
📋题目场景 Web服务使用md5(SECRET + query_string)作为请求签名。你是普通用户,已知一个有效请求:query_string = "action=view&user=guest",对应签名为sig = "a8f5f167f44f4964e6c998dee827110c"。SECRET长度为12字节。你需要构造一个包含action=admin的合法请求。 |
分析攻击条件:
- 已知哈希值(sig)✓
- 已知原始数据(query_string)✓
- 已知secret长度(12)✓
- 使用MD5(Merkle-Damgård结构)✓
import hashpumpy from urllib.parse import quote import hashlib class LengthExtensionAttack: def __init__(self, algorithm='md5'): self.algorithm = algorithm def generate_payload(self, original_hash, original_data, append_data, secret_length): """ 生成攻击 Payload """ # 执行长度扩展攻击 # 注意:hashpumpy 默认处理 MD5,其他算法需指定 new_hash, new_data = hashpumpy.hashpump( original_hash, original_data, append_data, secret_length, self.algorithm ) return new_hash, new_data def url_encode(self, data): """ URL 编码处理 safe='=&' 表示保留 = 和 & 符号不编码,方便阅读,也可以去掉 safe 参数全部编码 """ return quote(data, safe='=&') # ========================================== # 实战场景配置 # ========================================== if __name__ == "__main__": # 1. 题目已知信息 original_hash = "a8f5f167f44f4964e6c998dee827110c" original_data = b"action=view&user=guest" append_data = b"&action=admin" secret_length = 12 # 如果不知道,通常需要从 1 到 20 爆破 print(f"--- 开始攻击 ---") print(f"原始数据: {original_data}") print(f"原始签名: {original_hash}") print(f"目标追加: {append_data}") print("-" * 30) # 2. 执行攻击 attacker = LengthExtensionAttack(algorithm='md5') new_hash, new_data = attacker.generate_payload( original_hash, original_data, append_data, secret_length ) # 3. 结果处理 url_encoded = attacker.url_encode(new_data) print(f"新签名: {new_hash}") print(f"新数据 (Hex): {new_data.hex()}") print(f"新数据 (URL编码): {url_encoded}") print("-" * 30) # 4. 构造最终请求 final_request = f"?query={url_encoded}&sig={new_hash}" print(f"最终请求: {final_request}") # ========================================== # 本地验证 (模拟服务端逻辑) # ========================================== # 假设我们猜对了 secret 长度,并且知道 secret 内容用于验证(实际攻击中不知道) # 这里仅用于演示验证逻辑是否正确 print("\n--- 本地验证 (模拟服务端) ---") # 注意:因为不知道真实 secret,这里无法真实验证 md5(secret + data) # 但如果题目给了验证接口,你可以用上面的 final_request 去请求 # 仅演示:如果我们知道 secret 是 "testsecret12" (长度12) mock_secret = b"testsecret12" # 服务端接收到的数据其实是:original_data + padding + append_data # 也就是 new_data server_calc_hash = hashlib.md5(mock_secret + new_data).hexdigest() # 如果攻击成功,new_hash 应该等于 server_calc_hash # (这里因为 mock_secret 是我瞎编的,所以大概率不相等,除非原题 secret 也是这个) # 但在 CTF 中,只要服务端不报错且返回 admin 页面,即视为成功 print(f"如果服务端 Secret 长度为 12,且算法为 MD5") print(f"攻击 Payload 已生成,请尝试提交。")7.6 哈希算法的“万能胶”——Hash Injection (Flask/Jinja2)
背景:
在Web开发中,Python的Flask框架(及其模板引擎Jinja2)非常流行。在2026年的今天,虽然Flask已迭代多代,但老版本遗留的“哈希比较漏洞”依然是CTF中Web题的经典考点。
原理:
当Web应用使用==或===比较哈希值,且后端语言(如Python、PHP)将某些特定字符串(如0e\d+)解析为科学计数法(0的任意次方等于0)时,就会产生逻辑漏洞。
CTF场景:
题目通常是一个登录系统,要求输入的密码哈希值与数据库中的哈希值“相等”。攻击者不需要知道原密码,只需要找一个哈希值以0e开头的字符串。
代码示例:
# 模拟存在漏洞的验证逻辑 def vulnerable_check(input_pass, stored_hash): # 用户控制的 input_pass 经过哈希后与 stored_hash 比较 user_hash = md5(input_pass.encode()).hexdigest() # 危险的比较方式:如果解释器将字符串转为数字,"0e123" == "0e456" 会成立 return user_hash == stored_hash # 攻击载荷(寻找 Hash 以 0e 开头的字符串) # 例如:md5("240610708") = "0e462097431906509019562988736854" # 如果 stored_hash 是另一个 0e 开头的哈希,验证就会通过7.7 中文环境下的特殊攻击面 (cmd5.com 特色)
背景:
结合你提到的cmd5.com和当前地点(中国浙江),CTF题目中经常出现针对中文用户习惯的哈希。
内容补充:
- 中文弱口令:中国用户的常用密码往往是拼音+数字(如
zhangsan123)或键盘序列(如1qaz2wsx)。在字典攻击中,需要专门针对中文环境的字典(如darkc0de的中文变种)。 - 编码差异(GBK vs UTF-8):
- 在某些老旧的Web系统(尤其是国内老系统)中,输入可能使用 GBK 编码。
- 攻击点:同一个汉字在 GBK 和 UTF-8 下的字节流不同,导致哈希值完全不同。如果题目背景是“破解一个老式OA系统的密码”,可能需要考虑编码转换。
实战技巧:
# 例子:同一个汉字“密”在不同编码下的哈希 import hashlib text = "密" md5_utf8 = hashlib.md5(text.encode('utf-8')).hexdigest() md5_gbk = hashlib.md5(text.encode('gbk')).hexdigest() print(f"UTF-8 MD5: {md5_utf8}") # 不同的值 print(f"GBK MD5: {md5_gbk}") # 不同的值 # 在CTF中,如果已知目标系统是老式Windows简体中文版,可能需要尝试GBK编码。7.8 哈希猫池与API自动化 (2026年现状)
背景:
你提到的cmd5.com和crackstation.net都提供了API。在2026年的CTF自动化脚本编写中,选手通常不会手动查表,而是编写“哈希中控台”。
内容补充:
多线程哈希破解脚本:
介绍如何编写一个脚本,同时调用多个在线查询接口(cmd5, hashkiller, myhashfw等),并处理API的并发请求和验证码(CAPTCHA)绕过。
代码示例(伪代码/框架):
import requests import threading class HashCracker: def __init__(self): self.api_keys = { 'cmd5': 'YOUR_KEY', 'hashkiller': 'ANOTHER_KEY' } def query_cmd5(self, hash_value): # cmd5.com 的 API 接口调用 url = "https://cmd5.com/api.aspx" data = {'hash': hash_value, 'key': self.api_keys['cmd5']} response = requests.post(url, data=data) return response.json() def query_hashkiller(self, hash_value): # hashkiller 的接口逻辑 pass def crack(self, hash_list): results = {} threads = [] # 创建多线程并发查询 for h in hash_list: t = threading.Thread(target=self.query_all_apis, args=(h,)) threads.append(t) t.start() for t in threads: t.join() return results # 这种自动化工具在处理大量哈希(如日志分析、批量解密)时非常有用。7.9 针对慢哈希的绕过 (Bcrypt/Argon2)
背景:
现代Web应用(如Django, Flask-Security)不再使用MD5/SHA,而是使用Bcrypt或Argon2。虽然它们很难被彩虹表攻击,但在CTF中常出现配置错误。
内容补充:
配置错误导致的弱安全性:
- 轮数过低:Bcrypt 的安全性依赖于
rounds/cost参数。如果题目中设置的 cost=4(正常是 12+),那么就可以使用暴力破解。 - 长度截断:某些旧版 Bcrypt 实现只取密码的前 72 个字节。如果题目是“找回一个超长密码”,可能只需要爆破前 72 字节。
工具推荐:
hashcat模式 3200 (Bcrypt) 在 2026 年已经可以通过 FPGA 或云 GPU 集群进行高速破解,如果 cost factor 较低的话。
7.10 总结与防御 (Defense in Depth)
内容补充:
在章节结尾,总结如何在 2026 年正确使用哈希:
- 永远不要使用 MD5/SHA1:即使是加盐,也因为算力提升变得不安全。
- 使用专用算法:对于密码存储,使用
Argon2id(winner of Password Hashing Competition) 或scrypt。 - HMAC 替代拼接:为了防止长度扩展攻击,不要使用
Hash(secret + message),而应该使用标准的HMAC-SHA256算法。
HMAC 代码示例(防御 Length Extension):
import hmac import hashlib # 正确的做法 secret = b'super_secret_key' message = b"user=guest" # 使用 HMAC,内部结构是 hash((key ⊕ opad) || hash((key ⊕ ipad) || message)) # 这种结构天然免疫长度扩展攻击 digest = hmac.new(secret, message, hashlib.sha256).hexdigest() print(digest)