[Security 101] Same Origin Policy

Anh Hoang
SotaTek
Published in
21 min readFeb 1, 2019

Không khí tết đang tràn về với SotaTek. Quả thật, biết trước sắp được nghỉ lại khiến cho năng suất lao động trở nên trì trệ ít nhiều. Và tất nhiên, chính bản thân tôi cũng bị ảnh hưởng tương tự. Lòng trắc ẩn cảm thấy tội lỗi nhưng cơ thể như tự điều phối hành động khiến tôi chỉ muốn ra ngoài chạy tung tăng hơn là bấm ra một loạt các dòng Code mà những ngày bình thường tôi vẫn thấy vô cùng hứng thú. Chính vì lẽ đó nên tôi quyết định ngồi lại, thay đổi không khí bằng cách viết tiếp series Security 101 của mình.

Trong bài viết trước về Tấn công CSRF, ngoài việc đề cập tới một hình thức tấn công vô cùng phổ biến và nổi tiếng, tôi đã có đề cập tới một thuật ngữ có tên: “Same-Origin Policy” — tạm dịch là “Điều khoản cùng nguồn gốc”. Thuật ngữ này là vô cùng đơn giản để hiểu, nhưng chúng ta sẽ cùng nhau đi sâu hơn nữa để tìm hiểu nguồn gốc và những hệ quả xung quanh bản thân điều khoản bảo mật này.

DẪN DẮT

Phần lớn khi tìm hiểu về các hình thức bảo mật hệ thống Web, thường thì các phương án đều tập trung vào 2 nguồn dẫn chính là: Server và phía Client. Chúng ta muốn đảm bảo rằng người dùng không được xác thực sẽ không được phép truy cập vào dịch vụ; chúng ta sẽ muốn đảm bảo rằng kẻ tấn công sẽ không thể phá tầng bảo mật cơ sở dữ liệu và lấy cắp đi các thông tin quý giá; chúng ta cũng sẽ không muốn một kẻ nào đó tấn công khiến tốc độ duyệt Web của các người dùng khác bị giảm xuống rõ rệt… Đó chắc chắn là những tư duy bảo mật cực kỳ quan trọng để chống lại các cuộc tấn công với mục đích xấu. Nhưng ta sẽ cùng nhau đề cập tới một hướng đi khác hoàn toàn mà hầu hết mọi người đã bỏ quên — về một thứ tối quan trọng đưa chúng ta đến với thế giới của Internet — Trình duyệt và bảo mật xung quanh nó.

Bản thân trong trình duyệt Web có các phương thức được tích hợp để ngăn chặn các trang Web độc hại đánh cắp dữ liệu cá nhân của người dùng. Trình duyệt hạn chế các phương thức mà Website có thể giao tiếp với máy chủ Web và với các cửa sổ khác trên cùng một trình duyệt. Tuy nhiên, nó sẽ chỉ có thể hạn chế sự giao tiếp kể trên chứ không thể ngăn chặn chúng một cách triệt để. Thông thường, những hạn chế rất tinh vi này có thể đủ để ngăn chặn các cuộc tấn công. Nhưng nếu một ứng dụng Web có những lỗ hổng nhất định trong các dòng Code — và đôi khi những lỗ hổng rất nhỏ dễ bị bỏ qua — sẽ khiến toàn bộ niềm tin của chúng ta bị đổ bể, và kẻ tấn công sẽ lợi dụng để bỏ qua các hình thức bảo mật vốn có của trình duyệt. Đó là lý do tại sao chúng ta lại cần phải tìm hiểu xem những hình thức bảo mật này là gì và bằng cách nào mà những kẻ tấn công có thể vượt qua chúng để thực hiện các cuộc tấn công quy mô của mình.

Same Origin Policy chính là một lựa chọn hoàn hảo để chúng ta bắt đầu câu chuyện — bởi về cơ bản, nó là nền tảng của hầu hết các nguyên tắc bảo mật của trình duyệt. Nếu thiếu nó, tất cả các Website trên Internet hoàn toàn có thể truy cập vào thông tin người dùng của các Website khác. Và cũng chính hình thức tấn công CSRF được nhắc tới trong bài trước là một phương thức tấn công đã có thể bỏ qua được lớp bảo mật này.

ĐỊNH NGHĨA

