Lỗ hổng Cross-Site Scripting [XSS] có thể và sẽ dẫn đến sự thỏa hiệp hoàn toàn của ứng dụng giao diện người dùng. Lỗ hổng XSS cho phép kẻ tấn công kiểm soát ứng dụng trong trình duyệt của người dùng, trích xuất thông tin nhạy cảm và thực hiện các yêu cầu thay mặt cho ứng dụng. Các framework hiện đại đi kèm với hệ thống phòng thủ tích hợp chống lại XSS, nhưng chúng thực sự đi được bao xa?
1 tháng 2 năm 2021 Bảo mật SPA Các ứng dụng XSS, góc cạnh, một trang
Một đoạn mồi ngắn về XSS
Lỗ hổng XSS là lỗ hổng tiêm chích, trong đó kẻ tấn công chèn một phần dữ liệu độc hại vào ứng dụng web. Dữ liệu được tiêm độc hại sẽ được trình duyệt chọn, diễn giải dữ liệu dưới dạng mã. Điều này làm cho tải trọng được đưa vào từ kẻ tấn công được thực thi dưới dạng mã ứng dụng hợp pháp, giúp kẻ tấn công có toàn quyền kiểm soát ứng dụng đang chạy trong trình duyệt của người dùng
Ví dụ mã bên dưới minh họa một ví dụ trong sách giáo khoa về lỗ hổng XSS và cuộc tấn công
Lỗ hổng DOM-XSS trong ứng dụng jQuery
...
$["#messageContainer"].append[myMessage];
...
Một thư độc hại chứa tải trọng JavaScript
Hello John!alert["Let's play hide and seek!"]
Nếu dữ liệu này được chèn vào trang, trình duyệt sẽ thực thi tập lệnh trong thông báo, kích hoạt hộp thoại cảnh báo. Ví dụ ở đây gọi hàm
Hello John!alert["Let's play hide and seek!"]
3, đây có lẽ là tác động ít nguy hiểm nhất của một cuộc tấn công XSS. Không tạo ra một sai sót. Trên thực tế, kẻ tấn công có thể đánh cắp dữ liệu nhạy cảm từ các trang, thu thập dữ liệu nhập của người dùng để đánh cắp mật khẩu hoặc thông tin thẻ tín dụng và thậm chí gửi yêu cầu đến máy chủ như thể chính ứng dụng hợp pháp gửi chúng. Sau khi kẻ tấn công thành công trong việc khai thác lỗ hổng XSS, bạn nên coi toàn bộ môi trường thực thi của ứng dụng trong trình duyệt đã bị xâm phạmCách phòng thủ tốt nhất trước các cuộc tấn công XSS là đảm bảo rằng trình duyệt sẽ không bao giờ xem dữ liệu dưới dạng mã. Một cách tiếp cận phổ biến để đạt được điều đó là áp dụng mã hóa đầu ra theo ngữ cảnh. Mã hóa đầu ra theo ngữ cảnh sẽ đảm bảo rằng dữ liệu được mã hóa cho ngữ cảnh mà nó kết thúc trên trang. Trong quá trình mã hóa, các ký tự nguy hiểm tiềm tàng được dịch thành các ký tự vô hại. Do cách dịch đó, trình duyệt sẽ xem các ký tự này là dữ liệu thay vì mã, tránh nhầm lẫn và ngăn chặn cuộc tấn công
Dưới đây là một ví dụ về tác động của việc áp dụng mã hóa đầu ra theo ngữ cảnh đối với dữ liệu chứa tải trọng độc hại
Một thông báo độc hại chứa tải trọng JavaScript, nhưng được mã hóa đúng cách để tránh XSS
Hello John!<script>alert["Let's play hide and seek!"]</script>
Như bạn có thể thấy, trình duyệt hiện được hướng dẫn hiển thị mã HTML
Hello John!alert["Let's play hide and seek!"]
4 và
Hello John!alert["Let's play hide and seek!"]
5, hiển thị các ký tự
Hello John!alert["Let's play hide and seek!"]
6 và
Hello John!alert["Let's play hide and seek!"]
7. Vì đây là dữ liệu rõ ràng nên trình duyệt sẽ không nhầm lẫn giữa dữ liệu với mãGóc áp dụng tự động thoát
Khi một ứng dụng đưa dữ liệu vào trang bằng cơ chế nội suy của Angular [i. e. , sử dụng
Hello John!alert["Let's play hide and seek!"]
8], Angular nhận thức được mối nguy hiểm tiềm ẩn của XSS. Do đó, Angular sẽ tự động đảm bảo rằng dữ liệu kết thúc trên trang sẽ không gây ra các cuộc tấn công XSS. Đoạn mã dưới đây cho thấy một ràng buộc dữ liệu như vậyGóc tự động thoát khỏi tất cả các ràng buộc dữ liệu dựa trên phép nội suy
{{review}}
Cơ chế phòng thủ thoát tự động này, mà Angular gọi là Thoát theo ngữ cảnh nghiêm ngặt, rất quan trọng để đảm bảo bảo vệ cơ bản chống lại các cuộc tấn công XSS. Khi các khung phía máy chủ và khung phía máy khách không cung cấp mức bảo vệ tối thiểu này, các ứng dụng sử dụng các khung đó thường gặp phải rất nhiều lỗ hổng XSS
Góc áp dụng hành vi nhạy cảm theo ngữ cảnh này trên tất cả các ràng buộc dữ liệu dựa trên phép nội suy. Ví dụ: liên kết dữ liệu vào thuộc tính HTML sẽ đảm bảo rằng dữ liệu không thể thoát khỏi thuộc tính và kích hoạt việc thực thi mã tập lệnh bổ sung
Khi trốn thoát hơi xa
Các ứng dụng trong thế giới thực thường gặp phải các yêu cầu khi chúng cần hiển thị mã HTML động. Mã HTML đó thường bắt nguồn từ các nguồn không đáng tin cậy, chẳng hạn như dữ liệu do người dùng cung cấp. Hình ảnh dưới đây minh họa một tình huống phổ biến của trường hợp sử dụng này
Hình ảnh từ trang web CKEditor minh họa các tính năng của trình soạn thảoTrình soạn thảo văn bản đa dạng thức, chẳng hạn như CKEditor, thường tạo HTML dưới dạng đầu ra. Tài liệu mà bạn có thể xem bên trên hiển thị hình ảnh có thành phần
Hello John!alert["Let's play hide and seek!"]
9, tiêu đề có thẻ
Hello John!<script>alert["Let's play hide and seek!"]</script>
0 và các đoạn văn bản có thẻ
Hello John!<script>alert["Let's play hide and seek!"]</script>
1. Để hiển thị đầu ra của trình chỉnh sửa một cách chính xác, trình duyệt cần có khả năng phân tích cú pháp và hiển thị mã HTMLTrong các khóa đào tạo của mình, tôi sử dụng một ứng dụng đào tạo thu thập các đánh giá về nhà hàng. Ứng dụng này muốn người dùng có thể sử dụng các cấu trúc HTML lành tính trong các bài đánh giá của họ. Mặc dù các bài đánh giá ít phức tạp hơn so với tài liệu của CKEditor, nhưng các yêu cầu kỹ thuật là như nhau
Đối với những trường hợp sử dụng này, Angular hiển thị thuộc tính
Hello John!<script>alert["Let's play hide and seek!"]</script>
2. Lưu ý các dấu ngoặc vuông ở đây, cho biết đây là thuộc tính Góc, không phải thuộc tính
Hello John!<script>alert["Let's play hide and seek!"]</script>
3 của DOM API gốc. Bằng cách ràng buộc các giá trị với
Hello John!<script>alert["Let's play hide and seek!"]</script>
2, chúng tôi hướng dẫn Angular tự động làm sạch dữ liệu trước khi đưa nó vào trang. Bạn có thể xem tải trọng mẫu và dữ liệu được hiển thị bên dướiDữ liệu do người dùng cung cấp là một vectơ tấn công phổ biến để khai thác lỗ hổng XSS
Hello John!alert["Let's play hide and seek!"]
6Nếu chúng tôi chạy bài đánh giá này thông qua trình khử trùng Angular, nó sẽ xuất hiện dưới dạng bài đánh giá mà bạn có thể xem bên dưới
Bài đánh giá do người dùng cung cấp sau khi đã được khử trùng bằng trình khử trùng HTML
Hello John!alert["Let's play hide and seek!"]
7Cơ chế hoạt động ở đây là khử trùng HTML bằng cách sử dụng trình khử trùng HTML tích hợp của Angular. Trình khử trùng HTML biết phần tử và thuộc tính nào an toàn [e. g. ,
Hello John!<script>alert["Let's play hide and seek!"]</script>
5 và
Hello John!<script>alert["Let's play hide and seek!"]</script>
6] và có khả năng không an toàn [e. g. ,
Hello John!<script>alert["Let's play hide and seek!"]</script>
7 và thuộc tính
Hello John!<script>alert["Let's play hide and seek!"]</script>
8 của phần tử
Hello John!<script>alert["Let's play hide and seek!"]</script>
9]. Trình khử trùng sẽ để lại tất cả các phần HTML an toàn trong dữ liệu nhưng loại bỏ tất cả các phần nguy hiểmLưu ý rằng vệ sinh nghe giống như lọc [a. k. a, những thứ bạn làm với biểu thức chính quy], nhưng đó là một cách tiếp cận hoàn toàn khác. Trình khử trùng sẽ phân tích dữ liệu thành cây cú pháp và đưa ra quyết định bảo mật dựa trên cây đó. Một chất khử trùng tốt sử dụng danh sách các giá trị an toàn và coi mọi thứ không có trong danh sách là không an toàn
Vì vậy, chúng ta không thể làm mọi thứ rối tung lên trong Angular?
Angular thực hiện công việc xuất sắc trong việc giúp các nhà phát triển viết mã an toàn. Bất kỳ ai xây dựng ứng dụng Angular theo “cách thông thường” đều có thể yên tâm rằng chúng sẽ không gây ra lỗ hổng XSS. Tìm cách vượt qua sự phòng thủ của Angular tốn khá nhiều công sức. Hãy xem xét một số mẫu bạn có thể muốn tránh trong các ứng dụng của mình
Bỏ qua bảo mật
Angular cung cấp một cách để xuất HTML thô mà không áp dụng bất kỳ biện pháp bảo vệ XSS nào. Chức năng này có sẵn thông qua chức năng
{{review}}
0. Đúng như tên gọi của hàm, cơ chế này bỏ qua hoàn toàn các cơ chế bảo mật của Angular. Sử dụng nó trên dữ liệu không đáng tin cậy sẽ dẫn đến lỗ hổng XSSVí dụ mã bên dưới cho biết cách sử dụng hàm
{{review}}
0Đánh dấu đoạn mã tĩnh là an toàn bằng hàm bypassSecurityTrustHtml
Hello John!alert["Let's play hide and seek!"]
5Việc gán một đoạn mã an toàn cho [innerHTML] không kích hoạt trình khử trùng của Angular
Hello John!alert["Let's play hide and seek!"]
6Hành vi này cực kỳ nguy hiểm khi bị lạm dụng, như tên của chức năng gợi ý. Trường hợp sử dụng duy nhất có thể chấp nhận được là xuất một đoạn mã tĩnh, mã do chính bạn viết. Không bao giờ sử dụng chức năng này với bất kỳ giá trị không đáng tin cậy nào
Ngoài ra, nếu bạn cần sử dụng nó để xuất mã tĩnh, hãy xem xét đóng gói hàm này trong một hàm trợ giúp có tên apt [e. g. ,
{{review}}
2]. Tên như vậy truyền đạt rõ ràng mục đích của chức năng này để tránh bất kỳ vấn đề nào về việc sử dụng sai hoặc tái cấu trúc sau này. Ngoài ra, mẫu mã hóa này cho phép bạn cấm sử dụng
{{review}}
0 trong cơ sở mã của mình, ngoại trừ chức năng trợ giúp duy nhất đóSử dụng các phần tử DOM gốc
Góc hoạt động ở cấp độ các thành phần và không thực sự ở cấp độ các phần tử HTML riêng lẻ. Chắc chắn, các mẫu chứa các phần tử HTML, nhưng các ứng dụng Angular hiếm khi tham chiếu các phần tử này một cách rõ ràng từ mã. Hiếm khi là từ khóa ở đây. Hầu hết các ứng dụng không cần truy cập trực tiếp vào các phần tử HTML, nhưng một số trường hợp sử dụng cụ thể yêu cầu quyền truy cập cấp thấp như vậy
Đó chính xác là lý do tại sao Angular hỗ trợ sử dụng ElementRef. Ví dụ mã bên dưới cho biết cách tham chiếu một phần tử trong mã của thành phần bằng ElementRef. Phần tử này được định nghĩa một mẫu là
{{review}}
4Sử dụng ElementRef để chỉ một phần tử HTML cụ thể
Hello John!<script>alert["Let's play hide and seek!"]</script>
0Với tham chiếu này, giờ đây chúng ta có thể truy cập phần tử DOM gốc. Quyền truy cập như vậy hữu ích cho việc xử lý sự kiện chi tiết nhưng cũng cho phép các mẫu mã không an toàn, chẳng hạn như ví dụ mã bên dưới
Hành vi nguy hiểm khi sử dụng các phần tử DOM gốc
Hello John!<script>alert["Let's play hide and seek!"]</script>
1Trong ví dụ mã này, chúng tôi đưa dữ liệu trực tiếp vào DOM. Vì chúng tôi đang xử lý các API DOM gốc, nên Angular không còn có thể bảo vệ chúng tôi khỏi các cuộc tấn công XSS tiềm ẩn. Mã này cực kỳ không an toàn và vượt qua các biện pháp phòng thủ XSS tích hợp của Angular
Không có lý do chính đáng tại sao một mẫu mã như thế này nên được sử dụng trong ứng dụng Angular. Tôi khuyên bạn nên quét cơ sở mã của mình để đảm bảo không có mẫu này và thiết lập quy tắc linting để đảm bảo mẫu này sẽ không bao giờ xuất hiện trong cơ sở mã
Sử dụng API Renderer2
Việc sử dụng
Hello John!<script>alert["Let's play hide and seek!"]</script>
3 trên các phần tử DOM gốc không phải là cách duy nhất bạn có thể vượt qua các biện pháp phòng thủ an toàn theo mặc định của Angular. Với ElementRef, bạn cũng có thể sử dụng API Renderer2 để thao tác DOM. Cơ chế này có lẽ còn nguy hiểm hơn vì API Renderer2 là API góc hợp pháp. Thật không may, API này không áp dụng các biện pháp bảo vệ XSS tự động và nên tránh càng nhiều càng tốtVí dụ mã bên dưới cho thấy cách sử dụng API Renderer2 với ElementRef để đặt thuộc tính
Hello John!<script>alert["Let's play hide and seek!"]</script>
3 của một phần tử, bỏ qua các biện pháp phòng thủ XSS của AngularHành vi nguy hiểm khi sử dụng API Renderer2
Hello John!alert["Let's play hide and seek!"]
0Cũng giống như việc sử dụng API DOM gốc, tôi khuyên bạn nên quét cơ sở mã của mình để đảm bảo mẫu này không xuất hiện và thiết lập quy tắc linting để đảm bảo mẫu này sẽ không bao giờ được đưa vào cơ sở mã
Hội thảo bảo mật kéo dài 2 ngày này tiến xa hơn rất nhiều so với các biện pháp phòng thủ XSS tích hợp của Angular
Tham gia hội thảo này để tìm hiểu thêm về các biện pháp bảo mật tốt nhất hiện tại cho các ứng dụng Angular
Thêm thông tinĐợi một chút, còn URL thì sao?
Một nguồn lỗ hổng XSS phổ biến khác là các URL. Hãy xem URL được hiển thị bên dưới
Tất cả các trình duyệt hiện đại đều hỗ trợ URL JavaScript trong thuộc tính phần tử
Hello John!alert["Let's play hide and seek!"]
1URL này sử dụng
{{review}}
7 làm lược đồ, thay vì
{{review}}
8 hoặc
{{review}}
9. Khi trình duyệt nhìn thấy một URL như vậy trong tài liệu HTML, trình duyệt thường xem đó là mã JavaScript cần được thực thi. Bạn có thể thử xem điều gì sẽ xảy ra nếu một URL
{{review}}
7 kết thúc bằng
Hello John!<script>alert["Let's play hide and seek!"]</script>
8 của phần tử
Hello John!alert["Let's play hide and seek!"]
62 bằng cách nhấp vào đây. Khi người dùng có thể kiểm soát một URL như vậy, nó có thể được sử dụng để kích hoạt việc thực thi mã JavaScript độc hạiNgày nay, có thể nói rằng
{{review}}
7 URL là một sai lầm đau đớn trong quá khứ. Thật không may, các ứng dụng đã dựa vào hành vi này cho các tính năng hợp pháp, gây khó khăn cho việc tắt tính năng này ở cấp trình duyệt. Tuy nhiên, ngày càng có nhiều khung ứng dụng hiện đại không khuyến khích hoặc ngăn cản việc sử dụng các URL
{{review}}
7. Angular thực hiện chính xác điều đó thông qua một trình khử trùng URL cụ thểTrình khử trùng góc đảm bảo rằng các URL được tạo động an toàn để sử dụng trong ứng dụng. Nhìn vào mã cho thấy rằng trình khử trùng chỉ cho phép các URL an toàn đã biết và thêm tiền tố vào các URL khác bằng lược đồ
Hello John!alert["Let's play hide and seek!"]
65. Cơ chế này ngăn chặn hiệu quả XSS thông qua URLThậm chí tốt hơn, Angular áp dụng cơ chế này ngay lập tức
Không nhanh lắm, các URL tài nguyên không hoạt động tốt
Nếu bạn đã từng thử tải một iframe bằng cách gán một biến cho thuộc tính
Hello John!alert["Let's play hide and seek!"]
66 trong Angular, bạn có thể đã gặp lỗi như lỗi hiển thị bên dướiAngular từ chối tải các URL tài nguyên được gán động để tránh tải nội dung không an toàn hoặc không mong muốnĐây là Angular bảo vệ ứng dụng khỏi nội dung có thể gây hại. Tốt, nhưng tại sao lại có thông báo lỗi?
URL tài nguyên về cơ bản là các URL kích hoạt ngay việc tải tài nguyên. Ví dụ là iframe, tập lệnh, biểu định kiểu, v.v. Bao gồm các URL
{{review}}
7 ở một vị trí như vậy có thể nguy hiểm, nhưng còn nhiều điều nữa. Bạn muốn tải nội dung hoạt động như vậy từ đâu? Thay vì đoán xem cái gì an toàn và cái gì không, Angular sai lầm ở khía cạnh an toàn bằng cách từ chối tải tài nguyên động. Đó là lý do tại sao bạn nhận được thông báo lỗi. Vì vậy, về bản chất, Angular bảo vệ bạn khỏi những mẫu nguy hiểm này
Để kích hoạt hành vi này, ứng dụng của bạn sẽ phải chứng minh tính hợp lệ của URL mà bạn đang cố tải. Về bản chất, điều này có nghĩa là bạn sẽ phải đảm bảo URL được xây dựng an toàn hoặc bạn sẽ phải xác thực URL trước khi gán nó cho một thành phần HTML. Để ngăn Angular kích hoạt lỗi, bạn phải đánh dấu URL là an toàn trước khi gán nó. Bạn có thể làm điều đó bằng cách sử dụng hàm
Hello John!alert["Let's play hide and seek!"]
69, như hình bên dướiBiến một URL động thành một URL an toàn bằng cách sử dụng hàm bypassSecurityTrustResourceUrl
Hello John!alert["Let's play hide and seek!"]
2Lưu ý rằng trong ví dụ mã này, chúng tôi tạo URL Youtube an toàn bằng cách chỉ cho phép người dùng cung cấp ID video. Bằng cách đó, chúng tôi chắc chắn rằng URL trỏ đến
Hello John!alert["Let's play hide and seek!"]
70. Đảm bảo ứng dụng của bạn tuân theo một mẫu tương tự để đảm bảo an toàn cho URL. Không đánh dấu một URL không xác định là an toàn vì điều này có thể gây ra các lỗ hổng trong ứng dụng của bạnVà những gì về kết xuất trang phía máy chủ
Cái hay của Angular là nó là một framework giải quyết các trường hợp sử dụng phổ biến. Kết quả là mã Angular chạy trên trình duyệt chính là mã đang chạy trên máy chủ. Mặc dù điều này có vẻ hợp lý, nhưng điều này không phải lúc nào cũng đúng với các framework khác, chẳng hạn như React [Hãy xem loạt bài gồm 3 phần này về XSS trong React]
Cụ thể, đối với Angular, điều này có nghĩa là nếu bạn tuân theo các nguyên tắc viết mã an toàn, như đã thảo luận trong bài viết này, thì các trang được hiển thị phía máy chủ của bạn cũng được bảo mật. Angular Universal cũng bảo vệ chống lại các lỗ hổng bên ngoài khác, điều này thật tuyệt. Thật không may, điều này không phải lúc nào cũng đúng với các dự án dựa trên Angular. Bạn có thể đọc thêm về điều đó trong bài viết này về XSS đến
Hello John!alert["Let's play hide and seek!"]
71, một lỗ hổng đã có trong ScullyTóm tắt và hướng dẫn mã hóa an toàn
Điều đó đưa chúng ta đến phần tóm tắt của bài viết này về XSS trong các ứng dụng Angular. Để kết thúc, hãy tóm tắt lại những điều quan trọng nhất
- Nếu bạn tuân thủ cách làm này của Angular, Angular sẽ giúp bạn đảm bảo tính bảo mật cho ứng dụng của mình
- Để đảm bảo bạn để Angular thực hiện công việc của mình, hãy tránh thao tác DOM trực tiếp thông qua ElementRef, API Renderer2 hoặc API DOM gốc
- Đảm bảo ứng dụng của bạn không sử dụng hàm
72 với dữ liệu độngHello John!alert["Let's play hide and seek!"]
Đó là nó. Ba nguyên tắc đơn giản này sẽ giúp bạn tránh XSS trong Angular. Và nếu bạn thích bài viết này, đừng quên chia sẻ nó với bạn bè và đồng nghiệp Angular của bạn