上新!更强大的JS引擎:goja
字数 1133 2025-08-19 12:42:07

Goja JavaScript引擎替换Otto的全面教学文档

1. 背景与动机

Yaklang引擎原本使用Robertkrimen/otto(简称otto)作为JavaScript解释器,但遇到以下问题:

  • otto的语法树解析实现存在缺陷
  • 处理压缩后的JavaScript第三方依赖(如CryptoJS)时出现解析错误
  • 性能较低

因此决定采用dop251/goja(简称goja)作为替代方案。

2. Goja的优势

2.1 功能特性

  • 完整的ECMAScript 5.1实现
  • 部分ECMAScript 6.0功能支持
  • 通过了大部分tc39测试套件(官方ECMAScript一致性测试)

2.2 性能提升

  • 比otto快6~7倍

2.3 兼容性

  • 80%的API保持不变
  • 升级对用户几乎无感知

3. 不兼容点

以下功能可能存在差异:

  • js.ASTWalk
  • js.GetSTType
  • Value结构体方法

4. 新特性:内置第三方JavaScript依赖API

4.1 主要特点

  • 快速使用JavaScript第三方依赖
  • 初次编译后缓存,后续无需重新编译
  • 解决常见库(如CryptoJS)的使用问题

4.2 使用示例

_, value = js.Run(`
    CryptoJS.HmacSHA256("Message", "secret").toString();
`, js.libCryptoJSV3())
println(value.String())

5. 实战案例:CryptoJS.AES(CBC)前端加密登陆表单

5.1 目标分析

分析Vulinbox靶场中的高级前端加解密与验签实战案例,前端使用CryptoJS.AES(CBC)模式加密登录表单。

5.2 前端加密逻辑

var iv = CryptoJS.lib.WordArray.random(128/8);

function generateKey() {
    return CryptoJS.enc.Utf8.parse("1234123412341234") // 16位密钥
}

const key = generateKey()

function Encrypt(word) {
    return CryptoJS.AES.encrypt(word, key, {iv: iv}).toString();
}

function getData() {
    return {
        "username": document.getElementById("username").value,
        "password": document.getElementById("password").value,
    }
}

function outputObj(jsonData) {
    const word = JSON.stringify(jsonData);
    return {
        "data": Encrypt(word),
        "key": key.toString(),
        iv: iv.toString(),
    }
}

function submitJSON(event) {
    event.preventDefault();
    const url = "/crypto/js/lib/aes/cbc/handler";
    let jsonData = getData();
    let submitResult = JSON.stringify(outputObj(jsonData), null, 2)
    // 发送submitResult到后端
}

5.3 提炼加密代码

key = CryptoJS.enc.Utf8.parse("1234123412341234");
iv = CryptoJS.lib.WordArray.random(128/8);

function Encrypt(word) {
    return CryptoJS.AES.encrypt(word, key, {iv: iv}).toString();
}

function getData(username, password) {
    return {
        "username": username,
        "password": password,
    }
}

function outputObj(jsonData) {
    const word = JSON.stringify(jsonData);
    return {
        "data": Encrypt(word),
        "key": key.toString(),
        iv: iv.toString(),
    }
}

jsonData = getData("username", "password");
submitResult = JSON.stringify(outputObj(jsonData), null, 2);

5.4 Yak爆破实现

for user in ["user", "admin"] {
    for pass in ["pass", "123456"] {
        vm, _ = js.Run(`
            key = CryptoJS.enc.Utf8.parse("1234123412341234");
            iv = CryptoJS.lib.WordArray.random(128/8);
            
            function Encrypt(word) {
                return CryptoJS.AES.encrypt(word, key, {iv: iv}).toString();
            }
            
            function getData(username, password) {
                return {
                    "username": username,
                    "password": password,
                }
            }
            
            function outputObj(jsonData) {
                const word = JSON.stringify(jsonData);
                return {
                    "data": Encrypt(word),
                    "key": key.toString(),
                    iv: iv.toString(),
                }
            }
            
            jsonData = getData(%#v, %#v);
            submitResult = JSON.stringify(outputObj(jsonData), null, 2);
        ` % [user, pass], js.libCryptoJSV3())
        
        body = vm.Get("submitResult").String()
        rsp, _ = poc.Post(
            "http://127.0.0.1:8787/crypto/js/lib/aes/cbc/handler", 
            poc.replaceBody([]byte(body), false)
        )
        println(string(rsp.RawPacket))
    }
}