Same-Origin Policy thực chất là một thỏa thuận giữa các đơn vị xây dựng trình duyệt — như Microsoft, Apple, Google, Mozilla và Opera — nhằm xây dựng một chuẩn để hạn chế các chức năng trong các dòng code được chạy trên trình duyệt của người dùng. Bạn có thể nghĩ tại sao đây là một việc làm đúng đắn và tại sao chúng ta lại muốn giới hạn các chức năng trong các dòng code như vậy. Nếu có, thì bạn hãy yên tâm là tôi sẽ sớm đi vào chi tiết nhằm giải đáp thắc mắc đó trong các mục tiếp theo. Trước hết, hãy tin tôi một điều rằng: nếu không có Same Policy Origin, thì thế giới Web — World Wide Web đầy hiện đại và có trật tự nhất định, sẽ trở thành một thế giới hoang dã — Wild Wide Web chỉ trong vòng một nốt nhạc. Không một dữ liệu nào sẽ được bảo vệ an toàn, và bạn chắc chắn sẽ không nghĩ tới việc dùng thẻ ngân hàng của mình để thanh toán di động nữa đâu.

Một cách ngắn gọn, Same-Origin Policy biểu trưng cho việc: khi một người dùng xem một trang Web trên trình duyệt của họ, các đoạn mã chạy trên trang Web đó sẽ chỉ có thể được đọc hoặc định nghĩa nội dung của một trang Web khác nếu chúng có cùng một “nguồn gốc — Origin”. Và Origin ở đây được hiểu là sự kết hợp của cả 3 yếu tố: giao thức ứng dụng Web (HTTP/HTTPS), cổng giao tiếp (Port, thường là 80 với HTTP và 443 với HTTPS), và tên miền (vâng, www.google.com, www.facebook.com...)

Không định nghĩa nào đơn giản hơn bằng cách gán nó với một ví dụ cụ thể. Hãy tưởng tượng bạn đang cảm thấy hình nền Desktop của bạn quá nhàm chán và bạn muốn lên Google để tìm cho mình một tấm ảnh ưng ý. Vậy bạn sẽ mở trình duyệt và truy cập vào trang https://www.google.com/imghp và nhập từ khóa tìm kiếm. Nếu bên dưới nó có những đoạn mã đang chạy, thì sẽ có loại trang Web nào khác mà các đoạn Code có thể tương tác với?

  • http://www.google.com/imghp: Không, các mã sẽ không thể được đọc tới trang Web này do giao thức giữa 2 trang Web là khác nhau.
  • https://www.instagram.com: một cách rõ ràng là 2 tên miền khác nhau hoàn toàn nên câu trả lời là không.
  • https://photo.google.com/imghp: trang Web có đôi chút khéo léo trong việc đặt tên, nhưng câu trả lời vẫn là không vì sự khác biệt về Domain.
  • https://google.com/imghp: cũng rất khéo léo trong việc bỏ qua tiền tố www, nhưng sẽ chẳng thể bỏ qua được sự thật rằng có sự khác biệt về domain. Nên câu trả lời vẫn là không.
  • https://www.google.com:8000/imghp: câu trả lời cho trang Web này có đôi chút phức tạp. Nếu bạn đang sử dụng các trình duyệt ngoài Internet Explorer (IE) của Microsoft, thì đoạn mã sẽ không được đọc bởi có sự khác biệt về cổng kết nối (Ghi nhớ rằng với giao thức HTTP, nếu bạn không định nghĩa đích danh cổng giao tiếp, thì mặc định nó sẽ là cổng 80. Vậy nên http://google.com sẽ tương tự với http://google.com:80). Sự thật là, IE hoạt động có đôi chút khác biệt với các trình duyệt hiện đại. IE sẽ không sử dụng cổng kết nối như một phần của việc định nghĩa Origin — mà nó sử dụng các định nghĩa về “Vùng bảo mật — Security Zone”, gồm: “Internet”, “Local Intranet”, “Trusted Site”, và “Restricted Site”. Người dùng có thể định nghĩa Website nào nằm trong vùng nào, vì vậy nếu bạn đang sử dụng trình IE, thì các đoạn mã có thể đọc được.
  • https://www.google.com/search: trong trường hợp này, cả 3 yêu cầu bắt buộc đã được đảm bảo: cùng một giao thức (HTTPS), cùng cổng (80), cùng tên miền (www.google.com). Và cuối cùng thì câu trả lời đã là: Có — các đoạn mã có thể làm việc với Website này. Như vậy ta có thể thấy, các định hướng khác nhau trên cùng một Origin không ảnh hưởng tới Same-Origin Policy.

