XSS tấn công và phòng thủ: Chi tiết về cách tấn công XSS dựa trên DOM

hacked

DOM là một đặc tả kỹ thuật của W3C (World Wide Web Consortium), nó định nghĩa các mô hình đối tượng để đại diện cho cấu trúc XML và HTML.

Trong thế giới của XML (eXtensible Markup Language), có 2 bộ phân tích cú pháp chính là DOM và SAX. SAX là một cơ chế phân tích cú pháp, nó nhanh hơn rất nhiều và cũng tốn ít bộ nhớ hơn, nhưng không trực quan. Bởi vì, trong SAX không dễ dàng để đi ngược lại các nút tài liệu (có nghĩa là đây là cơ chế phân tích một chiều). Mặt khác, bộ phân tích dựa trên DOM tải toàn bộ tài liệu như một cấu trúc các đối tượng, nó bao gồm các phương thức, và các biến để dễ dàng di chuyển bên trong tài liệu và thay đổi các nút, các giá trị và các thuộc tính.

Các trình duyệt Web làm việc với DOM. Khi một trang web được tải về, trình duyệt sẽ phân tích các trang đó và tạo ra một cấu trúc các đối tượng. Hàm getElementsByTagName là một hàm DOM tiêu chuẩn, nó được sử dụng để xác định vị trí các nút XML/HTML dựa trên tên thẻ của chúng.

XSS dựa trên DOM là việc khai thác lỗ hổng kiểm tra xác thực đầu vào gây ra trên máy khách chứ không phải máy chủ. Nói một cách khác, XSS dựa trên DOM không phải là kết quả của lỗ hổng bởi các đoạn script trên máy chủ, mà là một xử lý dữ liệu người dùng cung cấp không chính xác qua Javascript trên máy khách. Cũng giống như các lỗ hổng XSS khác, XSS dựa trên DOM có thể được sử dụng để ăn cắp các thông tin bí mật hoặc hijack tài khoản người dùng. Tuy nhiên, cần phải hiểu rằng, lỗ hổng bảo mật này hoàn toàn phụ thuộc vào Javascript và việc sử dụng các dữ liệu động nhận được từ cấu trúc DOM một cách không an toàn.

Dưới đây là một ví dụ đơn giản về XSS dựa trên DOM được đưa ra bởi Amit Klein trong bài báo của anh ấy "DOM based Cross Site Scripting or XSS of the Third Kind":

<HTML>
	<TITLE>Welcome!</TITLE>
	Hi
	<SCRIPT>
		var pos=document.URL.indexOf(“name=)+5;
		document.write(document.URL.substring(pos,document.URL.length));
	</SCRIPT>
	<BR>
	Welcome to our system
	...
</HTML>
```

Khi phân tích đoạn code trên, bạn dễ dàng nhận ra rằng developer đã
quên mất việc xử lý giá trị của tham số `name`, thứ mà được viết ngay
bên trong tài liệu khi mà nó được nhận. Trong phần tiếp theo, chúng ta
sẽ nghiên cứu một vài ví dụ XSS dựa trên DOM dựa trên các ứng dụng giả
tưởng do chúng ta tạo ra.

# Xác định các lỗ hổng XSS dựa trên DOM

Hãy theo các trình tự sau để xác định các lỗ hổng XSS dựa trên DOM sử
dụng các ứng dụng giả tưởng Javascript và XML (AJAX) không đồng bộ.

Trước hết, hãy tạo 1 trang web trên máy tính cá nhân của bạn với code
sau:

```html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <link rel="stylesheet"
          href="http://www.gnucitizen.org/wordpress/wp-content/themes/gnucitizen.org-v2/styles/concrete/screen.css" type="text/css"/>
    <script src="http://code.jquery.com/jquery-2.1.1.min.js"
            type="text/javascript"></script>
    <title>Awesome</title>
  </head>
  <body>
    <div id="header">
      <h1>Awesome</h1>
      <p>awesome ajax application</p>
    </div>
    <div id="content">
      <div>
        <p>Please, enter your nick and press
          <input id="name" name="name" type="text" size="50"/><br/>
          <input id="chat" name="chat" value="Chat" type="button"/>
      </div>
    </div>
    <script>
        $('#chat').click(function () {
            var name = $('#name').val();
            $('#content > div').fadeOut(null, function () {
                $(this).html('<p>Welcome ' + name + '! You can type your message into the form below.</p><textarea class="pane">' + name + ' ></textarea>');
                    $(this).fadeIn();
                });
            });
    </script>
    <div id="footer">
      <p>Awesome AJAX Application</p>
    </div>
  </body>
