# 1337UP LIVE CTF 2024 Tuần vừa rồi team bọn mình - UIT.0r2 có tham gia giải 1337UP LIVE CTF 2024. Các thành viên trong team gồm có 5 bạn lớp ATTN2024: - Lê Trí Đức (dvck13) - Đặng Minh Tú (l1ttl3) - Phạm Nguyễn Thành Long (tatsuya_akira) - Nguyễn Tuấn Hùng (hn1106) - Vũ Hoàng Long (logn) Sau đây là writeup của bọn mình cho các chall trong giải. (Note: các bài được đánh dấu * là những chall bọn em giải ra được sau khi giải kết thúc) # Warmup ## Warmup - Sanity Check ## Warmup - IrrORversible > So reversible it's practically irreversible > nc irrorversible.ctf.intigriti.io 1330 Sau khi chạy lệnh `nc` ở trên thì ta vào được chall như dưới đây: ![image](https://hackmd.io/_uploads/ByiZ8SOfkl.png) Nhập một xâu các kí tự `a` vào ![image](https://hackmd.io/_uploads/HkKE8SdGyg.png) Từ đề bài ta được gợi ý rằng flag có thể được mã hóa bằng phép XOR thông thường. Từ tính chất của phép XOR như ta đã biết thì ta chỉ cần XOR lại ciphertext với xâu `aaaaaaa...` để ra flag. # OSINT ## OSINT - No comment ![image](https://hackmd.io/_uploads/S1JveYPfkl.png) Chall cho ta một file ảnh. Đại loại thì nó như sau: ![](https://scontent.fsgn16-1.fna.fbcdn.net/v/t39.30808-6/467401401_1627944761096135_4126601616935726885_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=127cfc&_nc_ohc=ulYZJmbdgNkQ7kNvgECaLp4&_nc_zt=23&_nc_ht=scontent.fsgn16-1.fna&_nc_gid=A3NVkZNIMQf4dJqxBj6MycL&oh=00_AYCN7jdRN__U3QAI2osJ4cd0cOC3YdjYCwV4p0_WYCgsGQ&oe=674CF4B8) Ta thử gg images tấm ảnh này thì kết quả cũng không thu được gì. Thử tải file ảnh này về rồi sử dụng `exiftool` để đọc metadata của ảnh ![image](https://hackmd.io/_uploads/SJxTgYvMke.png) Ta để ý tới dòng `comment` ở trên. Đây có vẻ là địa chỉ dẫn tới nơi chứa bức ảnh gốc trên web imgur. Ta thử truy cập vào `https://imgur.com/a/pq6TgwS` ![](https://i.imgur.com/4iHa2Uf.png) Có vẻ ta đã đi đúng hướng. Copy đoạn tin nhắn mã hóa và thả vào cyberchef. Kết quả thu được như dưới đây: ![image](https://hackmd.io/_uploads/Bk1H-KvMyx.png) Đường link trên không truy cập vào được trên Chrome nên ta sẽ sử dụng Tor để vào. Sau khi vào được thành công thì ta thấy một Paste nhưng yêu cầu nhập mật khẩu để vào. Mật khẩu của ta chính là dòng chữ: `long_strange_trip` Sau khi vào thành công thì ta thấy có một đoạn tin mã hóa khác nữa: `25213a2e18213d2628150e0b2c00130e020d024004301e5b00040b0b4a1c430a302304052304094309` Ta thử decode base64 thì không thu được gì. Tiếp tục mò trên web hồi nãy: ![image](https://hackmd.io/_uploads/By3UzFPz1l.png) Có vẻ gợi ý là sử dụng phép XOR để giải mã flag, trong đó khóa để XOR cũng chính là mật khẩu lúc nãy Ta viết đoạn code sau để thực hiện phép XOR ``` def hex_to_bytes(hex_string): return bytes.fromhex(hex_string) hex_string = "25213a2e18213d2628150e0b2c00130e020d024004301e5b00040b0b4a1c430a302304052304094309" hex_bytes = hex_to_bytes(hex_string) key = b'long_strange_trip' def xor_bytes(data, key): return bytes([data[i] ^ key[i % len(key)] for i in range(len(data))]) result_bytes = xor_bytes(hex_bytes, key) result = result_bytes.decode('latin-1') print("Kết quả XOR:", result) ``` Thu được flag: `INTIGRITI{instagram.com/reel/C7xYShjMcV0}` ## OSINT - Trackdown > There's a fugitive on the loose and we need to track him down! He posted this to social media recently, do you know where the photograph was taken from? If you can provide the precise building, we can move in immediately 🚔 > [File](https://ctf.intigriti.io/files/0d58ff042c2539178a21230189c2df59/trackdown.jpg?token=eyJ1c2VyX2lkIjo3ODksInRlYW1faWQiOjM5NywiZmlsZV9pZCI6NjB9.ZznwVg.jU3J4ay2UwcRDOfiOPHOcIAVt_I) Chall cho ta một tấm ảnh và yêu cầu ta tìm vị trí mà người chụp bức ảnh này đang đứng ![](https://scontent.fsgn16-1.fna.fbcdn.net/v/t39.30808-6/467345183_1627936824430262_2950041537123432696_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=127cfc&_nc_eui2=AeFDJacK43G3K9m4kXAd0UqAcgFmDV8P09lyAWYNXw_T2T8fg4eand8OqHpPbOz3KYv26Xo20j-EQMj1aAE5UCEq&_nc_ohc=MoBm0Qpgj4gQ7kNvgFVRr5R&_nc_zt=23&_nc_ht=scontent.fsgn16-1.fna&_nc_gid=AhPk7DrTBm_2imbxkvZV45n&oh=00_AYBcx6unnG6CcZkV9JREha_M9VXLah4A9Gwgtg-hurgx4g&oe=673FDE21) Zoom to lên một chút thì ta thấy đối diện là Trang Tien Plaza. Ta thử tìm kiếm bức ảnh trên Google Images thì thấy một bài báo đăng tấm hình y hệt: ![image](https://hackmd.io/_uploads/HyutauPMyx.png) Ok có vẻ ta đã đi đúng hướng. Tiếp theo ta tra cụm "quán cafe đối diện tràng tiền plaza" :) ![image](https://hackmd.io/_uploads/rki26dPfJe.png) Đây chính là flag cần tìm : `INTIGRITI{sicuisine&mixology}` ## OSINT - Trackdown 2 > We didn't get him in time 😫 Thankfully, we don't believe he's fled the country yet. He uploaded another photo this morning, it's as if he's taunting us! Anyway, this may be our last chance - do you know where he is right now? Chall tiếp theo cũng cho ta một tấm ảnh ![](https://i.imgur.com/3UWFRAk.jpeg) Ta thử tìm kiếm bức ảnh này trên GG Images ![](https://i.imgur.com/La53j7h.png) Từ thông tin thu được ta có thể đoán được người chụp bức ảnh này có vẻ đang ở một địa điểm nào đó ở quận 1. Vì khi check view từ Sogo Hotel Saigon trông khá giống với view mà ta đang thấy ở trong bức ảnh. Ta tìm kiếm trên gg map để xem thử Sogo Hotel Saigon ở đâu Nhưng đây không phải là địa chỉ mà ta đang đứng. Chú ý trong bức ảnh ta đang đứng đối diện một nhà thờ và một khách sạn khác có tên là A25 Hotel (zoom to lên), vị trí của ta ở ngay dưới là một bến xe buýt. Từ các thông tin này ta có thể xác định được vị trí đứng ở trên gg map: Giao của các tòa nhà đó sẽ là vị trí mà ta đang đứng ![](https://i.imgur.com/rASuWmi.png) Flag is: `INTIGRITI{expressbymvillage}` ## OSINT - Private Github Repository > Bob Robizillo created a public instructions for Tiffany, so she can start work on new secret project. can you access the secret repository? Từ đề bài gợi ý cho ta đi tìm user có tên là Bob Robizillo trên github. Ta gõ tên cần tìm lên thanh tìm kiếm của github thì được kết quả là một acc github như dưới đây ![image](https://hackmd.io/_uploads/HykTodDMkg.png) Để tìm thêm thông tin về tài khoản github này ta thử tra đường dẫn url trên yandex ![image](https://hackmd.io/_uploads/rJY12uPM1x.png) Ta thấy một trang dẫn đến kho lưu trữ gist: ![image](https://hackmd.io/_uploads/HyMe0uwfkg.png) Click vào xem thì thấy một email mà Bob gửi cho Tiffany nói về việc cài SSH Key để cấp quyền vào một repo có tên là `1337up` trên github. Ngoài ra ta còn được cho thêm một đoạn tin nhắn được mã hóa: ![image](https://hackmd.io/_uploads/r1_zAdvzyx.png) Ta thử copy đoạn mã của Bob và decode nó xem: ![image](https://hackmd.io/_uploads/H1JuJtvfkl.png) Nhận thấy rằng sau khi decode base64 ra thì nó có 2 byte đầu là `PK` suy ra nó là một file `zip`. Vì vậy ta sẽ lưu cái base64 decode này vào một file `zip` và sau đó `unzip` nó ra để xem thông tin bên trong. ![image](https://hackmd.io/_uploads/S1RRetDM1g.png) `cat id_rsa` để xem nó có gì ![image](https://hackmd.io/_uploads/B1SeZFPz1g.png) Wow! là một `openssh private key`. Như vậy ta đã có khóa private để có thể clone `private repo` của Tiffany về. Giờ ta sẽ copy file `id_rsa` này vào thư mục `~/.ssh` để set sshkey cho máy của mình. ![image](https://hackmd.io/_uploads/rJeWfYDzkg.png) Sau đó kiểm tra sshkey đã được thiết lập đúng chưa. ![image](https://hackmd.io/_uploads/HJ_8Gtwf1l.png) Thông báo như vậy tức là ta đã thiết lập thành công sshkey cho máy của mình và ta cũng đã biết được `username` của github Tiffany đó là `nitrofany` từ đó ta sẽ clone được private repo `1337up` về máy của mình. ![image](https://hackmd.io/_uploads/r1kbXtwG1e.png) Sau khi clone được repo `1337up` về thì ta thấy được có một folder `config`. Sau khi `git log` thì ta thấy được lịch sử các commit của repo này. `git show` từng cái một thì ta thấy được ở lần commit `5c18888418fd3f2a9d76cfd278b69c1f7c41ba4f` thì họ đã tích hợp một repo khác (cụ thể là repo `01189998819991197253`) vào project hiện tại. Có lẽ họ đã làm gì đó với file `flag.txt`. ![image](https://hackmd.io/_uploads/rJh04Kwzyg.png) Ta thử clone repo `01189998819991197253` về để xem có gì. ![image](https://hackmd.io/_uploads/HJrABYDzJx.png) Flag is: `INTIGRITI{9e0121bb8bce15ead3d7f529a81b77b4}` ## OSINT - Bob L'éponge (*) > I'm an epic hacker and I'm trying to start a YouTube channel to show off my skills! I've been playing around with some of the video settings and stumbled upon a few cool features. Can you find the secret I've hidden?https://youtu.be/DXZrAGYS6X8 Bài khá tricky. Đầu tiên mình truy cập vào đường link dẫn tới một video trên youtube ![image](https://hackmd.io/_uploads/B1yb0PvMyx.png) Mình thử xài web sau để xem metadata của video: https://mattw.io/youtube-metadata/ Thử lướt xuống cuối và đọc các thông tin thì ta cũng không phát hiện được gì thêm. Sau đó mình thử vào kênh youtube của người này để xem còn video nào khác không. Ta phát hiện một điểm khả nghi: ![image](https://hackmd.io/_uploads/BJmskuDf1e.png) Ở trên phần giới thiệu thì ta biết kênh này chỉ có 2 vid nhưng trong danh sách phát lại có tới 3 vid. Cho nên có một vid đã được để ở chế độ không công khai? ![image](https://hackmd.io/_uploads/r1jkx_Pz1l.png) Đó chính là video thứ 2. Sau đó mình `Ctrl+U` để xem nguồn trang và thử `Ctrl+F` cụm `INTIGRITI{` thì được luôn flag :) ![image](https://hackmd.io/_uploads/HkCfx_Pz1e.png) Flag is: `INTIGRITI{t4gs_4r3_m0stly_0bs0l3t3_zMlH7RH6psw}` # Crypto ## Crypto - Schrödinger's Pad >Everyone knows you can't reuse a OTP, but throw in a cat and a box.. Maybe it's secure? > `nc pad.ctf.intigriti.io 1348` Chall cho ta một file python: ```python= import os import socket import threading import random import traceback import string FLAG = os.getenv("FLAG", ( "Not the flag you're searching for, Keep looking close, there's plenty more. " "INTIGRITI{TODO} A clue I might be, but not the key, The flag is hidden, not in me!!!" )) MAX_LENGTH = 160 def otp(p, k): k_r = (k * ((len(p) // len(k)) + 1))[:len(p)] return bytes([p ^ k for p, k in zip(p, k_r)]) def check_cat_box(ciphertext, cat_state): c = bytearray(ciphertext) if cat_state == 1: for i in range(len(c)): c[i] = ((c[i] << 1) & 0xFF) ^ 0xAC else: for i in range(len(c)): c[i] = ((c[i] >> 1) | (c[i] << 7)) & 0xFF c[i] ^= 0xCA return bytes(c) def handle_client(client_socket): try: # Set socket timeout to prevent hanging client_socket.settimeout(60) KEY = ''.join(random.choices( string.ascii_letters + string.digits, k=160)).encode() message = ( "Welcome to Schrödinger's Pad!\n" "Due to its quantum, cat-like nature, this cryptosystem can re-use the same key\n" "Thankfully, that means you'll never be able to uncover this secret message :')\n\n" ) client_socket.send(message.encode()) client_socket.send( f"Encrypted (cat state=ERROR! 'cat not in box'): {otp(FLAG.encode(), KEY).hex()}\n".encode( ) ) client_socket.send(b"\nAnyway, why don't you try it for yourself?\n") plaintext = client_socket.recv(1024).strip() if len(plaintext) > MAX_LENGTH: client_socket.send( f"Plaintext too long! Max allowed length is {MAX_LENGTH} characters.\n".encode( ) ) return cat_state = random.choice([0, 1]) ciphertext = otp(plaintext, KEY) c_ciphertext = check_cat_box(ciphertext, cat_state) cat_state_str = "alive" if cat_state == 1 else "dead" client_socket.send( f"Encrypted (cat state={cat_state_str}): {c_ciphertext.hex()}\n".encode( ) ) except socket.timeout: client_socket.send(b"Error: Connection timed out.\n") except BrokenPipeError: print("Client disconnected abruptly.") except Exception as e: print(f"Server Error: {e}") traceback.print_exc() finally: client_socket.close() def start_server(): try: server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("0.0.0.0", 1337)) # Increase backlog size for more concurrent connections server.listen(100) print("Server started on port 1337") while True: try: client_socket, addr = server.accept() print(f"Accepted connection from {addr}") # Create a daemon thread to handle each client client_handler = threading.Thread( target=handle_client, args=(client_socket,)) # Daemon thread will exit automatically when main thread ends client_handler.daemon = True client_handler.start() except Exception as e: print(f"Error accepting connection: {e}") traceback.print_exc() except Exception as e: print(f"Critical server error: {e}") finally: server.close() print("[*] Server shutdown") if __name__ == "__main__": start_server() ``` Đoạn code trên cho chúng ta một chuỗi hex là kết quả của hàm `otp(FLAG, KEY)`. Sau đó server muốn ta nhập vào một chuỗi `plaintext` có không quá 160 kí tự (thì ta sẽ nhập chuỗi có 160 kí tự luôn :Đ) và gửi cho ta một đoạn mã hex `c_ciphertext`. ![image](https://hackmd.io/_uploads/H1pB5YvGkx.png) Đọc code thì ta thấy rằng `c_ciphertext` được xây dựng từ `plaintext` và `KEY` mà ta đã có được `c_ciphertext` và `plaintext` rồi thì ta sẽ tìm cách để suy ra được giá trị `KEY`. Ta sẽ viết được 2 hàm decrypt của `otp` và `check_cat_box` như sau: ```python= def otp_decrypt(p, k): k_r = (k * ((len(p) // len(k)) + 1))[:len(p)] return bytes([p ^ k for p, k in zip(p, k_r)]) def check_cat_box_decrypt(ciphertext, cat_state): c = bytearray(ciphertext) if cat_state == 1: for i in range(len(c)): c[i] ^= 0xAC c[i] = (c[i] >> 1) | ((c[i] & 1) << 7) else: for i in range(len(c)): c[i] ^= 0xCA c[i] = ((c[i] << 1) & 0xFF) | (c[i] >> 7) return bytes(c) ``` Từ đó ta làm ngược lại với đoạn code này để tìm `KEY` từ những dữ liệu nhận được từ server: ```python= cat_state = random.choice([0, 1]) ciphertext = otp(plaintext, KEY) c_ciphertext = check_cat_box(ciphertext, cat_state) cat_state_str = "alive" if cat_state == 1 else "dead" ``` Khi đã có được `KEY` thì việc đơn giản tính `otp_decrypt(KEY, Encrypted)` là lấy được `FLAG`. Code: ```python= def otp_decrypt(p, k): k_r = (k * ((len(p) // len(k)) + 1))[:len(p)] return bytes([p ^ k for p, k in zip(p, k_r)]) def check_cat_box_decrypt(ciphertext, cat_state): c = bytearray(ciphertext) if cat_state == 1: for i in range(len(c)): c[i] ^= 0xAC c[i] = (c[i] >> 1) | ((c[i] & 1) << 7) else: for i in range(len(c)): c[i] ^= 0xCA c[i] = ((c[i] << 1) & 0xFF) | (c[i] >> 7) return bytes(c) c_ciphertext = "4b49e0d0c0d647585f5d60db46e6ca634e625fda57ccd15ee061da63c1e2575cdec3d96043cf48ce5dcdcfc9e156ce6350506266e6615ce2566062dadcdbd9e1505258da5bdb5e626253c853ce46c2c8e04ec2d0dcd9ce6157d6d6d063dec0da4c53d0e0e646c05f5c4353dcd152dc4dc350cfd25b52ce51dbe0d15e62e2d9d64058c3de5ecf4fcddcd6dcc1665ee3cfcd5dcb465766dde14c49d9e651c1d6c6" c_ciphertext = bytes.fromhex(c_ciphertext) ciphertext = check_cat_box_decrypt(c_ciphertext, 0) plaintext = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" plaintext = plaintext.encode('utf-8') KEY = otp_decrypt(ciphertext, plaintext) Encrypted = "31055d271a3d132a2d2b46640b1902531c10232f7a0c773940572f46025c7a2e201d231852222a3d0728392e6311125667605467094413053405467230632f5e3034212f6e632051423645260658170c5b0c5f750434495f2e7938395b3f106d4c3d27155119066a2a1326287732280b1d741821373e56760a5b773b4541222b042b00203c0205016179391f5d68540a022b440b7a562a410914675d39193c57" Encrypted = bytes.fromhex(Encrypted) FLAG = otp_decrypt(KEY, Encrypted) print(FLAG.decode('utf-8')) ``` Flag is: `INTIGRITI{d34d_0r_4l1v3}` ## Crypto - kRSA > RSA-2048 is considered secure and SSL/TLS often use it for key exchange. So this custom protocol between Alice and Bob should be pretty secure right? > `nc krsa.ctf.intigriti.io 1346` Chall cho ta một file python như dưới đây: ```python= from Crypto.Util.number import * import signal def timeout_handler(signum, frame): print("Secret key expired") exit() signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(300) FLAG = "INTIGRITI{fake_flag}" SIZE = 32 class Alice: def __init__(self): self.p = getPrime(1024) self.q = getPrime(1024) self.n = self.p*self.q self.e = 0x10001 def public_key(self): return self.n,self.e def decrypt_key(self, ck): phi = (self.p-1)*(self.q-1) d = inverse(e, phi) self.k = pow(ck, d, n) class Bob: def __init__(self): self.k = getRandomNBitInteger(SIZE) def key_exchange(self, n, e): return pow(self.k, e, n) alice = Alice() bob = Bob() n,e = alice.public_key() print("Public key from Alice :") print(f"{n=}") print(f"{e=}") ck = bob.key_exchange(n, e) print("Bob sends encrypted secret key to Alice :") print(f"{ck=}") alice.decrypt_key(ck) assert(alice.k == bob.k) try: k = int(input("Secret key ? ")) except: exit() if k == bob.k: print(FLAG) else: print("That's not the secret key") ``` Về cơ bản thì đây là hệ mật mã RSA với modulo $n$ có độ lớn là 2048 bit (khá lớn) và số mũ $e=65537$. Khi kết nối đến server thì ta sẽ thu được 3 giá trị đó là $n,e$ và $ck$ ![image](https://hackmd.io/_uploads/Sk80ovvGkg.png) Yêu cầu của bài là nhập vào secret key $k$ ban đầu trước khi được mã hóa trong đó $\displaystyle ck=k^{e}\bmod n$ Vấn đề ở đây là số $n$ quá lớn nên ta không thể nào tính $\displaystyle d=e^{-1}\bmod( \varphi ( n))$ theo cách thông thường được. Điểm yếu duy nhất của thuật toán mã hóa RSA ở trên mà ta có thể hi vọng khai thác được đó chính là độ lớn của khóa $k$ - 32 bit. Điều này chứng tỏ khóa $k$ mà ta cần tìm không quá lớn nên ta có thể bruteforce được. Ta viết lại khóa $\displaystyle k=r.s$. Thì khi đó $\displaystyle k^{e} =r^{e} .s^{e} \equiv ck(\bmod n)$. Suy ra ta có được \begin{gather*} r^{e} .s^{e} .\left( r^{e}\right)^{-1} \equiv ck.\left( r^{e}\right)^{-1}(\bmod n)\\ \leftrightarrow s^{e} \equiv ck.\left( r^{e}\right)^{-1}(\bmod n) \end{gather*} Như vậy ý tưởng của ta là sẽ tạo một mảng lưu các số $\displaystyle ck.\left( r^{e}\right)^{-1}\bmod n$. Do $\displaystyle r$ rất nhỏ so với hai số nguyên tố $\displaystyle p,q$ là ước của $\displaystyle n$ nên ta sẽ luôn tính được nghịch đảo của $\displaystyle r^{e}$ theo modulo $\displaystyle n$. Vậy thuật toán của ta sẽ là: lặp qua các giá trị $s$ từ $[1...2^{16}]$ và sử dụng thuật toán tìm kiếm nhị phân để tìm giá trị $r$ trong mảng đã tạo. Nếu trong trường hợp $\displaystyle r >2^{16}$ thì ta có thể thử mở rộng giá trị lên $\displaystyle 2^{18} ,2^{20}$ và thử đến khi nào ra flag :)))) Code: ```python= from Crypto.Util.number import * from pwn import * def binary_search(r_possible, val): l, r = 0, len(r_possible) - 1 while l <= r: mid = (l + r) // 2 if r_possible[mid][1] == val: return r_possible[mid][0] if r_possible[mid][1] < val: l = mid + 1 else: r = mid - 1 return -1 def rsaMeetInTheMiddleAttack(e, n, ck, max_val): r_possible = [] for r in range(1, max_val + 1): r_possible.append([r, (ck * inverse(pow(r, e, n), n)) % n]) r_possible = sorted(r_possible, key=lambda x: x[1]) for s in range(1, max_val + 1): r = binary_search(r_possible, pow(s, e, n)) if r != -1: return (r * s) % n return 0 conn = remote("krsa.ctf.intigriti.io", 1346) conn.recvuntil("n=") n = int(conn.recvline().decode()) conn.recvuntil("e=") e = int(conn.recvline().decode()) conn.recvuntil("ck=") ck = int(conn.recvline().decode()) conn.recvuntil("?") max_val = pow(2, 18) k = rsaMeetInTheMiddleAttack(e, n, ck, max_val) conn.sendline(str(k).encode()) flag = conn.recvline() print(f"Flag is: {flag.decode()}") ``` Flag is: `INTIGRITI{w3_sh0uld_m33t_1n_th3_m1ddl3}` Ngoài thuật sử dụng binary search như trên thì ta có thể tạo hash table để tìm kiếm `r,s` như sau : ```python= from Crypto.Util.number import * from pwn import * def meet_in_the_middle(n,e,ck,max_val): r_map={} for r in range(1,max_val+1): r_val=(ck * inverse(pow(r, e, n), n)) % n r_map[r_val]=r # lưu r với key là r_val với mỗi r for s in range(1,max_val*4+1): s_val=pow(s,e,n) if s_val in r_map r=r_map[s_val] return (r*s) % n return 0 conn = remote("krsa.ctf.intigriti.io", 1346) conn.recvuntil("n=") n = int(conn.recvline().decode()) conn.recvuntil("e=") e = int(conn.recvline().decode()) conn.recvuntil("ck=") ck = int(conn.recvline().decode()) conn.recvuntil("?") max_val = pow(2, 18) k = meet_in_the_middle(n,e,ck,max_val) conn.sendline(str(k).encode()) flag = conn.recvline() print(f"Flag is: {flag.decode()}") ``` # Forensics ## Forensics - Logging >Any ideas what this log is about? 🤔 Flag format: INTIGRITI{.*} [File](https://ctf.intigriti.io/files/1b870e8ac2a0f7e8a8f7878f085b0c5b/app.log?token=eyJ1c2VyX2lkIjo3ODksInRlYW1faWQiOjM5NywiZmlsZV9pZCI6NTF9.Zzrq3Q.KMmkauzu6gFyCN2pz1L65yQ01es) Chall cho ta một file audit log ## Forensics - CTF Mind Tricks > There's an ongoing investigation into the communications of two potential hackers. As far as I can see, they only shared some music with each other but the feds are convinced something nefarious is going on. Let me know if you can find anything 🔎 >[File pcap](https://ctf.intigriti.io/files/b6e5504ddffac2cc339719ea5c9482e4/mind_tricks.pcap?token=eyJ1c2VyX2lkIjo3ODksInRlYW1faWQiOjM5NywiZmlsZV9pZCI6NTJ9.ZzrhtA.enTKU5UBm6FsU6M4H9HwEvstLd0) Chall cho ta một file pcap. Từ đề bài ta được gợi ý về một file âm thành được trao đổi giữa 2 người. Ta vào `file->export objects->smb..` để tải file `.wav` về ![image](https://hackmd.io/_uploads/HyStXP_zyl.png) Nói thêm về giao thức SMB. Giao thức SMB hay còn gọi là Server Message Block Protocol là một giao thức mạng được dùng để chia sẻ tệp tin và dữ liệu ở trong một mạng nội bộ (LAN). Còn các giao thức truyền dữ liệu khác như FTP tập trung vào việc truyền tải tệp giữa máy khách và máy chủ. Tiếp tục với chall của ta, sau khi tải được file `.wav` về thì ta tiếp tục xem và phân tích tệp trên Audacity. Ta chỉ cần chỉnh file về dạng biểu đồ Spectrogram để đọc được flag ![](https://i.imgur.com/mxrF5oo.png) Flag : `INTIGRITI{hidden_in_music_1337}` ## Forensics - The Puzzled Protocol (*) > In a world where the machines talk in codes, Two protocols clash on their invisible roads. One speaks control, the other knows the grid, Hidden among them, a secret is hide. Modbus whispers commands to open the gate, DNP3 listens and alters its fate. Some signals are true, some meant to deceive, Only the sharp-eyed can truly perceive. Flags are fragmented, scattered in disguise, The real one’s elusive, behind layers of lies. Find the whispers that tell the right tale, Or be lost in the noise, destined to fail. [FILE Pcap](https://ctf.intigriti.io/files/c96565bc327e85a6dd3be9af2b98cd70/puzzled_protocol.pcap?token=eyJ1c2VyX2lkIjo3ODksInRlYW1faWQiOjM5NywiZmlsZV9pZCI6NTN9.ZzrncA.KEt6ipu25WA4aCcDCKQftKTF3xg) Chall lần này tiếp tục cho ta một file pcap. Trong đề bài có thông tin về hai giao thức truyền thông là `Modbus` và `DNP3`. Ta thử tìm kiếm và đọc thông tin về hai giao thức này để xem có gợi ý gì không. Về cơ bản thì hai giao thức này hoạt động khá giống nhau dựa trên kiến trúc master-client. Ta tiếp tục phân tích các payload có trong file pcap. Trong file pcap chỉ bao gồm các gói tin được truyền thông qua hai giao thức là TCP và UDP. Tiếp theo ta sẽ dụng dụng các lệnh `tshark` để lọc ra data từ info column của các packet. Nhưng vấn đề ở đây là file pcap có quá nhiều gói tin, cụ thể là 300 gói tin nên để đọc hết data của 300 gói là điều không thể. Ta cần lược bỏ đi các gói tin chứa data bị lặp lại để rút gọn đi số lượng các gói tin cần phân tích. Nhưng rất tiếc là `tshark` không được tích hợp sẵn command để làm việc này nên ta cần tìm một hướng đi khác. ![image](https://hackmd.io/_uploads/S1rs1YOf1g.png) Ta thử đọc qua một số gói tin đầu thì ta có điểm đặc biệt là ở frame 1, data của nó sẽ là ``` 00000000 54 43 50 20 44 61 74 61 3a 20 77 37 37 43 6d 63 TCP Data : w77Cmc 00000010 4f 34 77 36 66 43 6d 38 4f 6b 77 70 37 44 70 73 O4w6fCm8 Okwp7Dps 00000020 4f 31 77 36 6e 43 6d 73 4f 6b 77 37 37 44 75 4d O1w6nCms Okw77DuM 00000030 4b 61 77 36 62 44 6c 77 3d 3d Kaw6bDlw == ``` Phần Header của nó là TCP Data còn đính kèm ở sau là một chuỗi dữ liệu được mã hóa (có thể là base64). Ta thử dùng `tshark` để lọc ra các chuỗi data có chứa xâu "Data" Chạy lệnh sau: `tshark -r puzzled_protocol.pcap -Y 'frame contains "Data"' ` Ta thu được 6 packet có chứa xâu kí tự Data trong đó: ![image](https://hackmd.io/_uploads/ryiBZtdMJx.png) Chạy lại và thêm flag `-V` vào để hiện thị nội dung của các packet đó. Từng payload của mỗi frame như sau : ``` Frame 1: Data: w77CmcO4w6fCm8Okwp7DpsO1w6nCmsOkw77DuMKaw6bDlw== Frame 26: Data: w6PDpMO+w6PDrcO4w6PDvsOjw5HDrMKew6HCmcO1w6zDpsKew63Dlw== Frame 31: Data: w6PDpMO+w6PDrcO4w6PDvsOjw5HDp8Olw67DqMO/w7nDtcOlw7zCmcO4w7jDo8OuwpnDtQ== Frame 51: Data: w6PDpMO+w6PDrcO4w6PDvsOjw5HDrMKew6HCmcO1w6zDpsKew63Dlw== Frame 61: Data: w67DpMO6wpnDtcKZw7nDqcKew7rCmcO1 Frame 76: Data: w6PDpMO+w6PDrcO4w6PDvsOjw5HDrMKew6HCmcO1w6zDpsKew63Dlw== ``` Có 3 frame có data bị lặp lại là 26,51 và 76. 3 frame còn lại là 1,31,61 chỉ xuất hiện một lần. Ở cuối có kí tự `==` gợi ý cho ta biết data được mã hóa base64 nên ta thử quăng lên cyberchef coi có được gì không ![image](https://hackmd.io/_uploads/Sk9sy5dfyx.png) Nếu như không decode được theo cách thông thường thì ta có thể nghĩ tới ý tưởng bruteforce, vì ta đã biết được các kí tự đầu của flag là `INTIGRITI{` nên ta có thể sử dụng thông tin này để decode. Thông thường sẽ có 3 cách để decode đó là Known-plaintext attack, Two time pad attack và cuối cùng là Bruteforce keys. Brutefore ở đây ta sẽ xor xâu data ở trên với các khóa từ 0 tới 255 bit. Cụ thể: ``` Import base64 # XOR Decryption Function def xor_decrypt(data, key): return ''.join(chr(ord(c) ^ key) for c in data) # Decode Base64 data def decode_base64(encoded_data): return base64.b64decode(encoded_data).decode('utf-8') # Try all possible XOR keys def brute_force_xor(encoded_data): encrypted_data = decode_base64(encoded_data) for key in range(256): # Check all possible 1-byte keys (0-255) decrypted_data = xor_decrypt(encrypted_data, key) print(f"Key: {hex(key)} | Decrypted: {decrypted_data}") # Encoded data encoded_flag = "w6PDpMO+w6PDrcO4w6PDvsOjw5HDrMKew6HCmcO1w6zDpsKew63Dlw==" brute_force_xor(encoded_flag) ``` ![image](https://hackmd.io/_uploads/ryjaGsVQye.png) Làm tương tự với 3 xâu còn lại: Phần cuối flag: ![image](https://hackmd.io/_uploads/Skz47iNm1l.png) Phần đầu flag: ![image](https://hackmd.io/_uploads/Sk3I7iEQJg.png) Phần giữa flag: ![image](https://hackmd.io/_uploads/B1cY7jVQkg.png) Ghép lại: : `INTIGRITI{MODBUS_OV3RRID3_DNP3_3SC4P3_T3RM1N4L_C0NTR0L}` Note: Lúc đầu mình đã thử Two time pad attack nhưng gặp vấn đề trong khâu xử lí dữ liệu nên cuối cùng vẫn không thể thành công. # Web ## Web - Pizza Paradise > Something weird going on at this pizza store!! https://pizzaparadise.ctf.intigriti.io Ở bài này mình thấy rằng có một trang web pizza với không một nút nào sử dụng được, đồng thời đọc sơ qua đoạn code html cũng không có gì khả nghi cả. Vì vậy mình đã mò thử file robots.txt, và nó hiện ra kết quả như sau: ![image](https://hackmd.io/_uploads/B1KKJF5Gyg.png) Mình thấy rằng có một đường dẫn khá khả nghi ở trên đầu, thế nên mình đã thử truy cập vào thử. Nhìn sơ qua, trang web chứa một form được xác thực bằng code js ở frontend, cùng với một file auth.js chứa username cho sẵn và một password được mã hóa SHA256. ![image](https://hackmd.io/_uploads/rkWtetczJx.png) ![image](https://hackmd.io/_uploads/r1Vcxtcz1g.png) ![image](https://hackmd.io/_uploads/rJNjgY5z1l.png) Decode password bằng tool [Sha256 Encrypt & Decrypt](https://md5decrypt.net/en/Sha256/) ta được password là ***intel420***. Đây là hình ảnh sau khi đăng nhập thành công.![image](https://hackmd.io/_uploads/SJeoWFczyl.png) Quan sát trang web, ta thấy rằng ở đây có một chức năng mà ta có thể khai thác, đó là chức năng download ảnh. Ngay khi thấy chức năng này, mình liền nghĩ tới path traversal, vì vậy mình bỏ trang web vào Burp Suite và kiểm tra các request. ![image](https://hackmd.io/_uploads/H1rgmtcG1e.png) Ngay khi bấm nút tải, mình bắt được một request chứa param download cùng một đường dẫn tới file ảnh secret1.png. Mình thử thay đổi param bằng các loại đường dẫn khác /etc/passwd như ../etc/passwd hay ../../etc/passwd, mình cũng thử các file khác những tất cả đều trả về kết quả ***File path not allowed!***![image](https://hackmd.io/_uploads/rJck4Fqzye.png) Chính vì thế mình thử ngó qua các file khác. Mình nhận ra rằng tất cả path đều cần ***/assets/images/***, để thử nghiệm giả thuyết này, mình thử xóa cả tên file ảnh và tải ***/assets/images/*** thì nhận kết quả sau.![image](https://hackmd.io/_uploads/r1XKEtqf1l.png) Yea, vậy họ chỉ giới hạn cụm ***/assets/images/***. Vậy mình chỉ cần thêm ../ đằng sau là đã có thể path traversal rồi.![image](https://hackmd.io/_uploads/ryyzBK9Gkg.png) Vì không biết đọc file nào nên mình sử thử đọc source file ***/topsecret_a9aedc6c39f654e55275ad8e65e316b3.php*** xem có chút manh mối nào không.![image](https://hackmd.io/_uploads/rkfqSFqMye.png) May mắn thay, flag thực sự nằm ngay trên file **"top secret"**. >Flag: INTIGRITI{70p_53cr37_m15510n_c0mpl373} ## Web - BioCorp >BioCorp contacted us with some concerns about the security of their network. Specifically, they want to make sure they've decoupled any dangerous functionality from the public facing website. Could you give it a quick review? https://biocorp.ctf.intigriti.io Ở bài này, tác giả cho ta source code để review, khi đọc sơ qua mình sẽ chỉ chú ý vào file panel.php bởi file chứa những code php khá khả nghi. ![image](https://hackmd.io/_uploads/rkDYLFqfkg.png) Mình thấy rằng, trang web này sử dụng xml để load các data XML. Vì vâỵ, mình nghi ngờ rằng trang web này có khả năng khai thác XXE Injection. Đầu tiên ta sẽ phải bypass các điều kiện if ở trên bằng việc đặt header ***X-Biocorp-Vpn*** thành ***80.187.61.102***. ![image](https://hackmd.io/_uploads/Hk16uK9Mke.png) Vậy là ta đã có thể thực hiện các thao tác trên trang web rồi. Bây giờ ta sẽ upload data xml theo format tác giả đã để trong file code. ``` <?xml version="1.0" encoding="UTF-8"?> <reactor> <status> <temperature>420</temperature> <pressure>1337</pressure> <control_rods>Lowered</control_rods> </status> </reactor> ``` Lúc này ta sẽ thay đổi đoạn code một chút. Sử dụng thẻ DOCTYPE để đặt biến xxe chứa một đường dẫn như sau: `<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///flag.txt"> ]>` Sau đó đặt ***&xxe;*** vào một thẻ nào đó bên trong thẻ status. ``` <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///flag.txt"> ]> <reactor> <status> <temperature>&xxe;</temperature> <pressure>1337</pressure> <control_rods>Lowered</control_rods> </status> </reactor> ``` Cuối cùng, chỉ cần đặt header ***Content-type: application/xml*** và đổi phương thức http POST và nhận flag. ![image](https://hackmd.io/_uploads/HyGHsYcMyg.png) >Flag: INTIGRITI{c4r3ful_w17h_7h053_c0n7r0l5_0r_7h3r3_w1ll_b3_4_m3l7d0wn} ## Web - Fruitables Ở trang web này, khi mình vào tất cả các trang thì nhận thấy rằng các web này không có chức năng gì khả dụng để khai thác. Vì vậy mình dùng công cụ ***Dirsearch*** để tìm các endpoint ẩn và ra kết quả như sau: ![image](https://hackmd.io/_uploads/SkyR2t9zke.png) Ok, mình tìm thấy một đường link dẫn tới account.php. Khi vào trang web, mình thấy được một trang đăng kí, thế nhưng khi bấm đăng kí thì có vẻ không khả quan lắm vì họ không cho đăng kí thêm tài khoản. ![image](https://hackmd.io/_uploads/HkHUpYcfke.png) Mình thử submit một vài kí tự đặc biệt thì có một bất ngờ nho nhỏ, đó là họ sử dụng SQL để truy vấn đăng nhập, thế nhưng lại không rào ***SQL Injection*** ![image](https://hackmd.io/_uploads/By2haK5f1x.png) Chính vì vậy, mình sẽ thử khai thác bằng tool ***sqlmap*** để lôi bảng users ra. ![image](https://hackmd.io/_uploads/ryXXCYqfyl.png) Trong bảng users, người ra đề đã mã hóa hàm băm mật khẩu lại, vậy nên ta sẽ dùng john để xác định loại hàm băm đó là gì bằng cách sử dụng [Hash Identifer](https://hashes.com/en/tools/hash_identifier) ![image](https://hackmd.io/_uploads/S1gFPZ5qMJe.png) Đây là hàm băm Bcrypt có biến thể là Blowfish, dùng tool John The Ripper để crack password (sử dụng từ điển rockyou.txt) ![image](https://hackmd.io/_uploads/rk9Jz99fJl.png) Vậy password của admin sẽ là ***futurama***. Sử dụng username và password đã tìm được, ta đăng nhập vào và tìm được 1 trang upload. Khi mình thử upload một file php lên thì họ đã rào lại file của mình, vì trang web của họ chỉ nhận file png và jpeg. ![image](https://hackmd.io/_uploads/HkmpGcqMkl.png) Mình thử đổi tên đuổi file khi update load vì nghĩ rằng họ kiểm tra file của mình ở tên file, thế nhưng có vẻ không khả quan lắm. ![image](https://hackmd.io/_uploads/HJUgN9qMJx.png) Lúc này, mình nghĩ đến việc họ sử dụng hàm có thể check magic byte của file php. Vậy nên mình sẽ gửi trước một request chứa ảnh jpg, sau đó chỉnh sửa các byte về đoạn code php. ><?php echo exec('cat /*'); ?> ![image](https://hackmd.io/_uploads/Bk3QI59zJx.png) Khi ta sử dụng Dirsearch, ngoài file account.php ta còn tìm được đường dẫn thư mục uploads, có lẽ các file khi được upload sẽ lưu trên đó. Truy cập vào /uploads/(tên file server trả về), ta tìm được được đoạn flag. ![image](https://hackmd.io/_uploads/SkFgDc9Myx.png) >Flag: INTIGRITI{fru174bl35_vuln3r4b1l17y_ch3ckm8}