PHÂN BIỆT QUAN TRỌNG GIỮA: CLIENT-SIDE VÀ SERVER-SIDE

Sẽ là vô cùng quan trọng để lưu ý rằng Same-Origin Policy sẽ không có tác dụng với các trang mà tất cả các đoạn mã được nằm trên máy chủ. Máy chủ của www.google.com có thể thoải mái truy cập tới www.photo.google.com, facebook.com, genk.vn, hoặc thậm chí là là các trang Web nội bộ mà không thể truy cập được qua Internet. Same-Origin Policy chỉ được áp dụng lên trình duyệt chạy các đoạn mã trên hệ thống Client. Bạn có thể thắc mắc rằng: khác biệt giữa chúng là gì, và tại sao trình duyệt lại phải làm công việc phức tạp để giải quyết các rắc rối đó trong khi lại không có bất kỳ sự ngăn chặn nào liên quan tới các đoạn mã nằm trên máy chủ. Câu trả lời vô cùng đơn giản: Cookies.

Chúng ta có thể nhớ lại bài viết trước của tôi có đề cập tới việc: khi ta truy cập vào một trang Web bằng trình duyệt, thì chúng sẽ tự động gửi đi tất cả các Cookie được lưu cho trang đó đồng thời với các Request tới Server. Ví dụ, nếu tôi truy cập vào trang Web: genk.vn, trình duyệt của tôi sẽ gửi Cookie đi kèm với Request của tôi (trong trường hợp này, tên của Cookie đó là “x-main”) với giá trị sử dụng cho việc định danh tôi với Genk (hãy thử ví dụ giá trị đó là “anhhv”).

Nếu tôi là người duy nhất trên thế giới có x-main Cookie với giá trị “anhhv”, máy chủ Genk sẽ nhận biết được rằng tôi là một người đang truy cập vào Website, và sẽ cá nhân hóa nội dung sao cho phù hợp. Đó là lý do tại sao khi tôi truy cập Genk (hoặc bạn có thể thấy rõ nhất là Facebook) thì giao diện trên máy tôi sẽ khác với máy của bạn. Banner có thể hiển thị là: Xin chào AnhHV, và các bài viết sẽ được phân bố khác sao cho phù hợp với thói quen đọc bài viết của tôi, bài viết tôi đang lưu hoặc bài viết tôi sẽ có ý định xem sau.

Tuy nhiên, nếu tôi tạo nên một trang Web khác (ví dụ như: www.anhhv.com) và lập trình cho các đoạn mã bên phía máy chủ thực hiện một Request tới www.genk.vn, thì dữ liệu trả về sẽ không có bất kỳ cấu hình cá nhân nào của tôi cả, vì Request gửi đi sẽ không đi kèm với dữ liệu Cookie cá nhân của tôi. Và nếu tôi là người truy cập vào trang www.anhhv.com, thì trình duyệt sẽ chỉ gửi đi các Cookie tương ứng với trang Web www.anhhv.com chứ không bao gồm các Cookie của trang như www.facebook.com hoặc google.com.

Vậy điều quan trọng nhất của Same-Origin Policy không phải là ngăn chặn các trang Web đọc được nguồn dữ liệu từ Website khác, mà là để ngăn chặn các Website đọc các thông tin cá nhân hóa (hay cụ thể hơn, là dữ liệu xác thực), dữ liệu nhạy cảm hoặc nguồn dữ liệu riêng tư của các Website khác. Vậy là chúng ta có thể hiểu phần lớn về Same-Origin Policy rồi. Giờ hãy cùng tìm hiểu tại sao chúng ta lại cần tới nó.

MỘT THẾ GIỚI KHÔNG CÓ SAME-ORIGIN POLICY

Có một điều xảy ra với người lập trình ứng dụng và kẻ tấn công như một lẽ hiển nhiên đó là họ đều cảm thấy “ngán” với Same-Origin Policy, với cùng một lý do: nó ngăn chặn việc lấy dữ liệu từ các trang Web khác. Mục đích của việc này có thể khác nhau: kẻ tấn công muốn có dữ liệu để đem bán kiếm lời. Lập trình viên thì sẽ sử dụng cho việc sáng tạo ra các ý tưởng mới lạ như một hình thức “phối hợp” chức năng giữa các Website khác nhau. Nhưng đứng giữa “Tốt hơn” và “Tệ đi”, Same-Origin Policy có trong mình cả 2 điều đó.

