II. Phân tích và khai thác các lỗ hổng Server-side request forgery (tiếp)
5. Kiểm tra lỗ hổng SSRF
SSRF thường xuất hiện ở các chức năng có quá trình trao đổi dữ liệu với một trang web khác. Và chúng ta cũng cần có một phương pháp để kiểm tra khả năng xảy ra của lỗ hổng SSRF. Một trong những phương pháp kiểm tra phổ biến và đơn giản nhất chính là kỹ thuật out-of-band (OAST). Trong bài viết này tôi sẽ trình bày kỹ thuật với công cụ Burp Collaborator.
Nếu các bạn để ý thì trong các bài lab trước tôi cũng có sử dụng tới kỹ thuật này nhằm xác định chính xác hơn khả năng xảy ra lỗ hổng SSRF.
Một ví dụ khác cho kỹ thuật out-of-band (OAST) qua bài lab sau.
Phân tích lab Blind SSRF with out-of-band detection
Miêu tả: Trang web sử dụng một phần mềm tìm nạp và phân tích URL được xác định trong header Referer khi người dùng truy cập vào trang hiển thị chi tiết sản phẩm. Để giải quyết bài lab, chúng ta cần sử dụng chức năng này thực hiện một kịch bản DNS lookup với server Burp Collaborator.
Khi truy cập vào trang hiển thị chi tiết sản phẩm, quan sát request trong Burp Suite:
Chúng ta thấy header Referer mang giá trị địa chỉ URL của trang được truy cập ngay trước khi chúng ta truy cập tới giao diện hiển thị chi tiết sản phầm /product?productId=1
Thay đổi giá trị header Referer thành địa chỉ tới domain Burp Collaborator.
Gửi request, quan sát tại Burp Collaborator Client thu được request đến từ server trang web chứa lỗ hổng SSRF.
Như vậy chúng ta đã thực hiện DNS lookup thành công, tại header Referer có khả năng bị tấn công SSRF, bài lab được giải quyết!
Việc xác nhận lỗ hổng SSRF thông qua kỹ thuật out-of-band (OAST) không chỉ giúp chúng ta tiết kiệm thời gian, tài nguyên trong quá trình kiểm thử sản phẩm, mà còn là một bước bắt buộc trong việc khai thác lỗ hổng Server-side request forgery ở dạng blind - Một dạng lỗ hổng SSRF không trả về kết quả trong giao diện, sẽ được chúng ta bàn bạc và phân tích kỹ hơn ở phần sau.
6. SSRF và bypass white-based input filters
Bên cạnh sử dụng black list, một số trang web sử dụng white list gồm các phần tử bắt buộc phải xuất hiện trong các tham số. Việc sử dụng white list thường mang lại hiệu quả tốt hơn black list, tuy nhiên các nhà phát triển cần cập nhật white list liên tục mỗi khi có chức năng hoặc các phần tử mới cần bổ sung. Việc cài đặt cơ chế ngăn chặn dựa theo white list vẫn có khả năng bị kẻ tấn công vượt qua bởi các ký tự có chức năng đặc biệt trong URL.
6.1. Cài đặt cơ chế ngăn chặn white list dựa theo port
Một trang web có một cơ chế ngăn chặn lỗ hổng SSRF bằng cách giới hạn chỉ cho phép người dùng truy cập tới port tại local. Đoạn code vận hành với ngôn ngữ PHP như sau:
<?php
$url = 'http://' . $_GET['url'];
$parsed_url = parse_url($url);
if ($parsed_url['port'] == '80') { readfile($url);
} else { echo "SSRF attack detected!";
}
Tham số url
truyền giá trị qua phương thức GET, sau đó trang web thực hiện hàm parse_url() phân tích cú pháp URL giá trị này và kiểm tra biến $parsed_url['port']
có bằng hay không, nếu có thực hiện hàm readfile()
đọc giá trị từ $url
. Điều này chỉ cho phép người dùng có thể truy cập tới nội dung public tại port :
Đoạn code không hề có vấn đề gì? Thực tế không phải như vậy, lỗ hổng xảy ra ở cách hoạt động xung đột của hai hàm parse_url()
và readfile()
trong PHP. Xét giá trị url=localhost:8888:80
, thực tế ở đây, hai hàm readfile()
và parse_url()
đang quan tâm hai port khác nhau!
Theo cách định nghĩa trong ngôn ngữ PHP thì: readfile()
sẽ mặc định chọn port xuất hiện đầu tiên (sau dấu hai chấm), còn parse_url()
sẽ chọn port xuất hiện cuối cùng!
Và hình ảnh phía trên cũng chính là cách vượt qua cơ chế ngăn chặn tấn công trong đoạn code chúng ta đang xét:
6.2. Cài đặt cơ chế ngăn chặn white list dựa theo host
Chúng ta sẽ tiếp cận với cơ chế ngăn chặn đơn giản bằng cách kiểm tra các phần tử trong danh sách white list có xuất hiện trong chuỗi URL hay không. Từ đó thực hiện ngăn chặn các hành vi tấn công SSRF.
Xem xét đoạn code viết bằng ngôn ngữ Python sau:
from flask import *
import requests app = Flask(__name__) @app.route('/ssrf')
def follow_url(): url = request.args.get('url', '') whitelist = ['google.com', 'youtube.com'] check = 0 for i in whitelist: if i in url: check = 1 if check: return (requests.get(url).text) else: return ("SSRF Attack detected!") if __name__ == '__main__': app.run(host = "0.0.0.0", port = 1234)
Đoạn code trên chứa một white list
kiểm tra biến url
cần chứa một trong các phần tử google.com
hoặc youtube.com
, với mong muốn sẽ ngăn chặn kẻ tấn công thực hiện khai thác lỗ hổng SSRF vào các trang web khác, chỉ cho phép hai trang web trên trong white list.
Điều này có nghĩa rằng, để khai thác được lỗ hổng SSRF thì trong tham số url
luôn cần chứa giá trị google.com
hoặc youtube.com
. Ý tưởng để vượt qua cơ chế ngăn chặn này là chúng ta có thể "nhúng" địa chỉ vào URL này. Thực hiện bằng các ký tự đặc biệt như @
, #
, &
, .
. Kết quả sau đúng với phần lớn các ứng dụng web:
https://expected-host@evil-host
https://evil-host#expected-host
https://expected-host.evil-host
Ngoài ra có thể sử dụng kỹ thuật URL encode và kết hợp khéo léo các kỹ thuật trên với nhau.
Quay lại ví dụ phía trên, chúng ta có thể "nhúng" địa chỉ localhost:8888
vào tham số url
vượt qua cơ chế white list bằng một vài cách như sau:
- Cách : Sử dụng ký tự
@
, payload:url=http://google.com@localhost:8888
- Cách : Sử dụng ký tự
#
. Nếu trực tiếp truyền giá trịurl=http://localhost:8888#google.com
thì khi thực thirequest.args.get('url', '')
trang web chỉ nhận được giá trịurl=http://localhost:8888
do ký tự#
lúc này được hiểu với vai trò anchor link. Bởi vậy cần thực hiện URL encode ký tự#
thành%23
để khi URL decode tự động ký tự#
được hiểu là một fragment, payload:url=http://localhost:8888%23google.com
Xem xét một trường hợp khác, sử dụng white list thực hiện ngăn chặn tấn công SSRF với đoạn mã nguồn viết bằng PHP như sau:
<?php
$url = 'http://' . $_GET['url'];
$parsed_url = parse_url($url);
if ($parsed_url['host'] == 'google.com') { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_exec($ch);
} else { echo "SSRF attack detected!";
}
Đoạn code sử dụng hàm parse_url()
trong PHP phân tích cú pháp URL đầu vào, kiểm tra giá trị host $parsed_url['host']
bắt buộc phải là google.com, tiếp theo sử dụng hàm curl_exec()
của thư viện cURL hiển thị nội dung trang web.
Mục đích của kẻ tấn công là đọc nội dung tại http://11.0.0.1:1234
là một địa chỉ chỉ có thể truy cập từ mạng nội bộ. Chắc hẳn qua ví dụ cài đặt white list với port ở phía trên các bạn cũng đã đoán ra vấn đề ở đây rồi phải không? Đúng vậy, lỗ hổng SSRF xảy ra do định nghĩa về giá trị host của hàm parse_url()
và thư viện cURL không đồng nhất. Chẳng hạn với URL http://viblo@evil.com@google.com
(chuỗi viblo ở đầu có thể thay bằng bất kỳ giá trị khác):
Như trong hình vẽ, thư viện cURL sẽ ưu tiên host là evil.com
, đối với hàm parse_url() lại lựa chọn google.com
. Bởi vậy, chúng ta có thể xây dựng payload tấn công như sau: url=viblo@11.0.0.1:1234@google.com
Các tải liệu tham khảo
- https://portswigger.net/web-security/ssrf
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Request%20Forgery
- https://book.hacktricks.xyz/pentesting-web/ssrf-server-side-request-forgery
- https://www.blackhat.com/docs/us-17/thursday/us-17-Tsai-A-New-Era-Of-SSRF-Exploiting-URL-Parser-In-Trending-Programming-Languages.pdf