How to Prevent XSS

Một cách tổng quát, để chống XSS attack thì cần sử dụng hai lớp phòng thủ sau:

  • Encode dữ liệu đầu ra.
  • Validate dữ liệu đầu vào.

Ngoài ra, cũng có thể sử dụng CSP.

Encode Data on Output

Cần phải encode user input trước khi nó được ghi vào trang web. Loại encoding sẽ tùy thuộc vào ngữ cảnh ghi dữ liệu.

Trong ngữ cảnh HTML, các ký tự bị cấm cần phải được chuyển thành các thực thể HTML (HTML entity). Ví dụ:

  • < chuyển thành: &lt;
  • > chuyển thành: &gt;

Trong ngữ cảnh JavaScript, các ký tự không phải là chữ số cần được Unicode-escape. Ví dụ:

  • < chuyển thành: \u003c
  • > chuyển thành: \u003e

Đôi khi ta cần kết hợp nhiều loại encoding theo một thứ tự nào đó. Ví dụ, để chèn user input vào event handler thì cần phải Unicode-escape trước rồi HTML-encode sau:

<a href="#" onclick="x='This string needs two layers of escaping'">test</a>

Validate Input on Arrival

Các ví dụ validate user input:

  • Nếu người dùng gửi lên một URL mà được chèn vào response thì cần đảm bảo protocol của URL là an toàn chẳng hạn như HTTP hoặc HTTPS. Nếu không, kẻ tấn công có thể sử dụng các protocol nguy hiểm như javascript hoặc data để tấn công XSS.
  • Nếu giá trị của query param luôn là số thì cần đảm bảo user input có dạng số.
  • Chỉ cho phép một số ký tự không nguy hiểm ở trong user input.

Hành động cần làm khi các điều kiện validation bị vi phạm là chặn user input và không nên xóa input nhằm làm cho request hợp lệ vì nó dễ gây ra lỗi.

Ngoài ra, các input validation rule nên sử dụng whitelist thay vì blacklist. Ví dụ, thay vì từ chối các protocol nguy hiểm thì chỉ nên cho phép các protocol an toàn và từ chối các protocol còn lại. Việc này sẽ giúp tránh được trường hợp xuất hiện các loại protocol nguy hiểm mới hoặc kẻ tấn công cố tình làm rối protocol để né tránh blacklist.

Allowing “safe” HTML

Nếu ứng dụng có yêu cầu nghiệp vụ cho phép người dùng nhập vào HTML thì cần sử dụng whitelist bao gồm các tag và attribute an toàn. Tuy nhiên, do có sự khác biệt giữa parsing engine của trình duyệt và các vấn đề chẳng hạn như XSS mutation nên cách tiếp cận này thường khó để triển khai một cách an toàn.

Một cách tiếp cận khác là sử dụng các thư viện JavaScript để filter và encode trong trình duyệt của người dùng chẳng hạn như DOMPurify hoặc các thư viện cho phép người dùng nhập vào với định dạng Markdown rồi chuyển sang HTML. Tuy nhiên, tất cả các thư viện này thường xuyên gặp lỗ hổng XSS nên việc sử dụng chúng cũng không phải là một giải pháp hoàn hảo. Nếu có sử dụng thì cần cập nhật liên tục.

How to Prevent XSS in PHP

Để chặn XSS trong ngữ cảnh HTML khi sử dụng PHP thì nên encode user input bằng hàm htmlentities. Ví dụ:

<?php echo htmlentities($input, ENT_QUOTES, 'UTF-8');?>

Giải thích các tham số:

  1. User input.
  2. Flag ENT_QUOTES giúp chỉ định mọi ký tự nháy kép (") đều sẽ bị encode.
  3. Character set.

PHP không hỗ trợ hàm để Unicode-escape nên ta cần phải tự viết:

<?php
	function jsEscape($str) {
	    $output = '';
	    $str = str_split($str);
	    for($i=0;$i<count($str);$i++) {
	        $chrNum = ord($str[$i]);
	        $chr = $str[$i];
	        if($chrNum === 226) {
	            if(isset($str[$i+1]) && ord($str[$i+1]) === 128) {
	                if(isset($str[$i+2]) && ord($str[$i+2]) === 168) {
	                    $output .= '\u2028';
	                    $i += 2;
	                    continue;
	                }
	                if(isset($str[$i+2]) && ord($str[$i+2]) === 169) {
	                    $output .= '\u2029';
	                    $i += 2;
	                    continue;
	                }
	            }
	        }
	        switch($chr) {
	            case "'":
	            case '"':
	            case "\n";
	            case "\r";
	            case "&";
	            case "\\";
	            case "<":
	            case ">":
	                $output .= sprintf("\\u%04x", $chrNum);
	            break;
	            default:
	                $output .= $str[$i];
	            break;
	    }
	    }
	    return $output;
	}
?>

Sử dụng trong ngữ cảnh JavaScript như sau:

<script>x = '<?php echo jsEscape($_GET['x'])?>';</script>

How to Prevent XSS Client-side in JavaScript

Đối với client-side script thì cần phải tự viết các hàm giúp HTML-encode và Unicode-escape.

Hàm giúp HTML-encode:

function htmlEncode(str){
    return String(str).replace(/[^\w. ]/gi, function(c){
        return '&#'+c.charCodeAt(0)+';';
    });
}

Sử dụng như sau:

<script>document.body.innerHTML = htmlEncode(untrustedValue)</script>

Hàm giúp Unicode-escape:

function jsEscape(str){
    return String(str).replace(/[^\w. ]/gi, function(c){
        return '\\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4);
    });
}

Sử dụng như sau:

<script>document.write('<script>x="'+jsEscape(untrustedValue)+'";<\/script>')</script>
list
from outgoing([[Port Swigger - Preventing XSS Attacks]])
sort file.ctime asc

Resources