Để chứng minh cho điều đó, hãy cùng tưởng tượng ra một thế giới không tồn tại Same-Origin. Trong thế giới này, một người bán hoa đang xây dựng một Website mới cho riêng mình. Người đó biết rằng, có rất nhiều người mua hoa để tặng mẹ trong dịp sinh nhật. Vì vậy, khi bạn truy cập vào trang Web, ví dụ www.buyaflowertoday.com, các đoạn mã bên phía Client sẽ tạo một Request tới trang calendar.google.com. Đoạn mã sẽ đọc toàn bộ dữ liệu bên trong Google Calendar và tìm xem có dữ liệu thuộc về ngày nào có tiêu đề là “Sinh nhật mẹ” hay không.

Nếu đoạn mã tìm thấy ngày đó, bước tiếp theo sẽ là kiểm tra trong Email cá nhân của bạn xem bạn có quên dịp sinh nhật của mẹ vào năm ngoái hay không. Một lần nữa, một Request được gửi tới Gmail, Yahoo Mail hoặc Hotmail để lấy các Email cá nhân của bạn và tìm xem ngày đó năm ngoái có Email nào có các từ như “thất vọng”, “mẹ buồn lắm con ơi”… hay không.

Và cuối cùng, đoạn mã sẽ kiểm tra xem số tiền còn lại trong tài khoản của bạn là bao nhiêu, và cửa hàng hoa Online sẽ đưa ra những gợi ý tốt nhất dựa trên tất cả các dữ liệu kể trên. Nếu quá trình kiểm tra tài khoản cho thấy bạn vừa chi một số tiền rất lớn cho việc mua xe nên hiện tại bạn không có xu nào dính túi, nhưng bạn sẽ có lương vào tuần tới, cửa hàng sẽ hoàn thiện toàn bộ quá trình cá nhân hóa của mình để đưa ra cho bạn một Offer như sau:

“Chào mừng bạn đã đến với cửa hàng hoa của chúng tôi! Bạn có đang lưu ý tới việc sẽ có sinh nhật mẹ mình vào ngày 24 tới? Tôi nghĩ về việc bạn đã quên đi sinh nhật của mẹ bạn vào năm ngoái, thì chắc chắn một đóa hoa hồng bó tròn tráng lệ chắc chắn sẽ làm mẹ anh cảm động. Bạn hãy yên tâm rằng chúng tôi sẽ chỉ thanh toán vào thứ 6 tuần tới. Tôi tin tình cảm của anh với mẹ sẽ xứng đáng hơn một chiếc xe Porsche siêu sang đó”.

Như vậy, tôi tin rằng bạn có thể thấy cả mặt tốt và mặt xấu của Same-Origin Policy. Một thế giới không có nó sẽ chìm đắm trong các ứng dụng tự động siêu tiện lợi, nhưng lại phải trả giá đắt cho cả 2 vấn đề về sự riêng tư và bảo mật.

NHỮNG NGOẠI LỆ VỚI SAME-ORIGIN POLICY

Tuy nhiên, ngay chính tại thế giới thực này, vẫn luôn có những cách dành cho ứng dụng để tránh được Same-Origin Policy. Với những mục đích chính đáng như cửa hàng hoa Online kể trên — nhưng cần cải thiện khả năng bảo mật cao hơn — là thật tốt khi cho phép bỏ qua. Vậy nên các đơn vị phát triển trình duyệt Web và các Plugin hỗ trợ trình duyệt sẽ cho phép một khả năng vượt qua Same-Origin Policy dưới một sự kiểm soát nhất định. Nhưng nếu bạn sử dụng chúng sai cách, bạn có thể khiến cho ứng dụng của bạn rộng cửa chào đón các kẻ tấn công và khiến người dùng gặp những rắc rối cực kỳ lớn liên quan tới các dữ liệu cá nhân nhạy cảm. Hãy cùng xem xét một vài cách để giải quyết Same-Origin Policy và một vài cách để đảm bảo điều này được thực hiện an toàn nhất có thể.

THUỘC TÍNH HTML <script>

