前端加密对抗常见场景突破之进阶

admin 2025年4月8日20:42:35评论1 views字数 11970阅读39分54秒阅读模式
 

文章作者:先知社区(1398133550745333)

文章来源:https://xz.aliyun.com/news/17099

前言

前面分析了几种最基础的,但是现在的前端加密往往都比较复杂,下面是几种更复杂的场景如何破局

AES+RSA 加密

这种加密方法说实话很难破局,所以我们的思路就得更换了

首先看前端的代码

functionsendDataAesRsa(url{    const formData = {        username: document.getElementById("username")            .value,        password: document.getElementById("password")            .value    };    const jsonData = JSON.stringify(formData);    const key = CryptoJS.lib.WordArray.random(16);    const iv = CryptoJS.lib.WordArray.random(16);    const encryptedData = CryptoJS.AES.encrypt(jsonData, key, {            iv: iv,            mode: CryptoJS.mode.CBC,            padding: CryptoJS.pad.Pkcs7        })        .toString();    const rsa = new JSEncrypt();    rsa.setPublicKey(`-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRvA7giwinEkaTYllDYCkzujviNH+up0XAKXQot8RixKGpB7nr8AdidEvuo+wVCxZwDK3hlcRGrrqt0Gxqwc11btlMDSj92Mr3xSaJcshZU8kfj325L8DRh9jpruphHBfh955ihvbednGAvOHOrz3Qy3CbocDbsNeCwNpRxwjIdQIDAQAB-----END PUBLIC KEY-----`);    const encryptedKey = rsa.encrypt(key.toString(CryptoJS.enc.Base64));    const encryptedIv = rsa.encrypt(iv.toString(CryptoJS.enc.Base64));    fetch(url, {            method"POST",            headers: {                "Content-Type""application/json"            },            body: JSON.stringify({                encryptedData: encryptedData,                encryptedKey: encryptedKey,                encryptedIv: encryptedIv            })        })        .then(response => response.json())        .then(data => {            if (data.success) {                alert("登录成功");                window.location.href = "success.html";            } else {                alert("用户名或密码错误");            }        })        .catch(error => console.error("请求错误:", error));    closeModal();}

首先一大难点就是

const key = CryptoJS.lib.WordArray.random(16);const iv = CryptoJS.lib.WordArray.random(16);

我们的 key 和 iv 是随机数了,而且更重量级的是

const rsa = new JSEncrypt();rsa.setPublicKey(`-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----`);const encryptedKey = rsa.encrypt(key.toString(CryptoJS.enc.Base64));const encryptedIv = rsa.encrypt(iv.toString(CryptoJS.enc.Base64));

还会 RSA 去加密 AES 密钥和 IV,我们看看包

POST /encrypt/aesrsa.php HTTP/1.1Hostqianduan:7952Content-Length463User-AgentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Content-Typeapplication/jsonAccept*/*Originhttp://qianduan:7952Refererhttp://qianduan:7952/easy.phpAccept-Encodinggzip, deflate, brAccept-Languagezh-CN,zh;q=0.9CookiePHPSESSID=8l013iet6fdujht6mblfj2o1seConnectionkeep-alive{"encryptedData":"+JIRH8zG7tyYWeEsIKXgMhqIZbGlXDjkdSU6fxvy+01BKXW1iSddu4MFKkGGN8El","encryptedKey":"Tc5a9SAX0t3xqEkRaW+JW+XaVIwDg7OAjRGV15wetu604albcLMBdXhvC6QGNsceAwVJ41eFm8Hout+feHoDJcZ8Q4A3JknNu+ugi81G+KRgNHCbMIXEWpiuOYdfD6Dm9a1HsO3nhT3KhU/fRyEqnYdDNmsQR1yxHWpEejoN28A=","encryptedIv":"QMB7/xRAxtdlhdQZUK4HcqleVpSuf8cZCuW84NWa+/CqgtpiZv8QHOtze5FL67YKbvIthiGKr0AQMJ95e+/hnuY8zGVLACcZ+LniQt7cJ5QNBex8tx+8GIYy1VNIh3+cKNyW7pXHsxZvN80WCtOZDg0hs5JCMh2qmXDtOEICHjE="}

可以看见很复杂
这里有两个思路,直接改之后的逻辑,和修改加密的逻辑

修改加密逻辑

我们直接把 key 和 iv 固定或者更容易的是直接把第一关的代码直接覆盖掉,把这串代码

{    const formData = {        usernamedocument.getElementById("username")            .value,        passworddocument.getElementById("password")            .value    };    const jsonData = JSON.stringify(formData);    const key = CryptoJS.enc.Utf8.parse("1234567890123456");    const iv = CryptoJS.enc.Utf8.parse("1234567890123456");    const encrypted = CryptoJS.AES.encrypt(jsonData, key, {            iv: iv,            modeCryptoJS.mode.CBC,            paddingCryptoJS.pad.Pkcs7        })        .toString();    const params = `encryptedData=${encodeURIComponent(encrypted)}`;    fetch(url, {            method"POST",            headers: {                "Content-Type""application/x-www-form-urlencoded; charset=utf-8"            },            body: params        })        .then(response => response.json())        .then(data => {            if (data.success) {                alert("登录成功");                window.location.href = "success.html";            } else {                alert("用户名或密码错误");            }        })        .catch(error => {            console.error("请求错误:", error);        });    closeModal();}

直接覆盖

前端加密对抗常见场景突破之进阶

然后我们再抓一个包

POST /encrypt/aesrsa.php HTTP/1.1Hostqianduan:7952Content-Length84User-AgentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Content-Typeapplication/x-www-form-urlencoded; charset=utf-8Accept*/*Originhttp://qianduan:7952Refererhttp://qianduan:7952/easy.phpAccept-Encodinggzip, deflate, brAccept-Languagezh-CN,zh;q=0.9CookiePHPSESSID=8l013iet6fdujht6mblfj2o1seConnectionkeep-aliveencryptedData=nArXfVdnoe67UzojAPP2X%2B6qSiznLMBAI3a5Bi%2BzlNxdbZ%2FOS8HQ6QCw6cm3stZk

可以看到就和第一关一模一样了

前端加密对抗常见场景突破之进阶

一样可以解码成功,但是后来发现不行

HTTP/1.1 200 OKDateWed, 01 Jan 2025 09:28:51 GMTServerApache/2.4.39 (Win64) OpenSSL/1.1.1b mod_fcgid/2.3.9a mod_log_rotate/1.02X-Powered-ByPHP/8.0.2ExpiresThu, 19 Nov 1981 08:52:00 GMTCache-Controlno-store, no-cache, must-revalidatePragmano-cacheKeep-Alivetimeout=5, max=100ConnectionKeep-AliveContent-Typeapplication/jsonContent-Length40{"success":false,"error":"Missing data"}

似乎后端会校验我们的参数,这里我再该了一下,尝试只修改 key 和 iv

前端加密对抗常见场景突破之进阶

然后

前端加密对抗常见场景突破之进阶

需要改一下正则

前端加密对抗常见场景突破之进阶

任然可以解码成功

前端加密对抗常见场景突破之进阶

成功

修改返回逻辑

经过前些管卡的尝试,我们自动了成功返回的是
{"success":true}
失败的并不重要

我们尝试直接修改前端的返回逻辑

拦截响应改为

HTTP/1.1 200 OKDateWed, 01 Jan 2025 09:38:35 GMTServerApache/2.4.39 (Win64) OpenSSL/1.1.1b mod_fcgid/2.3.9a mod_log_rotate/1.02X-Powered-ByPHP/8.0.2ExpiresThu, 19 Nov 1981 08:52:00 GMTCache-Controlno-store, no-cache, must-revalidatePragmano-cacheKeep-Alivetimeout=5, max=100ConnectionKeep-AliveContent-Typeapplication/jsonContent-Length56{"success":true}

发送就登录成功

前端加密对抗常见场景突破之进阶

DES 规律 key

DES 加密,这个可以说是 AES 的前人吧
DES(Data Encryption Standard) 是一种对称加密算法,用于加密和解密数据。它被广泛用于商业和政府领域,但由于其密钥长度较短(56 位),如今已经被认为是不够安全的,并逐渐被更强大的加密算法(如 AES)所取代。

前端逻辑

functionencryptAndSendDataDES(url{    const username = document.getElementById("username")        .value;    const password = document.getElementById("password")        .value;    const key = CryptoJS.enc.Utf8.parse(username.slice(08)        .padEnd(8'6'));    const iv = CryptoJS.enc.Utf8.parse('9999' + username.slice(04)        .padEnd(4'9'));    const encryptedPassword = CryptoJS.DES.encrypt(password, key, {        iv: iv,        mode: CryptoJS.mode.CBC,        padding: CryptoJS.pad.Pkcs7    });    const encryptedHex = encryptedPassword.ciphertext.toString(CryptoJS.enc.Hex);    fetch(url, {            method"POST",            headers: {                "Content-Type""application/json"            },            body: JSON.stringify({                username: username,                password: encryptedHex            })        })        .then(response => response.json())        .then(data => {            if (data.success) {                alert("登录成功");                window.location.href = "success.html";            } else {                alert("用户名或密码错误");            }        })        .catch(error => console.error("请求错误:", error));    closeModal();}

这里的 key 和 iv 生成是有规律的

密钥(key)生成规律

const key = CryptoJS.enc.Utf8.parse(username.slice(08).padEnd(8, '6'));

从用户名中提取前 8 个字符(username.slice(0, 8))。
如果用户名长度不足 8 个字符,使用 '6' 填充直到达到 8 字节。

假设用户输入的用户名是 "admin123":

slice(0, 8) 提取的是 "admin123"(这个用户名恰好是 8 个字符,所以不需要填充)。
因此,key 就是 "admin123"。
如果用户名是 "alice"(只有 5 个字符),则:

slice(0, 8) 提取的是 "alice"。
使用 padEnd(8, '6') 填充,得到 "alice666",因此密钥是 "alice666"。

初始化向量(IV)生成规律

const iv = CryptoJS.enc.Utf8.parse('9999' + username.slice(04).padEnd(4, '9'));

使用 "9999" 开头。
从用户名中提取前 4 个字符(username.slice(0, 4))。
如果用户名的长度不足 4 个字符,使用 '9' 填充。

假设用户名是 "admin123""9999" 与 slice(04) 的结果 "admi" 组合,生成 IV 为 "9999admi"因此,iv 就是 "9999admi"如果用户名是 "alice"(长度为 5 个字符):"9999" 与 slice(04) 的结果 "alic" 组合,生成 IV 为 "9999alic"因此,iv 就是 "9999alic"如果用户名是 "cat"(长度为 3 个字符):"9999" 与 slice(04) 的结果 "cat",然后用 padEnd(4'9') 填充,生成 IV 为 "9999cat9"因此,iv 就是 "9999cat9"

首先 admin 是固定的

POST /encrypt/des.php HTTP/1.1Hostqianduan:7952Content-Length50User-AgentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Content-Typeapplication/jsonAccept*/*Originhttp://qianduan:7952Refererhttp://qianduan:7952/easy.phpAccept-Encodinggzip, deflate, brAccept-Languagezh-CN,zh;q=0.9CookiePHPSESSID=8l013iet6fdujht6mblfj2o1seConnectionkeep-alive{"username":"admin","password":"6446171321693d04"}

而且 admin 是明文传输的,我们只需要关注 password

而且 key 和 iv 都是根据 admin 来的

按照这个规律

key 就是 admin666

iv 是  9999admi

前端加密对抗常见场景突破之进阶
前端加密对抗常见场景突破之进阶

也是可以成功解码的

前端加密对抗常见场景突破之进阶

成功

明文加签

首先需要了解 HMAC

HMAC(Hash-based Message Authentication Code)是一种基于哈希算法的消息认证码,用于验证数据的完整性和身份验证。它结合了一个密钥和消息的哈希值,使得即便消息被篡改,也无法计算出正确的签名。

原理

消息和密钥:HMAC 使用一个密钥(secret_key)和消息(data_to_sign)来生成签名。消息通常包含请求的参数或内容(如用户名、密码等),而密钥通常由服务器保管,不公开。

哈希算法:HMAC 使用一种哈希算法(通常是 SHA256)对消息和密钥进行加密运算。生成的签名确保了消息的完整性和不可篡改性。

签名校验:客户端将请求和生成的签名一起发送给服务器,服务器会根据相同的密钥和数据重新计算签名,如果计算出的签名与传递过来的签名一致,说明数据未被篡改。

抓一个包

POST /encrypt/signdata.php HTTP/1.1Hostqianduan:7952Content-Length161User-AgentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36Content-Typeapplication/jsonAccept*/*Originhttp://qianduan:7952Refererhttp://qianduan:7952/easy.phpAccept-Encodinggzip, deflate, brAccept-Languagezh-CN,zh;q=0.9CookiePHPSESSID=8l013iet6fdujht6mblfj2o1seConnectionkeep-alive{"username":"admin","password":"123","nonce":"vu9dot4yxef","timestamp":1735727921,"signature":"c756b9fde51748fe2a243464fde981b504e60537e64443c0860067c027ec4dfa"}

这次的特点很明显,有签名,然后传输的是明文

我们看看 js 代码

function sendDataWithNonce(url) {    const username = document.getElementById("username")        .value;    const password = document.getElementById("password")        .value;    const nonce = Math.random()        .toString(36)        .substring(2);    const timestamp = Math.floor(Date.now() / 1000);    const secretKey = "be56e057f20f883e";    const dataToSign = username + password + nonce + timestamp;    const signature = CryptoJS.HmacSHA256(dataToSign, secretKey)        .toString(CryptoJS.enc.Hex);    fetch(url, {            method"POST",            headers: {                "Content-Type""application/json"            },            bodyJSON.stringify({                username: username,                password: password,                nonce: nonce,                timestamp: timestamp,                signature: signature            })        })        .then(response => response.json())        .then(data => {            if (data.success) {                alert("登录成功");                window.location.href = "success.html";            } else {                alert(data.error || "用户名或密码错误");            }        })        .catch(error => console.error("请求错误:", error));    closeModal();}

首先生成了 nonce

nonce 是一个随机数,用于防止重放攻击。重放攻击是指攻击者截取有效的请求,并在之后重新发送该请求。使用 nonce 可以确保每次请求都有一个独一无二的标识,从而防止重放攻击。

然后生成时间戳

timestamp 是当前时间的秒级时间戳,它可以确保请求在一定的时间窗口内有效,防止因请求过期而被重放

然后就是签名的生成了

漏洞产生点

假设后台并没有对 timestamp 做有效的超时校验。这意味着服务器并不会检查 timestamp 是否过期。

我们就攻击思路如下

首先修改我们需要修改的数据,比如 password 字段等,然后保留原 nonce 和 timestamp:可以将原来的 nonce 和 timestamp 保持不变。这是关键,nonce 是唯一的标识,而 timestamp 如果没有有效的时间限制,就可以被一直使用

两个关键点

nonce 是唯一的,但没有时间限制:nonce 是为了防止重放攻击而设计的,理论上每个 nonce 都只能使用一次。如果服务器保存已经使用的 nonce 并拒绝重复的 nonce,攻击者就无法利用这个 nonce 再发送请求。

timestamp 没有超时限制:如果服务器没有对 timestamp 做超时校验(比如,时间戳与当前时间差超过了 30 秒就会被拒绝),那么攻击者就可以绕过这个机制。攻击者只需要在修改密码后,保持原始的 timestamp,并重新计算签名,就能伪造一个有效的请求。

参考https://mp.weixin.qq.com/s/ZgD7qAQAsNlZgLtdZVCoFw

由于插件没有对应的解密算法,我们可以使用本地的算法

from flask import Flask, requestimport jsonimport reimport base64from Crypto.Cipher import AESfrom Crypto.Util.Padding import padfrom Crypto.Util.Padding import unpadfrom urllib.parse import quotefrom urllib.parse import unquotefrom Crypto.Cipher import DESimport hashlibimport hmacfrom binascii import hexlifyfrom binascii import unhexlifyapp = Flask(__name__)username = "admin"nonce = "q3az51nhwqq"timestamp = 1700000000secret_key = "be56e057f20f883e"@app.route('/encode',methods=["POST"])  # base64加密def encrypt():    param = request.form.get('dataBody')    re_pass = r'"password":"(.*?)",'    re_sign = r'"signature":"(.*?)"'    re_nonce = r'"nonce":"(.*?)",'    re_timestamp = r'"timestamp":(.*?),'    password = re.search(re_pass, param).group(1)    new_nonce = re.search(re_nonce, param).group(1)    new_timestamp = re.search(re_timestamp, param).group(1)    data_to_sign = f"{username}{password}{nonce}{timestamp}"    new_signature = hmac.new(secret_key.encode('utf-8'), data_to_sign.encode('utf-8'), hashlib.sha256).hexdigest()    new_param = re.sub(re_sign, f'"signature":"{new_signature}"', param)    new_param = re.sub(re_nonce, f'"nonce":"{nonce}",', new_param)    new_param = re.sub(re_timestamp, f'"timestamp":"{timestamp}",', new_param)    return new_param@app.route('/decode',methods=["POST"])def decrypt():    param = request.form.get('dataBody')    return paramif __name__ == '__main__':      app.run(host="0.0.0.0",port="5000")

参考链接:

https://github.com/SwagXz/encrypt-labs

 

原文始发于微信公众号(七芒星实验室):前端加密对抗常见场景突破之进阶

 

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年4月8日20:42:35
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   前端加密对抗常见场景突破之进阶https://cn-sec.com/archives/3927779.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息