CTF SERIES 0x2 -  Pham Viet An

CTF SERIES 0x2 - Pham Viet An

Contents

Mô tả

image.png

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^

image.png

Mình được redirect đến / với nội dung

image.png

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

image.png

Main script là server.ts (là server.js sau khi được build), ở đây có 2 route //auth kèm với 2 function tương ứng dùng để handle requests lần lượt là: indexRouterauthRouter

image.png

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

image.png

usernamepassword đượ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

image.png

Đoạn xử lí này cũng khá đơn giản, kiểm tra xem usernamepassword 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.

image.png

Vậy vấn đề ở đây là gì ?

  • Thứ nhất: giá trị Role.USERmặc định là bằng 1 (util.ts)

image.png

và khi ta register thì giá trị role cũng được gán bằng 1 nốt 😔

image.png

  • Thứ hai: user admin sẽ được add vào userDatabase đầu tiên nhưng ta sẽ không thể tìm được pass của user này

image.png

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

image.png

\=> 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] {}!user == false

image.png

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.

image.png

Nếu gửi password= thì sao ?

image.png

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

image.png

Vậy nếu chỉ gửi mỗi username=_proto__ thì sẽ ntn ?

image.png

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 /:

image.png

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

image.png

COMMUNITY-CTF{s0ftware_engineer_at_their_finest}