Thuộc tính HTML chắc chắn là phương thức được sử dụng rộng rãi nhất về giao tiếp Cross-Origin. Nếu bạn mở nhiều trang trên mạng, khả năng chắc chắn là bạn có thể tìm thấy một thẻ <script> đâu đó trong Code của trang. Thẻ <script> là một khối dùng để chứa các đoạn mã chạy trên phía Client, thường là JavaScript. Ví dụ, đây là một đoạn mã để hiển thị một khung thông báo ngay trên trình duyệt của bạn với nội dung: ‘Hello JavaScript World!’:

<script>alert(‘Hello JavaScript World!’);</script>

Bên cạnh việc tự định nghĩa các đoạn mã bên trong thẻ <script>, bạn còn có thể sử dung thuộc tính “src” để tải các đoạn mã từ các nơi khác trên Internet:

<script src=”http://www.jslibrary.com/script.js"></script>

Đây chính là nơi giao tiếp Cross-Origin hình thành, bởi vì Same-Origin Policy sẽ không áp dụng lên thẻ <script src>. Tất cả các trang đều được phép tải và chạy các đoạn mã từ mọi nơi trên Internet.

Bạn nên cực kỳ thận trọng khi sử dụng nó trong các ứng dụng của mình trừ khi bạn có toàn quyền kiểm soát tập lệnh mà thẻ đó đang tải. Vì vậy, nếu giả sử bạn đang sở hữu trang www.websiteA.com, và bạn sử dụng thẻ <script src> để tải mã từ các vị trí trên trang www.websiteA.com, hoàn toàn OK:

<script src=”http://www.websiteA.com/script.js"/>

Nhưng cần cẩn thận nếu ta trỏ thẻ <script src> tới các tên miền khác mà bạn không sở hữu:

<script src=”http://www.websiteB.com/script.js"/>

Bất cứ khi nào người dùng truy cập vào trang Web chứa các thẻ này, trình duyệt sẽ tự động tải các đoạn mã từ www.websiteB.com và chạy nó. Nếu các đoạn mã đó là các đoạn mã độc, thì chắc chắn trách nhiệm là từ phía bạn. Và kể cả là khi đó là một trang Web đáng tin cậy và không chứa mã độc, thì khi trang Web đó bị hack, thì rồi sẽ đến trang của bạn, rồi người dùng của bạn bị chịu ảnh hưởng.

JSON VÀ JSONP

Có một điểm bất lợi khi sử dụng thẻ <script src>: khi bạn trỏ thẻ <script> tới bất kỳ trang nào trên Internet mà không liên quan tới Same-Origin Policy, nó chỉ chạy khi nguồn được định nghĩa trong thuộc tính “src” là đúng định dạng, với các đoạn mã hợp lệ. Nếu bạn trỏ nó tới một trang HTML bất kỳ, Code trên trang của bạn sẽ trả về lỗi và bạn sẽ không thể xem được dữ liệu mà bạn đang cố gắng lấy. Nó phải là các mã kịch bản, hợp lệ và chỉ chứa mã kịch bản.

Vào đầu năm 2000s, Douglas Crockford, hiện đang làm việc tại PayPal, nhận ra rằng sẽ là khả thi để tạo ra một định dạng dữ liệu sẽ được xem là một đoạn mã JavaScript hợp lệ. Ông ấy gọi nó là JSON, viết tắt của JavaScript Object Notation. Nếu bạn muốn tạo một đối tượng Object để mô tả một tập báo chí mới, nó có thể được định nghĩa như sau:

{“author”: “AnhHV”,“title”: “Security 101”,“release_date”: “30/01/2019”}

Rất nhiều các Web Service giờ sử dụng JSON là một định dạng dữ liệu bên cạnh XML vì JSON là một định dạng nhỏ gọn, gần với ngôn ngữ đời thường, và bởi JSON cũng chính là một đối tượng hợp lệ của JavaScript, dễ dàng làm việc với các mã JavaScript. Tuy nhiên, Web Service gửi dữ liệu dưới dạng ký tự, không phải dưới dạng một đối tượng của JavaScript. Nếu bạn nhận được một chuỗi dưới dạng JSON, bạn cần phải chuyển nó sang dạng Object trước khi làm việc với nó. Hiện nay có rất nhiều cách để làm điều đó, nhưng tôi sẽ nói về một cách làm trước đó mà đã phải bỏ đi vì nó vô cùng nguy hiểm với mã JavaScript.

