Java代码审查(二):应用安全问题
由于安全问题经常被忽视与无视,因此有必要专门指出一些常见的安全隐患以及预防措施。不过Web安全问题已是老生常谈,网上资料也非常丰富,故本文仅仅点到为止,不再详细解释。
总体原则
- 开发人员要了解各类攻击方式
- 绝不信任用户输入,开发时要假设用户的输入存在各种攻击
注入类
跨站脚本攻击(XSS)
- 示例:在填写信息时,在正常内容中夹带
<script>alert(1);</script>
。如展示信息时弹出一个对话框,上面写着“1”,那么说明注入成功。假如代码不是弹框而是后台偷偷执行一段恶意脚本……- 对策
- 优雅的做法:如无特殊需要,页面上的动态输出均要对HTML字符进行转义。
- 简单粗暴的做法:控制用户输入,设置XSS相关敏感词清单,发现之后拦截或清理。
- 对策
SQL注入
示例:假如用以下SQL进行登录信息校验
那么登录时在密码中输入public boolean isValidLogin(String username, String password) { String sql = "SELECT 1 FROM users WHERE username='" + username + "' AND password='" + password + "'"; ... }
' or ''='
,SQL就会变成
结果就会变成不知道密码也能直接进去了。SELECT 1 FROM users WHERE username='admin' AND password='' OR ''=''
另外,假如密码输入的是
'; drop table users; --
……- 对策
- 优雅的做法:一律避免直接拼SQL,只用PreparedStatement这种能防止注入的方式。
- 简单粗暴的做法:(1)控制用户输入,设置SQL注入相关敏感词清单,发现之后拦截或清理;(2)设置转义的公共函数,在拼接SQL时总是对来自用户的输入进行转义。
- 对策
路径注入
- 示例:用户输入文件名,即可查询
/opt/data/info
目录中的文件。如果程序控制不严,而且用户输入了../../../etc/passwd
,那么就能看到系统有哪些用户了。- 对策:过滤敏感词,例如“./”、“../”等。
恶意文件执行
- 示例:应用提供了文件上传功能,上传1.jpg之后可通过
http://www.xxx.com/files/1.jpg
网址访问。如果系统未进行控制,那么上传精心设计的a.jsp并访问http://www.xxx.com/files/a.jsp
就可以控制服务器了。- 对策
- 检查待上传文件的文件类型。
- 对服务器进行设置,或者对目录加以处理,防止服务器执行存放目录下的文件。
- 或者不要直接用路径访问文件,而是使用一个Web请求间接访问文件,例如
http://www.xxx.com/getfile.jsp?id=1056
。
- 对策
CSRF
- 示例:登录自己的A网站,然后在与其无关的B网站中对A网站的一个功能发POST请求,如A系统未做控制,那么表单会提交成功。假如B系统是背着用户偷偷发的……
- 对策
- 优雅的做法:每个form都配上随机产生的token,提交表单时后台校验token是否有效。
- 简单粗暴的做法:校验HTTP请求中的Referer,白名单之外的域名一律拦截。
- 对策
越权访问
直接对象引用
- 示例:自己的工作单是
http://www.xxx.com/business/view.jsp?id=1024
,将1024改成其他数字就看到了其他人的工作单。- 对策:加入访问控制检测,只允许查询自己的工作单。例如校验工作单的办理者是否为本人,或者在SQL上面加入权限相关条件,使不具备访问权限的数据查不出来。
认证不全
- 示例:张三有敏感数据查看权限,而李四没有。李四发现张三查看敏感数据的网址是
http://www.xxx.com/business/viewsecret.jsp
,之后将其记录下来,并使用自己账号访问这个地址,结果也看到了敏感数据。- 对策:实现一个用户访问控制框架,对于每个URL,先校验用户有没有访问权限,如果没有则拦截。
缺少后台校验
- 示例:在业务申请页面上选择A,系统提示“不符合条件,无法保存”。通过浏览器的检查元素功能选中A,保存成功。
- 对策:无论前台是否有校验,后台务必进行校验。
- 注意校验可以大致分为两类,一类是有效性校验,例如某个字段不能为空,另一类是业务层面的校验。建议后者设计统一的校验框架。
信息泄漏
- 示例1:登录系统,用户名错误时系统提示“用户名错误”,密码错误时系统提示“密码错误”。这样攻击者看到“密码错误”时便知道用户名是正确的。
- 对策1:模糊地提示“用户名或密码错误”,并且加入验证码,或者多次失败后锁定账号,以防暴力猜测密码。
- 示例2:在错误信息中出现SQL语句。这样攻击者一旦攻进数据库便会更加容易知道该查哪些表的哪些数据。
- 对策2:防范措施:检查报错内容,并且检查有没有关闭类似“Debug模式”的设置,不要暴露程序源代码或SQL这样的敏感信息。
- 示例3:直接展示用户身份证号、手机号等敏感数据。
- 对策3:如无必要展示则不展示,或者进行脱敏处理(138****1234、320101******121234)。
拒绝服务
- 示例1:查询功能未控制提交频率,也没有验证码,那么用户可以频繁查询,甚至使用机器人暴力地刷结果。
- 对策1:对于耗时的操作,控制用户提交的频率,或者引入验证码。
- 示例2:分页功能每页最多显示100条,如果程序未在后台进行限制,那么可以通过构造请求使每页查出10000条甚至更多,从而影响系统性能。
- 对策2:构造SQL时估计数据规模,并控制数据上限,防止查出过多记录。
- 示例3:查询功能要求用户必须填A、B、C中至少一个查询条件,如后台未做控制,那么用户可以构造请求,A、B、C三个条件都不填,从而拖累系统。
- 示例3:考虑到“什么条件都没有”情况下应当如何防止SQL查询结果爆炸。
其他
- 示例1:用明文/用MD5保存密码。一旦数据库信息泄漏,密码就直接暴露了。
- 对策1:密码必须加密之后再存到数据库中,而且不能直接MD5或者MD5里面套MD5。
- 示例2:提交表单时未控制页面按钮与用户操作,使得用户可以容易地多次提交表单。
- 对策2:提交表单时将提交按钮变灰,增加动画反馈,使用户不会直接重复点按钮;提交完成之后重定向到编辑页面,而不是直接停留在POST提交的链接,以免用户因按F5而重复提交。
- 示例3:未使用HTTPS请求。
- 注意:HTTP请求内容可以很容易地监听和篡改,但HTTPS几乎不能。
- 示例4:正式环境忘记删除调试文件(例如.git、.svn、.DS_Store)
扩展阅读
- WEB应用安全测试备忘单
本系列目录
- 代码审查问题
- 应用安全问题
- 关于编程习惯的要求
- 使用Phabricator进行人工代码审查
- 使用FindBugs和SonarQube等工具进行扫描
- ……