</html>
```

Sau đó, hãy mở trang Web bằng 1 trình duyệt (Yêu cầu phải kích hoạt
Javascript) và kết quả sẽ giống như hình dưới.

![Chat screen](http://tech.blog.framgia.com/vn/wp-content/uploads/2014/10/Screen-Shot-2014-10-25-at-3.54.57-PM.png)

Khi trang Web đã được tải lên hết, bạn hãy điền tên và ấn nút
`Chat`. Đây chỉ là một ví dụ nên ứng dụng mới chỉ dừng lại ở việc
chuyển từ màn hình nhập tên sang màn hình chat, mà vẫn chưa có khả
năng sử dụng ứng dụng để giao tiếp giữa nhiều người. Tuy nhiên, chúng
ta hãy tập trung vào đề tài đang nghiên cứu, đó là các lỗ hổng bảo mật
của ứng dụng thay vì thiết kế chức năng của nó. Hình dưới đây là kết
quả sau khi bạn nhập tên.

![Chat](http://tech.blog.framgia.com/vn/wp-content/uploads/2014/10/Screen-Shot-2014-10-25-at-3.55.18-PM.png)

Chú ý rằng, ứng dụng AJAX này không yêu cầu máy chủ phải thực hiện bất
kỳ chức năng nào. Hãy nhớ rằng, bạn đang chạy trang Web ngay trên máy
của bạn, mọi thứ được xử lý ngay trên trình duyệt thông qua Javascript
và jQuery. Nếu bạn cẩn thận kiểm tra cấu trúc và logic của đoạn mã
Javascript, bạn sẽ nhìn ra lỗ hổng bảo mật của ứng dụng này, một lỗ
hổng XSS. Phần lỗ hổng đó chính là đoạn mã dưới đây.

```javascript
$(this).html('<p>Welcome ' + name + '! You can type your message into the form below.</p><textarea class="pane">' + name + ' ></textarea>');
```

Như những gì chúng ta thấy, ứng dụng chứa một chuỗi ký tự HTML thông
qua một hàm HTML của jQuery. Hàm HTML này sẽ thay đổi nội dung của
thành phần được chọn trên trang Web. Chuỗi ký tự này là dữ liệu lấy từ
trường input `nickname`. Trong trường hợp này, tôi đã cho giá trị nhập
vào là `Naa`. Tuy nhiên, ứng dụng không hề chọn lựa hay xử lý giá trị
này, chúng ta có thể nhập vào bất cứ giá trị nào kể các các chuỗi ký
tự HTML, hoặc một phần các đoạn script, ví dụ như hình dưới đây.

![XSS input](http://tech.blog.framgia.com/vn/wp-content/uploads/2014/10/Screen-Shot-2014-10-25-at-4.12.21-PM.png)

Nếu bạn ất chút `Chat`, bạn sẽ tiêm một tải trọng mã độc vào trong
DOM. Tải trọng này biến chuỗi ký tự chat như sau:

```html
<p>Welcome <script>alert('xss')</script>! You can type your message into the form
below.</p><textarea class="pane"><script>alert('This website is hacked')</script> >
```

Đây được biết đến chính là tấn công XSS dựa trên DOM không duy
trì. Kết quả khai thác giống như hình dưới.

![XSS](http://tech.blog.framgia.com/vn/wp-content/uploads/2014/10/Screen-Shot-2014-10-25-at-4.12.38-PM.png)

# Khai thác lỗ hổng XSS không duy trì dựa trên DOM

Giống như các lỗ hổng XSS thông thường khác đã được đưa ra từ những
bài trước, XSS dựa trên DOM có thể là duy trì và/hoặc không duy
trì. Trong phần này, chúng ta sẽ xem xét XSS không duy trì dựa trên
DOM.

Sử dụng ứng dụng từ phần trước, chúng ta thay đổi nó một chút để nó có
thể khai thác từ xa. Mã chương trình mới sẽ như sau:

```html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
   <link rel="stylesheet"
     href="http://www.gnucitizen.org/wordpress/wp-content/themes/gnucitizen.org-v2/styles/concrete/screen.css" type="text/css"/>
   <script src="http://code.jquery.com/jquery-2.1.1.min.js"
     type="text/javascript"></script>
   <title>Awesome</title>
 </head>
 <body>
   <div id="header">
     <h1>Awesome</h1>
     <p>awesome ajax application</p>
   </div>
   <div id="content">
   </div>
   <script>
     var matches = new String(document.location).match(/[?&]name=([^&]*)/);
     var name = 'guest';
     if (matches)
       name = unescape(matches[1].replace(/\+/g, ' '));
     $('#content').html('<p>Welcome ' + name + '! You can type your message into the form below.</p><textarea class="pane">' + name + ' ></textarea>');
   </script>
   <div id="footer">
     <p>Awesome AJAX Application</p>
   </div>
 </body>