var jsonString = ‘{“author”: “AnhHV”, “title”: “Security 101”, “release_date”: “30/01/2019”}’;var magazine = eval(‘(‘ + jsonString + ’)’);

Việc này cực kỳ nguy hiểm trừ khi bạn nắm quyền điều khiển các chuỗi JSON đó. Nếu kẻ tấn công có phương án thay đổi các giá trị bên trong dữ liệu JSON, hắn có thể thêm các dòng Code độc hại, và trình duyệt sẽ thực hiện chúng trong quá trình chuyển đổi sang JSON. Lý do là bởi, eval là một hàm có khả năng phản ứng với các chuỗi như các đoạn mã và thực hiện chúng. Và chính vì thế, nó là một hàm khuyến cáo không nên sử dụng trong hệ thống.

Tại đây, bạn có thể hỏi tại sao chúng ta phải mất công chuyển đổi JSON từ chuỗi. và sao chúng ta không sử dụng luôn thẻ <script src> để lấy chuỗi JSON trực tiếp. Vì sau tất cả, JSON cũng là một đoạn mã hợp lệ, vì vậy <script src> chắc chắn có thể thực hiện điều đó mà không có lỗi. Đúng là bạn có thể làm điều đó, tuy nhiên có một số lý do sẽ khiến bạn suy nghĩ lại. Đầu tiên, sử dụng thẻ <script src> cũng giống như hàm eval, vì vậy bạn chỉ làm khi bạn thực sự tin tưởng nguồn gốc của dữ liệu, nếu không bạn có thể khiến trang Web của bạn bị xâm hại. Hai là trừ khi trang đó hỗ trợ định dạng JSON có cơ chế callback được gọi là JSONP, bạn sẽ không thể làm gì với các dữ liệu trả về. Và ba là, kể cả khi có yếu tố trên, nó vẫn rất nguy hiểm để sử dụng.

Lý do bạn không nên làm việc trực tiếp với JSON nếu bạn cố gắng lấy dữ liệu đó bằng thẻ <script src> là khi JSON là một đối tượng JavsScript hợp lệ, nó cũng chính là vấn đề: một đối tượng — Object. Nó không được đặt tên, hoặc có phương án để truy cập dữ liệu đó trong mã. Trên thực tế, nếu mã trang không có cách nào để truy cập tới đối tượng JSON, nó sẽ được tự động xóa đi bởi Engine nội tại.

Nhà phát triển ứng dụng đôi khi phải làm việc với một biến thể của JSON được gọi là JSONP, viết tắt của JSON with Padding. JSONP giống với JSON, ngoại trừ dữ liệu được đặt bên trong một hàm JavsScript được gọi bởi Client gọi tới nó. Ví dụ, với chính dữ liệu tạp chí kể trên, dữ liệu được chứa bên trong một hàm có tên displayInfo, ta sẽ có định dạng như sau:

displayInfo({“author”: “AnhHV”, “title”: “Security 101”, “release_date”: “30/01/2019”})

Những gì ta cần làm là thêm hàm displayInfo vào trong đoạn mã xây dựng trang Web. Nhưng cũng có những lý do mà bạn tránh dùng JSONP. Hãy đổi chiều sang việc bạn cung cấp một phương thức JSONP cho người khác sử dụng, và bạn biết rằng Same-Origin Policy không tác động tới JSONP. Nhưng vì không có sự bảo vệ đó, sẽ không có bất cứ cách nào có thể ngăn chặn kẻ tấn công lấy được dữ liệu. Vì vậy, tôi khuyên bạn không nên sử dụng JSONP và tìm cách để phòng tránh, trừ khi dữ liệu đó được phân phối rộng rãi và mọi người đều có quyền xem nó.

TẬP TIN ADOBE FLASH PLAYER CROSS-DOMAIN POLICY

Adobe Flash là một Framework lập trình được sử dụng để tạo nên các ứng dụng phong phú trên Internet (Rich Internet Application — RIA) với nhiều tính đa phương tiện và tương tác người dùng so với các ứng dụng Web phổ thông. Ứng dụng Flash chạy trên trình duyệt của người dùng, giống như các đoạn mã JavaScript. Chắc chắn các bạn đã chơi qua rất nhiều các trò chơi được viết dưới dạng Flash. Tôi vẫn nhớ tới Popcap — nổi tiếng với một loạt các tựa Game chất lượng trên nền tảng Flash như Xếp kim cương chẳng hạn. Hoặc cả những Banner quảng cáo ngày xưa, hoàn toàn viết bằng Flash.

