2023国赛华南赛区题目学习
OwnSquirrelly
Squirrelly 是一个用 JavaScript 实现的现代、可配置且速度极快的模板引擎。它与 ExpressJS 一起开箱即用,完整版的 gzip 压缩后仅重约 4KB。
app.js
,发现题目是nodejs编写的网站服务,端口3000
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 var createError = require ('http-errors' );var express = require ('express' );var session = require ('express-session' );const squirrelly = require ('squirrelly' )var randomize = require ('randomatic' );var path = require ('path' );var cookieParser = require ('cookie-parser' );var logger = require ('morgan' );var utils = require ('./utils' );var indexRouter = require ('./routes/index' );var usersRouter = require ('./routes/users' );var app = express ();app.set ('views' , path.join (__dirname, 'views' )); app.set ('view engine' , 'squirrelly' ); app.use (logger ('dev' )); app.use (express.json ()); app.use (express.urlencoded ({ extended : true })); app.use (cookieParser ()); app.use (express.static (path.join (__dirname, 'public' ))); app.use (session ({ name : 'thejs.session' , secret : randomize ('Ye0h' , 16 ), resave : true , saveUninitialized : true })) app.use ('/' , indexRouter); app.use ('/users' , usersRouter); app.use (function (req, res, next ) { next (createError (404 )); }); var server = app.listen (3000 , function ( ) { var host = server.address ().address var port = server.address ().port console .log ("Listening on http://%s:%s" , host, port) });
访问,发现是一个login页面
点击登录,跳转到Register页面
但是注册报错,看看users.js
1 2 3 4 5 6 7 var connection = mysql.createConnection ({ host :'localhost' , user :'root' , password :'123456' , database :'ctf' });
发现有个初始化数据库,再看看列名和数据表名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 if (req.method == "POST" ) { var username = req.body .username ; var password = req.body .password ; if (username !== undefined && password !== undefined ) { var querySql = "select * from users where username = ?" ; connection.query (querySql, [username], function (err, result ) { if (err) { return res.render ('register' , { message : 'Error!' }); } result = JSON .parse (JSON .stringify (result))[0 ]; if (result && result.username !== undefined ) { return res.render ('register' , { message : 'User already exists.' }); }else { var insertSql = "insert into users (`username`, `password`) value (?, ?)" ; connection.query (insertSql, [username, utils.md5 (password)], function (err, result ) { if (err) { return res.render ('register' , { message : 'Error!' }); } return res.render ('register' , { message : 'Register successed.' }); }); } }); } } });
于是构建该数据库和对应表和列
1 2 3 4 5 6 7 8 CREATE database ctf;use ctf; CREATE TABLE users ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR (50 ) NOT NULL , password VARCHAR (255 ) NOT NULL , PRIMARY KEY (id) );
注册成功,进行登录
查看代码,分析题目,发现是Squirrel
是一个模板引擎,所以猜测改代码中存在ssti
,或者XSS
,或者RCE
但是考虑到比赛不出网,所以先自己寻找注入点
下面是两篇参考文章,
NVD - CVE-2021-32819 (nist.gov)
漏洞分析:CVE-2021-32819 - FreeBuf网络安全行业门户
因为是js
,所以查看res.render
存在的地方,寻找可以控制的参数
发现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 router.get ('/' , function (req, res, next ) { if (req.session .userID === undefined || req.session .userID === null ) { return res.redirect ('/users/login' ); } if (!utils.isSafeObj (req.query .information )) { return res.render ('home' , { username : req.session .userID }); } else { return res.render ('home' , req.query .information ) } });
最后一行中,req.query.information
是可控的,而且是将其直接进行渲染,所以这里尝试修改GET
发送information
参数进行ssti
注入
1 return res.render ('home' , req.query .information )
而渲染模板home
,就是home.squirrelly
,是squirrel
特有的模板文件格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <body > <div class ="container" > <div class ="home-content" > <img src ="/images/avatar.jpg" alt ="Avatar" class ="avatar" > <h2 class ="username" > {{ it.username }}</h2 > <p class ="timestamp" > Current Time: <span id ="current-time" > </span > </p > </div > </div > <script > function updateCurrentTime ( ) { var currentTime = new Date ().toLocaleString (); document .getElementById ('current-time' ).textContent = currentTime; } setInterval (updateCurrentTime, 1000 ); </script > </body >
{{ it.username }}
,这里就是下面代码传入的动态渲染参数,是登录后显示
1 2 3 4 5 6 if (!utils.isSafeObj (req.query .information )) { return res.render ('home' , { username : req.session .userID }); } else { return res.render ('home' , req.query .information ) }