XSS vulnerabilities - Lỗ hổng XSS (Phần 3)
II. Phân tích, phòng chống các lỗ hổng Reflected XSS và Stored XSS (tiếp)
Ở phần trước, hàm htmlspecialchars()
đã bảo vệ trang web trước các ký tự nhạy cảm trong lỗ hổng XSS như <
, >
, "
, ... Nhưng liệu nó đã thực sự an toàn?
Xét tình huống một trang web cho phép người dùng để lại tên tác giả, lời chúc cùng link contact được gán trong tên tác giả. Mã nguồn như sau:
import mysql.connector
import html
from flask import Flask, request, redirect
app = Flask(__name__)
# Connect to MySQL database
cnx = mysql.connector.connect(user='root', password='password', database='mydatabase')
cursor = cnx.cursor()
@app.route('/')
def index():
# Retrieve all the data from the database
cursor.execute("SELECT * FROM hpny")
datas = cursor.fetchall()
# Build the HTML for the datas
html = '<h1>Happy new year!</h1>'
for data in datas:
html += f'<p>Wishes from <a href="{data[3]}">{data[1]}<a>: {data[2]}</p><br>'
html += '''
<form action="/submit" method="post">
Name: <input type="text" name="name"><br><br>
Wishes: <textarea name="wishes"></textarea><br><br>
Website: <input type="text" name="website"><br>
<input type="submit" value="Submit">
</form>
'''
return html
@app.route('/submit', methods=['POST'])
def submit():
# Get the user's data from the request
# prevent XSS attack with html.escape()
name = html.escape(request.form['name'])
wishes = html.escape(request.form['wishes'])
website = html.escape(request.form['website'])
# Insert the wishes into the database
cursor.execute("INSERT INTO hpny (name, wishes, website) VALUES (%s, %s, %s)", (name, wishes, website))
cnx.commit()
return redirect('/')
if __name__ == '__main__':
app.run(host = '0.0.0.0', port = 9999)
Trang web đã sử dụng hàm html.escape()
trong Python - có chức năng tương tự hàm htmlspecialchars()
thực hiện ngăn chặn lỗ hổng XSS:
Chúng ta quan sát cách trang web gán đường link contact:
html += f'<p>Wishes from <a href="{data[3]}">{data[1]}<a>: {data[2]}</p><br>'
Chú ý rằng đoạn code trên trực tiếp ghép giá trị tham số website
vào thuộc tính href
, nên chúng ta vẫn có thể tạo payload tấn công XSS bằng pseudo-protocol javascript như sau (tham số website
):
javascript:alert('XSS')
Kết quả khi người dùng click vào link <a href="javascript:alert('XSS')">hacker<a>
sẽ kích hoạt hộp thoại alert:
Với tình huống này các bạn đọc có thể luyện tập thêm tại các bài lab:
- Stored XSS into anchor href attribute with double quotes HTML-encoded
- Stored XSS into onclick event with angle brackets and double quotes HTML-encoded and single quotes and backslash escaped
Để khắc phục lỗ hổng XSS tại tình huống này chúng ta có thể yêu cầu người dùng nhập đúng định dạng một link website bình thường bằng Regular expression (biểu thức chính quy). Ví dụ sử dụng hàm match()
trong thư viện re:
import re
website = request.form['website']
if not re.match(r"^(http:|https:)//[0-9a-zA-Z]+$", website):
raise ValueError("Invalid website")
III. Phân tích, phòng chống các lỗ hổng DOM-based XSS
1. Document Object Model (DOM) - Mô hình đối tượng Document
Theo định nghĩa từ w3schools:
The W3C Document Object Model (DOM) is a platform and language-neutral interface that allows programs and scripts to dynamically access and update the content, structure, and style of a document.
Chúng ta có thể hiểu đơn giản DOM giúp một trang web truy nhập, thay đổi nội dung một cách dynamic (động) phần source code trang web (HTML) từ đó thay đổi nội dung hiển thị, tùy vào từng thao tác người dùng hoặc cách hoạt động từ trang web.
Một HTML DOM tree của Object có dạng như sau:
(https://www.w3schools.com/js/jshtmldom.asp)
Chúng ta cùng so sánh với đoạn code html sau để dễ dàng hình dung hơn:
<html>
<head>
<title>This is title</title>
</head>
<body>
<h1>Viblo</h1>
<a href = "https://viblo.asia/">viblo.asia</a>
</body>
</html>
Các thẻ html được quản lý trong DOM:
- Thẻ cao nhất là
<html>
- Tiếp theo phân thành nhánh gồm thẻ
<head>
và<body>
- Bên trong
<head>
chứa các thẻ<title>
,<style>
, ... - Bên trong
<body>
chứa các thẻ<a>
,<h1>
,<div>
, ... - Và tiếp tục quá trình phân nhánh như trên ...
Sử dụng DOM, chúng ta có thể truy nhập tới từng node trong cây DOM trên, thực hiện các thao tác thêm, sửa, xóa.
2. DOM-based XSS
Các lỗ hổng DOM-based XSS xảy ra khi mã độc được chèn vào trang web bằng cách sử dụng các tài nguyên không được lưu trữ trên máy chủ, mà được tải từ máy chủ và xử lý trên trình duyệt của người dùng.
Để hiểu rõ hơn về dạng lỗ hổng này, chúng ta cùng xem xét một vài lab sau:
Kiểm tra chức năng Search với một chuỗi ngẫu nhiên:
Chú ý đoạn script xử lý chuỗi tìm kiếm:
- Biến
query
lấy giá trị tham sốsearch
từ URL qua hàmURLSearchParams()
. - Nếu biến
query
khác rỗng thì gọi hàmtrackSearch()
, sử dụng hàmdocument.write()
ghi nội dung:
'<img src="/resources/images/tracker.gif?searchTerms='+query+'">'
Do biến query
chúng ta có thể thay đổi được nên đồng nghĩa với việc chúng ta có thể tác động vào giá trị thẻ <img>
. Chẳng hạn, tận dụng thuộc tính onerror để kích hoạt sự kiện alert()
. Chúng ta cần giá trị src trong thẻ <img>
gặp lỗi. Xây dựng payload như sau:
abc" onerror="alert(1)
Khi đó document.write()
ghi nội dung <img src="/resources/images/tracker.gif?searchTerms=abc" onerror="alert(1)">
, giá trị src gặp lỗi nên sự kiện alert(1) thực hiện, bài lab hoàn thành.
Bạn đọc có thể làm bài lab tương tự DOM XSS in document.write sink using source location.search inside a select element, DOM XSS in innerHTML sink using source location.search
3. DOM XSS trong jQuery
Thư viện jQuery trong ngôn ngữ Javascript cũng có thể gây ra các lỗ hổng DOM XSS. Chẳng hạn hàm attr()
- dùng để lấy hoặc thiết lập giá trị của một thuộc tính trên một phần tử HTML, ẩn chứa nguy cơ tạo ra lỗ hổng DOM XSS khi việc cài đặt chưa chặt chẽ. Xét tình huống trong lab DOM XSS in jQuery anchor href attribute sink using location.search source.
Trang web Submit feedback sử dụng thư viện jQuery. Chức năng Back gọi hàm attr() lấy giá trị tham số returnPath
trong URL để thiết lập giá trị cho thuộc tính href:
Do giá trị tham số returnPath
có thể thay đổi bởi người dùng nên kẻ tấn công có thể sửa tham số returnPath
nhận giá trị javascript:alert('XSS')
nhằm kích hoạt thông báo alert().
Payload hoàn thành bài lab: javascript:alert(document.cookie)
Một lab khác về lỗ hổng DOM XSS trong thư viện jQuery dành cho bạn đọc: DOM XSS in jQuery selector sink using a hashchange event
4. Phát hiện và khắc phục DOM XSS
Trong lỗ hổng DOM-based XSS, source là nguồn dữ liệu không được kiểm duyệt mà được chèn vào trang web, còn sink là nơi mà dữ liệu đó được hiển thị. Do đó, source và sink là hai yếu tố quan trọng trong việc phát hiện và khắc phục lỗ hổng DOM-based XSS.
Một số sinks có thể dẫn tới lỗ hổng:
document.write()
document.writeln()
document.domain
element.innerHTML
element.outerHTML
element.insertAdjacentHTML
element.onevent
Các hàm trong thư viện jQuery cũng có thể đóng vai trò sinks dẫn tới lỗ hổng:
add()
after()
append()
animate()
insertAfter()
insertBefore()
before()
html()
prepend()
replaceAll()
replaceWith()
wrap()
wrapInner()
wrapAll()
has()
constructor()
init()
index()
jQuery.parseHTML()
$.parseHTML()
Để khắc phục lỗ hổng này, chúng ta cần đảm bảo rằng dữ liệu đầu vào được kiểm duyệt kỹ trước khi được chèn vào trang web tại sink.
Các tài liệu tham khảo
- https://portswigger.net/web-security/cross-site-scripting
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS%20Injection
- https://book.hacktricks.xyz/pentesting-web/xss-cross-site-scripting
- https://www.w3schools.com/js/js_htmldom.asp
©️ Tác giả: Lê Ngọc Hoa từ Viblo
All rights reserved