</html>
```

Lưu đoạn mã trong 1 tập tin (ví dụ `test.html`) và mở nó bằng trình
duyệt Web. Bạn sẽ thấy nó ngay lập tức cho bạn một cái tên đó là
`guest`. Bạn có thể thay đối điều này bằng cách thêm vào truy vấn 1
tham số ở cuối URL, sau `test.html` như sau:

test.html?name=Naa


Nếu bạn làm điều này, bạn sẽ thấy tên bạn có không còn là `guest` nữa
mà là `Naa`. Bây giờ, hãy thử khai thác các lỗ hổng của ứng dụng này,
bằng cách thêm chuỗi ký tự sau vào thanh địa chỉ:

test.html?name=<script>alert('This website is hacked!!')</script>


Kết quả của tấn công này sẽ giống như hình dưới đây:

![XSS](http://tech.blog.framgia.com/vn/wp-content/uploads/2014/10/Screen-Shot-2014-10-25-at-4.54.33-PM.png)

Hãy chú ý rằng những thiết đặt giống như ứng dụng mẫu này rất phổ biến
trong các ứng dụng AJAX. Người dùng không cần thiết phải nhập nickname
của họ trong tất cả những lần họ sử dụng ứng dụng. Họ có thể đơn giản
là đánh dấu trang cho chứa nickname sẵn cho họ, đó là một tính năng
rất tiện lợi. Tuy nhiên, nếu developer không xử lý đầu vào, một lỗ
hổng XSS rất dễ bị khai thác giống như ví dụ trong phần này.

# Khai thác lỗ hổng XSS duy trì dựa trên DOM

Các ứng dụng AJAX thường được xây dựng để mô phỏng các chương trình
trên máy tính cá nhân thông thường. Một developer có thể tạo ra các
hộp thoại, các cửa sổ, tương tác với các hình ảnh và thay đổi các
thuộc tính từ xa và có thể thay đổi kể cả các dữ liệu được lưu trên
máy chủ.

Vị dụ mẫu của chúng ta là một ứng dụng không có giao diện thân thiện
cho lắm. Nickname liên tục phải được thêm vào mỗi lần có người muốn
chat. Và ở phần này, chúng ta sẽ xây dựng một ứng dụng với tính năng
mới, đó là ghi nhớ nickname kể từ lần cuối cùng người dùng truy cập
vào ứng dụng. Lưu đoạn mã sau vào 1 tập tin, tuy nhiên lần này, bạn
phải đưa nó lên một máy chủ Web nếu muốn sử dụng nó.

```html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
   <link rel="stylesheet"
     href="http://www.gnucitizen.org/wordpress/wp-content/themes/gnucitizen.org-v2/styles/concrete/screen.css" type="text/css"/>
   <script src="http://code.jquery.com/jquery-2.1.1.min.js"
     type="text/javascript"></script>
   <title>Awesome</title>
 </head>
 <body>
   <div id="header">
     <h1>Awesome</h1>
     <p>awesome ajax application</p>
   </div>
   <div id="content">
   </div>
   <script>
     var matches = new String(document.location).match(/[?&]name=([^&]*)/);
     if (matches) {
       var name = unescape(matches[1].replace(/\+/g, ' '));
       document.cookie = 'name=' + escape(name) + ';expires=Mon 29-Oct-2014 00:00:00 GMT';
     } else {
       var matches = new String(document.cookie).match(&?name=([^&]*)/);
       if (matches)
         var name = unescape(matches[1]).replace(/\+/g, ' '));
       else
         var name = 'guest';
     }
     $('#content').html('<p>Welcome ' + name + '! You can type your message into the form below.</p><textarea class="pane">' + name + ' ></textarea>');
   </script>
   <div id="footer">
     <p>Awesome AJAX Application</p>
   </div>
 </body>
