我在开发中常忽视的安全问题
前言
前不久,开发的任务告一段落。后来得知项目中bug比较多,需要去逐一检查修复,苦于没有专业的测试工程师,只好硬着头皮上(觉得能学到很多东西),也学会了一些安全测试常用软件基本操作,比如Burpsuite、sqlmap等。今天做一些总结吧,以此提醒日后的开发过程中不仅要考虑代码的质量规范等问题,还要注意尽量减少安全问题的出现,以往只是埋头开发,并不太关注安全问题。所以这次对我来说收获不小!
一、SQL注入
Sql注入(SQL injection)是指攻击者在服务器端构造数据库执行代码可以在服务器中数据库得到执行。由于攻击代码在数据库中执行,根据连接用户的权限,可以读、修改数据库资料甚至执行数据库外部命令,典型的攻击方法为窃取数据库资料、控制操作系统等。
使用sqlmap可对url进行SQL注入扫描,进而能攻破DB,获取表信息等,是十分需要我们开发者关注的问题,但是已经有很多手段能防止SQL注入,最简单的就是使用Mybatis持久框架,同时摒弃声明式注解SQL拼接:比如@Select等注解中的SQL语句可能需要拼接,推荐使用XML编写SQL,尽量使用#{}参数传入,而不是${}传入
解决办法:
1、增加全局防注入功能,从客户端获取到的参数都必须通过安全校验,防范以下常见攻击字符:
'|"|>|..|and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|script|frame|;|or|-|+|,|)|etc|style|expression
注:对获取的参数进行安全检测之前应首先统一字符编码和大小写,避免攻击者通过编码和大小写混用绕过安全检查。
2、摒弃SQL动态拼接的查询方式,使用参数化查询
3、采用Mybatis等持久化框架官方推荐的SQL方式进行SQL编写查询
二、XSS漏洞
与SQL注入类似,SQL注入是注入可执行的SQL代码到数据库服务端,而XSS简单来说就是注入可执行的具有危害的脚本代码,具体来说:跨站脚本(Cross-site Scripting简称XSS)是指攻击者输入攻击代码到服务器端,代码在其它用户的浏览器中得到执行。由于攻击代码在受害者的浏览器中执行,可以读、修改和传输任何浏览器可以读取的资料,典型的攻击方法为窃取Cookie、网页重定向等。
往往在页面的一些输入框:
或者Url中增加后缀或者改写参数为xss脚本代码,如:xxx/name=Lijian&id=123"()%26%25<acx><ScRiPt%20>alert('xss')</ScRiPt>
xxx/serviceid=1%22%3e%3cscript%3ealert(1)%3c%2fscript%3e
采取措施:
1、增加全局防护功能,从客户端获取到的参数都必须通过安全校验,防范常见攻击字符:注:对获取的参数进行安全检测之前应首先统一字符编码和大小写,避免攻击者通过编码和大小写混用绕过安全检查。
2、将获取到的数据进行HTML转码再存入数据库或输出。
3、通过提高Seesion、Cookie等安全(设置HttpOnly,Secure等属性)
三、传输安全
1、关于Cookie安全:
1) 加密会话(SSL)缺少HttpOnly属性
有效防止客户端脚本,比如JS脚本获取Cookie信息并且发送到指定站点提供黑客使用。准确来说是防止非HTTP协议程序接口获取Cookie(Java后端可以获取Cookies,前端通过Ajax获取)
Java语言设置httpOnly
response.setHeader("Set-Cookie", "cookiename=value;Path=/;Domain=domainvalue;Max-Age=seconds;HTTPOnly");
2) 加密会话(SSL)缺少Secure属性
如果设置了Secure=true,那么这个cookie只能依靠https协议发送,不能使用http协议。即此时的Cookie是在https时被创建的,如果改为http页面则无法使用Cookie,通常结合httpOnly一起配置使用提高Cookie安全
3) 防Cookie伪造
传统的Cookie(name, value)形式,网上流传的是将value值加上保存时间savaTime、自定义密钥、签名等方式
4) 尽量不要存放敏感信息
不要在Cookie中放userInf等信息,比如手机号码,邮箱等,即使放也要加密!
2、关于密码加密:
除了规定高强度的复杂性外,还不能只是简单的MD5加密,网上有很多的MD5解密网站可进行破解
四、敏感信息传输
1、Html或者JS代码不要存在敏感重要的信息
众所周知,前端代码不存在加密,大部分都是可见的,所以在前端开发过程中尽量不要留一些隐私敏感信息,比如测试时候用来测试预留的银行卡号等信息应该删除
至少在前端展示的时候用“●●●●”或者“****”代替,而不是直接显示在前端!
如敏感信息App Secret Key
前端中:
而不是:
2、除了密码不要明文传输,用户名视情况而进行加密
当然像一些手机号码也需要加解密,比如替换中间的几位数,对中间几位数进行加密隐藏等!
3、敏感信息尽量不要放在session中,即使放也要加密
对Session的key值尽量不要使用简单的userId等,可以再加UUID,或者SessionId
五、暴力破解
1、用户名的枚举
常见登录页面,当输入用户名之后(还未输入密码进行登录)可能会返回“用户名不存在”的提示,这样的提示可能会让黑客进行大量的用户枚举破解用户名。我们应该统一返回“用户名或密码错误”的提示,混淆防止黑客对用户名进行暴力破解。
2、关键信息输入未设置错误次数可能会被暴力破解
如常见的密码输入,用户输入的次数超过一定数量后对用户进行暂时锁定或者强制修改密码等;手机验证码输入次数达到上限后,同样进行锁定(时间锁定/操作锁定)
3、篡改返回数据包,可绕过验证码
当然登录页面还会密码还有验证码等的校验,比如注册/登录页面中往往最后有个输入(手机)验证码的文本框,
像我这样的新手每次都会行云流水,忘情地写代码,自我感觉棒棒哒:嗯…..用户输入验证码之后我们后端要验证验证码是否正确,我要开发一个checkVeriCode验证验证码是否正确的接口,emmmm……..正确就返回true,错误就返回false。然后返回true之后,就可以继续调我们的/login登录接口,或者注册/register接口,So easy!超简单(无比膨胀)。
面对自己的ignorance,我只想说:千万不要单独开发验证验证码是否正确的接口,进行调用!千万不要单独开发验证验证码是否正确的接口,进行调用!千万不要单独开发验证验证码是否正确的接口,进行调用!最好就使用一个接口(login接口或者register接口中)将验证验证码环节放到其中,点击注册/登录按钮之后调用登录/注册接口,先进行验证码的校验,正确则直接进入登录/注册保存,错误则提示验证码失败。简单来说我们打开F12开发者模式,只能看到一个接口,而不会出现checkVeriCode接口与/login(/register)两个接口,前一个接口影响后一个接口。
总结起来就是我们在开发埋头苦干忘我地编写代码的时候,尽量减少“多接口强耦合”开发,即后一个接口会受前一个接口返回包影响(这种影响往往是比较重要的),而前一个接口返回包往往很容易被篡改(你说还有比true/fasle更容易被篡改的嘛?)
Request:输入错误的验证码----->调用checkVeriCode--------请求我不拦截你放你走
Response:checkVeriCode接口返回false----->站住你别走,我要拦截你,把你打(改)成true---->好了,你可以走了--->验证码输入正确---->成功破解登录/注册。
我们使用Burpsuite中我们截取校验验证码接口,输入错误的验证码123456
返回false数据包,没有任何问题。这是正确的
下面我们篡改false为true之后返回到客户端,即可绕过验证码环节
4、邮箱/短信轰炸
这种邮件/短信轰炸是比较好理解的,黑客获取到相关的接口之后进行返回调用,进而对指定手机号码/邮箱进行轰炸,严重的话邮箱服务器等困难会崩溃。
那么此时我们简单的就认为那还不简单,对同一个手机号码/邮件进行次数限定不就完了?真的是这样就可以了吗?当时我一开始的时候也是这么想的!当时我想的最简单的做法就是:在规定T时间内每次对同一个手机号码发送验证码后,存入Redis中的次数(有效期T)加1,达到一定数目后,进行“操作频繁提示”。
但其实我们还忽略了另外一个问题,如果攻击者使用同一IP不同手机号码进行攻击,他的目标不再是轰炸手机,而是让服务器崩溃,那么我们仍然要对同一个IP进行次数限制。需要注意的是对方可能使用的是代理IP,我们还需要检测真实IP,网上有很多检测是否是代理IP,获取真实IP的方法,简单的判断:HTTP头部X-Forward-For第一个IP就是我们的真实IP。
六、服务器泄露
1、 服务器版本泄露
这个我们通常负载均衡使用Nginx服务器,有时候错误输入URL可能会跳转到Nginx的错误页面显示404,此时会显示一个Nginx的版本号,或者欢迎页面
解决办法:
1)去除欢迎页面
找到index.html欢迎页面,注释html代码即可。
[open@dcspap01 ~]$ cd /usr/local/nginx/html
2)去除版本号:
第一步:修改nginx.conf,http中增加:server_tokens off
[open@dcspap01 ~]$ cd /home/open
[open@dcspap01 ~]$ vi nginx.conf
第二步:编辑fastcgi.conf文件,找到fastcgi_param SERVER SOFTWARE 去掉后面的参数$nginx_version,即隐藏nginx版本号
[open@dcspap01 ~]$ cd /usr/local/nginx
[open@dcspap01 ~]$ vi fastcgi.conf
第三步:重启nginx,需要root用户登录
[open@dcspap01 ~]$ cd /usr/local/nginx/
[open@dcspap01 ~]$ ./nginx -s reload
2、点击劫持漏洞
HTTP 响应头信息中的X-Frame-Options,可以指示浏览器是否应该加载一个 iframe 中的页面。如果服务器响应头信息中没有X-Frame-Options,则该网站存在点击劫持攻击风险。
修改web服务器配置,添加X-Frame-Options响应头。赋值有如下三种:
- DENY:不能被嵌入到任何iframe或者frame中。
- SAMEORIGIN:页面只能被本站页面嵌入到iframe或者frame中。
- ALLOW-FROM url:只能被嵌入到指定域名url的框架中。
解决办法
Java代码:
response.addHeader("x-frame-options","SAMEORIGIN");
Nginx配置:(添加到 'http', 'server' 或者 'location' 的配置中:)
add_header X-Frame-Options SAMEORIGIN
Apache配置:
Header always append X-Frame-Options SAMEORIGIN
3、 谨慎启用OPTIONS
OPTIONS方法请求web服务器告知其支持的各种功能。可以询问服务器通常支持哪些方法,或者对某些特殊资源支持哪些方法。如果单独再详细了解OPTIONS的话估计又得一大篇了,搜所以这么简单的总结下:
OPTIONS通常存在于前后端分离的跨域请求中:浏览器自发起一次preflight request(预检请求),以检测实际请求是否安全,这也是黑客通常针对的点,那么我们后端需要做什么呢,就需要做一些简单的跨域设置,允许部分的HTTP METHOD
response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS"); response.setHeader("Access-Control-Allow-Headers", "x-requested-with"); response.addHeader("Access-Control-Max-Age", "1800");
启用OPTIONS请求后,返回Allow头,表明后端服务器支持的HTTP方法,有些方法DELETE、HEAD等方法是没必要或者不安全的,需要禁止,同时如果服务器不需要支持WebDAV,请务必禁用它,或禁止不必要的HTTP方法!
总结
1)我不是专业的安全测试,所以提到的问题可能不是很完善,解决办法也可能不是很好,我相信肯定有很多的更好的解决办法;
2)在这里我只是想提醒作为菜鸟开发的话不仅要关注代码质量,对代码负责。还要对安全问题负责,我认为这才是好的(秃头)程序猿应该全方位考虑到的;
3)“不光还要懂开发懂代码,还要全局掌握一些知识”这是我对自己的要求,这些问题看似简单但据我经历来看,很多开发者都会忽视(我为什么还要懂安全问题,我又不是测试)。