KMA CTF 2023 LẦN 1 WRITE UP
Web challenges
Vào đây!
Phân tích
Vào chall ta nhận được full source code bằng nodejs, đọc sơ qua thì thấy web có 3 api
/login
: Đăng nhập với 2 input là user và passregister
: Đăng ký với 3 input là user, pass và bio/
: Show source code
Test thử các api đó xem nó hoạt động sao:
/register
:
/login
:
Vì khi đăng ký password được hash md5 nhưng khi đăng nhập lại không hash md5 password mình nhập nên mình cần tự md5 password trước khi đưa vào request
Các tính năng chỉ có vậy.
Quay lại đọc kỹ hơn vào source code thì ta thấy có nodejs và mysql
Một hướng khai thác bypass login khi hệ thống sử dụng nodejs và mysql là đây
Ta thấy rằng:
- Hệ thống lấy thẳng dữ liệu từ
req.body
để truyền vào câu truy vấn:
- Khi truyền dữ liệu với Content-Type: application/x-www-form-urlencoded:
- Khi truyền dữ liệu với Content-Type: application/json:
- Trong nodejs khi bạn
parse()
lại json thì nó sẽ trả về đối tượng object trước. Do đó khi thay giá trị của chuỗipassword
thành một object thì object đó sau khi parse sẽ được truyền vào câu truy vấn và gây ra lỗi:
- Sửa object rỗng trên thành một object có giá trị là cặp
"key": "value"
:
- Vẫn gây ra lỗi nhưng câu truy vấn của trúng ta lúc này là:
- Wow
key
vàvalue
đã được đưa vào câu truy vấn và tạo thành một chuỗi so sánh. key
được kẹp trong cặp ký tự`
(`key`
) mà ký tự`
(backtick) trong MySQL được sử dụng để bao quanh các đối tượng cơ sở dữ liệu như tên bảng, tên cột, tên trường và các từ khóa.- Giải thích dễ hiểu thì đoạn
password = `key` = 'value'
này có thể hiểu là so sánh cộtpassword
bằng cộtkey
nếu bằng sẽ trả về 1 (true) không bằng sẽ trả về 0 (false) sau đó lấy 2 giá trị true và false tiếp tục so sánh với'value'
Khai thác
- Dựa trên ý tưởng payload như vậy ta gửi request:
- Câu truy vấn sẽ trở thành:
- Tại đây ta có cột
password
bằng cột`password`
nên sẽ trả vềtrue
và bằng với giá trịtrue
ở vế so sánh sau và thế là ta đã bypass được đoạn login trên.
Áp dụng payload tương tự vào challenge và thế là có được flag :v
More info:
- Không chỉ riêng object, mysqljs còn bị lỗi tương tự khi ta truyền vào mảng:
user[username]=1&pass[password]=1
- Nếu không biết tên cột thì ta có thể sử dụng payload ngược lại, ví dụ:
Bởi vì lúc này cột password
bằng cột `username`
sẽ trả về false
và bằng với false
ở vế so sánh sau.
Jo`in Le'm
Phân tích
Vào challenge ta nhận được 1 đoạn code php với rất nhiều câu lệnh goto trông rất rối mắt @@
Sau một hồi ngồi đọc và làm "đẹp" (đối với mình) thì mình thu được:
Cái này trong lúc làm mình follow theo nên vẫn hiểu, giờ ngồi viết lại, đọc mà thấy rối ngang :v
Nói sơ qua thì:
- Đầu tiên đoạn code này thực hiện hàm
show_source()
- Sau đó define hàm
curl()
, hàm này sẽ kiểm tra xem response có bị redirect đi đâu không, nếu có thì tiếp tục thực hiện curl đến đường dẫn được redirect đó và trả về content khi curl đến đường dẫn cuối. - Để gọi được hàm
curl()
ở trên cần phải bypass qua rất nhiều điều kiện check, đầu tiên là checkscheme
của biến$url
xem có phải làhttp
hoặchttps
hay không? Nên việc dùngfile://<file_name>
hoặc tương đương đều không được. - Qua check
scheme
chương trình tiếp tục check đếnhost
của$url
xem có phảihost
hoặcgethostbyname()
ra ip là127.0.0.1
không? Nên việc dùnghttp://127.0.0.1/<file_name>
vàhttps://localhost/<file_name>
hoặc tương đương đều không được. - Qua check
host
chương trình tiếp tục check xemurlencode($url)
vàcurl_escape(..., $url)
có ra kết quả giống nhau không nếu giống thì sẽdie
chương trình luôn. - Khi đã vượt qua tất cả những điều kiện trên thì hàm
curl($url)
với$url
do chúng ta control mới được thực hiện. Và nó sẽ được in ra ngay trên màn hình.
Đầu tiên mình muốn biết hàm curl()
kia khi được gọi nó sẽ trả về như thế nào nên mình đã thuận theo những điều kiện check như là scheme
phải là https
và host
không được là localhost
nên mình chọn $url=https://example.com
. Nhưng để call được hàm curl()
thì vẫn cần phải pass qua 1 điều kiện so sánh về encode nữa. Sau một hồi search thì mình có tìm ra ký tự đó là +
(%20) và tiến hành curl thử:
Dĩ nhiên không được vì không có url nào là https://example.com%20
cả nên mình thêm ?a=+
và urlencode nó để trong biến $url
vẫn có ký tự +
nhưng url chỉ có https://example.com
:
Nhưng vẫn không được vì khi ta sử dụng dấu +
thì sẽ làm hỏng gói tin http request dẫn đến không nhận về được gì.
Mình tiếp tục search thì tìm ra thêm 1 ký tự nữa đó chính là ký tự ~
. Thực hiện curl với ký tự này:
Vậy là đã có thể thực hiên curl được!
Vì chưa biết flag nằm ở đâu nhưng tâm link mách bảo mình rằng nó nằm trong local nên mình tìm cách bypass qua những điều kiện check scheme
và host
để vô được local thì hàm curl()
được define đã giúp mình làm việc đó khi nó cho phép thực hiện curl
đến url redirect. Vậy nên ý tưởng là mình sẽ web host 1 website php với tính năng là khi được GET thì nó sẽ redirect đến đường dẫn file://<file_name>
và file://<file_name>
sẽ được curl()
rồi trả về content tại màn hình.
Khai thác
-
Thực hiện tạo file
a.php
:<?php header("Location: file:///etc/passwd"); ?>
-
Host server php bằng command:
$ php -S 127.0.0.1:9000
-
Ngrok để public file
a.php
ra ngoài internet bằng command:$ ngrok http 9000
-
Curl đến public site:
Trong lúc diễn ra giải thì mình bế stuck từ đây khi không tìm được gì :<
Sau khi kết thúc giải được hint từ tác giả và những người solved mình mới có thể đi tiếp:
- Sửa header Location thành
file:///proc/mounts
và thực hiện curl:
- Sửa header Location thành
file:///home/siuvip_saoanhbatduocem/etc/passwd
và thực hiện curl:
Flag Holder
Phân tích
Vào challenge ta nhận được một form submit khi click Submmit
thì ta sẽ được kết quả
Ngoài ra còn 1 path được ẩn đi đóa là path /source
ta có thể tìm thấy nó qua scan path hoặc Ctrl + U
View source:
Đoạn code trên là một ứng dụng web Flask gồm 3 route:
/
Show form submit/render
Hiển thị nội dung được tạo bởi form trên/source
Show source
Để có được flag thì ta phải thêm được chuỗi {FLAG}
vào trong biến template
.
Mới nhìn sơ qua thì rất dễ để đoán bài này dính lỗi SSTI nhưng sau khi thử bypass qua blacklist mãi không được cộng thêm với việc có hint
và
và cả của người đã solved vì mình làm bài này sau khi giải đã kết thúc :<
Mình bắt đầu đi search và tìm được bài này
Hiểu đơn giải thì ký tự İ
(chữ I
in hoa của Thổ Nhĩ Kỳ) sau khi đi qua hàm lower()
của python sẽ được tác ra là hai ký tự là U+0049
Chữ La tinh in hoa (I
) và U+0307
là dấu chấm ở bên trên
Vậy nên sau khi lower()
ký tự İ
thì len của nó sẽ tăng lên gấp đôi.
Khai thác
Hàm lower()
được gọi khi hàm waf()
được gọi để check blacklist và chỉ lấy 20 ký tự đầu để kiểm tra. Nên dựa theo phân tích bên trên ta chỉ cần input chuỗi template
có 10 ký tự đầu là "İİİİİİİİİİ"
còn 10 ký tự sau có thể nhập tùy ý vì sau khi lower()
ta có len("İİİİİİİİİİ".lower()) = 20
vào trong 20 ký tự này thì không có trong blacklist.
Vậy nên ta có payload cuối cùng là ?template=İİİİİİİİİİ{FLAG}&variable=a
:
Updating
All rights reserved