Post

(Vietnamese) A&D saarCTF 2025 write up

My write up for the routersploit challenge in saarCTF 2025

(Vietnamese) A&D saarCTF 2025 write up

The network architecture of the competition

Vì là một cuộc thi A&D CTF, điều quan trọng đầu tiên là phải hiểu được mô hình mạng của giải đấu do Ban tổ chức cung cấp. Thì saarCTF 2025 có mô hình mạng được đề cập như hình dưới đây:

image

image

Theo cách mình hiểu, hạ tầng cuộc thi sẽ được chia làm 2 vùng chính trong cùng một dải mạng. Dải mạng này được saarCTF gọi là Game network, Game Network là một mạng lớn, nhận dải địa chỉ cụ thể là 10.32.0.0/15. Dải cụ thể được ban tổ chức đề cập là 10.32.0.0 - 10.33.255.255

Mỗi đội tham gia sẽ được cấp cho một dải mạng con trong mạng đó, chia theo kiểu FLSM (Fixed Length Subnet Mask) thay vì VLSM, điều này nhằm mục đích dễ quản lý vì có thể sử dụng công thức để tính thẳng thay vì ngồi mò lại checklist VLSM xem cái này chia cho thằng nào

Mỗi đội tham gia sẽ cầm về một mạng con được chia theo FLSM, cụ thể là 10.(32+X).Y.0/24, trong đó:

  • X = Team_id // 200
  • Y = Team_id % 200
  • .1 phải dành cho Router riêng của team nếu có dùng
  • .2 phải dành cho Vuln box
  • .254 phải dành cho Game Router của ban tổ chức, mọi traffic liên quan tới BTC (Check service, check flag, đại diện cho traffic của BTC nếu ta đọc được trong log)

Ví dụ với đội InfosecPTIT mình là đội có team_id = 70 => Đội mình cầm về ip 10.32.70.0/24 tức đội mình có quyền sử dụng các ip nằm trong vùng từ 10.32.70.0 => 10.32.70.255 và bắt buộc phải đánh địa chỉ các thiết bị sử dụng đúng như trên

Setting up and connecting to the vuln box

Giải này cung cấp cho chúng ta một con vuln box nhưng có rất nhiều cách host con vuln box này lên khác nhau. Tất cả các cách đều được hướng dẫn rất chi tiết, bao gồm:

  • Cloud-hosted: Ban tổ chức và nhà tài trợ sẽ hỗ trợ một vuln box trong cloud server, ta có thể ssh vào con vuln box này khi cuộc thi bắt đầu
  • Self-hosted:
    • Sử dụng VirtualBox image được saar cung cấp
    • Sử dụng Router, Server, … (rất nhiều nhưng mình đọc không hiểu gì về phần này nên tạm thời để lại)

Trong cuộc thi này thì đội mình, cụ thể là các anh (vì mình khi này ngồi đần ra chưa biết phải làm gì vì não chưa tải kịp) đã chọn cách sử dụng cloud-hosted. Khi đó mình đơn giản là được anh đội trưởng ném cho file config wireguard, bật wg lên và import vào rồi chiến =))

image

Sau khi cuộc thi kết thúc, mình đã ngồi nghiên cứu lại mọi thứ để hiểu hơn mọi người đã làm gì trong cuộc thi vừa rồi. Theo cách mình hiểu, mọi thứ liên quan tới cuộc thi (Game Network) đều cần thông qua WireGuard VPN trừ cái scoreboard ra thôi

Với lựa chọn Cloud Hosting như bên trên đã đề cập, captain của team sẽ gen một cặp private - public ssh key, giữ lại private cho riêng mình và gửi lại public key cho hệ thống để từ đó có thể ssh vào con vuln box khi cuộc thi diễn ra

image

Để ssh được vào vuln box của team, tức ip 10.32.70.2 thì trước tiên cần phải reach được tới dải mạng 10.32.70.0/24 này của team đã. Để làm được việc này thì ta cần sử dụng WireGuard VPN, tool này sẽ giúp captain và toàn bộ thành viên trong đội trở thành một peer trong dải mạng của cuộc thi (Game Network)

