FSRC经验分享”系列文章,旨在分享焦点安全工作过程中的经验和成果,包括但不限于漏洞分析、运营技巧、SDL推行、等保合规、自研工具等。
本文为焦点科技信息安全部日常工作中总结的代码审计相关checklist,整体分为上下两部分。本文为上部,内容包括代码审计的概念、相关工具、常见漏洞的审计方式(包括搜索范围、内容、判断依据及修复方式)。
欢迎各位安全从业者持续关注!
7181字 阅读时间约18分钟
什么是代码审计?
代码审计是一种很常见的发现漏洞的方法,特别是甲方自行白盒审计。但是只要看代码就是代码审计么?什么才是专业的代码审计?
在FSRC看来,代码审计分为4部分:
谁:代码审计值得是具有安全和开发经验的人员
对象:阅读程序源代码或者经过反编译之后的代码
手段:借助自动化代码分析工具或者人工阅读的方式
目的:发现系统代码中存在的安全风险和设计缺陷,引导开发人员修复,保障系统运行安全
污点分析原理
污点分析是一种跟踪并分析污点信息在程序中流动的技术。在漏洞分析中通常将污点分析抽象成一个三元组的表示方式——<sources,sinks,sanitizers>
-
sources: 污点源,直接引入导致危险发生的不信任数据的位置,以SQL注入为例,
id=1
存在SQL注入,污点源为id
参数; -
sinks: 污点汇聚点,直接进行危险操作或者隐私泄露到外界的位置,以SQL注入为例,污染汇聚点为
executeQuery()
相关调用执行SQL语句的位置; -
sanitizers: 无害化处理,使用转义、过滤、阻断、加密等手段不再对系统安全产生危害的位置,以SQL注入为例,无害化处理指的是SQL语句的过滤位置、或者SQL语句产生结果的判断位置(不完全)
污点分析就是分析程序中是否存在未经过无害化处理的污染源通过传播路径到达污点汇聚点,对系统产生危害。污点分析可以分成以下的几个阶段:
-
判断列举污点汇聚点;
-
寻找到污染汇聚点对应参数的污点来源;
-
判断从污点源到五点汇聚点是否存在可能的通路;
-
判断是否存在无害化处理、无害化处理是否能够完全处理污点源数据的所有情况。
污点分析是信息流分析技术中的一种实践的技术,广泛应用于静态分析安全测试中,是对人工代码审计的一种抽象。几乎所有的漏洞都可以按照污点分析的方式发现,但是分析的复杂度并不相同。对于SQL等常见的漏洞,污点汇聚点(危险函数)单一且容易发现,相对来说分析起来比较容易;但是对于逻辑漏洞、信息泄露、XSS等漏洞覆盖范围广、产生情况复杂、没有准确的危险函数,白盒发现较为困难且准确度不高。
代码审计流程
早期的代码审计由于目标系统规模小、代码量较少、代码之间的逻辑调用关系简单清晰,使用人工审计的方式就可以覆盖整个系统了。人工代码审计通常有三种思路:
-
全文通读了解代码每部分的功能以及数据流向,结合具体的功能点发现代码中存在的问题。这种方式全面但是耗时耗力;
-
危险函数定位法,通过定位上面提到的sinks,找到危险函数之后向上排查,看危险函数数据来源,是否存在无害化处理,这种方式快捷方便,但不全面,对逻辑漏洞没有发现的能力;
-
对具体的功能点进行建模和审计,根据需求文档或者单一的功能点,分析可能出现的风险项,逐条排查。速度较快,对逻辑漏洞也能很好的把握。排查效果取决于安全人员的威胁建模能力和对目标的了解程度。
由于目标项目在发展的过程中逐渐复杂化以及代码量的指数级增长,系统和系统之间调用关系复杂,人工审计的方式已经无法做到全面的审计。一些对应的工具介入,极大的提高了代码审计人员的工作效率。静态代码审计工具原理发展如下:
-
关键字的匹配
-
基于AST代码分析
-
基于IR/CFG的代码分析
-
QL概念
市面上常见的代码审计工具很多,主要分为以下四类。此处列举部分,没有好坏之分,大家根据习惯使用即可。
各类代码编辑器,IDEA、VSCode、eclipse,顺手就行
Jd-gui、jadx、wJa(有动态调试功能),顺手就行
部分可以自动检测相关代码的工具,checkmarx、Seay、Fortify SCA、找八哥、CODESEC等
一些辅助工具,CodeQL、soot、dependencyCheck、ysoserial、JNDI-Injection-Exploit等
SQL注入
String sql = "
@Select
@Update
@Delete
@Insert
"SELECT
"UPDATE
"INSERT
"DELETE
${sql}
${
.executeQuery(
-
原生JDBC是否存在直接拼接SQL语句(使用
+
,或者使用StringBUilder append()
),未经过预编译; -
Mybatis使用
${}
; -
Hibernate、JPA默认是经过预编译的,但是如果开发自己编写的SQL语句,也需要进行检查;
-
Java是强类型语言,当注入参数为long、int等数字类型时无法进行注入;
-
找到危险函数位置之后,向上搜索,找函数、方法调用位置,直到请求入口(
controller层
),判断是否存在无害化处理、无害化处理是否严格; -
注意开发可能设置全局过滤。
-
参数固定为数字类型时,使用数字类型接收,或者转为数字类型;
-
预编译,原生JDBC使用
?
参数占位,之后使用.preparedStatement
,Mybatis使用#{}
替换${}
; -
对于Mybatis中无法使用
#{}
的场景: -
like
:使用CONCAT('%',#{},'%')
-
in
:使用<foreach
-
order by
:代码上做白名单 -
设置过滤器,严格限制传入参数
SSRF
new URL(
URLConnection
Request.*.execute
ImageIO.read(
HttpClient
ClientHttpRequest
RestTemplate
.postForObject
.getForObjec
-
该漏洞经常出现在客户端传入文件、图片的URL地址(通常存储在NAS、OSS上)通过URL获取相关的文件或者当前请求需要访问其他请求,请求地址由客户端传入;
-
主要看参数是否可控,是否存在过滤,协议、端口等的限制措施、相关的限制措施是否完善;
-
通常情况下,项目会封装一个用于发起请求的方法,除上述关键字还需要找该方法全部调用位置。
-
当目标请求为域名时,获取域名所对应的IP地址,防止内部解析绕过;
-
设置内网地址黑名单或白名单;
-
设置协议白名单;
-
检查对应的IP地址是否为黑名单地址;
-
禁止302跳转,或者存在302跳转时递归获取跳转的URL,判断是否为黑名单地址;
-
禁止其他非必要的协议。
XXE
XML
XMLReader
SAXParser
SAXBuilder
DocumentBuilder
document.parse(
javax.xml.parsers.DocumentBuilderFactory;
javax.xml.parsers.SAXParser
javax.xml.transform.TransformerFactory
javax.xml.validation.Validator
javax.xml.validation.SchemaFactory
javax.xml.transform.sax.SAXTransformerFactory
javax.xml.transform.sax.SAXSource
org.xml.sax.XMLReader
org.xml.sax.helpers.XMLReaderFactory
org.dom4j.io.SAXReader
org.jdom.input.SAXBuilder
org.jdom2.input.SAXBuilder
javax.xml.bind.Unmarshaller
javax.xml.xpath.XpathExpression
javax.xml.stream.XMLStreamReader
org.apache.commons.digester3.Digester
-
解析器解析的XML需要外部可控;
-
未禁用DTD或者允许外部实体;
-
大多数项目都会封装一个用于解析XML的方法,因此除上述关键字以外,还需要寻找对应方法的调用位置逐个判断。
XXE修复方式相对简单,禁用DTDs或者禁止使用外部实体即可。
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); //禁用DTDs (doctypes),几乎可以防御所有xml实体攻击
//如果不能禁用DTDs,可以使用下两项,必须两项同时存在
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); //防止外部普通实体POC 攻击
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); //防止外部参数实体POC攻
任意文件操作
new File(
String path
String fileName
new FileInputStream(
new FileOutputStream(
new FileReader
response.setContentType("application/octet-stream; charset=utf-8");
file.delete();
FileUtils.
new ZipEntity(
file.getName(
.unzip(
.mkdirs(
stream.write(
save2File(
fos、fis.close()
public class ZipUtil
MultipartFile(
file.getOriginalFilename(
upload
download
上传 // 搜注释
下载 // 搜注释
-
未对文件路径、文件名称、文件类型做合理限制,上传文件路径或者文件名称可存在
../
目录跨越的操作; -
在解压缩文件时未对压缩包中的文件类型进行限制;
-
未限制上传文件大小;
-
通常情况下在解压缩文件时,开发一般都会创建一个临时目录,解压完成之后将临时文件夹删除,如果临时文件夹名称可以控制,则可以达到任意文件删除的目的;
-
判断文件类型的操作时,开发一般先获取到文件名称,然后使用
filename.substring(filename.lastIndexOf("."));
获取文件后缀名,如果此处使用filename.indexOf(".")
则可能存在绕过可能。
-
判断上传数据包的
content-type
-
设置上传文件类型白名单,上传文件后重命名,重命名类型不从上传文件中获取;
-
限制文件上传的大小;
-
当传递参数为文件路径时,需要判断路径中是否存在
../
跨目录操作; -
当参数同时存在
path
和fileName
时,分别对path
、fileName
和拼接结果进行判断和限制; -
限制下载、删除可操作的根文件夹。
命令执行
String cmd
String command
ProcessBuilder
Runtime
.exec(
new ScriptEngineManager() // 加载JS文件
new Yaml(); // snakeyaml凡序列化漏洞执行命令
new GroovyShell();
执行命令参数可控,为进行白名单或者过滤操作或不严格,未进行转义特殊字符操作或不严格。
-
非必要不调用系统命令;
-
调用系统命令时不使用前台传入的命令,使用
id
的方式选择可执行的命令; -
设置可执行命令白名单,不允许使用
&& || & | ;
等命令并列的特殊字符; -
控制执行命令用户权限;
-
命令执行漏洞在实际工作中发现的较少。
不安全的反序列化
readObject(
-
反序列化数据可控,执行反序列化操作,反序列化对象readObject方法中存在危险操作。
-
通常情况下是三方组件中存在漏洞,导致反序列化。因此只需要判断项目中是否引用了包含漏洞的三方组件版本即可,如果引入了则建议升级,如果无法升级,则看是否满足利用条件,并利用waf拦截相关的请求。
-
设置可反序列化的类白名单,不允许名单外的类进行反序列化;
-
使用安全的三方组件。
URL跳转
String url
String returnUrl
String returnPath
String path
sendRedirect
forword
redirect:
.setHeader("refresh"
.setStatus(302)
.setHeader("location"
-
跳转的URL地址用户可控,未经过过滤判断或过滤判断不严格;
-
URL跳转漏洞可配合SSRF漏洞,当SSRF不完全校验地址时,可以利用URL跳转漏洞请求跳转之后的地址。
-
设置跳转地址白名单
-
如果跳转地址是固定的,则可以使用
id
索引地址,防止用户直接传入; -
先生成跳转链接及其签名,跳转前进行签名验证。
硬编码
pass
pwd
key
accessKeyId
accesskey
accessid
代码中有明文的密码、密钥等信息(通常不包含单元测试java文件)
-
加密存储到配置文件中,然后代码中读取配置文件获取密码、密钥;
-
使用配置中心或者存储到数据库中。
不安全的传输方式
DES
DESUtil
SHA1
ECB
RSA // 看密钥长度
MD5 // 存储数据需要加盐
-
使用不安全的加密方式加密数据;
-
安全的加密方式密钥长度不符合要求
-
开发人员经常在前端使用AES加密数据发送到后端,因为AES是对称加密的,前端必定存在AES密钥(JS或者请求获取)导致数据加密传输形同虚设;
-
有些开发人员为了测试方便,会预留加解密接口,通常名称为
decrypt
和encrypt
或者jiami
和jiemi
; -
base64不是加密方式,曾经见过请求头中的认证信息是base64编码的用户名和密码串。
-
使用安全的非对称加密算法
-
加密算法密钥长度应该符合安全要求
-
使用MD5加密存储密码信息时应当加盐(建议使用表中UUID、createDate等具有迷惑性质的随机盐)
日志伪造
.info(
.error(
.debug(
.warn(
-
日志打印内容可控;
-
日志内容未过滤
-
日志内容固定;
-
过滤打印内容,设置可打印字符白名单,不允许打印换行
n
敏感信息泄露
password
pass
address
idNo
phoneNo
……
-
是否存在统一报错返回;
-
返回结果是否包含敏感内容。
-
对于大型项目来说,返回的结果往往被封装成实体后返回,因此可以查找返回的结果封装中是否包含以上的字段,如果包含则追踪到对应的位置,查看是否进行脱敏或者清空。
-
定制统一报错页面或者统一报错json返回;
-
只返回必要的信息,密码等字段不应返回,敏感字段脱敏返回。
安全配置问题
pom
文件或者lib
中是否存在可能有配置错误的组件,然后查看对应的配置。常见的有可能存在问题的组件如下:Swagger 、Shiro、SpringSecurity、Druid、Spring boot actuator
相对于白盒而言,这种配置错误导致的未授权问题黑盒审计更为方便。批量访问对应URL判断是否能够访问成功即可。
-
swagger不建议对公网开放;
-
如果确实存在开放的必要,则必须进行身份认证和授权操作;
-
可以配置密码密钥的组件需要配置密码和密钥,并保证密钥的复杂度。
XSS
-
HTML实体化
-
Cookie设置httponly
-
过滤特殊字符、过滤事件标签
污点分析技术的原理和实践应用(by 王蕾)
https://www.cnki.com.cn/Article/CJFDTotal-RJXB201704009.htm
JAVA代码审计之XXE与SSRF(by 皮皮鲁)
https://xz.aliyun.com/t/2761
攻击JWT的一些方法(by Stefano)
https://xz.aliyun.com/t/6776
浅谈Cookie和Cookie安全(by 浅海科技)
https://juejin.cn/post/6959830432519520292
本文中提到的相关资源已在网络公布,仅供研究学习使用,请遵守《网络安全法》等相关法律法规。
本文编辑:小错
原文始发于微信公众号(焦点安全应急响应中心):Java代码审计checklist(上)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论