Ứng dụng Flash cũng có khả năng thực hiện các Request giữa các trang có tên miền khác nhau, nhưng được hạn chế một cách chặt chẽ hơn việc trang Web sử dụng thẻ <script src>. Để có thể cho phép các ứng dụng Flash có thể đọc được dữ liệu từ nguồn khác, thì người sở hữu Website được trỏ đến — là các Website không phải là ứng dụng Flash — cần phải đặt trên đó một tập tin chính sách có tên crossdomain.xml với nội dung chỉ định chính xác ứng dụng nào được phép tạo một Cross-Domain Request.

Ví dụ, hãy cho rằng một ứng dụng Flash được đặt trên đường dẫn www.websiteA.com/flash.swf, và bạn muốn ứng dụng có thể tải các dữ liệu lịch cá nhân người dùng ở trang www.websiteB.com/calendar. Để việc đó có thể được thực hiện, người quản trị trang www.websiteB.com cần phải tạo một tệp tin crossdomain.xml và đặt nó vào thư mục gốc của Website.

Tuy nhiên, kể cả tệp tin crossdomain.xml được đặt trên trạng cũng không có nghĩa là trang đó hoàn toàn mở cửa cho mọi Cross-Origin Request. Chính sách Cross-Domain của Flash có thể được cấu hình chỉ cho phép một số Request từ các trang xác định. Đây là một ví dụ về tệp tin crossdomain.xml chỉ cho phép Request tới từ trang www.websiteA.com:

<cross-domain-policy><allow-access-from domain=”www.websiteA.com" /></cross-domain-policy>

Bạn có thể liệt kê nhiều tên miền trong tệp tin crossdomain.xml. Dưới đây là một ví dụ:

<cross-domain-policy><allow-access-from domain=”www.websiteA.com" /><allow-access-from domain=”www.websiteB.com" /><allow-access-from domain=”*.websiteC.com” /></cross-domain-policy>

Kể cả việc cho phép toàn bộ quyền truy cập từ Internet:

<cross-domain-policy><allow-access-from domain=”*” /></cross-domain-policy>

Tất nhiên, việc rộng cửa chào đón toàn bộ ứng dụng Internet có quyền truy cập vào hệ thống chính là một việc làm quá sức nguy hiểm. Nó giống như lời tuyên bố: “Vào đây và lấy dữ liệu của tôi đi” vậy. Điều này chỉ thực sự an toàn nếu trang Web của bạn không có những dữ liệu nhạy cảm của người dùng.

Tất nhiên, thực tế ngày nay thì Flash đã bị khai tử vì những lỗi bảo mật yếu kém bên dưới nó. Nhưng chúng ta có thể thấy được về mặt lịch sử thì đây là một phương pháp cho phép ta vượt qua Same-Origin Policy.

MICROSOFT SILVERLIGHT

Giống với Flash, Microsoft cũng có cho mình một Plugin RIA, với cái tên Silverlight. Cross-Origin trên Silverlight hoạt động giống với Cross-Origin trên Flash. Silverlight thậm chí còn kiểm tra chính xác tệp tin crossdomain.xml theo cách của Flash. Silverlight sẽ kiểm tra nó một cách riêng biệt, đi kèm với một tệp tin dành riêng cho nó với cái tên clientaccesspolicy.xml. Đây là một ví dụ về nội dung của file đó:

<access-policy><cross-domain-access><policy><allow-from http-request-headers=”*”><domain uri=”www.websiteA.com” /><domain uri=”www.websiteB.com” /><allow-from><grant-to><resource path=”/” include-subpaths=”true”/></grant-to></policy></cross-domain-access></access-policy>

Và tất nhiên, nó cũng cho phép cấu hình để mọi ứng dụng Website trên Internet có thể được truy cập.

Tất nhiên, ứng nguy hại cũng giống như với Flash, và sự thật là giờ đây, giống với số phận của Flash thì Silverlight cũng đã bị khai tử.

XMLHTTPREQUEST (AJAX) VÀ CROSS-ORIGIN RESOURCE SHARING

