因为帮我内推阿里的师傅告诉我以后可能要做源码审计的工作,先学习一下spotbugs和find-sec-bugs 的扫描规则实现,并且尝试添加一个规则。
添加扫描规则——添加一个Detector 添加扫描规则主要是继承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 CorsRegistryCORSDetector extends OpcodeStackDetector { @Override 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 ); 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 CorsRegistryCORSDetector 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 CorsRegistryCORSDetector (BugReporter bugReporter) { super (bugReporter); } @Override protected InjectionPoint getInjectionPoint (InvokeInstruction invoke, ConstantPoolGen cpg, InstructionHandle handle) { assert invoke != null && cpg != null ; if (CORS_REGISTRY_ALLOWED_ORIGINS_METHOD.matches(invoke, cpg)) { return new InjectionPoint(new int [] { 0 }, PERMISSIVE_CORS); } return InjectionPoint.NONE; } @Override protected int getPriorityFromTaintFrame (TaintFrame fact, int offset) throws DataflowAnalysisException { Taint originsTaint= fact.getStackValue(0 ); if (originsTaint.getConstantOrPotentialValue().contains("*" )) { return Priorities.HIGH_PRIORITY; } else { return Priorities.IGNORE_PRIORITY; } } }
注意这里getPriorityFromTaintFrame()
方法写的是有问题的,还是因为变长参数问题,导致originsTaint.getConstantOrPotentialValue()
只能得到数组长度却不能拿到内容。
这里第二个难点就是获取函数原型,可以考虑是用javap:1 javap -cp C:\Users\x5651\.m2\repository\org\springframework\spring-webmvc\5.1.6.RELEASE\spring-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" ); } @Override protected int getPriority (Taint taint) { if (!taint.isSafe() && taint.hasTag(Taint.Tag.COMMAND_INJECTION_SAFE)) { return Priorities.IGNORE_PRIORITY; } else { return super .getPriority(taint); } } }