I. Đặt vấn đề
1. Các lỗ hổng Client-side
Trải qua hành trình dài với các lỗ hổng Server-side, từ chủ đề này trở đi Viblo sẽ cùng các bạn khám phá và phân tích các lỗ hổng Client-side. Khác với các lỗ hổng server-side xuất hiện trong phần mềm hoặc hệ thống máy chủ, lỗ hổng Client-side thường xảy ra tại chính trình duyệt của người dùng dẫn đến kẻ tấn công có thể tiếp cận, truy cập dữ liệu không mong muốn, không được phép.
2. Lỗ hổng Cross-site scripting (XSS)
Cross-site scripting (thường được gọi là XSS) là một dạng lỗ hổng Client-side tiêu biểu nhất. Trong quá trình khai thác, kẻ tấn công thực hiện việc nhúng mã độc (thường là JavaScript) vào trang web của người khác, sau đó khi người dùng truy cập vào trang web đó, mã độc sẽ được thực thi trên trình duyệt của họ, từ đó đạt được mục đích của kẻ tấn công. Thông thường, các cuộc tấn công XSS thường có mục đích đánh cắp cookie, token của nạn nhân và mạo danh họ để leo thang lên các cuộc tấn công sâu hơn. Trong năm , lỗ hổng Cross-site scripting được xếp ở vị trí số tại Top lỗ hổng bảo mật web của OWASP: A07:2017-Cross-Site Scripting (XSS). Tới năm , nó được xếp chung vào nhóm lỗ hổng A03:2021-Injection ở vị trí thứ
3. Các dạng lỗ hổng Cross-site scripting (XSS)
XSS thường được chia làm dạng chính:
- Reflected XSS: xảy ra khi mã độc được truyền vào trang web bằng cách sử dụng một liên kết hoặc biểu mẫu web. (Script độc hại có nguồn gốc từ request HTTP hiện tại)
- Stored XSS: xảy ra khi mã độc được lưu trữ trên máy chủ và được thực thi khi người dùng truy cập trang web có chứa mã độc đó. (Script độc hại có nguồn gốc từ phía máy chủ, chẳng hạn database)
- 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. (Script độc hại tồn tại trong client-side code)
Chúng ta sẽ phân tích kỹ hơn từng dạng lỗ hổng tại các phần sau.
II. Phân tích, phòng chống các lỗ hổng Reflected XSS và Stored XSS
1. Phân biệt Reflected XSS và Stored XSS
- Reflected XSS
Dạng lỗ hổng Reflected XSS xảy ra khi mã độc được truyền vào trang web bằng cách sử dụng một liên kết hoặc biểu mẫu web. Tức script độc hại có nguồn gốc từ request HTTP hiện tại. Xem xét ví dụ đơn giản sau:
<!DOCTYPE html>
<html>
<body> <form> Enter your name: <input type="text" name="name"><br> <input type="submit" value="Submit">
</form> <?php
if (isset($_GET['name'])) { echo "<p>Welcome to Viblo, " . $_GET['name'] . "!</p>";
}
?> </body>
</html>
Tham số name
nhận giá trị từ input người dùng và hiển thị trực tiếp ra giao diện.
Lúc này, thay vì nhập các chuỗi tên người dùng thông thường, kẻ tấn công có thể nhập input là một chuỗi javascript, chẳng hạn <script>alert('XSS')</script>
, khi đó trang web thực thi đoạn code echo "<p>Welcome to Viblo, <script>alert('XSS')</script>!</p>";
sẽ gọi hàm alert() đưa ra thông báo XSS trên màn hình:
Điều này chứng tỏ trang web chứa lỗ hổng Reflected XSS. Đôi khi, chúng ta có thể thay thế hàm alert() bằng print(), do sau khi Chrome 92 ra mắt vào ngày tháng năm , các lỗ hổng XSS trong các cross-domain iframes không còn kích hoạt cảnh báo hàm alert().
Bạn đọc có thể thực hành kỹ thuật "chứng tỏ" lỗ hổng XSS này trong lab Reflected XSS into HTML context with nothing encoded.
- Stored XSS
Dạng lỗ hổng Stored XSS xảy ra khi mã độc được lưu trữ trên máy chủ và được thực thi khi người dùng truy cập trang web có chứa mã độc đó. Script độc hại có nguồn gốc từ phía máy chủ, chẳng hạn database. Ví dụ, khi người tấn công bình luận một chuỗi chứa mã độc vào một bài viết trên một trang web công cộng, mã độc đó được lưu trữ và hiển thị trong bài viết đó, người dùng khác truy cập vào dẫn tới thực thi script và trở thành nạn nhân.
Xét ví dụ sau:
import mysql.connector
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 comments from the database cursor.execute("SELECT * FROM comments") comments = cursor.fetchall() # Build the HTML for the comments html = '<h1>Comments</h1>' for comment in comments: html += f'<p>{comment[1]}</p>' html += ''' <form action="/submit" method="post"> <textarea name="comment"></textarea> <input type="submit" value="Submit"> </form> ''' return html @app.route('/submit', methods=['POST'])
def submit(): # Get the user's comment from the request comment = request.form['comment'] # Insert the comment into the database cursor.execute("INSERT INTO comments (comment) VALUES (%s)", (comment,)) cnx.commit() return redirect('/') if __name__ == '__main__': app.run(host = '0.0.0.0', port = 9999)
Trang web cho phép người dùng để lại bình luận. Tham số comment
nhận giá trị trực tiếp từ input người dùng và lưu vào database. Điều này dẫn tới khả năng tấn công Stored XSS (Thực tế còn chứa một số lỗ hổng khác như SQL injection, nhưng trong trường hợp này chúng ta không xét đến). Chẳng hạn, kẻ tấn công có thể bình luận nội dung <script>alert('XSS')</script>
, mã độc javascript này được lưu trữ vào database và hiển thị ra giao diện mỗi khi người dùng truy cập - từ đó đưa ra thông báo XSS như hình sau:
Có thể quan sát cụ thể source code trang web lúc này, mã độc đã bị "nhúng" vào source code:
Đây là một ví dụ cơ bản về lỗ hổng XSS dạng Stored: Lỗ hổng xảy ra khi trang web lưu trữ các script vào database, khi đó mỗi lần người dùng truy cập, trang web lấy dữ liệu từ database (chứa script độc hại) và thực thi tại giao diện nạn nhân. Bạn đọc có thể làm quen với lỗ hổng Stored XSS trong bài lab Stored XSS into HTML context with nothing encoded.
Các tài liệu tham khảo
- https://portswigger.net/web-security/cross-site-scripting
- https://owasp.org/Top10/
- https://portswigger.net/research/alert-is-dead-long-live-print
- https://portswigger.net/web-security/cross-site-scripting/cheat-sheet
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS%20Injection
- https://book.hacktricks.xyz/pentesting-web/xss-cross-site-scripting