Một cách thức khác để xây dựng các ứng dụng RIA là sử dụng JavaScript XMLHttpRequest. Giống như Flash và Silverlight, XMLHttpRequest cho phép các đoạn mã trên Client khởi tạo Request tới các máy chủ khác để lấy dữ liệu mà không cần phải tải lại trang (chúng ta đã nghe nhiều về phương thức này dưới cái tên Ajax, là một từ viết tắt cho Asynchronous JavaScript And XML). Một lợi thế của Ajax so với các RIA Framework khác là đây là phương thức nội tại bên trong JavaScript và không cần yêu cầu người dùng phải cài thêm các Plugin nào khác. Và với các thiết bị iOs của Apple (iPhone, iPad, iPod) không hỗ trợ Flash và Silverlight (giờ cũng chả có mấy thiết bị hỗ trợ nữa rồi), các tổ chức xây dựng RIA cần phải sử dụng Ajax.

Giống như Flash và Silverlight, XMLHttpRequest sẽ bị hạn chế bởi Same-Origin Policy. Mặc định thì chúng chỉ có thể tạo Request tới cùng một nguồn mà ứng dụng đó được đặt và triển khai trên đó. Tuy nhiên cũng giống Flash và Silverlight, bạn có thể ngăn chặn Same-Origin Policy tác động lên Ajax. Vì không có một hình thức tương đương như crossdomain.xml trên Ajax, mà dùng một giải pháp thay thế mang tên Cross-Origin Resource Sharing (CORS).

Thay thế cho các tệp tin xác thực, CORS sử dụng Header trả về của phương thức HTTP để điều khiển quyền Cross-Origin. Nếu bạn muốn máy chủ Web cho phép các ứng dụng Ajax từ các nguồn khác truy cập, bạn cần thêm một tùy chọn Header có tên Access-Control-Allow-Origin vào Response trả về. Bằng việc đặt giá trị là “*”, tức cho phép toàn bộ các ứng dụng khác có quyền truy cập, hoặc chỉ định chính xác một tên miền như “www.websiteA.com" trong trường hợp cụ thể.

Nếu dựa trên các lưu ý mà tôi đã đề cập ở mục Flash và Silverlight, chắc hẳn bạn cũng sẽ tự luận ra về việc tránh sử dụng tùy chọn “*” cho CORS. Nhưng có một điều khác biệt giữa CORS với các phương thức Cross-Origin khác đó là mặc định, trình duyệt sẽ không gửi đi Cookie người dùng hoặc các thông tin xác thực kèm theo CORS Request. Không có Cookie, máy chủ sẽ không thể xác thực được người dùng nào đang tạo Request, nên nó không thể trả về các dữ liệu cá nhân, vì vậy sẽ ít nguy hiểm hơn hẳn.

Chúng ta có thể gửi đi Cookie với CORS, nếu trong mã của Client thêm cho mình thuộc tính “withCredentials” bên trong nội tại XMLHttpRequest và bên phía Server sẽ thêm giá trị “Access-Control-Allow-Credentials: true” vào phản hồi. Nếu bạn quyết định cho phép các thông tin xác thực CORS trên Request thì khả năng cao là bạn nên cân nhắc về việc không sử dụng tùy chọn “*” trên trường Access-Control-Allow-Origin.

Bạn cần phải lưu ý rằng CORS không áp dụng lên các trình duyệt đang ở các phiên bản quá cũ (giờ còn ai dùng các phiên bản trình duyệt cũ nữa thì đúng là thần thánh). Và ở các trình duyệt cũ đó, nó không sử dụng XMLHttpRequest mà là XDomainRequest với nhiều hạn chế và lỗi bảo mật nghiêm trọng hơn. Và vì nó đã quá cũ rồi nên tôi sẽ không đề cập thêm mà các bạn sẽ tự tìm hiểu nhé.

KẾT LUẬN

Trình duyệt thật không đơn giản như những gì ta đang thấy và sử dụng hàng ngày. Nhờ có những nỗ lực này mà chúng ta và bản thân các nhà phát triển có thể yên tâm hơn trong quá trình làm việc. Tuy nhiên, điều gì cũng có khuyết điểm riêng bên trong đó và như một câu nói rất nổi tiếng trong một bộ Film: "Luật pháp sinh ra là để lợi dụng, không phải để tuân thủ" - cho chúng ta thấy là sẽ luôn có cách để "lách luật" và gây ra các cuộc tấn công nguy hại. Bản thân các nhà phát triển cần có những tư duy trong quá trình viết Code để đảm bảo cho sản phẩm của chúng ta luôn được đặt trên một tính bảo mật cao nhất.

--

--