I. Đặt vấn đề
1. HTTP Host Header
Trong giao thức HTTP, trường Header Host được sử dụng để chỉ định tên miền (domain name) của máy chủ web được đang được truy cập hoặc đang trả về nội dung. Host header được sử dụng trong các yêu cầu HTTP cho phép máy chủ web nhận biết tên miền được yêu cầu và phục vụ nội dung phù hợp.
Xét thao tác truy cập URL https://viblo.asia/followings
, quan sát trong Burp Suite, có thể thấy header Host tại dòng của request:
Trong đó, viblo.asia
là tên miền của máy chủ web mà yêu cầu này được gửi đến. Khi server nhận được yêu cầu này, Header Host được sử dụng để xác định nội dung phù hợp trả về cho yêu cầu này.
2. Tấn công tiêu đề Host trong giao thức HTTP
(hình ảnh từ https://portswigger.net/)
Tấn công tiêu đề Host trong giao thức HTTP (HTTP Host header attack) là dạng tấn công dựa vào hành vi thay đổi hoặc sử dụng không đúng trường header Host và một số trường header có tính năng tương tự. Lỗ hổng xảy ra khi hệ thống cấu hình sai hoặc do các lỗi logic trong lập trình.
II. Phân tích các dạng HTTP Host header attack và ngăn chặn
1. Warm up
Ví dụ với ngôn ngữ PHP, chúng ta có thể lấy giá trị Host header trong yêu cầu người dùng bằng $_SERVER['HTTP_HOST']
. Và một trong những nguyên nhân phổ biến nhất dẫn đến dạng tấn công này là do lập trình viên sử dụng $_SERVER['HTTP_HOST']
khi cần dùng tới giá trị của domain mà không có quy trình kiểm tra tốt cho giá trị này.
Các bạn có thể dựng một web đơn giản với mục đích thử nghiệm bằng đoạn code như sau:
$host = $_SERVER['HTTP_HOST'];
echo "Host: " . $host;
Chương trình sẽ hiển thị giá trị header Host khi chúng ta truy cập:
Bởi vì trang web không thực hiện kiểm tra giá trị header Host được người dùng gửi tới, kẻ tấn công có thể dễ dàng thay đổi giá trị header Host bằng Burp Suite:
2. Tấn công Host header với tính năng đặt lại mật khẩu
Một chức năng thường thấy trong dạng tấn công này là Đặt lại mật khẩu, đây cũng là một tính năng quen thuộc với người sử dụng và xuất hiện hầu hết trong tất cả website quản lý người dùng bằng tài khoản.
Thông thường, tính năng reset password sẽ gửi một đường dẫn đặt lại mật khẩu tới email của người dùng yêu cầu. Phần lớn đường dẫn có dạng như sau:
https://<domain-name-of-website>/?token=<random-token>
Khi lập trình viên xây dựng chương trình cho chức năng này sẽ cần sử dụng đến tên miền của ứng dụng. Xét đoạn code sau:
if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Get user input $email = $_POST['email']; // Validate email if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $error = 'Invalid email address'; } else { // Generate a random token $token = bin2hex(random_bytes(32)); // Send reset link to user's email $reset_link = 'https://' . $_SERVER['HTTP_HOST'] . '/forgotpass?token=' . $token; // For testing: echo "Reset link: " . $reset_link; // Process ... }
}
?>
Đoạn code trên đã tạo một đường dẫn đặt lại mật khẩu gửi tới email của người dùng, trong đó phần domain website đã trực tiếp ghép chuỗi từ giá trị $_SERVER['HTTP_HOST']
:
$reset_link = 'https://' . $_SERVER['HTTP_HOST'] . '/forgotpass?token=' . $token;
$_SERVER['HTTP_HOST']
lấy giá trị từ header Host trong HTTP request nên nó hoàn toàn có thể bị thay đổi bởi kẻ tấn công do chức năng không chứa các bước kiểm tra giá trị này. Chẳng hạn:
Khi đó, kẻ tấn công có thể lợi dụng chức năng này nhằm gửi một email giả mạo domain (thường là domain do kẻ tấn công sở hữu) đến nạn nhân, khi họ không chú ý và click vào đường link đó, kẻ tấn công sẽ lấy được giá trị token của họ và thực hiện đổi mật khẩu email nạn nhân bằng token đó.
Ví dụ với Collaborator client trong Burp Suite:
Bạn đọc có thể luyện tập hình thức tấn công này tại bài lab Basic password reset poisoning.
Để ngăn chặn tấn công bằng cách thay đổi giá trị header Host trong request, có thể sử dụng một số cách như sau:
Đối với ứng dụng chứa domain xử lý đặt lại mật khẩu duy nhất, không cần lấy giá trị $_SERVER['HTTP_HOST']
.
$reset_link = 'http://example.com/forgotpass?token=' . $token;
Nếu có nhiều domain, có thể sử dụng whitelist:
$expected_domains = array('example.com', 'example2.com', 'example3.com');
$host = $_SERVER['HTTP_HOST']; // Compare the Host header value with the expected domains
if (!in_array($host, $expected_domains)) { // Log or handle the error, and reject the request die('Invalid Host header');
}
Trong trường hợp đường link đặt lại mật khẩu chứa thông tin do người dùng nhập, nên kiểm tra tính hợp lệ của giá trị input:
$email = $_POST['email']; // Assuming user input is used to retrieve email // Validate and sanitize the email address
$email = filter_var($email, FILTER_VALIDATE_EMAIL);
if (!$email) { // Log or handle the error, and reject the request die('Invalid email address');
} // Generate the reset password link using the sanitized email address
$reset_link = 'http://example.com/forgotpass?token=' . $token . '&email=' . urlencode($email);
3. Ứng dụng hỗ trợ một số header khác
Trong quá trình kiểm tra khả năng tấn công HTTP Host header, bên cạnh header Host, chúng ta cũng cần chú ý đến một số header khác có tính năng tương tự:
X-Forwarded-Host
X-Host
X-Forwarded-Server
X-HTTP-Host-Override
Forwarded
Trong trường hợp ứng dụng hỗ trợ các header này, kẻ tấn công hoàn toàn có thể lợi dụng chúng nhằm bypass cơ chế phòng ngừa của ứng dụng, hoặc ghi đè giá trị Host.
Điều này thường xảy ra đối với các ứng dụng sử dụng kiến trúc hệ thống trung gian (chẳng hạn một máy chủ reverse proxy). Ví dụ, header X-Forwarded-Host
thường được sử dụng để gửi thông tin về host gốc của yêu cầu từ client tới server backend. Header này được thêm vào yêu cầu HTTP bởi reverse proxy để truyền tải thông tin về host mà client gửi yêu cầu ban đầu. X-Forwarded-Host
giúp khắc phục vấn đề một reverse proxy có thể phục vụ nhiều tên miền khác nhau trên cùng một địa chỉ IP bằng cách xác định tên miền gốc mà client đã sử dụng, cho phép reverse proxy định tuyến yêu cầu đúng đến server backend phù hợp với tên miền đã được yêu cầu.
Khi trong request chứa header X-Forwarded-Host
, nhiều framework sẽ sử dụng nó thay vì header Host, dẫn đến kẻ tấn công có thể bypass cơ chế kiểm tra tính hợp lệ của request dựa vào thay đổi giá trị header X-Forwarded-Host
.
GET /example HTTP/1.1
Host: vulnerable-website.com
X-Forwarded-Host: bad-stuff-here
Ví dụ một ứng dụng có mã nguồn như sau:
const express = require('express');
const app = express(); // Định nghĩa route
app.get('/example', (req, res) => { // Lấy giá trị của header Host từ header X-Forwarded-Host const host = req.header('x-forwarded-host') || req.header('host'); // Trả về giá trị của header Host res.send(`Host: ${host}`);
}); app.listen(3000, () => { console.log('Listen on port 3000');
});
Trong ví dụ này, server sử dụng Express framework định nghĩa route /example
để xử lý yêu cầu GET. Trong đó, giá trị của header Host có thể được lấy từ X-Forwarded-Host hoặc Host, tùy thuộc vào sự tồn tại hay không của X-Forwarded-Host. Sau đó, giá trị này được gửi về phản hồi cho người dùng.
Bài lab luyện tập kỹ thuật khai thác này: Password reset poisoning via middleware
Hình thức tấn công HTTP Host header mang nét tương đối đặc trưng bởi cách thức thực hiện dựa vào việc thêm / thay đổi giá trị các header đặc biệt như Host
, X-Forwarded-Host
, ... nên về phương pháp ngăn chặn cơ bản chúng ta chỉ cần thêm một bước kiểm tra các header này. Ví dụ đoạn code sau kiểm tra giá trị header X-Forwarded-Host
trong request gửi tới:
// Nếu giá trị của header X-Forwarded-Host không hợp lệ, sử dụng giá trị của header Host
if (!isValidHost(xForwardedHost)) { res.send(`Host: ${host}`);
} else { // Xử lý lỗi khi giá trị của header X-Forwarded-Host không hợp lệ res.status(400).send('Invalid header X-Forwarded-For');
}
Ngoài ra, có thể tùy chỉnh cấu hình reverse proxy hoặc web server không cho phép ghi đè các header, hoặc thiết lập sẵn tên miền hiện tại trong cấu hình của server, không cho phép lấy giá trị tên miền từ người dùng.