2023CISCN华南

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();

// view engine setup
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')));

// start session
app.use(session({
name: 'thejs.session',
secret: randomize('Ye0h', 16),
//randomize的第一个参数是生成的字符串的长度,第二个参数是生成的字符串的字符范围
//randomize的作用是生成一个随机的字符串,用于加密session
resave: true,
saveUninitialized: true
}))

app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
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页面

image-20230701223652900

点击登录,跳转到Register页面

image-20230703231445526

但是注册报错,看看users.js

1
2
3
4
5
6
7
// init database
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)
);

image-20230703232247545

注册成功,进行登录

image-20230703232345400


查看代码,分析题目,发现是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
/* GET users listing. */
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是一个对象,所以可以直接传入
//模板是home.squirrelly
}
});

最后一行中,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>
// Update current time
function updateCurrentTime() {
var currentTime = new Date().toLocaleString();
document.getElementById('current-time').textContent = currentTime;
}

// Update current time every second
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)
}