</html>

Lý do phải lưu tập tin này trên máy chủ là bởi vì, ứng dụng này sử dụng cookie. Tính năng cookie này có sẵn cho bất kỳ ứng dụng nào lấy dữ liệu và các tài nguyên khác từ xa về thông qua các giao thức http://https://. Và bởi vì ứng dụng là Javascript, không cần các đoạn script trên máy chủ. Bất cứ máy chủ Web nào cũng có thể dùng để lưu trữ kiểu ứng dụng này. Trong môi trường Windows, bạn có thể tải về và cài đặt WAMP hoặc cài đặt Apache trên Linux và thay đổi các thiết đặt tương ứng.

Bạn có thể tương tác với ứng dụng của bạn tương tự như các ví dụ trước, chỉ có điều khác đó là một khi bạn đã nhập tên thông qua test.html?name=[YourName], bạn không cần phải làm thêm lần nào nữa, bởi vì thông tin sẽ được lưu trữ ở cookie trong trình duyệt của bạn. Bởi vậy, hãy thiết lập tên thông qua URL sau:

http://<your server>/test.html?name=Naa

Một khi trang Web được tải xong, bạn sẽ được đặt tên là Naa. Sau thời điểm này, bất cứ khi nào bạn truy cập địa chỉ http://<your server>/test.html, ứng dụng Web sẽ kiểm tra và đọc tên bạn từ cookie, và sẽ tải nó vào trong ứng dụng.

Bây giờ, ứng dụng của chúng ta có lỗ hổng XSS duy trì dựa trên DOM, một lỗ hổng nghiêm trọng hơn những lỗ hổng ở các ví dụ trước. Ví dụ, một kẻ tấn công có thể dễ dàng thay đổi cookie của ứng dụng thông qua tấn công SCRF (Cross-site request forgery) được thực thi từ một trang Web độc hại hoặc đơn giản hơn là thông qua URL. Ví dụ, điều gì sẽ xảy ra khi bạn truy cập 1 trang Web độc hại có chứa đoạn Javascript sau:

var img = new Image();
img.src =
'http://www.awesomechat.com/awesome.html?name=Naa<script>alert("Hacked !!")</script>';

Đoạn Javascript độc hại trên sẽ thiết lập cookie của bạn trở thành Naa<script>alert("Hacked !!")</script>. Bởi vì các developer không xử lý giá trị tên, một thẻ script đã được tiêm nhiễm vào ngay trong cookie, nó sẽ là một ứng dụng với backdoor được duy trì. Kể từ bây giờ, kẻ tấn công có thể làm bất cứ điều gì hắn thích với ứng dụng của bạn ở một địa chỉ khác, ví dụ http://www.awesomechat.com (một trang Web giả mạo).

Có điểm rất quan trọng, bạn cần phải nhớ rằng, các lỗ hổng XSS duy trì dựa trên DOM không chỉ giới hạn ở cookie. Các đoạn Javascript độc hại có thể được lưu trữ ở trong các phương tiện lưu trữ trên máy tính cá nhân của các trình duyệt như Firefox hay Chrome, trong cookie có trình chạy Flash, hoặc ngay trên URL. Các nhà phát triển Web nên cẩn thận về dữ liệu được lưu trữ và luôn luôn xử lý, xác thực các giá trị được nhập vào.

Trong bài tiếp theo, tôi sẽ giới thiệu những kiến thức tiếp theo liên quan đến các kiểu tấn công XSS.