web安全中有不少种攻击手段,除了SQL注入外,比较常见的还有 XSS 和 CSRF等javascript
XSS其实就是Html的注入问题,攻击者的输入没有通过严格的控制进入了数据库,最终显示给来访的用户,致使能够在来访用户的浏览器里以浏览用户的身份执行Html代码。php
数据流程为:攻击者的Html输入—>web程序—>进入数据库—>web程序—>用户浏览器。html
跨站脚本,顾名思义,更多的状况下是注入一些js代码,实现站点影响或窃取用户信息等目的。java
通常的攻击方式:node
><script>alert(document.cookie)</script> ='><script>alert(document.cookie)</script> "><script>alert(document.cookie)</script> <script>alert(document.cookie)</script> <script>alert(vulnerable)</script> %3Cscript%3Ealert('XSS')%3C/script%3E <script>alert('XSS')</script> <img src="javascript:alert('XSS')"> <img src="http://xxx.com/yyy.png" onerror="alert('XSS')"> <div style="height:expression(alert('XSS'),1)" />(这个仅限 IE 有效)
经过在站点中可输入的文本区域,输入相似上述提到的js代码,若站点未对数据进行验证处理,脚本就会存入数据库,进而显示给其余用户,则其余用户将会受到影响。web
而影响方式主要有几个:ajax
1. 若是是这种无聊恶意的数据库
<script>alert(哈哈哈你关不掉个人~)</script>
用户打开相应站点则..关不掉..express
或者恶意更改站点原数据浏览器
<script> window.onload = function() { var links=document.getElementsByTagName("a"); for(var i=0,j=links.length;i<j;i++){ links[i].href="http://ad.com/"; } }; </script>
2.窃取cookie,或者更直接的是拿到sessionId(拿到该用户的登陆凭证)
若是须要收集来自被攻击者的数据(如cookie或其余敏感信息),能够自行架设一个网站,让被攻击者经过JavaScript等方式把收集好的数据做为参数提交,随后以数据库等形式记录在攻击者本身的服务器上。
而使用方式能够是暴力地直接跳转到恶意站点并附带参数,软暴力地则可使用 img link script 标签src属性直接加载某个恶意站点,或者使用ajax暗地操刀。
3.利用可被攻击的域受到其余域信任的特色,以受信任来源的身份请求一些平时不容许的操做(这个已经属于csrf范畴了)
通常的防护措施:
1.永远不相信用户的输入。须要对用户的输入进行处理,只容许输入合法的值,其它值一律过滤掉。
某些状况下,咱们不能对用户数据进行严格的过滤,那咱们也须要对标签进行转换。
less-than character (<) | < |
greater-than character (>) | > |
ampersand character (&) | & |
double-quote character (") | " |
space character( ) | |
Any ASCII code character whose code is greater-than or equal to 0x80 | &#<number>, where <number> is the ASCII character value. |
好比用户输入:
<script>window.location.href=”http://www.baidu.com”;</script>,
保存后最终存储的会是:
<script>window.location.href="http://www.baidu.com"</script>
在展示时浏览器会对这些字符转换成文本内容显示,而不是一段可执行的代码。
许多语言都有提供对HTML的过滤:
PHP的htmlentities()或是htmlspecialchars()。 Python的cgi.escape()。 ASP的Server.HTMLEncode()。 ASP.NET的Server.HtmlEncode()或功能更强的Microsoft Anti-Cross Site Scripting Library Java的xssprotect(Open Source Library)。 Node.js的node-validator。
2.HttpOnly防止劫取Cookie
HttpOnly最先由微软提出,至今已经成为一个标准。浏览器将禁止页面的Javascript访问带有HttpOnly属性的Cookie。
目前主流浏览器都支持,HttpOnly解决是XSS后的Cookie支持攻击。
好比php代码的使用
<?php header("Set-Cookie: cookie1=test1;"); header("Set-Cookie: cookie2=test2;httponly",false); setcookie('cookie3','test3',NULL,NULL,NULL,NULL,false); setcookie('cookie4','test4',NULL,NULL,NULL,NULL,true); ?> <script> alert(document.cookie); </script>
js只能读到没有HttpOnly标识的Cookie
XSS 是实现 CSRF 的诸多途径中的一条,但绝对不是惟一的一条。通常习惯上把经过 XSS 来实现的 CSRF 称为 XSRF。
CSRF 顾名思义,是伪造请求,冒充用户在站内的正常操做。咱们知道,绝大多数网站是经过 cookie 等方式辨识用户身份(包括使用服务器端 Session 的网站,由于 Session ID 也是大多保存在 cookie 里面的),再予以受权的。因此要伪造用户的正常操做,最好的方法是经过 XSS 或连接欺骗等途径,让用户在本机(即拥有身份 cookie 的浏览器端)发起用户所不知道的请求。
要完成一次CSRF攻击,受害者必须依次完成两个步骤:
1.登陆受信任网站A,并在本地生成Cookie。
2.在不登出A的状况下,访问危险网站B。
看到这里,你也许会说:“若是我不知足以上两个条件中的一个,我就不会受到CSRF的攻击”。
是的,确实如此,但你不能保证如下状况不会发生:
1.你不能保证你登陆了一个网站后,再也不打开一个tab页面并访问另外的网站。
2.你不能保证你关闭浏览器了后,你本地的Cookie马上过时,你上次的会话已经结束。(事实上,关闭浏览器不能结束一个会话,但大多数人都会错误的认为关闭浏览器就等于退出登陆/结束会话了……)
3.上图中所谓的攻击网站,多是一个存在其余漏洞的可信任的常常被人访问的网站。
上面大概地讲了一下CSRF攻击的思想,下面我将用几个例子详细说说具体的CSRF攻击,这里我以一个银行转帐的操做做为例子(仅仅是例子,真实的银行网站没这么傻:>)
示例1:
银行网站A,它以GET请求来完成银行转帐的操做,如:
http://www.mybank.com/Transfer.php?toBankId=11&money=1000
危险网站B,它里面有一段HTML的代码以下:
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
首先,你登陆了银行网站A,而后访问危险网站B,噢,这时你会发现你的银行帐户少了1000块……
为何会这样呢?缘由是银行网站A违反了HTTP规范,使用GET请求更新资源。在访问危险网站B的以前,你已经登陆了银行网站A,而B中的<img>以GET的方式请求第三方资源(这里的第三方就是指银行网站了,本来这是一个合法的请求,但这里被不法分子利用了),因此你的浏览器会带上你的银行网站A的Cookie发出Get请求,去获取资源“http://www.mybank.com/Transfer.php?toBankId=11&money=1000”,
结果银行网站服务器收到请求后,认为这是一个更新资源操做(转帐操做),因此就马上进行转帐操做……
示例2:
为了杜绝上面的问题,银行决定改用POST请求完成转帐操做。
银行网站A的WEB表单以下:
<form action="Transfer.php" method="POST"> <p>ToBankId: <input type="text" name="toBankId" /></p> <p>Money: <input type="text" name="money" /></p> <p><input type="submit" value="Transfer" /></p> </form>
后台处理页面Transfer.php以下:
<?php session_start(); if (isset($_REQUEST['toBankId'] && isset($_REQUEST['money'])) { buy_stocks($_REQUEST['toBankId'], $_REQUEST['money']); } ?>
危险网站B,仍然只是包含那句HTML代码:
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
和示例1中的操做同样,你首先登陆了银行网站A,而后访问危险网站B,结果…..和示例1同样,你再次没了1000块~T_T,此次事故的缘由是:银行后台使用了$_REQUEST去获取请求的数据,而$_REQUEST既能够获取GET请求的数据,也能够获取POST请求的数据,这就形成了在后台处理程序没法区分这究竟是GET请求的数据仍是POST请求的数据。在PHP中,可使用$_GET和$_POST分别获取GET请求和POST请求的数据。在JAVA中,用于获取请求数据request同样存在不能区分GET请求数据和POST数据的问题。
示例3:
通过前面2个惨痛的教训,银行决定把获取请求数据的方法也改了,改用$_POST,只获取POST请求的数据,后台处理页面Transfer.php代码以下:
<?php session_start(); if (isset($_POST['toBankId'] && isset($_POST['money'])) { buy_stocks($_POST['toBankId'], $_POST['money']); } ?>
然而,危险网站B与时俱进,它改了一下代码:
<html> <head> <script type="text/javascript"> function steal() { iframe = document.frames["steal"]; iframe.document.Submit("transfer"); } </script> </head> <body onload="steal()"> <iframe name="steal" display="none"> <form method="POST" name="transfer" action="http://www.myBank.com/Transfer.php"> <input type="hidden" name="toBankId" value="11"> <input type="hidden" name="money" value="1000"> </form> </iframe> </body> </html>
若是用户还是继续上面的操做,很不幸,结果将会是再次不见1000块……由于这里危险网站B暗地里发送了POST请求到银行
总结一下上面3个例子,CSRF主要的攻击模式基本上是以上的3种,其中以第1,2种最为严重,由于触发条件很简单,一个<img>就能够了,而第3种比较麻烦,须要使用JavaScript,因此使用的机会会比前面的少不少,但不管是哪一种状况,只要触发了CSRF攻击,后果都有可能很严重。
理解上面的3种攻击模式,其实能够看出,CSRF攻击是源于WEB的隐式身份验证机制!WEB的身份验证机制虽然能够保证一个请求是来自于某个用户的浏览器,但却没法保证该请求是用户批准发送的.
那有什么防护方法?
通常防护CSRF有三种方法,判断referer、验证码、token。
1.判断 referer
根据HTTP协议,在HTTP头中有一个字段叫Referer,它记录了该HTTP请求的来源地址。
在一般状况下,访问一个安全受限页面的请求必须来自于同一个网站。
好比某银行的转帐是经过用户访问http://bank.test/test?page=10&userID=101&money=10000页面完成,用户必须先登陆bank.test,而后经过点击页面上的按钮来触发转帐事件。当用户提交请求时,该转帐请求的Referer值就会是转帐按钮所在页面的URL(本例中,一般是以bank. test域名开头的地址)。
而若是攻击者要对银行网站实施CSRF攻击,他只能在本身的网站构造请求,当用户经过攻击者的网站发送请求到银行时,该请求的Referer是指向攻击者的网站。
所以,要防护CSRF攻击,银行网站只须要对于每个转帐请求验证其Referer值,若是是以bank. test开头的域名,则说明该请求是来自银行网站本身的请求,是合法的。若是Referer是其余网站的话,就有多是CSRF攻击,则拒绝该请求。
好比php的:
<?php if(eregi(”bank.test”, $_SERVER[’HTTP_REFERER’])) { do_something(); } else { echo “Malicious Request!”; } ?>
这个检测则会轻易的忽略掉来自某个攻击者伪造的HTTP Referer欺骗,
因为HTTP Referer是由客户端浏览器发送的,或者其余在恶意脚本中伪造HTTP头并发送的方法。攻击者可使用以下代码是伪造无效的。
header(”Referer: bank.test”);
但缺点是并非全部浏览器都支持referer头,或者一些flash的提交也不支持,因此存在着缺陷。
2.验证码
另一个解决这类问题的思路则是在用户提交的每个表单中使用一个随机验证码,让用户在文本框中填写图片上的随机字符串,而且在提交表单后对其进行检测。
这个方法曾经在以前被人们放弃,这是因为验证码图片的使用涉及了一个被称为MHTML的Bug,可能在某些版本的微软IE中受影响。
而验证码的过分使用也会影响到用户体验。
3.token
1)在请求地址中添加token并验证
CSRF攻击之因此可以成功,是由于攻击者能够伪造用户的请求,该请求中全部的用户验证信息都存在于Cookie中,所以攻击者能够在不知道这些验证信息的状况下直接利用用户本身的Cookie来经过安全验证。
由此可知,抵御CSRF攻击的关键在于:在请求中放入攻击者所不能伪造的信息,而且该信息不存在于Cookie之中。
鉴于此,系统开发者能够在HTTP请求中以参数的形式加入一个随机产生的token,并在服务器端创建一个拦截器来验证这个token,若是请求中没有token或者token内容不正确,则认为多是CSRF攻击而拒绝该请求。
仍是用php举例:
让咱们从令牌值的生成开始:
<?php function gen_token() { // Generate the md5 hash of a randomized uniq id $hash = md5(uniqid(rand(), true)); // Select a random number between 1 and 24 (32-8) $n = rand(1, 24); // Generate the token retrieving a part of the hash starting from // the random N number with 8 of lenght $token = substr($hash, $n, 8); return $token; } ?>
PHP函数uniqid()容许web开发者根据当前的时间(毫秒数)得到一个惟一的ID,这个惟一ID有利于生成一个不重复的数值。
咱们检索相应ID值的MD5散列,然后咱们从该散列中以一个小于24的数字为开始位置,选取8位字母、
返回的$token变量将检索一个8位长的随机令牌。
如今让咱们生成一个Session令牌,在稍后的检查中咱们会用到它。
<?php function gen_stoken() { // Call the function to generate the token $token = gen_token(); // Destroy any eventually Session Token variable destroy_stoken(); // Create the Session Token variable session_register(STOKEN_NAME); $_SESSION[STOKEN_NAME] = $token; } ?>
在这个函数中咱们调用gen_token()函数,而且使用返回的令牌将其值复制到一个新的$_SESSION变量。
如今让咱们来看启动完整机制中为咱们的表单生成隐藏输入域的函数:
<?php function gen_input() { // Call the function to generate the Session Token variable gen_stoken(); // Generate the form input code echo “<input type=\”hidden\” name=\”" . FTOKEN_NAME . “\” value=\”" . $_SESSION[STOKEN_NAME] . “\”> “; } ?>
咱们能够看到,这个函数调用了gen_stoken()函数而且生成在WEB表单中包含隐藏域的HTML代码。
接下来让咱们来看实现对隐藏域中提交的Session令牌的检测的函数:
<?php function token_check() { // Check if the Session Token exists if(is_stoken()) { // Check if the request has been sent if(isset($_REQUEST[FTOKEN_NAME])) { // If the Form Token is different from Session Token // it’s a malicious request if($_REQUEST[FTOKEN_NAME] != $_SESSION[STOKEN_NAME]) { gen_error(1); destroy_stoken(); exit(); } else { destroy_stoken(); } // If it isn’t then it’s a malicious request } else { gen_error(2); destroy_stoken(); exit(); } // If it isn’t then it’s a malicious request } else { gen_error(3); destroy_stoken(); exit(); } } ?>
这个函数检测了$_SESSION[STOKEN_NAME]和$_REQUEST[FTOKEN_NAME]的存在性(我使用了$ _REQUEST方法来使得GET和POST两种方式提交的表单变量均可以被接受),然后检测他们的值是否相同,所以判断当前表单提交是不是通过认证受权的。
这个函数的重点在于:在每次检测步骤结束后,令牌都会被销毁,而且仅仅在下一次表单页面时才会从新生成。
这些函数的使用方法很是简单,咱们只须要加入一些PHP代码结构。
下面是Web表单:
<?php session_start(); include(”functions.php”); ?> <form method=”POST” action=”resolve.php”> <input type=”text” name=”first_name”> <input type=”text” name=”last_name”> <!– Call the function to generate the hidden input –> <? gen_input(); ?> <input type=”submit” name=”submit” value=”Submit”> </form>
下面是解决的脚本代码:
<?php session_start(); include(”functions.php”); // Call the function to make the check token_check(); // Your code … ?>
2)在HTTP头中自定义属性并验证
自定义属性的方法也是使用token并进行验证,和前一种方法不一样的是,这里并非把token以参数的形式置于HTTP请求之中,而是把它放到HTTP头中自定义的属性里。
经过XMLHttpRequest这个类,能够一次性给全部该类请求加上csrftoken这个HTTP头属性,并把token值放入其中。
这样解决了前一种方法在请求中加入token的不便,同时,经过这个类请求的地址不会被记录到浏览器的地址栏,也不用担忧token会经过Referer泄露到其余网站。