初探 JS 逆向之 encrypt-labs

本文最后更新于 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-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRvA7giwinEkaTYllDYCkzujvi
NH+up0XAKXQot8RixKGpB7nr8AdidEvuo+wVCxZwDK3hlcRGrrqt0Gxqwc11btlM
DSj92Mr3xSaJcshZU8kfj325L8DRh9jpruphHBfh955ihvbednGAvOHOrz3Qy3Cb
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 加密,但加密使用的 keyiv 是16位随机数,得到 encryptedData,然后对 keyiv 进行 rsa 加密得到encryptedKeyencryptedIv,再将这三个参数传入数据包中发包进行验证

将随机 16 位的 keyiv 进行固定,右键选择替换内容,使用本地替换的方式固定 keyiv

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 加密,keyiv 都使用了username 的值,key 是 8 位,如果 username 不满 8 位则用 6 补满,iv 是 8 位,9999 + username 的前四位,所以输入 admin,key 和 iv 值如下:

1
2
key:admin666
iv:9999admi

autoDecoder 解密

明文加签

抓包拿到数据,多了两个参数分别是 noncesignature ,还有个时间戳

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 time
import hmac
import hashlib
import requests
import random
import 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;
}

// Encrypt the timestamp
let encryptedTimestamp;
try {
encryptedTimestamp = rsaEncrypt(timestamp, publicKey);
} catch (error) {
console.error("Encryption error:", error);
return null;
}

const dataToSend = {
username: username,
password: password,
random: encryptedTimestamp // Replace timestamp with encrypted version
};

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 time
import json
import requests
from base64 import b64encode
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import 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)

成功


初探 JS 逆向之 encrypt-labs
http://example.com/2025/05/27/JS-逆向之-encrypt-labs/
作者
butt3rf1y
发布于
2025年5月27日
许可协议