因为帮我内推阿里的师傅告诉我以后可能要做源码审计的工作,先学习一下spotbugs和find-sec-bugs的扫描规则实现,并且尝试添加一个规则。
添加扫描规则主要是继承Detector,本文介绍以下几种主要的Detector:
OpcodeStackDetector
检查每一个Java虚拟机操作码(继承其中的sawOpcode(int seen)
,seen
即操作吗int表示),其中可以获取调用的方法名——getNameConstantOperand()
,获取调用者——getClassConstantOperand()
,获取函数调用的参数——stack.getStackItem(0)
。可以做类似于正则匹配的简单扫描工具。
例如扫描registry.addMapping.addMapping("/**").allowedOrigins("*")
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class extends OpcodeStackDetector { public void sawOpcode(int seen) { //检测调用方法名 if (seen == Const.INVOKEVIRTUAL && getNameConstantOperand().equals(“allowedOrigins”)) { // 检测调用对象 if (“org/springframework/web/servlet/config/annotation/CorsRegistration”.equals(getClassConstantOperand())) { OpcodeStack.Item item = stack.getStackItem(0); // 因为allowedOrigins参数时Strings… 所以不能直接提取而需要自己通过字节码提取 if(item.isArray()) { String[] strings=getStringArray(item); String pattern=”*”; for (String s: strings) { if (s.equals(pattern)) { bugReporter.reportBug(new BugInstance(this, “PERMISSIVE_CORS”, HIGH_PRIORITY) .addClassAndMethod(this).addSourceLine(this)); break; } } } } } } } |
以上是我为find-sec-bugs提交的一个真实的Detector,其中有一个坑就是allowedOrigins()
方法的参数是变长参数(实际上是一个数组),如果参数是String或是定长参数的话,直接用stack.getStackItem(0)
就可以拿到参数了,现在的话就需要自己写getStringArray(item)
方法,具体解决代码见find-sec-bugs#472
BasicInjectionDetector
该Detector以每次调用(invoke)为单位进行代码审计,通过污点传播技术,判断调用敏感函数时判断参数是否为用户可控(可以参考com.h3xstream.findsecbugs.file.PathTraversalDetector.java)
也可继承后重写getInjectionPoint()和getPriorityFromTaintFrame()方法,直接判断是否存在调用以及判断风险等级,这时退化成OpcodeStackDetector,例如,我们要检测CORS漏洞:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | public class extends BasicInjectionDetector { private static final String PERMISSIVE_CORS = “PERMISSIVE_CORS”; private static final String CORS_REGISTRY_CLASS = “org.springframework.web.servlet.config.annotation.CorsRegistration”; // 需要获取函数原型 private static final InvokeMatcherBuilder CORS_REGISTRY_ALLOWED_ORIGINS_METHOD = invokeInstruction() .atClass(CORS_REGISTRY_CLASS).atMethod(“allowedOrigins”) .withArgs(“([Ljava/lang/String;)Lorg/springframework/web/servlet/config/annotation/CorsRegistration;”); public (BugReporter bugReporter) { super(bugReporter); } /** * 每次调用时都会用该函数判断是否存在漏洞 * invoke:表示一次调用 */ protected InjectionPoint getInjectionPoint(InvokeInstruction invoke, ConstantPoolGen cpg, InstructionHandle handle) { assert invoke != null && cpg != null; // 可以通过一下方法获取InvokeMatcherBuilder的class、method、Signature // System.out.println(invoke.getClassName(cpg)); // System.out.println(invoke.getMethodName(cpg)); // System.out.println(invoke.getSignature(cpg)); if (CORS_REGISTRY_ALLOWED_ORIGINS_METHOD.matches(invoke, cpg)) { return new InjectionPoint(new int[] { 0 }, PERMISSIVE_CORS); } return InjectionPoint.NONE; } /** * 返回危险等级 */ protected int getPriorityFromTaintFrame(TaintFrame fact, int offset) throws DataflowAnalysisException { // Get the value of the Access-Control-Allow-Origin parameter (Second argument from setHeader(2nd,1rst)) Taint originsTaint= fact.getStackValue(0); if (originsTaint.getConstantOrPotentialValue().contains(“*”)) { //Ignore unknown/dynamic header name return Priorities.HIGH_PRIORITY; } else { return Priorities.IGNORE_PRIORITY; } } } |
注意这里getPriorityFromTaintFrame()
方法写的是有问题的,还是因为变长参数问题,导致originsTaint.getConstantOrPotentialValue()
只能得到数组长度却不能拿到内容。
这里第二个难点就是获取函数原型,可以考虑是用javap:
1 | javap -cp C:Usersx5651.m2repositoryorgspringframeworkspring-webmvc5.1.6.RELEASEspring-webmvc-5.1.6.RELEASE.jar -s org.springframework.web.servlet.config.annotation.CorsRegistration |
增加一个简单的污点传播规则
一个简单的污点传播只需要定义sink点和priority就行了,以命令注入的规则为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class CommandInjectionDetector extends BasicInjectionDetector { public CommandInjectionDetector(BugReporter bugReporter) { super(bugReporter); loadConfiguredSinks(“command.txt”, “COMMAND_INJECTION”); loadConfiguredSinks(“command-scala.txt”, “SCALA_COMMAND_INJECTION”); } protected int getPriority(Taint taint) { if (!taint.isSafe() && taint.hasTag(Taint.Tag.COMMAND_INJECTION_SAFE)) { return Priorities.IGNORE_PRIORITY; } else { return super.getPriority(taint); } |