node.js express框架基础 原型链污染
node.js express框架基础 原型链污染
前言:在打hackergame 2024中遇到了原型链污染的题,但不会nodejs,打算学一学然后将其做了
1.0 安装环境
由于我们需要学习的是express 框架,所以首先要先安装好所需的包
首先自然是先安装node.js 这个去官网下载即可
安装好后检测下npm 和node 指令是否可以使用,然后去安装框架
npm install express --save
npm install body-parser --save
npm install cookie-parser --save
npm install multer --save
检测是否可以正常运行,我们来运行一下下面的代码
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello World');
})
var server = app.listen(8081, function () {
var host = server.address().address
var port = server.address().port
console.log("应用实例,访问地址为 http://%s:%s", host, port)
})
在shell里 node 文件名
后
访问本地地址的8081端口,检测是否可以正常运行
1.1框架加载
var express = require('express');
var app = express();
这两行代码的作用是加载express框架并新建一个express应用
1.2 路由操作
app.get('/', function (req, res) {
res.send('Hello World');
})
该操作设置了一个路由,用于处理get请求,当用户访问'/'也就是根目录时,会执行第二个参数,也就
是回调函数里的内容,回调函数将一个字符串发回了客户端,如果我们想要发送文件,就要使用
sendFile了
比如:
app.get('/login',function(req,res){
res.sendFile(__dirname+'/login.html')
})
发送的内容是文件地址, __dirname 是代码文件的地址 需要注意的是,如果发送html文件则会被浏览
器渲染,浏览器会根据发送数据的MME类型来决定是渲染显示还是下载
如果有前端发送数据到网页里,我们也可以用get函数获得get请求发送的内容
app.get('/login',function(req,res){
u=req.query.username;
p=req.query.password;
// res.sendFile(__dirname+'/login.html');
});
query对应的是get请求,在url中的数据。后面的username 和password为发送数据的name 比如表单
中的name或者查询字符串中的数据的键
如果我们想要接受post发送的数据呢?
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var urlencodedParser = bodyParser.urlencoded({ extended: false });
app.get('/', function (req, res) {
res.send('Hello World');
})
app.post('/login', urlencodedParser, function (req, res){
req.send(req.body.username);
res.send(req.body.password);
})
var server = app.listen(8081, function () {
var host = server.address().address;
var port = server.address().port;
console.log("应用实例,访问地址为 http://%s:%s", host, port);
})
文件遍历:
const fs=require("fs");
fs.readdir("./",function (error,files){
console.log(files);
});
可以获得当前目录下的文件
1.3 命令执行和代码执行
先看命令执行:
const ch=require('child_process');
ch.exec("calc");
或者这样:
const ch=require('child_process');
ch.spawn("calc");
再看代码执行
eval("console.log(1);");
直接使用eval函数可以做到代码执行
1.4 原型链污染
在js中每一个类都有一个原型,每个对象都有一个内部属性 [[Prototype]]
,通常可以通过
__proto__
属性访问。这个属性指向该对象的原型对象。原型对象本身也可能有自己的原型对象,这
样就形成了一个原型链。
但是当我们使用字面量 {}
创建对象时,这些对象的原型默认指向 Object.prototype
。
Object.prototype
是所有对象的根原型,它包含了一些通用的方法和属性,如 toString
、
valueOf
等。
需要注意的是,当我们调用一个对象的属性时,会先查找本身的属性,如果自身没有就会沿着原型链查
找,知道找到该属性,这就产生了原型链污染漏洞,我们通过更改原型的属性来污染其他对象,看一个
简单的例子:
$ node
Welcome to Node.js v23.1.0.
Type ".help" for more information.
> a = {}
{}
> a.__proto__.test = 114
114
> b = {}
{}
> b.test
114
通过更改a的原型的属性,成功污染b的属性值
我们再转过来看hackergame 2024的node.js的题目,这就是一道原型链污染的题目
不过这个题和往常有点不一样,我们需要注意,对象的属性值是可以以数组形式访问的,就像这样
$ node
Welcome to Node.js v23.1.0.
Type ".help" for more information.
> a = {}
{}
> a["__proto__"]["test"] = 114
114
> b = {}
{}
> b["test"]
114
我们查看题目代码,发现了设置键值对的关键一步
app.post("/set", (req, res) => {
const { key, value } = req.body;
const keys = key.split(".");
let current = store;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!current[key]) {
current[key] = {};
}
current = current[key];
}
// Set the value at the last key
current[keys[keys.length - 1]] = value;
res.json({ message: "OK" });
});
问题的关键就在与这个处理嵌套结构的逻辑上,
我们传入__proto__.a
值给cat /flag
然后访问execute get传入cmd=a即可获得flag