Hiểu đúng bản chất thì máy của chúng ta không tồn tại sẵn đường đi từ mạng Internet tới dải ip 10.32.0.0/15 vì trên routing table nó chả biết dải ip này là thằng nào

WireGuard giúp ta tiến hành thêm một network interface mới (kiểu wg0) và viết hướng dẫn chuyển tiếp gói tin ra sao khi thấy ip trong dải 10.32.0.0 vào interface này. Từ đó máy tính biết traffic này cần phải có đường đi cụ thể như nào

Vì mình ngồi viết lại bài này khi cuộc thi đã kết thúc khá lâu (2 - 3 tháng gì đó), mình không thấy chỗ nào đề cập tới việc các kết nối WireGuard được tạo như thế nào, nhưng theo mình đọc và tìm hiểu dựa trên những file config cũ được anh captain gửi cho thì mình đang hiểu quá trình này diễn ra như sau:

  • Tạo một cặp private - public WireGuard key
    • wg genkey | tee private.key | wg pubkey | tee public.key
  • Vào mục Me & My team trên hệ thống ném cái public key của bản thân vào
  • Nhận về một file .conf của wireguard
  • Tải về và chỉnh sửa nội dung file config trên, bổ sung giá trị private key của bản thân vào để authen
  • Import vào wg và go

File config mẫu:

1
2
3
4
5
6
7
8
9
10
11
12
[Interface]
Address = 10.32.70.130

PrivateKey=5bpignwQeyckgO6BDJ069u/bVeJDxCa3uX22cL/7J5g=

MTU=1420

[Peer]
Endpoint=vpn.ctf.saarland:32777
AllowedIPs=10.32.0.0/15  
PublicKey=G0/ESRmrmURQpdatQaGtx722BmtL70zLpzf0jx26O1I=
PersistentKeepAlive = 20

Vậy tới đây là xong, ta ssh vào vuln box, deploy service lên và bắt đầu cuộc thi được rồi

Write up challenge routersploit

Overview

Một website đơn giản với chức năng đăng ký - đăng nhập, xem và mua sản phẩm, đánh giá đơn hàng thông qua notes - comment, quản lý thông tin tài khoản và thông tin hoá đơn

image

image

image

Nhìn cái website như này thì cùng lắm là chỉ có các lỗ hổng liên quan tới Access Control dạng xem hoá đơn của người dùng khác hoặc đăng nhập/đăng ký được một tài khoản vượt quyền hạn cho phép. Vì vậy tới đây mình khá chắc rằng flag của challenge này sẽ nằm trong các file PDF invoices của user khác do bot tạo hoặc của admin được tạo mới liên tục

Source code

Đọc source xong thì mình đã chắc chắn là mình đúng. Đây là một bài khá đơn giản, flow khai thác cụ thể như sau:

  • Vì nghi ngờ khả năng đọc được flag thông qua invoices, mình tìm ngay tới các file liên quan, trong đó account.php vì mình biết được thông qua dùng thử dịch vụ ở FrontEnd và invoices.php. Thì chúng này đều call tới get_invoices

image

image

  • get_invoices được định nghĩa tại mysql_functions.php do có liên quan tới database. Tại đây ta dễ dàng nhìn thấy logic của Access Control là vulnerable

image

1
2
3
4
        if (strpos($account_type, "admin") !== false && is_local_ip()) {
            $stmt = $pdo->prepare("SELECT * FROM invoices ORDER BY id DESC");
            $stmt->execute();
        }

=> Nếu trong $account_type có chứa string admin và hàm is_local_ip() trả về true, ta có thể đọc được toàn bộ hoá đơn có trên hệ thống

Trace sâu hơn, hàm is_local_ip() có thể dễ dàng bypass thông qua header X_Forwarded_For khi gửi request invoices

image

Về biến $account_type, ta có hai vị trí để có thể kiểm soát giá trị của biến này. Đó là ở chức năng đăng ký và thay đổi thông tin người dùng, nhìn chung format code vulnerable là như nhau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$account_type = isset($_POST["account_type"]) ? $_POST["account_type"] : null;
if ($error = validate_account_type($account_type)) return $error;

.....