6. 迁移指南

6.1 准备工作

  1. 确认现有代码中是否使用了otto特有的API
  2. 备份现有项目

6.2 迁移步骤

  1. 更新依赖到goja版本
  2. 替换导入路径
  3. 测试核心功能
  4. 检查不兼容点(如ASTWalk等)
  5. 更新第三方JS库的调用方式

6.3 测试要点

  1. 性能基准测试
  2. 功能一致性测试
  3. 第三方库兼容性测试

7. 最佳实践

  1. 性能优化:利用goja的缓存机制,避免重复编译
  2. 错误处理:增加对ES6特性的兼容性检查
  3. 安全实践:隔离敏感操作的JavaScript执行环境
  4. 调试技巧:利用vm.Get()检查中间状态

8. 常见问题解答

Q1: 为什么我的otto代码在goja中不工作?
A1: 检查是否使用了otto特有的API或行为,特别是AST相关操作。

Q2: 如何确保第三方JS库的兼容性?
A2: 使用js.libXXX()系列函数加载标准库,并测试压缩和非压缩版本。

Q3: 性能提升不明显怎么办?
A3: 检查是否有频繁创建VM实例的操作,尽量复用实例。

Q4: 如何处理ES6特性?
A4: goja对ES6支持有限,建议使用Babel等工具转译为ES5。

Goja JavaScript引擎替换Otto的全面教学文档 1. 背景与动机 Yaklang引擎原本使用Robertkrimen/otto(简称otto)作为JavaScript解释器,但遇到以下问题: otto的语法树解析实现存在缺陷 处理压缩后的JavaScript第三方依赖(如CryptoJS)时出现解析错误 性能较低 因此决定采用dop251/goja(简称goja)作为替代方案。 2. Goja的优势 2.1 功能特性 完整的ECMAScript 5.1实现 部分ECMAScript 6.0功能支持 通过了大部分tc39测试套件(官方ECMAScript一致性测试) 2.2 性能提升 比otto快6~7倍 2.3 兼容性 80%的API保持不变 升级对用户几乎无感知 3. 不兼容点 以下功能可能存在差异: js.ASTWalk js.GetSTType Value 结构体方法 4. 新特性:内置第三方JavaScript依赖API 4.1 主要特点 快速使用JavaScript第三方依赖 初次编译后缓存,后续无需重新编译 解决常见库(如CryptoJS)的使用问题 4.2 使用示例 5. 实战案例:CryptoJS.AES(CBC)前端加密登陆表单 5.1 目标分析 分析Vulinbox靶场中的高级前端加解密与验签实战案例,前端使用CryptoJS.AES(CBC)模式加密登录表单。 5.2 前端加密逻辑 5.3 提炼加密代码 5.4 Yak爆破实现 6. 迁移指南 6.1 准备工作 确认现有代码中是否使用了otto特有的API 备份现有项目 6.2 迁移步骤 更新依赖到goja版本 替换导入路径 测试核心功能 检查不兼容点(如ASTWalk等) 更新第三方JS库的调用方式 6.3 测试要点 性能基准测试 功能一致性测试 第三方库兼容性测试 7. 最佳实践 性能优化 :利用goja的缓存机制,避免重复编译 错误处理 :增加对ES6特性的兼容性检查 安全实践 :隔离敏感操作的JavaScript执行环境 调试技巧 :利用 vm.Get() 检查中间状态 8. 常见问题解答 Q1: 为什么我的otto代码在goja中不工作? A1: 检查是否使用了otto特有的API或行为,特别是AST相关操作。 Q2: 如何确保第三方JS库的兼容性? A2: 使用 js.libXXX() 系列函数加载标准库,并测试压缩和非压缩版本。 Q3: 性能提升不明显怎么办? A3: 检查是否有频繁创建VM实例的操作,尽量复用实例。 Q4: 如何处理ES6特性? A4: goja对ES6支持有限,建议使用Babel等工具转译为ES5。