最近几年层出不穷的安全漏洞,再一次要求大家提高安全意识。安全是很大的一个话题,下面是我平时工作总结出来的一些安全方面的经验。
关闭错误显示
很多网站不知道是运维人员的业余还是疏忽,网站的错误显示没有关闭。攻击者只需要通过调整参数,就可以获得错误的堆栈信息,从而推断出网站使用的相关技术栈,从而为后面的攻击大开方便之门。
避免SQL注入经验
关于SQL注入,只要是稍微有点经验的程序员都应该明白是怎么回事。此处不谈SQL注入的方法和原理,只谈我们在开发过程中怎样编程是较安全的方式,从而将注入的风险降到最低。
- 前面已经说过了关闭错误显示,只要我们关闭了错误显示,我们就可以将盲注的难度提升。
- 参数校验和过滤特殊符号,这对喜欢拼SQL的人来说是一个可行的方案,不过成本很高。
- 绑定变量,使用预编译语句。例如:PreparedStatement(需要驱动支持),框架层面如Mybatis中要尽量使用
#
符号,少用和不用$
,如果非要$
使用也要用方案2来进行补偿。
SQL注入已经经历10几个年头了,它的攻防都有非常成熟的案例,我个人觉得只要我们在开发的时候稍微留点心,就可以防住绝大多数的攻击。
用户账号体系安全
任何一家互联网公司或者软件公司都会遇到用户账号体系设计的需求,尤为重要的就是用户密码的安全。很多知名的互联网公司用户库的泄露更是给我们敲响了警钟,所以我们需要设计一套即使账号体系发生泄露,也不会造成较大的危害,即泄露也不能登录用户账号。关于账号体系的设计,网上的方案有很多,我觉得主要需要做好如下几点。
- 千万不要自己去写hash函数,如果你自信到能够写出SHA256、SHA512那就另当别论,建议使用SHA256、SHA512、RipeMD、WHIRLPOOL、SHA3等经过验证的hash函数,至于
MD5、SHA1这些毕竟有点老了建议不使用。 - 为了防止查表(包含彩虹表)破解、暴力破解,所以一定需要加盐。首先盐不能复用,每个用户一个盐,密码发生变动盐也要同步修改;其次盐不能被预测,所以建议使用伪随机数生成器(CSPRNG),各个平台都有相应的技术解决方案。至于盐放到密码后面还是前面,这个都可以,只要保持风格一致就行。
- hash一定要在服务端做,即使前端JS做了hash,前端hash依赖于JS,如果前端禁用JS,后端还需要模拟前端的hash。前端的盐千万别向后端请求去获取用户的盐,前端的盐可以用用户名+特定的字符串的规则来实现。
- 尽可能使用慢速hash函数,让破解的代价高昂到不可以接收,所以我们可以使用key扩展的hash函数,可以使用PBKDF2或者bcrypt,虽然慢速hash可能导致DDoS攻击,但是我们可以让迭代次数少一点或者在JS端来运行hash函数,如Stanford JavaScript Crypto Library。
- 密码重置的时候,随机token一定要跟账户绑定并设置时效,因为SMTP是明文传输协议,防止SMTP导致安全问题。
- 客户端hash并不能代替TLS,建议为了防止中间人攻击,认证等敏感信息接口还是走TLS。
我平时更多的是使用Spring-security中提供的BCryptPasswordEncoder
,它是OpenBSD-style Blowfish的实现,盐都不用保存了,它直接帮我搞定非常方便,代码如下:
1 | package info.yangguo.demo.security; |
CSPRNG方案
platform | CSPRNG |
---|---|
PHP | PHP mcrypt_create_iv,openssl_random_pseudo_bytes |
Java | java.security.SecureRandom |
Dot NET (C#, VB) | System.Security.Cryptography.RNGCryptoServiceProvider |
Ruby | SecureRandom |
Python | os.urandom |
Perl | Math::Random::Secure |
C/C++ (Windows API) | CryptGenRandom |
Any language on GNU/Linux or Unix | Read from /dev/random or /dev/urandom |
PHP PBKDF2 密码hash代码
1 |
|
java PBKDF2 密码hash代码
1 | /* |
ASP.NET (C#)密码hash代码
1 |
|
XSS防御
XSS漏洞千奇百怪,我们要防止XSS的攻击,可以遵循下面的一些原则:
- 尽量不要页面中插入不可信的数据,如果迫不得已要插入,我们需要进行编码,编码使用OWASP提供的ESAPI的函数来实现,最好别自己实现。
- 如果需要将不可信任的数据插入到HTML标签之间,需要对这些数据进行HTML Entity编码,ESAPI使用如下:
1 | String encodedContent = ESAPI.encoder().encodeForHTML(request.getParameter(“input”)); |
- 将不可信的数据插入到HTML属性里的时候,需要进行HTML属性编码,ESAPI使用如下:
1 | String encodedContent = ESAPI.encoder().encodeForHTMLAttribute(request.getParameter(“input”)); |
- 在将不可信数据插入到SCRIPT里时,需要对这些数据进行SCRIPT编码,ESAPI使用如下:
1 | String encodedContent = ESAPI.encoder().encodeForJavaScript(request.getParameter(“input”)); |
- 在将不可信数据插入到Style属性里时,对这些数据进行CSS编码,ESAPI使用如下:
1 | String encodedContent = ESAPI.encoder().encodeForCSS(request.getParameter(“input”)); |
- 在将不可信数据插入到HTML URL里时,对这些数据进行URL编码,我们可以进行URL编码和URL可信度的检查,ESAPI使用如下:
1 | String encodedContent = ESAPI.encoder().encodeForURL(request.getParameter(“input”)); |
1 | String userProvidedURL = request.getParameter(“userProvidedURL”);boolean isValidURL = ESAPI.validator().isValidInput(“URLContext”, userProvidedURL, “URL”, 255, false); |
使用富文本时,使用XSS规则引擎进行编码过滤,推荐使用OWASP AntiSamp或者Java HTML Sanitizer。
尽可能将Cookie设置为HttpOnly。
不论cookie还是localStorage都别存储敏感信息。
CSRF防御
跨站请求伪造,就是利用你的身份,以你的名义来发送而已请求
,例如:发送邮件,购买商品,转账。CSRF攻击步骤分为以下两步:
- 用户登录被攻击的网站A,并且A网站将cookie保存在用户浏览器。
- 在没有登出A的情况下,访问了恶意网站B。
防御手段如下:
- 良好的API设计,最好遵循Restful风格。GET操作只是获取资源,不能操作资源,它具备幂等性。如果需要对资源进行操作,请使用POST请求,当然你如果想使用PUT和DELETE,可以使用POST来模拟。
- Cookie Hashing,由于同源策略,攻击者获取不到被攻击网站的cookie(理论上),因此就不能正确构造表单数据,所以攻击就失败了。但是如果攻击者先通过XSS获取到cookie,然后在结合CSRF来攻击,就有可能成功。这种方式是最简单可行的方案,基本可以抵挡绝大部分的攻击。
- 验证码,可以完全解决CSRF,但是体验会非常差。
- One Time Token,其实是方案2的升级,但是得注意用户开多个页来浏览站点的情况,可以通过为一个用户授权多个token来解决,但是得设置生命周期。
- 验证HTTP头中的refer。
Cookie使用注意事项
- 尽可能将cookie设置为Http Only,这样可以降低XSS攻击概率。
- 如果cookie是由HTTPS设置的,那么将cookie设置为secure,防止HTTP 301重定向导致cookie泄露。
防点击劫持和拖放的欺骗劫持
1.X-Frame-Options。
2.if (top !==window) top.location = window.location.href;
(攻击者可以使用204转向或者禁用Javascript的方式来绕过(例如iframe沙箱))。
参考文章:hashing-security