资讯详情

加密靶场实战:AES+RSA混合加密的密钥管理漏洞破解

📅 2026/10/21 8:40:18 | 华诺云谱 👁 阅读
加密靶场实战:AES+RSA混合加密的密钥管理漏洞破解
做加密类靶场这几年我最深的体会是大多数看似“无解”的加密防护漏洞往往不在算法本身而在密钥管理和流程拼接上。这次拿到的encrypt靶场就是非常典型的例子——同一套数据同时使用AES和RSA前端加密、后端校验、会话传递全链路都有逻辑可挖。如果你也卡在AES密钥提取、RSA私钥利用或者密文解不开这类问题上这篇实战复盘会很有参考价值。我尽量把每一步的思路、工具和踩坑点都写清楚你照着做一遍基本就能摸清这类靶场的命门。1. 靶场环境与加密体系拆解1.1 靶场整体设计与攻击面分析encrypt靶场模拟的是一个带用户体系的业务系统登录、查询、修改资料、获取管理员标识这几个功能全部走了加密通道。表面上看所有请求体都是密文响应体也是密文好像无从下手。但拆开看就会发现它其实是把“传输加密”和“业务逻辑校验”混在一起了这刚好是很多真实项目的通病。先梳理整个攻击面。一个加密系统真正值得关注的无非四个环节密钥产生、密钥分发、加密算法实现、解密触发点。这个靶场的密钥产生在前端JS里完成密钥分发走了一个公开接口加密算法实现混合了AES和RSA两种解密触发点则散布在多个API接口中。这种设计注定了它不会是真的安全因为前端所有逻辑都是可以被翻出来分析的。实际测试时我先用抓包工具把登录接口的完整流程走了一遍。输入账号密码后前端生成一个随机的AES密钥用这个密钥加密账号密码再用RSA公钥把AES密钥本身加密两部分一起放进请求体。后端收到后先用RSA私钥解出AES密钥再用AES密钥解出账号密码完成校验。这套逻辑单看没有任何问题AES加密业务数据、RSA保护AES密钥正是混合加密的标准姿势。但问题在于那个RSA公钥是从一个公开的配置接口拉的而AES密钥是前端随机生成的。如果我能控制或者预测这个随机过程或者能拿到RSA对应的私钥整个加密体系就直接崩塌了。靶场的考点也恰好集中在这些地方随机数源的强度、RSA密钥对的存储位置、以及后端是否对解密结果做了额外校验。从攻击者的视角这种设计最少有四个可尝试的方向分析前端JS寻找硬编码的AES密钥或固定IV检查RSA公钥接口是否泄露了私钥文件测试AES加密是否使用了可预测的随机数作为密钥种子尝试通过重放或篡改密文探测后端的解密行为差异我实际测试下来前三个方向都出现了可利用的点最终突破口是其中之一。这个过程已经从“正面硬解”变成了“找设计缺陷”难度反而比直接攻击算法低很多。1.2 AES与RSA的协作逻辑及其脆弱点理解这个靶场的加密协作逻辑是能否拿下它的分水岭。AES和RSA分工完全不同AES是分组对称加密加密解密用同一个密钥特点是速度快、适合加密大量的业务数据RSA是非对称加密公钥加密、私钥解密特点是慢但能解决密钥传输的安全问题。两者组合就是业界标准的“数字信封”机制。数字信封的流程是这样的发送方随机生成一个AES密钥用AES密钥加密真正的业务数据接着用接收方的RSA公钥加密这个AES密钥最后把AES密文和RSA密文一起发给接收方。接收方用自己的RSA私钥解出AES密钥再用AES密钥解开业务密文。这个流程的理论基础非常扎实AES密钥用一次就丢每一次会话都是独立的密钥即使破解了一次会话也无法追溯其他会话。靶场并没有违背这个理论它老老实实地实现了数字信封。但理论安全不等于实现安全靶场在实际落地时留了几个后门。第一AES密钥的生成方式不是密码学安全的随机数。前端JS里用的是时间戳拼接字符串后做哈希的结果时间戳可以推测哈希算法也可以还原那么密钥本身就可预测。这相当于把锁的钥匙放在了门口的花盆底下而且花盆位置还是固定的。第二RSA密钥对的管理完全失控。靶场把私钥文件放在了一个静态资源目录里虽然文件名做了混淆但目录扫描就能翻出来。私钥一旦泄露所有会话的AES密钥信封都能解开整个数字信封机制形同虚设。第三AES加密模式用的是CBC且IV与密钥一起在前端生成并随请求发送。CBC模式本身没问题但IV如果可被攻击者控制或预测结合已知明文攻击就能在特定条件下还原或篡改密文块。这个靶场虽然没让我走到那一步但研究时确实验证了IV可控带来的风险。我在实战中反复强调一个观点攻击加密系统时别先想着破解算法先找实现层的失误。AES和RSA算法本身在数学上几乎无懈可击但密钥生成、密钥存储、随机数使用、模式选择这些环节才是漏洞的高发区。encrypt靶场把这一点展现得淋漓尽致。2. 核心加密逻辑分析与静态审计2.1 前端JS加密流程完整梳理拿到一个加密靶场第一件事绝对不是瞎抓包而是把前端代码完整读一遍。我使用浏览器开发者工具打开Sources面板逐一查看加载的JavaScript文件。这个靶场把所有加密相关的函数放在一个名叫crypto-helper.js的文件里命名还特意做了混淆但整体结构并不复杂核心部分几乎是教科书级别的实现。整个前端加密流程可以拆成五步页面加载时从/api/public/rsa-key接口获取RSA公钥存到全局变量用户点击登录按钮时触发加密函数加密函数首先生成AES密钥使用的是时间戳加随机字符再做SHA-256哈希的方式用AES密钥加密表单数据加密模式为CBC填充方式为PKCS7IV是一串写死的十六进制字符串用RSA公钥加密AES密钥本身最终把RSA密文、AES密文、IV一起打包成JSON发送到后端这个流程里关键的可疑点至少有三处。第一处是RSA公钥接口为什么密钥分发接口不设权限校验任何一个匿名用户都能访问这在设计上给后续攻击留下了伏笔。第二处是IV写死且与密钥一同传递这违反了IV应该随机生成的密码学基本原则。第三处是AES密钥的生成逻辑使用了非加密安全的随机源时间戳占了很大比重。我读取代码时特别留意了随机字符串的生成函数结果发现它用的不是Web Crypto API的crypto.getRandomValues而是简单的Math.random()加字符拼接。Math.random()的安全性一直备受质疑虽然不是完全可预测但在有足够样本的情况下可以部分恢复内部状态。更致命的是代码把时间戳和随机字符串一起哈希时间戳的精度是毫秒级可以精确枚举。前端审计完后我整理出了一份逻辑映射表把每个加密步骤对应的函数名、参数、输出格式全部记录下来。这一步很关键因为后面构造攻击脚本时需要完全模拟前端的加密行为才能让后端认为请求是合法的。审计前端代码不是走马观花而是要画出数据流图明确哪些变量可控、哪些函数可重放这样才能找到真正的进攻点。2.2 后端校验逻辑与接口行为探测前端看完之后接下来是对后端接口行为的探测。这一步的目标不是直接找到漏洞而是摸清后端的解密和校验流程。我使用的是Burp Suite把代理设为浏览器流量入口逐个访问所有可见的API接口观察返回状态码和响应体特征。靶场暴露的接口有这些GET /api/public/rsa-key— 获取RSA公钥无鉴权POST /api/login— 登录接收加密的账号密码GET /api/user/info— 查询用户信息需要请求头携带会话标识POST /api/user/update— 修改用户资料加密传输GET /api/admin/flag— 获取管理员标识需要管理员权限每个接口的返回格式都是JSON但错误信息不太一样。比如登录接口在解密失败时返回的是{code:400,msg:decrypt error}在账号密码错误时返回的是{code:401,msg:auth failed}。这两个不同的错误返回本身就暴露了解密流程和业务校验是分离的——先解密再做业务校验。利用这个特性我可以把解密过程当作一个“解密预言机”来用。发送不同的密文通过观察返回的是decrypt error还是auth failed就能判断某个密文是否能被正确解密。这在密码学攻击中是非常经典的思路——Padding Oracle Attack的核心前提就是这么来的。我还测试了接口对畸形输入的处理。比如把AES密文长度改掉、把IV改掉、把RSA密文改成空值后端都会返回不同的错误码。测试结果整理成一张表就非常有价值输入改动响应行为推断原样重放返回auth failed密文结构合法但业务校验不通过篡改AES密文任意一位返回decrypt error密文在解密阶段就失败了篡改IV任意一位返回auth failedIV不参与解密校验只影响明文结果篡改RSA密文返回decrypt errorRSA解密在AES解密之前失败会中断流程把AES密文置空返回参数错误后端有参数缺失校验这些响应的差异帮助我确认了后端解密顺序是“先RSA解AES密钥再用AES密钥解业务数据”而且解密失败和业务校验失败的响应是可区分的。这个信息对接下来的攻击链构建至关重要因为我需要知道自己的每一步操作在后端具体触发了哪个环节。2.3 密钥管理漏洞定位与提取密钥管理是本次实战的核心突破点。我在审计过程中锁定了三个方向RSA私钥泄露、AES密钥可预测、IV重用。先说RSA私钥的定位过程。前端代码里有一行静态资源引用路径看起来非常可疑路径中包含/assets/目录文件名是一串十六进制字符没有常见的.js或.css后缀反而更像一个无扩展名的文件。我直接在浏览器地址栏访问了这个路径结果直接下载下来一个PEM格式的文本文件。打开一看赫然就是RSA私钥。这里要说一下RSA私钥文件为什么能被直接访问本质上是静态资源配置不合理。很多服务端会把密钥文件误放在静态资源目录下觉得目录不可见就安全了。实际上目录扫描工具一跑所有文件都会暴露。靶场在这里特意模仿了真实环境中常见的“私钥放Web根目录”的错误操作。拿到私钥文件后我立刻用OpenSSL命令验证它的格式和位数openssl rsa -in private.pem -text -noout输出显示这是一个2048位的RSA私钥包含n、e、d、p、q等完整参数。有了私钥理论上可以解密任何使用对应公钥加密的数据整个数字信封的保护机制宣告失效。另一条线索是AES密钥的可预测性。前端生成AES密钥的代码如下function generateAesKey() { var randomPart generateRandomString(16); var timestamp Date.now().toString(); var hash CryptoJS.SHA256(timestamp randomPart).toString(); return CryptoJS.enc.Hex.parse(hash.substring(0, 64)); }这段代码的漏洞非常明显。generateRandomString虽然指定了16位长度但它使用的是Math.random()这个函数在浏览器环境中不保证任何密码学强度。更关键的是即便随机部分完全不可预测攻击者仍然可以通过截获RSA密文来解密得到AES密钥因为私钥已经泄露了。所以AES密钥可预测这条线在RSA私钥泄露后反而变成了备用方案。IV重用的问题也确认了。前端代码里IV是固定的十六进制字符串会话之间从不改变。在某些条件下IV固定配合CBC模式可以进行字节翻转攻击但在这个靶场里由于前面两条漏洞已经足以打通全链路IV重用的利用价值就没有那么突出了。不过我还是做了记录以备不同分支需要。整个密钥管理漏洞的定位过程让我再次确认加密系统的安全边界取决于它的短板而这个短板往往是人为操作失误不是算法选择的问题。3. 攻击链构建与解密实战3.1 完整拦截会话数据并提取加密报文密钥定位完成之后攻击链就清晰了。我需要做的第一件事是完整拦截一次正常的加密请求拿到一份真实的AES密文和RSA密文然后用自己的私钥和工具把明文恢复出来。我用Burp Suite的Proxy History功能筛选出登录接口的POST请求。请求体的格式是JSON字段包括cipherAes、cipherRsa和iv三个值。cipherAes是AES加密后的业务数据cipherRsa是RSA加密后的AES密钥iv在请求里也有但前端写死的情况下这个字段其实就是个冗余。把密文复制到本地文件后我开始用私钥解RSA密文。这里有个操作细节值得强调RSA加密的结果是二进制数据在传输时前端通常会做Base64编码。所以先要用Base64解码还原成二进制再交给OpenSSL处理。命令如下base64 -d cipher_rsa.b64 cipher_rsa.bin openssl rsautl -decrypt -inkey private.pem -in cipher_rsa.bin -out aes_key.binOpenSSL执行后生成了一个32字节的文件正好对应AES-256的密钥长度。用十六进制查看工具确认内容没有问题xxd aes_key.bin没有乱码说明密钥还原成功。这一步是整个攻击链中最关键的一步AES密钥一旦拿到手剩下的解密就是纯体力活了。接下来用AES密钥解业务密文。前端在加密时用的是AES-CBC加PKCS7填充所以OpenSSL命令要指定对应的模式和填充方式base64 -d cipher_aes.b64 cipher_aes.bin openssl enc -d -aes-256-cbc -K $(xxd -p aes_key.bin) -iv $(xxd -p iv.bin) -in cipher_aes.bin -out plaintext.txt这里-K参数接收十六进制格式的密钥所以先用xxd -p把二进制密钥转成十六进制字符串。IV同理。执行完后查看plaintext.txt明文完整暴露——账号密码、时间戳、用户角色信息全部在列。这一步的实操价值在于它揭示了数字信封的攻破路径不需要攻击AES算法也不需要攻击RSA算法只要私钥泄露整个链路就从“理论上安全”变成了“彻头彻尾的透明”。拿到私钥的那一刻这个靶场对我而言已经没有秘密了。3.2 通过泄露的RSA私钥还原会话密钥还原会话密钥的过程其实就是执行一次本该由后端完成的RSA私钥解密操作。由于私钥是PEM格式的标准文件整个过程在命令行里就能完成。但我在这里踩了一个小坑也是很多人容易犯错的点单独拿出来说一下。第一次执行openssl rsautl -decrypt命令时系统提示RSA operation error当时我以为是私钥文件损坏或者密文格式不对。检查后才发现问题出在Base64解码上——我从请求体里复制的RSA密文包含了换行符Base64解码时没有清理掉导致二进制数据多了一些不可见字节。清理之后重新解码命令就顺利执行了。另一个需要注意的点是RSA的填充方式。这个靶场用的RSA加密是常见的PKCS1 v1.5填充。如果私钥部分没有完整导出比如只导出n和d参数或者用错了填充方式解密也会失败。最简单的方法是直接把整个PEM私钥文件交给OpenSSL让它自己识别。成功解出AES密钥后我额外做了一个验证用这个密钥尝试解密另外几个会话的AES密文。结果有个意外发现——部分会话的AES密钥是完全相同的。原因很简单前端生成密钥的时间戳落在同一个毫秒区间内加上随机数部分弱导致了密钥碰撞。这个现象再次印证了弱随机数的危害不仅密钥可预测甚至会出现多个用户共用同一个密钥的情况。拿到AES密钥后解密业务数据只是时间问题。但如果只是停留在解密这一步充其量只能读到用户自己的数据离获取管理员标识还差得远。所以我又回到了接口列表盯上了那个/api/admin/flag接口。3.3 篡改密文实现越权访问与标识获取解密能力本身不产生越权越权需要改密文。当后端信任解密后的明文并直接使用明文中的角色字段做权限判断时我就可以通过构造一个“伪造明文”的合法密文来实现权限提升。这个过程在密码学上叫作“密文可控下的明文注入”。构造合法密文有两个条件一是有一个可用的AES密钥二是能用自己的密钥加密任意明文。这两个条件在拿到RSA私钥后都满足了。因为后端是根据会话标识来找到对应的AES密钥的而会话标识本身需要先通过合法流程获得。所以我先用正常流程登录一个低权限账号拿到一个合法的会话标识再用这个会话标识构造一个伪造的“管理员”请求。具体做法是把登录接口抓到的AES密文解开修改明文中的role字段把它从user改成admin然后用同一个AES密钥重新加密替换到请求体中发送。这里的关键在于后端在解密后并没有验证明文中的角色是否和数据库记录一致而是直接使用了明文里的信息。构造伪admin请求的Python脚本核心部分如下from Crypto.Cipher import AES from Crypto.PublicKey import RSA from Crypto.Util.Padding import pad import base64, json # 还原AES密钥 key bytes.fromhex(...) # 构造伪造明文 fake_data { action: get_flag, role: admin, user_id: 1 } plaintext json.dumps(fake_data).encode() iv bytes.fromhex(...) cipher AES.new(key, AES.MODE_CBC, iv) ciphertext cipher.encrypt(pad(plaintext, AES.block_size)) payload { cipherAes: base64.b64encode(ciphertext).decode(), cipherRsa: base64.b64encode(encrypt_rsa(key)).decode(), iv: iv.hex() } requests.post(https://target/api/admin/flag, jsonpayload)请求发出去之后接口返回的响应里直接包含了管理员标识。整条攻击链到此完整打通从获取公钥、找到私钥、解密AES密钥、伪造密文、越权获取标识每一步都走得有据可依。这个案例最值得学习的地方在于攻击链路并非依赖某一个孤立的漏洞而是层层递进、环环相扣的。4. 常见问题与排查技巧实录4.1 密文解密失败的类型化排查做加密类靶场解密失败是最常见的问题没有之一。我在实战中汇总了几类高频错误整理成速查表能帮你快速定位问题环节。现象可能原因排查方向OpenSSL报RSA operation errorBase64解码不完整、私钥格式不对、填充方式不匹配清理换行符重新解码检查PEM格式AES解密出现乱码密钥或IV长度不对、密文多字节、模式不匹配确认密钥32字节、IV16字节、CBC模式Python报ValueError: Data must be padded密文被截断、PKCS7填充不完整检查密文Base64解码后的字节长度是否为16的倍数解密成功但字段缺失明文格式与预期JSON不一致确认前端加密时的数据格式可能包含额外字段接口返回decrypt error后端解密失败检查请求体JSON字段名是否与后端预期一致解密失败时我习惯用排除法逐步验证。首先确认私钥是否能解RSA密文这一步失败就很底层因为后面所有的AES密钥都拿不到其次确认AES密钥还原后是否能解出已知明文格式的密文最后再检查请求构造是否完全复刻了前端的加密流程。有一个非常隐蔽的坑是AES密钥的编码方式。前端JS里生成的AES密钥是Hexadecimal格式的但传给AES加密时用的是CryptoJS.enc.Hex.parse()解析后的WordArray。如果用其他语言实现解密必须先把十六进制字符串转成对应的字节数组。如果直接把这个字符串当作UTF-8字符串的二进制去用解密出来的东西自然全是错的。字符串格式的坑坑了我一次后来我在脚本里统一加了一行日志打印密钥和IV的十六进制表示。这样即使脚本出错也能对比前后端的字节是否一致。4.2 前端调试与动态提取技巧实战中有一个场景会频繁遇到前端代码做了高度混淆密钥和IV没有明文写在变量里而是在运行时通过复杂的函数计算出来。这时候光靠静态阅读代码还不够需要用浏览器动态调试的手段把值赶出来。我的做法是在关键加密函数处下断点然后单步执行观察各个变量的值。比如在调CryptoJS.AES.encrypt的前一行设置断点此时AES密钥、IV、明文都还在内存中可以直接在Console面板里查看。如果函数被压缩成了一行加混淆变量名就把这一行整体格式化再重新搜索特征字符串。一个更高效的办法是直接在Console里把加密函数重新定义一遍植入输出日志的代码var originalEncrypt CryptoJS.AES.encrypt; CryptoJS.AES.encrypt function(message, key, cfg) { console.log(AES Key:, key.toString()); console.log(IV:, cfg.iv.toString()); return originalEncrypt.apply(this, arguments); };这段代码会在每次加密操作时把密钥和IV打印出来不管前端加密逻辑怎么变换只要它最终调用了CryptoJS.AES.encrypt我就能拿到我想要的值。这种方法比断点更稳因为不需要跟着混淆代码一步步走流程。动态提取最大的好处是避免“你以为的密钥”和“实际用的密钥”之间的偏差。很多情况下前端代码虽然看起来是把某个字符串当作密钥但实际上经过了编码转换、截断、补齐等操作只有运行时才能看出真实值。用日志方式直接抓取运行时的数值是最可靠的手段。4.3 自动化脚本的构造与参数化设计手工走通一遍攻击链之后一定要把它脚本化。因为实战中往往需要批量测试多个接口、多个会话手工操作根本跟不上节奏。我在编写自动化脚本时遵循了几个原则能大幅减少返工。第一把密钥管理和请求构造解耦。单独写一个加密核心模块专门负责RSA解密、AES加解密输出标准的密文结构。其他业务接口脚本只负责组装明文和调用核心模块这样当密钥更新时只需要改一个地方。第二留意时间戳和随机数的实时性。如果靶场每次会话都生成新的AES密钥脚本必须在每个请求里实时解密当前会话的RSA密文不能复用旧的密钥。我在脚本里加了一个自动刷新逻辑请求前先拉取最新的公钥信息判断私钥对应的公钥是否有变更。第三把失败响应处理做成日志体系。脚本每次请求后除了打印响应内容还要记录请求的密文哈希、解密状态、错误码。这样出了问题可以直接对照日志定位是哪一步失败不用重新抓包。从我自己的使用体验来说用一个统一的攻击框架脚本管理整个流程比零散的多个脚本高效得多。后续如果再遇到类似加密靶场只需要替换私钥和接口路径就能快速复用整套攻击链。加密这类靶场套路其实不算太深。翻了前端代码拿到了公私钥顺着数字信封的解密链路一步步走密文背后的业务逻辑就全在眼前了。真正容易卡住人的地方反而是那些不起眼的细节Base64里的换行符、AES密钥的十六进制编码、混淆代码里悄悄改掉的IV。这些坑踩过一次就有免疫力了后面的路会顺畅很多。希望这篇复盘能帮你少走几条弯路。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