III. Static extension cache rules attack
1. Cache rules
Cache rules dành cho static extension thường lưu lại các tệp tin .css
, .js
, ico
, jpg
, png
, gif
, ... trên server cache, vì đây là các file tĩnh và hầu như không thay đổi nội dung. Ý tưởng này khá đơn giản nên cache rules dành cho chúng thường được xây dựng đơn sơ:
rules: # Cache all requests with common static file extensions - condition: url: extensions: ["css", "js", "ico", "jpg", "png", "gif"] action: cache: true ttl: 86400 # Cache for 24 hours
Như vậy, khi người dùng truy cập tới URL kết thúc với một trong các extension trên sẽ khớp với cache rule này và nhận được nội dùng từ cache server (bộ nhớ đệm kéo dài giờ). Chẳng hạn http://example.com/a/b/c/d.css
2. Sự khác biệt của path mapping
Tiếp theo, chúng ta xem xét một chức năng cho phép người dùng truy cập hồ sơ cá nhân của bản thân, giao diện trả về các thông tin người dùng, truy cập qua URL http://example.com/user/123/profile/
. Server chính Express.js chứa route xử lý chức năng này như sau:
app.get('/user/:userId/profile', (req, res) => { const userId = req.params.userId; const userProfile = getUserProfile(userId); if (!userProfile) { return res.status(404).send('User not found'); } res.json(userProfile);
});
Khi người dùng truy cập tới /user/:userId/profile
, route chỉ lấy thông tin biến userId
rồi trả về dữ liệu khi tìm thấy thông tin qua database.
Attacker có thể lợi dụng cách xử lý này, gửi yêu cầu với URL http://example.com/user/123/profile/abcd.css
, lúc này:
- Server chính nhận giá trị
userId=123
trả về thông tin người dùng trong cơ sở dữ liệu. - Server cache nhận thấy yêu cầu kết thúc bởi extension
.css
ở cuối, phù hợp với cache rule nên sẽ thực hiện lưu nội dung server chính trả về vào server cache. Trạng thái response attacker lần đầu nhận được chứa headerX-Cache: miss
.
Như vậy, khi người dùng khác truy cập y hệt đường dẫn URL http://example.com/user/123/profile/abcd.css
sẽ nhận được nội dung cache từ hồ sơ của attacker.
Đổi ngược lại, kịch bản tấn công của attacker sẽ là gửi trực tiếp đường dẫn này cho nạn nhân để "đánh lừa" server cache lưu trữ lại thông tin hồ sơ của nạn nhân, rồi attacker truy cập các lần sau để nhận được thông tin đó: Response của nạn nhân chứa header X-Cache: miss
, của attacker chứa header X-Cache: hit
.
Bài lab dành cho bạn đọc luyện tập kỹ thuật khai thác trên: pExploiting path mapping for web cache deception
3. Sự khác biệt của ký tự phân cách (Delimiter)
Thực tế, trong quá trình kiểm thử hành vi xử lý khác biệt của server chính và server cache chúng ta thường sử dụng tới các ký tự phân cách. Thường thấy nhất là ký tự ?
giúp phân cách phần path và các tham số gửi kèm. Bên cạnh đó, với mỗi ký tự đặc biệt, các server sử dụng framework, ngôn ngữ lập trình khác nhau sẽ hiểu chúng theo cách khác nhau. Ví dụ, với ký tự ;
- Server sử dụng Java Spring framework sẽ chỉ hiểu phần đứng trước
;
là path cần truy cập và xử lý. Chẳng hạn server gốc khi nhận được/profile;foo.css
sẽ chỉ xử lý/profile
. - Server cache vẫn sẽ nhận và xử lý toàn bộ chuỗi
/profile;foo.css
này nên tồn tại nguy cơ bị tấn công bởi lỗ hổng web cache deception.
Đôi khi chúng ta không thể xác định chính xác các server xử lý các ký tự phân cách như thế nào. Để kiểm thử, chúng ta có thể dùng kỹ thuật fuzzing tất cả các ký tự đặc biệt và xem xét hành vi của response có thay đổi hay không (Lưu ý rằng cần xét thêm các trường hợp URL encoded của các ký tự này). Chẳng hạn Portswigger đưa ra danh sách các ký tự phân cách thường gặp tại Web cache deception lab delimiter list
Phân tích bài lab Exploiting path delimiters for web cache deception: Mục đích của chúng ta là đánh lừa người dùng carlos truy cập URL dẫn đến cache lại thông tin API key của anh ta.
Đăng nhập tài khoản wiener, tại trang hồ sơ cá nhân chứa thông tin API của mỗi người dùng:
Quan sát các request đã đi qua proxy history của Burpsuite, nhận thấy các file tĩnh như .css
, .ico
response trả về từ server cache:
Chúng ta có thể lợi dụng cache rules các file tĩnh này để khai thác lỗ hổng web cache deception, với mong muốn đánh lừa server chính cache lại hồ sơ người dùng chứa thông tin API key.
API tại /my-account
là file dynamic nên response không có header cho thấy dấu hiệu cache:
Thử chèn thêm chuỗi /abc.ico
vào sau /my-account
, nhận thấy cache rules được kích hoạt:
Tuy nhiên response trả về thông báo lỗi "Not Found", chứng tỏ server chính đã hiểu request này truy cập đến path /my-account/abc.ico
. Chúng ta mong muốn kích hoạt cache rules tại server cache (kết thúc bằng extension .ico
) và server chính "hiểu" request chúng ta đang truy cập tới /my-account
.
Hướng đi phù hợp và khả thi nhất hiện tại là "tìm kiếm" một ký tự phân cách nằm giữa my-account
và abc.ico
để đánh lừa server gốc. Thực hiện brute force với chức năng Intruder của Burpsuite, wordlist tại https://portswigger.net/web-security/web-cache-deception/wcd-lab-delimiter-list:
Kết quả cho thấy có thể sử dụng ký tự ;
đánh lừa server chính, response với header X-Cache: miss
cho thấy thông tin đã được lưu vào server cache với thời gian giây.
Công việc cuối cùng chính là gửi cho nạn nhân URL này:
https://0a0400f5037c8acc8175209a005a0068.web-security-academy.net/my-account;abc.ico
4. Ngăn chặn static extension cache rules attack
Để tránh trường hợp các nội dung động (như hồ sơ người dùng) bị lưu nhầm vào bộ nhớ cache, cần đảm bảo chỉ các tệp tin tĩnh mới được lưu trữ. Một cách ngăn chặn thường thấy là thêm điều kiện bổ sung để loại trừ các URL động có chứa biến người dùng hoặc phân đoạn không thuộc dạng tệp tĩnh.
Ví dụ cache rules sau khi tùy chính loại bỏ lưu trữ các nội dung thuộc /user/*/profile
rules: - condition: url: extensions: ["css", "js", "ico", "jpg", "png", "gif"] path: exclude: ["/user/*/profile"] action: cache: true ttl: 86400 # Cache trong 24 giờ
Hướng ngăn chặn khác là thay đổi cách hoạt động của route xử lý /user/:userId/profile
. Chúng ta có thể thiết lập Cache-Control headers cụ thể cho các nội dung động để ngăn không cho cache lưu trữ lại những dữ liệu này. Chẳng hạn, với các thông tin nhạy cảm như hồ sơ người dùng, có thể thêm Cache-Control: no-store
hoặc Cache-Control: private
để chặn lại bộ nhớ đệm của cache server.
Route minh họa:
app.get('/user/:userId/profile', (req, res) => { const userId = req.params.userId; const userProfile = getUserProfile(userId); if (!userProfile) { return res.status(404).send('User not found'); } res.setHeader('Cache-Control', 'no-store, must-revalidate'); res.json(userProfile);
});
Ngoài ra, có thể "làm chặt" thêm bằng cách kiểm tra URL và loại bỏ hoặc mã hóa các ký tự phân cách không cần thiết trước khi xử lý, giúp giảm nguy cơ máy chủ xử lý sai lệch các URL chứa ký tự đặc biệt.
app.get('/user/:userId/profile', (req, res) => { const userId = req.params.userId; if (/[^a-zA-Z0-9]/.test(userId)) { return res.status(400).send('Bad Request'); } const userProfile = getUserProfile(userId); if (!userProfile) { return res.status(404).send('User not found'); } res.setHeader('Cache-Control', 'no-store, must-revalidate'); res.json(userProfile);
});
Cuối cùng, kết hợp thêm tường lửa ứng dụng web (WAF) để chặn các yêu cầu bất thường. Các WAF hiện đại có thể phân tích các URL đáng ngờ và chặn các yêu cầu chứa phần mở rộng tệp không hợp lý hoặc các ký tự đặc biệt, giúp bảo vệ máy chủ khỏi các hành vi độc hại.