本文最后更新于 2025年5月27日 晚上
前言 学长推荐的一个靶场,对于 js 这一块还没接触过,所以来打打,感觉还是挺不错的
项目地址:https://github.com/SwagXz/encrypt-labs
作者:SwagXz
burp 自动加解密插件 autoDeceder:https://github.com/f0ng/autoDecoder (有时候会有点小问题)
浏览器插件:https://ctool.dev/
AES 固定 key 乱输,然后抓包
发现了 encryptedData
加密参数
1 2 3 { "encryptedData" : "nArXfVdnoe67UzojAPP2X+6qSiznLMBAI3a5Bi+zlNx05J187TZuSqk/iecDaXCv" }
查看 js 代码搜索关键字找到了 aes 加密代码,CBC 模式的 AES 加密,下断点执行,有固定的 key 和 iv,可以直接还原明文
key 和 iv
1 2 const key = CryptoJS .enc .Utf8 .parse ("1234567890123456" );const iv = CryptoJS .enc .Utf8 .parse ("1234567890123456" );
明文数据
1 { "username" : "admin" , "password" : "111" }
AES 服务端获取 Key 起了两个请求,分别获取 aes_key 和 aes_iv,然后利用算法对数据进行加密传输
1 { "aes_key" : "yC1rjcwgTO\/OFlmZteNzoQ==" , "aes_iv" : "8RQcaNn16DrwppYR+rrURg==" }
搜索关键字定位到算法部分,发现服务器请求获取 key 和 iv ,重发数据包发现 key 和 iv 短时间内不会发生变化
autoDecoder 配置
解密成功
RSA 加密 抓包拿到数据,出现 data 参数
搜索关键字,找到加密代码,打个断点调试
拿到数据,只有公钥,没有私钥,只能加密
1 2 3 4 5 6 -----BEGIN PUBLIC KEY -----MIGfMA 0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRvA7giwinEkaTYllDYCkzujviNH +up0XAKXQot8RixKGpB7nr8AdidEvuo+wVCxZwDK3hlcRGrrqt0Gxqwc11btlMDSj92Mr3 xSaJcshZU8kfj325L8DRh9jpruphHBfh955ihvbednGAvOHOrz3Qy3Cb ocDbsNeCwNpRxwjIdQIDAQAB -----END PUBLIC KEY -----
autoDecoder 配置
AES + RSA 加密 抓包看数据
搜索关键参数查找加密算法,下断点获取数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function sendDataAesRsa (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----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRvA7giwinEkaTYllDYCkzujvi NH+up0XAKXQot8RixKGpB7nr8AdidEvuo+wVCxZwDK3hlcRGrrqt0Gxqwc11btlM DSj92Mr3xSaJcshZU8kfj325L8DRh9jpruphHBfh955ihvbednGAvOHOrz3Qy3Cb ocDbsNeCwNpRxwjIdQIDAQAB -----END PUBLIC KEY-----` ); const encryptedKey = rsa.encrypt (key.toString (CryptoJS .enc .Base64 )); const encryptedIv = rsa.encrypt (iv.toString (CryptoJS .enc .Base64 ));
被加密的参数是 formData
也就是 "{"username":"admin","password":"123456"}"
经过 AES
加密,但加密使用的 key
和 iv
是16位随机数,得到 encryptedData
,然后对 key
和 iv
进行 rsa
加密得到encryptedKey
和 encryptedIv
,再将这三个参数传入数据包中发包进行验证
将随机 16 位的 key
和 iv
进行固定,右键选择替换内容,使用本地替换的方式固定 key
和 iv
1 2 const key = CryptoJS .enc .Utf8 .parse ("1234567890123456" );const iv = CryptoJS .enc .Utf8 .parse ("1234567890123456" );
右键浏览器(从 Firefox 换成了 Chrome)替换,选择本地替换,新建一个 js 文件替换
下断点调试
经过几次登录发现 encryptedData 不会变,但 Key 和 Iv 会改变,说明修改成功了
autoDecoder 配置和之前一样,解密成功
Des 规律 Key 抓包拿到数据,只对 password
进行了加密
1 { "username" : "admin" , "password" : "3981f487c5afde43" }
加密算法比较简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function encryptAndSendDataDES (url ) { const username = document .getElementById ("username" ) .value ; const password = document .getElementById ("password" ) .value ; const key = CryptoJS .enc .Utf8 .parse (username.slice (0 , 8 ) .padEnd (8 , '6' )); const iv = CryptoJS .enc .Utf8 .parse ('9999' + username.slice (0 , 4 ) .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 );
简单的 DES
加密,key
和 iv
都使用了username
的值,key
是 8 位,如果 username
不满 8 位则用 6 补满,iv
是 8 位,9999 + username
的前四位,所以输入 admin,key 和 iv 值如下:
1 2 key:admin666 iv:9999admi
autoDecoder 解密
明文加签 抓包拿到数据,多了两个参数分别是 nonce
,signature
,还有个时间戳
1 { "username" : "admin" , "password" : "123456" , "nonce" : "r4nauns4od" , "timestamp" : 1748175966 , "signature" : "9ffc131e7a68507c6338362ff38e1450ab12ccf22fc97d439115462566047ba7" }
加密算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 );
nonce:小写字母和数字组成的随机字符串
dataToSign:username + password + nonce + timestamp
signature:由 dataToSign
经过 SHA256
加密生成,secretKey
为固定值 be56e057f20f883e
使用了 HmacSHA256 对 username、password、nonce、timestamp 进行了签名校验,但是拿到了 Key,所以可以伪造签名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import timeimport hmacimport hashlibimport requestsimport randomimport pprint url = "http://172.22.9.123/encrypt-labs-main/encrypt/signdata.php" secret_key = "be56e057f20f883e" username = "admin" password = "123456" def generate_nonce (): return '' .join(random.choice('0123456789abcdefghijklmnopqrstuvwxyz' ) for _ in range (16 ))def generate_signature (username, password, nonce, timestamp, key ): data_to_sign = username + password + nonce + str (timestamp) return hmac.new(key.encode('utf-8' ), data_to_sign.encode('utf-8' ), hashlib.sha256).hexdigest()def send_signed_request (): nonce = generate_nonce() timestamp = int (time.time()) signature = generate_signature(username, password, nonce, timestamp, secret_key) headers = { "Host" : "172.22.9.123" , "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:138.0) Gecko/20100101 Firefox/138.0" , "Content-Type" : "application/json" , "Accept" : "*/*" , "Origin" : "http://172.22.9.123" , "Referer" : "http://172.22.9.123/encrypt-labs-main/easy.php" , } payload = { "username" : username, "password" : password, "nonce" : nonce, "timestamp" : timestamp, "signature" : signature } response = requests.post(url, json=payload, headers=headers) print ("Status Code:" , response.status_code) print ("Response Headers:" ) pprint.pprint(dict (response.headers))if __name__ == "__main__" : send_signed_request()
运行拿到结果
加签 key 在服务端 抓包返回了两个页面数据
第一个数据包获取 signature
第二个数据包加上获取的 signature
查看加密算法,发现先去服务端获取签名,然后对username,password,timestamp 进行签名再登录
因为加解密都在服务器端完成所以无法伪造
禁止重放 抓包,发现账号密码是明文的,多了一个 random 参数
定位算法调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 function generateRequestData ( ) { const username = document .getElementById ("username" ).value ; const password = document .getElementById ("password" ).value ; const timestamp = Date .now (); const publicKey = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRvA7giwinEkaTYllDYCkzujvi NH+up0XAKXQot8RixKGpB7nr8AdidEvuo+wVCxZwDK3hlcRGrrqt0Gxqwc11btlM DSj92Mr3xSaJcshZU8kfj325L8DRh9jpruphHBfh955ihvbednGAvOHOrz3Qy3Cb ocDbsNeCwNpRxwjIdQIDAQAB -----END PUBLIC KEY-----` ; function rsaEncrypt (data, publicKey ) { const jsEncrypt = new JSEncrypt (); jsEncrypt.setPublicKey (publicKey); const encrypted = jsEncrypt.encrypt (data.toString ()); if (!encrypted) { throw new Error ("RSA encryption failed." ); } return encrypted; } let encryptedTimestamp; try { encryptedTimestamp = rsaEncrypt (timestamp, publicKey); } catch (error) { console .error ("Encryption error:" , error); return null ; } const dataToSend = { username : username, password : password, random : encryptedTimestamp }; return dataToSend; }function sendLoginRequest (url ) { const dataToSend = generateRequestData ();
通过算法可以知道 random
是通过 encryptedTimestamp
来的,encryptedTimestamp
是通过时间戳经过 RSA
加密来的
防止请求被重放,同样写脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import timeimport jsonimport requestsfrom base64 import b64encodefrom Crypto.PublicKey import RSAfrom Crypto.Cipher import PKCS1_v1_5import pprint url = "http://172.22.9.123/encrypt-labs-main/encrypt/norepeater.php" public_key = """-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRvA7giwinEkaTYllDYCkzujvi NH+up0XAKXQot8RixKGpB7nr8AdidEvuo+wVCxZwDK3hlcRGrrqt0Gxqwc11btlM DSj92Mr3xSaJcshZU8kfj325L8DRh9jpruphHBfh955ihvbednGAvOHOrz3Qy3Cb ocDbsNeCwNpRxwjIdQIDAQAB -----END PUBLIC KEY-----""" def rsa_encrypt (data, public_key ): key = RSA.import_key(public_key) cipher = PKCS1_v1_5.new(key) encrypted_data = cipher.encrypt(data.encode('utf-8' )) return b64encode(encrypted_data).decode('utf-8' )def send_request (): ts = str (int (time.time() * 1000 )) encrypted_ts = rsa_encrypt(ts, public_key) headers = { "Host" : "172.22.9.123" , "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:138.0) Gecko/20100101 Firefox/138.0" , "Content-Type" : "application/json" , "Accept" : "*/*" , "Cookie" : "PHPSESSID=snuhsl9mkun6ftjd3481d3g5nd" , "Origin" : "http://172.22.9.123" , "Referer" : "http://172.22.9.123/encrypt-labs-main/easy.php" , } payload = { "username" : "admin" , "password" : "123456" , "random" : encrypted_ts } response = requests.post(url, headers=headers, json=payload) print ("Status Code:" , response.status_code) print ("Response Headers:" ) pprint.pprint(dict (response.headers)) print ("Response Body:" ) print (response.text)if __name__ == "__main__" : send_request() time.sleep(5 )
成功