Cross-site scripting
Last updated
Last updated
Cross-site scripting (XSS) là lỗ hổng bảo mật web cho phép kẻ tấn công xâm phạm các tương tác mà người dùng có với một ứng dụng có lỗ hổng. Nó cho phép kẻ tấn công phá vỡ y. XSS thường cho phép kẻ tấn công ngụy trang thành người dùng nạn nhân, thực hiện bất kỳ hành động nào mà người dùng đó có thể thực hiện và truy cập bất kỳ dữ liệu nào của người dùng. Nếu nạn nhân có đặc quyền với ứng dụng, kẻ tấn công có thể đạt được hoàn toàn quyền kiểm soát đối với tất cả chức năng và dữ liệu ứng dụng.
XSS hoạt dộng bằng cách thao tứng một trang web dễ bị tấn công để trả về JavaScript độc hại cho người dùng. Khi mã độc hại thực thi bên trong trình duyệt của nạn nhân, kẻ tấn công có thể xâm phạm hoàn toàn tương tác của họ với ứng dụng
Có 3 kiểu chính:
Stored XSS: Đoạn mã độc được lưu trữ trong cơ sở dữ liệu của ứng dụng và được phân phối lại cho người dùng khi truy cập trang
DOM-base XSS: Tấn công lơi dụng sự thay đổi trong Document Object Model (DOM) của trang web
Mạo danh hay ngụy trang thành ngươi dùng nạn nhân
Thực hiện bất kỳ hành đọng nào mà người dùng có thể thực hiện
Đọc bất kỳ dữ liệu nào mà người dùng có thể truy cập
Ghi lại thông tin đăng nhập của người dùng
Thực hiện phá hoại giao diện trực tuyến của trang web
Chèn chức năng trojan vào trang web
Tùy thuộc vào bản chất của ứng dụng, chức năng và dữ liệu ứng dụng, cũng như trạng thái của người dùng bị xâm phạm. Ví dụ:
Trong ứng dụng , nơi tất cả người dùng đều ẩn danh và mọi thông tin đều được công khai, tác động thường sẽ rất nhỏ.
Trong ứng dụng lưu trữ dữ liệu nhạy cảm, chẳng hạn như giao dịch ngân hàng, email hoặc hồ sơ chăm sóc sức khỏe, tác động thường sẽ rất nghiêm trọng.
Nếu người dùng bị xâm phạm có quyền cao hơn trong ứng dụng, thì tác động thường sẽ rất nghiêm trọng, cho phép kẻ tấn công kiểm soát hoàn toàn ứng dụng dễ bị tấn công và xâm phạm tất cả người dùng và dữ liệu của họ.
Khai thác lỗ hổng XSS để gửi cookie của nạn nhân đến tên miền của attacker, rồi đưa cookie vào trình duyệt mạo danh nạn nhân
Hạn chế:
Nạn nhân chưa đăng nhập
Ứng dụng sử dụng cờ HttpOnly ẩn cookie khỏi JS
Phiến có thể bị khóa bởi các yếu tố bổ sung như IP của người dùng
Phiên có thể bị hế thời gian trước khi giành được cookie
Giải pháp:
Website có lỗ hổng trong chức năng comment
Trong Burp Proxy quan sát thấy POST request.
Request này chứa nội dung đã nhập cùng csrf token, đồng thời cũng bao gồm cookie phiên. Quan sát cách dữ liệu xử lý qua inspect form comment
Để ý các trường required. Với csrf token và cookie, có thể lấy được qua
document. getElementsByName ( ' csrf' )[ 0 ]. value
document . cookie
Script nhằm tấn công CSRF (ở đây sử dụng event listener đảm bảo code không được thực thi cho tới khi DOM dược load hoàn toàn)
Cách khác: Script sử dụng Burp Collaborator
Tạo bình luận với nội dung comment là script trên
Giờ đây khi nạn nhân duyệt đến blog có comment này, cookie của họ sẽ được hiển thị tại comment
Intercept request tới My account và thay đổi cookie phiên
Lợi dụng trình quản lý mật khẩu tự động điền mật khẩu bằng cách tạo một trường nhập mật khẩu, sau đó đọc mật khẩu được tự động điền và gửi nó đến tên miền của kẻ tấn công
Giải pháp:
Phân tích tương tự Lab trước, nhưng scirpt lần này cần có trường input nhập username, password và mục tiêu đánh cắp là username và mật khẩu
Sử dụng Burp Collaborator
Script tấn công CSRF
Post comment
Một số trang web cho phép người dùng đã đăng nhập thay đổi địa chỉ email mà không cần nhập lại mật khẩu. Nếu phát hiện XSS, kẻ tấn công có thể kích hoạt chức năng này để thay đổi email của nạn nhân thành email mà chúng kiểm soát, sau đó sử dụng dụng năng đặt lại mật khẩu để truy cập tài khoản (bypass anti-CSRF-tokens)
Giải pháp:
Đăng nhấp và sử dụng tính năng thay đổi email. Quan sát POST request trong Burp Proxy
Tương tự các bài lab trước, bypass anti-csrf token bằng lỗ hổng XSS trong chức năng comment
C1:
C2:
Đăng thành công. Giờ đây bất kỳ ai xem bình luận sẽ gửi POST request thay đổi email của họ thành loile@test.com
Sử dụng hàm print()
(Thay cho alert()
do phiên bản 92 trở đi (ngày 20 tháng 7 năm 2021), các iframe cross-origin bị ngăn không cho gọi alert()
)
Tự động bằng các công cụ như: Burp Suite, OWASP ZAP, Netsparker, Acunetix, hoặc XSSer,...
Kiểm tra thủ công thường liên quan đến nhập các chuỗi ký tự đơn giản vào mọi điểm đầu vào của ứng dụng và kiểm tra HTTP response để xác định xem dữ liệu có được phản hồi lại hay không. Sau đó, kiểm tra kỹ từng vị trí để xác định liệu có thể sử dụng dữ liệu này để thực thi mã JS tùy ý không. Quá trình này giúp xác định loại XSS và chọn payload phù hợp để khai thác
Đối với DOM XSS từ tham số URL, quy trình tương tự thực hiện bằng cách nhập dữ liệu đơn giản vào tham số và sử dụng develop tool của trình duyệt để tìm kiếm dữ liệu trong DOM. Tuy nhiên với các loại DOM XSS trong đầu vào không dựa trên URL (như document.cookie
) hoặc sink không dựa trên HTML (như setTimeout
), việc phát hiện khó khăn hơn và đòi hỏi phải xem xét mã JS. Công cụ tự động kết hợp phân tích tĩnh và động cảu JS để tự động phát hiện lỗ hổng DOM một cách đáng tin cậy
Việc ngăn chặn XSS thường có thể đạt được qua hai lớp phòng thủ sau:
Mã hóa dữ liệu trên đầu ra
Mã hóa ngay trước khi dữ liệu có thể được kiểm soát bởi người dùng được ghi vào trang. Tùy cào ngữ cảnh mà dùng các kiểu encode khác nhau:
Trong HTML, nên chuyển đổi các giá trị không nằm trong whilelist thành các thực thể HTML
<
chuyển thành: <
>
chuyển thành: >
Trong ngữ cảnh chuỗi JavaScript, các giá trị không phải chữ và số nên được chuyển thành dạng escape Unicode:
<
chuyển thành: \u003c
>
chuyển thành: \u003e
Đôi khi cần áp dụng nhiều lớp encode theo đúng thứ tự. Ví dụ: Để nhúng an toàn đầu vào của người dùng vào một event handler, cần xử lý cả ngữ cảnh JavaScript và ngữ cảnh HTML. Vì vậy trước tên cần escape Unicode đầu vào, sau đó mã hóa HTML
Xác thực đầu vào khi nhận
Nếu người dùng gửi một URL sẽ được trả về trong các phản hồi, xác thực rằng URL đó bắt đầu bằng giao thức an toàn như HTTP và HTTPS
Nếu người dùng cung cấp một giá trị dự kiến là số, xác thực rằng giá trị thực sự chứa một số nguyên
Xác thực rằng đầu vào chỉ chứa một tập hợp ký tự mong đợi
Nên sử dụng whitelist hơn là blacklist. Ví dụ, thay vì cố gắng tạo danh sách tất cả các giao thức có hại (như javascript, data, ...), chỉ cần tạo danh sách các giao thức an toàn và loại bỏ bất kỳ thứ gì không có trong danh sách. Điều này đảm bảo rằng lớp phòng thủ không bị phá vỡ khi xuất hiện các giao thưc có hại mới và làm giảm khả năng bị tấn công thông qua việc ẩn giấu các giá trị không hợp lệ nhằm tránh bị phát hiện bởi danh sách đen.
Việc cho phép người dùng đăn các đoạn HTML nên được tránh bất cứ khi nào có thể, nhưng đôi khi điều này lại là yêu cầu của doanh nghiệp. Ví dụ, một trang blog có thể cho phép người dùng đăng bình luận với một số đoạn mã HTML giới hạn.
Cách tiếp cận cổ điển là cố gắng lọc bỏ các thẻ tiềm ẩn nguy hiểm và mã JavaScript bằng whitelist, blacklist,.. nhưng do sự khác biệt trong các công cụ phân tích cú pháp của trình duyệt và những điểm kỳ quặc như , cách này khó triển khai.
Lựa chọn ít tệ nhất là sử dụn thư viện JavaScript để thực hiện việc lọc và mã hóa trong trình duyệt người dùng, chẳng hạn như . Một số thư viện khác cho phép người dùng cung cấp nội dung theo định dạng markdown và chuyển đổi markdown thành HTML. Nên theo dõi các bản cập nhật vì các thư viện này đều có lỗ hổng XSS
Nhiều trang web hiện đại sử dụng server-side template engine như Twig và Freemarker để nhúng dữ liệu động trong HTML. Các công cụ này thường định nghĩa hộ thống escape riêng của chúng. Ví dụ, trong Twig, bạn có thể sử dụng bộ lọc e(), với một đối số xác định ngữ cảnh:
Một số công cụ tạo mẫu khác, như Jinja và React, tự động mã hóa (escape) nội dung động theo mặc định, điều này giúp ngăn chặn hầu hết các trường hợp XSS.
Nên xem xét kỹ các tính năng escape khi đánh giá việc sử dụng một công cụ tạo mẫu hoặc framework nào đó.
Chú thích:
Escape (mã hóa) là quá trình biến đổi các ký tự đặc biệt (như <
, >
, &
) thành các thực thể an toàn (như <
, >
, &
) để ngăn chặn việc chạy mã không mong muốn, như các cuộc tấn công XSS.
Việc chọn đúng template engine hoặc framework với hệ thống escape tốt là rất quan trọng trong việc bảo vệ trang web khỏi các lỗ hổng bảo mật.
Chú ý: Nếu ứng dụng nối trực tiếp đầu vào của người dùng vào chuỗi mâu, ứng dụng sẽ dẽ bị tấn công bởi server-side template injection (nghiêm trọng hơn XSS)
Sử dụng htmlentities
để mã hóa (escape) trong HTML
Trong PHP, có hàm tích hợp htmlentities
được sử dụng để mã hóa các thực thể (entities) HTML. Việc mã hóa này giúp bảo vệ các ứng dụng khỏi các cuộc tấn công XSS khi hiển thị nội dung người dùng nhập vào.
Cách sử dụng:
$input: Chuỗi đầu vào cần mã hóa.
ENT_QUOTES: Một cờ (flag) yêu cầu mã hóa cả dấu nháy đơn và dấu nháy kép.
'UTF-8': Bộ ký tự mã hóa, hầu hết các trường hợp sẽ sử dụng UTF-8.
Mã hóa trong ngữ cảnh chuỗi JavaScript
Khi dữ liệu được nhúng vào chuỗi JavaScript, việc mã hóa thông qua các ký tự Unicode là cần thiết để tránh các lỗ hổng bảo mật như XSS. PHP không có hàm tích hợp để thực hiện việc này, do đó cần viết hàm jsEscape
như sau để mã hóa chuỗi:
Khi muốn nhúng chuỗi đã mã hóa vào mã JavaScript, có thể sử dụng như sau:
$_GET['x']: Dữ liệu đầu vào từ người dùng (ví dụ: từ URL).
jsEscape: Mã hóa dữ liệu để đảm bảo an toàn khi chèn vào JavaScript.
Sử dụng template engine
1. Hàm htmlEncode
(Mã hóa HTML)
Trong JavaScript, không có API tích hợp sẵn để mã hóa HTML, vì vậy cần tự tạo hàm mã hóa các ký tự đặc biệt thành thực thể HTML.
Giải thích:
String(str)
: Chuyển đổi chuỗi đầu vào thành chuỗi.
replace(/[^\w. ]/gi, ...)
: Tìm các ký tự không phải là chữ cái, số, dấu chấm hoặc khoảng trắng và thay thế chúng.
charCodeAt(0)
: Trả về mã ký tự Unicode của ký tự đầu tiên.
&#...;
: Thay thế ký tự đặc biệt bằng mã thực thể HTML tương ứng.
Sử dụng:
htmlEncode(untrustedValue)
: Mã hóa chuỗi untrustedValue
để đảm bảo nó an toàn khi hiển thị trong HTML.
2. Hàm jsEscape
(Mã hóa Unicode cho JavaScript chuỗi)
Khi cần nhúng dữ liệu vào chuỗi JavaScript, các ký tự đặc biệt phải được mã hóa thành chuỗi Unicode để ngăn chặn XSS.
Giải thích:
String(str)
: Chuyển chuỗi đầu vào thành chuỗi.
replace(/[^\w. ]/gi, ...)
: Tìm và thay thế các ký tự không phải chữ cái, số, dấu chấm hoặc khoảng trắng.
charCodeAt(0).toString(16)
: Chuyển đổi ký tự thành mã Unicode dưới dạng hệ thập lục phân (hexadecimal).
'0000'+...slice(-4)
: Đảm bảo mã Unicode có độ dài 4 ký tự.
Sử dụng:
jsEscape(untrustedValue)
: Mã hóa chuỗi untrustedValue
để an toàn khi nhúng vào chuỗi JavaScript.
htmlEncode
: Được sử dụng khi dữ liệu người dùng cần được hiển thị trong ngữ cảnh HTML, bảo vệ khỏi XSS.
jsEscape
: Được sử dụng khi cần chèn dữ liệu người dùng vào một chuỗi JavaScript, mã hóa các ký tự đặc biệt để đảm bảo an toàn.
XSS trong jQuery thường xảy ra khi dữ liệu đầu vào được truyền vào selector. Các lập trình viên web thường sử dụng location.hash
và truyền nó vào selector, điều này sẽ gây ra lỗ hổng XSS vì jQuery sẽ hiển thị HTML. jQuery đã nhận ra vấn đề này và vá logic của selector để kiểm tra xem đầu vào có bắt đầu bằng dấu hash (#) không. Giờ jQuery chỉ hiển thị HTML khi đầu vào bắt đầu bằng <
. Để ngăn chặn XSS, khi truyền dữ liệu không tin cậy vào selector, cần mã hóa dữ liệu bằng cách sử dụng hàm như jsEscape
ở trên.
Content Security Policy (CSP) là tuyến phòng thủ cuối cùng chống lại XSS. Nếu các biện pháp ngăn chặn XSS thất bại, CSP giúp giảm thiểu tác động bằng cách giới hạn hành động của kẻ tấn công. CSP cho phép kiểm soát việc tải các script bên ngoài và thực thi script nội tuyến. Để triển khai CSP, cần sử dụng HTTP response header có tên là Content-Security-Policy
và khai báo chính sách.
Ví dụ CSP: default-src 'self'; script-src 'self'; object-src 'none'; frame-src 'none'; base-uri 'none';
Chính sách này chỉ cho phép tải tài nguyên từ cùng nguồn gốc với trang chính, giảm thiểu khả năng khai thác lỗ hổng XSS. Nếu phải tải tài nguyên từ bên ngoài, nên giới hạn những script không gây rủi ro, hoặc sử dụng chính sách dựa trên hash/nonce để đảm bảo chỉ các script có nonce khớp với giá trị trên server mới được thực thi.
: Đoạn mã độc được chèn vào URL hoặc các trường đầu vào và phản chiếu lại ngay lập tức trong phản hồi từ máy chủ.