Contents
Mô tả
Link challenge: https://authentication.anctf.tk/auth/login
Overview
Trang web chỉ có 2 chức năng cơ bản: login và register
Thử register với account to^:to^
Mình được redirect đến /
với nội dung
Phân tích
Từ source code, dễ thấy server được code bằng typescript và sau khi buid sẽ có được folder structure như hình dưới
Main script là server.ts
(là server.js
sau khi được build), ở đây có 2 route /
và /auth
kèm với 2 function tương ứng dùng để handle requests lần lượt là: indexRouter
và authRouter
Nói về authRouter
trước, hàm này được import từ auth.ts
và dùng để triển khai chức năng register + login:
- Register
username
và password
được lấy từ POST request, nếu chưa tồn tại username này thì bắt đầu lưu giá trị biến user
vào userDatabase
. Tiếp đó là tạo ra một giá trị random và lưu vào sessionDatabase
, cuối cùng là set cookie id bằng với newSession
.
- Login
Đoạn xử lí này cũng khá đơn giản, kiểm tra xem username
và password
gửi lên với POST request có match với giá trị lưu ở userDatabase
không ? nếu có thì bắt đầu tạo ra một phiên đăng nhập mới.
Về phần indexRouter
, sẽ check xem nếu role
của user hiện tại bằng với Role.USER
thì sẽ trả về No secret for you
thông qua biến secret
, ngược lại ta sẽ lấy được flag.
Vậy vấn đề ở đây là gì ?
- Thứ nhất: giá trị
Role.USER
mặc định là bằng 1 (util.ts
)
và khi ta register thì giá trị role
cũng được gán bằng 1 nốt 😔
- Thứ hai: user
admin
sẽ được add vàouserDatabase
đầu tiên nhưng ta sẽ không thể tìm được pass của user này
Sau một hồi suy nghĩ + dựa theo style của người ra đề ở challenge trước mình bắt đầu đi kiếm source của package @drstrain/database
, xem tại đây (lúc viết writeup mình nhìn lại mới để ý là tác giả tự code cái package này luôn 😬)
/**
* Class for in-memory key-value database
*
* @class
*/
export class Database {
namespace: string;
#db: Record<string, any>;
/**
* Create database instance
*
* @constructor
* @param {string} namespace - The namespace
*/
constructor(namespace?: string) {
this.#db = {};
this.namespace = namespace ?? '';
}
/**
* Get value by key
*
* @function
* @param {string} key - Key string to retrieve value from database
* @return {any} data
*/
get(key: string): any {
return this.#db[`${this.namespace}_${key}`];
}
/**
* Set key-value into database
*
* @function
* @param {string} key - Key string to store
* @param {any} value - Any value to be stored to database
*/
set(key: string, value: any) {
this.#db[`${this.namespace}_${key}`] = value;
}
/**
* Clear the database
*
* @function
*/
clear() {
this.#db = {};
}
}
data sẽ được lưu trong một object (biến db
) theo dạng ${this.namespace}_${key}
- value
, với namspace
được lấy từ contructor.
Quay lại source ta thấy, userDatabase = new Database();
và sessionDatabase = new Database('session');
, nên suy ra khi get()
sẽ có dạng như sau
db[`_${key}`] (
userDatabase
)db[`session_${key}`] (
sessionDatabase
)
Tới đây mình nảy ra một ý tưởng, nếu key=_proto__
thì sao nhỉ ?
\=> Sẽ được một [Object: null prototype]
Quay ngược lại hàm handle login
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;
}
Nếu làm cho key=_proto__
như cách ở trên thì ta đã có được user
này bằng một [Object: null prototype] {}
và !user == false
Vậy việc còn lại là làm sao để điều kiện user.password !== password
failed nữa là ta hoàn toàn có thể login vào.
Nhớ tới lời dạy của một người anh nào đấy 😒, mình bắt đầu setup và debug với intellij idea.
Nếu gửi password=
thì sao ?
lúc này điều kiện user.password !== password
return true
, nhưng như đã phân tích ở trên ta cần nó return false
Vậy nếu chỉ gửi mỗi username=_proto__
thì sẽ ntn ?
Ta pass được đoạn check if và thành công login, tới đây chỉ cần cóp cái id trả về ở response set vào cookie và access tới /
:
Có được flag, rõ ràng vì lúc này req.user.role
đã khác với Role.USER
(req.user = sessionDatabase.get(req.cookies.id);
và vì giá trị id này lưu cái Object: null prototype
khi nãy mình đã nói suy ra req.user.role===undefined
)
Cuối cùng là thử trên server
COMMUNITY-CTF{s0ftware_engineer_at_their_finest}