--- tags: CTF --- # VietAn Web challenge 0x2 - writeup ## Analysis ```typescript= // lib/index.ts router.get('/', (req: IRequest, res: Response) => { if (!req.user) return res.redirect('/auth/login'); const secret = req.user.role === Role.USER ? 'No secret for you' : process.env.FLAG; res.render('index', { user: req.user, secret, }); }); ``` Biết được flag nằm ở env và account login phải có role khác USER. ```typescript= // lib/util.ts import { Database } from "@drstrain/database"; import { Request } from 'express'; export enum Role { ADMIN = 0, USER = 1, } export interface User { username: string; password: string; role: Role; } const admin: User = { username: 'admin', password: process.env.ADMIN_PASSWORD, role: Role.ADMIN, } const userDatabase = new Database(); userDatabase.set('admin', admin); export { userDatabase }; export const sessionDatabase = new Database('session'); export interface IRequest extends Request { user: User; cookies: { id: string}; } ``` Khi server được run thì sẽ set 1 tài khoản admin với role là ADMIN (0) vào trong database. ```typescript= // lib/auth.ts type AuthBody = { username: string; password: string; } function handleLogin(body: AuthBody, res: Response): boolean { const { username, password } = body; const user = userDatabase.get(username); if (!user || user.password !== password) return false; const newSession = randomBytes(32).toString('hex'); sessionDatabase.set(newSession, user); res.cookie('id', newSession); return true; } router.post('/login', (req, res) => { if (handleLogin(req.body, res)) return res.redirect('/'); // Success res.render('auth', { title: 'Login', error: 'Wrong username or password' }); }); function handleRegister(body: AuthBody, res: Response): boolean { const { username, password } = body; if (userDatabase.get(username)) return false; // User already existed const user: User = { username, password, role: Role.USER, }; userDatabase.set(username, user); const newSession = randomBytes(32).toString('hex'); sessionDatabase.set(newSession, user); res.cookie('id', newSession); return true; } router.post('/register', (req, res) => { if (handleRegister(req.body, res)) return res.redirect('/'); // Success res.render('auth', { title: 'Register', error: 'Something is wrong', }); }); ``` Tại dòng 38 khi router `register` được gọi thì sẽ nhận tham số từ `req.body` và đưa vào hàm `handleRegister` tại dòng 24 để xử lí. Trong source code này, tác giả sử dụng lib của tác giả viết có tên là [@drstrain/database](https://github.com/phvietan/database). Cụ thể khi vô source code của lib đọc thì thấy được đoạn code này có thể dùng được `__proto__` khi `namespace` rỗng. ![](https://i.imgur.com/r487Thn.png) Source: https://github.com/phvietan/database/blob/main/src/index.ts#L28-L30 <!--Tại dòng 26 của file `lib/auth.ts` mình đã để ở trên thì có sử dụng đến hàm `get` trong lib `@drstrain/database`. Vậy bây giờ trace ngược xem hàm này có nhận `namespace` vào hay không?--> Dòng 21 ở hình dưới khai báo class `Database` với `namespace` rỗng. ![](https://i.imgur.com/GgdvST8.png) => Chúng ta có thể dùng `__proto__` như đã nói ở trên. Request login với `username` là `_proto__`. Giá trị trả về là 1 `Object.__proto__`. ![](https://i.imgur.com/PiclZdG.png) Vì `Object.__proto__` không tồn tại attribute `password` nên giá trị của `user.password` sẽ là `undefined`. Gửi request không có tham số `password` sẽ làm cho biến `password` lấy từ `req.body` lúc này là `undefined`. ![](https://i.imgur.com/dXnUBdX.png) Sau khi qua được câu lệnh if, giá trị được lưu vào `sessionDatabase` sẽ là giá trị của `Object.__proto__`. => Lúc này `role` ở trong `req.user` là `undefined` nên có thể qua được đoạn middleware ban đầu để hiện thị flag. ![](https://i.imgur.com/4CzAqOe.png) ## Exploit 1. Request login để lấy cookie. ![](https://i.imgur.com/I7TW7cg.png) 2. Sử dụng cookie để truy cập vô trang chủ lấy flag. ![](https://i.imgur.com/rAKK3uc.png)