Experience: Cross Side Script (XSS)
Description:
- Mình sẽ note một số kinh nghiệm mà mình lụm nhặt được khi gặp 1 số vấn đề về XSS ở đây :>
Vấn đề 1:
- Vì sao
<img src=x onerror=alert(1)>thường thành công còn<script>…</script>hay<iframe src=x onload=alert(1)>thường thất bại khi đưa HTML vào$(...)?
Lab thực hành:
Tóm tắt ngắn
Khi đưa một chuỗi HTML vào $(...) hoặc innerHTML trong các lab DOM XSS, payload dạng
1 | <img src=x onerror=alert(1)> |
thường thành công hơn so với
1 | <script>alert(1)</script> |
hoặc
1 | <iframe src=x onload=alert(1)></iframe> |
Lý do chính: <img> với onerror là một thuộc tính sự kiện của element — khi element được tạo và trình duyệt cố tải src, event error sẽ được kích hoạt và handler inline được chạy. Trong khi đó, <script> chứa mã cần được thực thi bởi engine JS (không chỉ tồn tại như thuộc tính), và iframe có lifecycle tải phức tạp nên không đảm bảo chạy ngay.
Giải thích chi tiết (theo trường hợp)
1) Tại sao <img src=x onerror=...> chạy rất đáng tin cậy
- Khi browser tạo một thẻ
<img>và chèn vào DOM, nó sẽ cố gắng tải tài nguyên chỉ định bởisrcngay lập tức. - Nếu
srckhông hợp lệ (ví dụx), sự kiệnerrorsẽ xảy ra và thuộc tính inlineonerror(một hàm) được gọi. onerrorlà thuộc tính của element — không cần trình duyệt phải “đánh giá” mã từ một node<script>riêng biệt.
Ví dụ:
1 | var img = document.createElement('img'); |
Khi jQuery parse '<img src=x onerror=alert(1)>' nó tạo ra element tương tự — và event sẽ chạy khi lỗi tải xảy ra.
2) Tại sao <script>alert(1)</script> thường không chạy
Khi chèn HTML bằng
innerHTMLhoặc khi jQuery parse một chuỗi HTML (ví dụ$(htmlString)), nhiều trình duyệt không tự động thực thi nội dung bên trong thẻ<script>mà chỉ tạo node DOM.jQuery có logic xử lý script trong một số phương thức (ví dụ
.append()), nó có thể tách và đánh giá các script bằng cách tạo thẻ<script>mới rồi chèn — nhưng hành vi thay đổi theo phiên bản jQuery và theo phương thức được dùng.Kết quả: chèn chuỗi chứa
<script>không đảm bảo mã trong<script>sẽ được thực thi.Thử minh họa:
1 | // không chắc chạy: innerHTML parsing |
Hệ quả thực tế: payload dạng <script>...</script> ít ổn định hơn, phụ thuộc vào cách parse/insert và phiên bản jQuery.
3) Tại sao <iframe src=x onload=...> thường thất bại hoặc không ổn định
iframecó lifecycle phức tạp:onloadchỉ chạy khi iframe thực sự tải tài nguyên ởsrc.src="x"có thể được hiểu là đường dẫn tương đối — trình duyệt có thể cố tải./x(trả 404) hoặc xử lý khác nhau giữa các browser;onloadkhông đảm bảo chạy trong mọi tình huống.- Khi iframe được tạo từ chuỗi HTML bằng
innerHTML/jQuery, việc attach/triggeronloadcòn phụ thuộc vào thời điểm và cơ chế chèn; đôi khi thuộc tính inline không được thực thi như mong muốn. - Ngoài ra còn có các yếu tố khác: CSP, SOP (cross-origin), hoặc cơ chế của trang test có thể ngăn hoặc thay đổi hành vi tải iframe.
Tóm lại: iframe onload phức tạp và không đáng tin bằng img onerror.
Điểm mấu chốt về cách browser và jQuery/DOM xử lý HTML chuỗi
- Event attributes (ví dụ
onerror,onclick) là thuộc tính của element. Nếu element đó được tạo và browser nhìn thấy event phù hợp (ví dụ lỗi tải), handler sẽ chạy. <script>tags chứa mã cần được thực thi bởi JS engine — việc này không luôn xảy ra khi chèn bằnginnerHTMLhoặc$(); jQuery có code để xử lý scripts trong vài trường hợp nhưng không phải mọi khi.- Hành vi phụ thuộc vào:
- Phiên bản jQuery và API bạn dùng (
$(frag)khác.append(frag)…), - Cách browser parse/insert (
innerHTMLvscreateElement), - CSP / Same-origin / timing / server responses.
- Phiên bản jQuery và API bạn dùng (
Vì vậy, attacker thường chọn payload ngắn và đáng tin nhất: <img src=x onerror=...>.
Kết luận — tại sao lab khuyên dùng <img onerror=print()>
img onerrorđơn giản và reliable vì dựa trên event của element khi trình duyệt cố tảisrc.scriptcần được thực thi — chèn bằng chuỗi HTML không đảm bảo thực thi.iframe onloadphụ thuộc nhiều yếu tố (load timing, đường dẫn, CSP, browser) nên ít ổn định.
Vì vậy trong nhiều lab DOM XSS, payload an toàn và đáng tin nhất để trigger là \<img src=x onerror=...\>.
Ghi chú thêm
- Hành vi thực tế có thể thay đổi giữa các phiên bản jQuery và các browser. Luôn test trên môi trường mục tiêu.