function validate_account_type($type, $goto = null)
{
    $valid = strpos($type, "s") !== false && strpos($type, "n") !== false;

    if (!$valid && $goto != null) {
        goto_page($goto);
    }

    if (!$valid) return "Unknown account type";
}

validate_account_type() sẽ gán giá trị biến $valid là false và ta sẽ bị redirect về index page ngay nếu $account_type được truyền vào không chứa ký tự 'n' và ký tự 's', song mục tiêu của ta đang là $account_type phải chứa cả string 'admin' => 'admins' thoả mãn => Challenge solved

Exploit script

Mình không tự viết script exploit mà đã sử dụng AI. Vì là một cuộc thi, mình muốn ưu tiên hơn cho việc tối ưu thời gian và điểm số, những gì mình đã làm là nêu flow khai thác chính xác cho AI, những yêu cầu quan trọng về ip đối thủ, multithread để ra được thành phẩm tốt nhất.

Sau khoảng 5 tiếng cuộc thi diễn ra, tức loanh quanh 1h sáng, mình đã có một script ưng ý để chạy từ đó tới cuối giải.

Khi viết bài này tức vào Tết 2026, mình nhận ra sau khi cài lại win mình đã không backup lại các data giải quan trọng.., anyway mình sẽ để lại một video mình tâm đắc vì trông rất kỷ niệm bên dưới =))))

Yapping

Mặc dù challenge cũng đơn giản không hề khó cũng không hề đánh đố, trick lỏ gì nhưng trong cuộc thi cũng phải mất tới 3 tiếng để có đội đầu tiên gặt được first blood dù các đội thi đấu đều là những đội A&D nổi tiếng trên thế giới.

Mình nghĩ đây cũng là lý do mình thích A&D CTF hơn Jeopardy, nếu nhìn dưới góc độ của một đội thì chúng ta phải làm nhiều cái hơn, học được nhiều cái hơn. Từ đọc hiểu mô hình mạng, dựng được con vuln box tới việc set up, dựng thêm tools sử dụng khi thi Attack Defense cho phù hợp với chiến thuật của đội (Quan trọng nhất và phải điều chỉnh thường xuyên nhất), còn nếu nhìn từ dưới góc độ một player thuần exploit mình thấy cũng chả khác jeopardy là bao

Còn chưa kể đến nếu có những challenge RCE được, thì bên công lại tìm cách Persistence, bên thủ thì phải vọc cho hết đường exploit, vừa phải tìm cách xem thằng kia vừa ném cho mình những con mã gì, memshell hay crontab, … để tránh mất flag liên tục.

Trong một cuộc thi A&D có rất nhiều biến cố có thể xảy ra, tại giải saarCTF 2025 này thì đội mình có 4 người - là team PTIT.CBS hẹn nhau train qua đêm cho cuộc thi Sinh viên An ninh mạng quan trọng, ban đầu bọn mình vì không chuẩn bị kỹ nên đã để toàn bộ service chết 3 tiếng do chưa dựng được vuln box. Sau khi mọi người ngồi đọc và tìm hiểu kỹ lại thì hoá ra cũng không phải do đội mình dựng gà mà chọn set up khó mà chưa chuẩn bị nhiều, cuối cùng lại về với cloud-host đơn giản.

Cá nhân mình phụ trách challenge này trong giải, mình khá vui khi bài của mình là bài gánh điểm cho đội. Đem về hơn 10000 flag với tổng số điểm là khoảng 4700.

image

Giải này diễn ra vào ngày 8/11/2025, trước kỳ thi SVANM đúng 1 tuần. Trong giải bọn mình cũng gặp các đối thủ quen mặt, chung cuộc đội mình xếp thứ 55, nếu tính Việt Nam thì mình xếp thứ 3, sau Wanna.W1n của UIT (hạng 45) và Universea của KMA (hạng 50). Một kết quả mình có thể nói là không quá tệ để chạy đà cho kỳ thi sau đó, vì theo đó bọn mình không thua số điểm về flag mà chỉ thua về điểm service do không đọc kỹ hướng dẫn từ BTC

Scoreboard Reference: https://ctf.saarland/static/scoreboard/

Cảm ơn mọi người đã đọc tới đây, chúc các bạn một ngày tốt lành

This post is licensed under CC BY 4.0 by the author.