现有静态代码审计工具
之前童话师傅写过一篇Cobra静态代码审计工具的源码分析,所以先看看这个工具吧。
源码在:
https://github.com/WhaleShark-Team/cobra
后来还有Lo写的一个改版:
https://github.com/LoRexxar/Cobra-W
-
Find Security Bugs
这个只是提供给IDE的插件。其测试用例可以作为参考。 -
Fortify:不开源。
-
RIPS:只开源了PHP版本,Java的是商业版本。
参考:https://en.wikipedia.org/wiki/RIPS -
PMR:
参考:https://pmd.github.io/
https://github.com/pmd/pmd
感觉还比较新,最近一年刚出来。
不过主要是做编码规范的,不是找漏洞的那种。 -
spotbugs:
也是编码规范的。
Cobra
跟一下流程:
根据传入的target,若未提供sid,则调用get_sid()新建一个sid。(根据这里的逻辑分支可以看出,如果某次任务退出了,下次可以通过命令行提供这个sid以继续之前的任务?)
Running(a_sid).status(data)
这一句是把状态信息写入到target对应的文件中。
即便target一样,由于有随机数的存在,所以每次get_sid的结果是不一样的:
new一个Running实例,然后将状态以字典的形式传入status函数。
这里学习到了如何给正在打开的文件加锁的方式:
import fcntl
fcntl.flock(f, fcntl.LOCK_EX)
参考:https://blog.51cto.com/zhou123/1650185
最后是关键的开启扫描任务:
cli.start(args.target, args.format, args.output, args.special_rules, a_sid, args.dels)
跟进start函数,
init_list主要是确定给定的target是list还是普通字符串。一般是普通字符串,于是认为总的目标数为1,初始化这个sid对应的文件,
然后list就是跟新一下刚才生成的文件。
最后几行是生成report的链接。
files, file_count, time_consume = Directory(target_directory).collect_files()
用于搜集这个目录下有哪些文件,多少文件,以及花费的时间。
然后下面是检测(Detection):
主要是通过detection.py
来完成的:
先加载了cobra下面的一个配置文件:cobra/rules/frameworks.xml
,这个文件里面写了一些框架比如ThinkPHP、Jommla、CI等的特征(目录名、文件名等)。
然后是真正的扫描函数:
scan(target_directory=target_directory, a_sid=a_sid, s_sid=s_sid, special_rules=pa.special_rules,
language=main_language, framework=main_framework, file_count=file_count, extension_count=len(files))
跟进去看一下,主要是载入了rules目录下的各种配置文件,包括框架特征的frameworks.xml、语言(后缀名)特征的languages.xml,以及漏洞大类vulnerabilities.xml,以及各种CVI-
开头的配置文件,大概长这样:
其中以CVI-999
开头的规则是按年分的CVE漏洞版本规则:
然后把扫描到的规则文件
参考
- 代码审计工具 Cobra 源码分析(一)
- 代码审计工具 Cobra 源码分析(二)
- 开源静态代码审计软件分析比对
- 三款自动化代码审计工具
- 甲方代码审计之道与术
- 利用Cobra实现自动化代码审计的经验分享
Cobra测试
可测试示例Java代码:
- https://github.com/JoyChou93/java-sec-code
- https://github.com/find-sec-bugs/find-sec-bugs/tree/master/findsecbugs-samples-java/src/test/java/testcode
测试一下java-sec-code的代码
命令如下:
python cobra.py -t /home/cqq/repos/java-sec-code/src/main/java/org/joychou
结果显示:
总共有21个漏洞,3种漏洞,还有63个规则没有触发,有29个规则关闭了。
看一下对应CVI的id的规则xml文件:
我们不关心这个堆栈打印信息,于是将开关从on修改为off之后,只显示3个规则了。
各种CVI对应的漏洞类型
CVI-11:杂/http?http
CVI-12:SSRF(目前只有php的,待加入java的);
CVI-13:一些硬编码的安全风险;
CVI-14:各种XSS;
CVI-15:无此规则;
CVI-16:SQL注入;
CVI-165:LDAP注入;
CVI-167:XXE;
CVI-17:本地文件包含;
CVI-18:RCE;
CVI-19:各种信息泄露;
CVI-20:不安全的随机数(可移除);
CVI-21:url跳转;
CVI-360:大量的webshell检测
匹配模式
在各个CVI的xml文件中可以看到有匹配模式的差别。有四种模式:
regex-only-match
(不区分语言): 默认方式,如果匹配成功,则认为有漏洞;
regex-param-controllable
(支持PHP/Java):正则参数可控;
function-param-controllable
(仅支持PHP):函数参数可控;
find-extension
(寻找某些后缀的文件):匹配到某后缀则认为有漏洞;
详见cobra/const.py
:
# Match-Mode
mm_find_extension = 'find-extension'
mm_function_param_controllable = 'function-param-controllable'
mm_regex_param_controllable = 'regex-param-controllable'
mm_regex_only_match = 'regex-only-match'
match_modes = [
mm_regex_only_match,
mm_regex_param_controllable,
mm_function_param_controllable,
mm_find_extension
]
然后在匹配引擎中有具体的判断逻辑(还没看懂)
详见cobra/engine.py
在scan()这个函数中:
而判断参数是否可控,等的逻辑是在cobra/cast.py
中配置的。
CAST(Cross Abstract Syntax Tree)
在这里
有grep命令:
# 看到最终还是用OS上的grep工具去匹配(怪不得Mac有点问题,可能是跟Mac自带的grep工具有关)
# -s, --no-messages,表示不显示关于不存在/不可读文件的错误信息
# -r 表示查找某目录下的所有文件
# -n 表示显示行号
# -P 表示使用的是"Perl语言兼容的"" 正则表达式
grep -rnsP "pattern" file_to_grep.java
中间会找到一些行号,然后作为参数传递给sed:
还有sed命令(找到开始行和结束行之间的代码内容):
sed -n 1,3p src/main/java/org/joychou/controller/SSRF.java
目前了解的,有抽象语法树的,也有正则的。
不过看了一下,已有的规则中,模式是抽象语法树的,主要是php。java的也有但是比较少,暂时还写不出来,先用正则的试试吧。
后来发现cobra的文档里都有:
http://cobra.feei.cn/rule_template
还可以好好看看这个具体的例子:
http://cobra.feei.cn/rule_demo
第一次匹配,
第一次匹配成功之后进行二次匹配的规则:
实测发现:
"in-function-down"比“in-current-line”范围大,
比如:
这种代码:
Request.Get(url).execute().returnContent().toString();
两种方式都可以匹配。
但是如果是这种代码:
req = Request.Get(url);
return req.execute().returnContent().toString();
就只有"in-function-down"能匹配到了。
in-file-up
比如这个例子CVI-200001.xml
第一次匹配的规则是:
<match mode="regex-only-match"><![CDATA[new Random\s*\(|Random\.next]]></match>
即先匹配到new Random或者Random.next之后,需要确认这个Random确实是java.util.Random或者scala.util.Random这个包里的Random类,于是需要第二次匹配:
<match2 block="in-file-up"><![CDATA[((java|scala)\.util\.Random)]]></match2>
其中in-file-up
就表示第一条规则触发的所在行之上所在文件之内,因为这个类只有先引入才能使用,所以通过确认这个类是否被引入,来确认是否是我们关注的那个Random类。
这种方式可以用来匹配静态方法调用的情况。
判断参数是否可控的逻辑在:
cast.py#is_controllable_param
杂
原来还可以只扫描某两种漏洞:
# 扫描一个文件夹代码的某两种漏洞
$ python cobra.py -t tests/vulnerabilities -r cvi-190001,cvi-190002
漏洞测试用例
- https://github.com/JoyChou93/java-sec-code
- https://github.com/find-sec-bugs/find-sec-bugs/blob/master/findsecbugs-samples-java/src/test/java/testcode
- https://github.com/threedr3am/learnjavabug
SSRF
URLConnection/HttpURLConnection
存在漏洞的代码1
String url = request.getParameter("url");
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();//并不发起请求(只是得到一个对象)
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //发起HTTP请求
存在漏洞的代码2
String url = request.getParameter("url");
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();//并不发起请求(只是得到一个对象)
HttpURLConnection httpUrl = (HttpURLConnection)urlConnection;
BufferedReader in = new BufferedReader(new InputStreamReader(httpUrl.getInputStream())); //发起HTTP请求
urlConnection.connect()发起DNS请求;
urlConnection.getInputStream()发起HTTP请求
urlConnection.getLastModified();发起HTTP请求
url.openStream();发起HTTP请求
url.getContent();发起HTTP请求
import javax.imageio.ImageIO;
ImageIO.read(u);
URL跳转
- /urlRedirect/redirect?url=http://www.baidu.com
对应代码:
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.GetMapping;
@GetMapping("/redirect")
public String redirect(@RequestParam("url") String url) {
return "redirect:" + url;
}
检测方式:
"redirect:"
字符串,以及包名:org.springframework.web.bind.annotation.RequestParam
- /urlRedirect/setHeader?url=http://www.baidu.com
对应代码:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@RequestMapping("/setHeader")
@ResponseBody
public static void setHeader(HttpServletRequest request, HttpServletResponse response){
String url = request.getParameter("url");
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301 redirect
response.setHeader("Location", url);
}
检测方式:
.setHeader("Location"
字符串
- /urlRedirect/sendRedirect?url=http://www.baidu.com
对应代码:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@RequestMapping("/sendRedirect")
@ResponseBody
public static void sendRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException{
String url = request.getParameter("url");
response.sendRedirect(url); // 302 redirect
}
//TODO