在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似于 if-condition-then 这样的条件跳转语句,可以使用带有三个分支的节点来表示。
一般的,在源代码的翻译和编译过程中,语法分析器创建出分析树,然后从分析树生成AST。一旦AST被创建出来,在后续的处理过程中,比如语义分析阶段,会添加一些信息。
抽象语法树是程序源代码结构的树状表示。程序源代码经过词法分析器(Lexer)得到各种不同种类的单词(Token),再由语法分析器(Parser)分析和语法检查后得到抽象语法树(AST)。抽象语法树的根节点表示整个程序,内部节点是抽象语法结构或者单词。AST的核心在于它能与输入源代码中的各个语法元素一一对应。
如以下的C语言代码如图所示:
while (i<n){
sum + = A[i++];
}
02 Java AST抽象语法树
Spoon是一个开放源代码库,用于分析,重写,转换,翻译Java源代码。它解析源文件以构建具有强大分析和转换API的精心设计的AST。它完全支持Java 11、12、13、14之前的现代Java版本。Spoon是Inria的一个官方开源项目,并且是OW2开源联盟的成员。
java -cp /Users/58src/IDEA/spoon/target/spoon-core-8.4.0-SNAPSHOT-jar-with-dependencies.jar spoon.Launcher -i /Users/58src/IDEA/springboot-mybatis/src/main/java/cn/no7player/controller/HelloController.java --gui
package cn.no7player.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class HelloController {
@RequestMapping("/hello")
public String greeting(@RequestParam(value="name", required=false, defaultValue="World") String name, Model model) {
model.addAttribute("name", name);
return "hello";
}
}
污点分析的处理过程可以分为三个阶段:
识别污点源和汇聚点;
污点传播分析;
无害处理.
识别污点源
更多的污点分析技术可参考以下链接及其参考链接:https://www.k0rz3n.com/2019/03/01/%E7%AE%80%E5%8D%95%E7%90%86%E8%A7%A3%E6%B1%A1%E7%82%B9%E5%88%86%E6%9E%90%E6%8A%80%E6%9C%AF/
02 QL中的污点分析模型
/**
* @name Uncontrolled command line
* @description Using externally controlled strings in a command line is vulnerable to malicious
* changes in the strings.
* @kind path-problem
* @problem.severity error
* @precision high
* @id java/command-line-injection
* @tags security
* external/cwe/cwe-078
* external/cwe/cwe-088
*/
import java /** 导入codeql的java依赖 **/
import semmle.code.java.dataflow.FlowSources /** 导入java的Sources定义模块 **/
import semmle.code.java.security.ExternalProcess /** 导入定义java中执行系统命令模块 **/
import ExecCommon /** 导入对java中命令执行的 sources、sink、sanitizer定义模块 **/
import DataFlow::PathGraph /** 导入java的数据流控制模块 **/
/** 从DataFlow里导入 source、 sink、并且定义命令执行的参数为execArg **/
from DataFlow::PathNode source, DataFlow::PathNode sink, ArgumentToExec execArg
/** source、 sink、execArg 满足 execTainted的参数定义 **/
where execTainted(source, sink, execArg)
/** 搜索满足execTainted谓词定义的参数、污染源及sink点并输出(可以理解成取交集的过程) **/
select execArg, source, sink, "$@ flows to here and is used in a command.", source.getNode(),
"User-provided value"
/** 导入各种所依赖的配置 **/
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.ExternalProcess
import semmle.code.java.security.CommandArguments
/** 定义一个私有的Class作为远程命令执行的dataflow Config配置,并且这个Config配置继承自基础的Configuration **/
private class RemoteUserInputToArgumentToExecFlowConfig extends TaintTracking::Configuration {
RemoteUserInputToArgumentToExecFlowConfig() {
/** 定义该Config的别名为ExecCommon **/
this = "ExecCommon::RemoteUserInputToArgumentToExecFlowConfig"
}
/** 重写对Source的定义,满足Source是远程数据输入,instanceof语句为满足后续谓词条件,依旧是取交集操作 **/
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
/** 重写对Sink的定义,sink.asExpr为sink的表达式需要满足于ArgumentToExec的谓词定义 **/
override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof ArgumentToExec }
/** 重写对Sanitizer的定义,节点的数据类型满足PrimitiveType,BoxedType 或者是安全的命令调用方式,便认为是经过了净化**/
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType
or
node.getType() instanceof BoxedType
or
isSafeCommandArgument(node.asExpr())
}
}
/**
* Implementation of `ExecTainted.ql`. It is extracted to a QLL
* so that it can be excluded from `ExecUnescaped.ql` to avoid
* reporting overlapping results.
*/
/** 定义谓词 execTainted满足于RemoteUserInputToArgumentToExecFlowConfig条件,并且存在Source到Sink点的数据流 **/
predicate execTainted(DataFlow::PathNode source, DataFlow::PathNode sink, ArgumentToExec execArg) {
exists(RemoteUserInputToArgumentToExecFlowConfig conf |
conf.hasFlowPath(source, sink) and sink.getNode() = DataFlow::exprNode(execArg)
)
}
新建规则路径:java/ql/src/Security/CUSTOM/query/spring/SpringPath.ql
/**
* @name Spring controller bind path
* @description list all Spring controller path in method bind and class bind.
* @kind path-list
* @problem.severity information
* @precision Null
* @id java/Spring-path
* @tags Information-path
*/
import java
import semmle.code.java.frameworks.spring.SpringCustomController
import semmle.code.java.dataflow.FlowSources
from SpringWebApiBindMethod m
select
m as controllerMethod, m.getLocation() as location,
m.getBindPath() as methodBindPath,
m.getControllerClassBindPath() as classBindPath, "Spring bindPath"
扫描结果,classBindPath + methodBindPath为Spring的Web path。
import java
import semmle.code.java.Maps
import SpringController
/**
* Sping框架web请求绑定方法
*/
class SpringWebApiBindMethod extends SpringMvcControllerMethod {
SpringWebApiBindMethod() {
getAnAnnotation() instanceof SpringRequsetMappingCustomAnnotation
or
getAnAnnotation() instanceof SpringRestMappingCustomAnnotation
}
/**
* 获取方法绑定的路径RequsteMapping
*/
Expr getBindPath(){
result = getAnAnnotation().(SpringRequsetMappingCustomAnnotation).getValue("value")
or
result = getAnAnnotation().(SpringRestMappingCustomAnnotation).getValue("value")
}
/**
* 获取mvc控制器类绑定的路径,不存在返回空字符串
*/
string getControllerClassBindPath(){
if this.isSpringMvcControllerBindPath() then
result = this.getDeclaringType().getAnAnnotation().(SpringRequsetMappingCustomAnnotation).getValue("value").toString()
else result = ""
}
/**
* 该方法的mvc控制器是否绑定了路径前缀
*/
predicate isSpringMvcControllerBindPath() {
exists(SpringRequsetMappingCustomAnnotation a| this.getDeclaringType().getAnAnnotation() = a
and this.getDeclaringType().getAnAnnotation().(SpringRequsetMappingCustomAnnotation).getValue("value").toString().length()>0 )
}
}
/**
* 该方法的声明类型的直接类型或者间接类型满足Spring Controller的谓词定义
*/
class SpringMvcControllerMethod extends Method {
SpringMvcControllerMethod() {
getDeclaringType().getAnAncestor() instanceof SpringController
}
}
/**
* 该方法的声明类型名称包含Spring Request Mapping的注解
*/
class SpringRequsetMappingCustomAnnotation extends Annotation {
SpringRequsetMappingCustomAnnotation() {
getType().getAnAncestor().hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping")
or
getType().getAnAncestor().hasQualifiedName("org.springframework.web.bind.annotation", "RestController")
}
}
/**
* 该方法的声明类型名称包含Spring Rest Mapping的注解
*/
class SpringRestMappingCustomAnnotation extends Annotation {
SpringRestMappingCustomAnnotation() {
getType().getAnAncestor().hasQualifiedName("org.springframework.web.bind.annotation", "GetMapping")
or
getType().getAnAncestor().hasQualifiedName("org.springframework.web.bind.annotation", "PostMapping")
or
getType().getAnAncestor().hasQualifiedName("org.springframework.web.bind.annotation", "PutMapping")
or
getType().getAnAncestor().hasQualifiedName("org.springframework.web.bind.annotation", "DeleteMapping")
or
getType().getAnAncestor().hasQualifiedName("org.springframework.web.bind.annotation", "PatchMapping")
}
}
/**
* 输入源和数据流定义
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.frameworks.spring.SpringComponentScan
import semmle.code.java.frameworks.spring.SpringCustomController
/**
* Spring框架Servlet Input参数输入源
*/
class SpringCustomServletInputParameterSource extends RemoteFlowSource {
SpringCustomServletInputParameterSource() {
this.asParameter() = any(SpringRequestMappingParameter srmp | srmp.isTaintedInput())
}
override string getSourceType() { result = "Spring servlet input parameter" }
}
/**
* Spring框架Multipart FileS参数输入源
*/
class SpringCustomMultipartFileSource extends RemoteFlowSource {
SpringCustomMultipartFileSource() {
exists(MethodAccess ma, Method m |
ma = this.asExpr() and
m = ma.getMethod() and
m.getDeclaringType()
.getASourceSupertype*()
.hasQualifiedName("org.springframework.web.multipart", "MultipartFile") and
m.getName().matches("get%")
)
}
override string getSourceType() { result = "Spring MultipartFile getter" }
}
/**
* Spring框架Multipart request参数输入源
*/
class SpringCustomMultipartRequestSource extends RemoteFlowSource {
SpringCustomMultipartRequestSource() {
exists(MethodAccess ma, Method m |
ma = this.asExpr() and
m = ma.getMethod() and
m.getDeclaringType()
.getASourceSupertype*()
.hasQualifiedName("org.springframework.web.multipart", "MultipartRequest") and
m.getName().matches("get%")
)
}
override string getSourceType() { result = "Spring MultipartRequest getter" }
}
package org.joychou.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/fastjson")
public class Fastjson {
@RequestMapping(value = "/deserialize", method = {RequestMethod.POST})
@ResponseBody
public String Deserialize(@RequestBody String params) {
// 如果Content-Type不设置application/json格式,post数据会被url编码
try {
// 将post提交的string转换为json
JSONObject ob = JSON.parseObject(params);
return ob.get("name").toString();
} catch (Exception e) {
return e.toString();
}
}
controllerMethod | controllerMethod.getLocation | methodBindPath | classBindPath | Type |
---|---|---|---|---|
Deserialize | Fastjson:19[19-29] | "/deserialize" | "/fastjson" | Spring bindPath |
package org.joychou.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/fastjson")
public class Fastjson {
@RequestMapping(value = "/deserialize", method = {RequestMethod.POST})
@ResponseBody
public String Deserialize(@RequestBody String params) {
// 如果Content-Type不设置application/json格式,post数据会被url编码
try {
// 将post提交的string转换为json
JSONObject ob = JSON.parseObject(params);
return ob.get("name").toString();
} catch (Exception e) {
return e.toString();
}
}
/**
* @name FastJson deserializing of user-controlled data
* @description FastJson deserializing user-controlled data may allow attackers to
* execute arbitrary code.
* @kind path-problem
* @problem.severity error
* @precision high
* @id java/unsafe-fastjson-deserialization
* @tags security
* external/cwe/cwe-502/Fastjson deserialization
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.FastJson
import DataFlow::PathGraph
class UnsafeFastJsonSinkConfig extends TaintTracking::Configuration {
UnsafeFastJsonSinkConfig() { this = "UnsafeFastJsonConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof UnSafeFastJsonSink }
}
from DataFlow::PathNode source, DataFlow::PathNode sink, UnsafeFastJsonSinkConfig conf
where conf.hasFlowPath(source, sink)
select sink.getNode().(UnSafeFastJsonSink).getMethodAccess(), source, sink,
"Unsafe fastjson deserialization of $@.", source.getNode(), "user input"
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.DataFlow2
import semmle.code.java.dataflow.DataFlow3
import semmle.code.java.frameworks.FastJson
predicate unsafeFastjson(MethodAccess ma, Expr sink) {
exists(Method m | m = ma.getMethod() |
ma.getMethod() instanceof FastJsonParseMethod and
not fastJsonLooksSafe() and
sink = ma.getArgument(0)
)
}
class UnSafeFastJsonSink extends DataFlow::ExprNode {
UnSafeFastJsonSink() { unsafeFastjson(_, this.getExpr()) }
MethodAccess getMethodAccess() { unsafeFastjson(result, this.getExpr()) }
}
/**
* The class `com.alibaba.fastjson.JSON`.
*/
class FastJson extends RefType {
FastJson() { this.hasQualifiedName("com.alibaba.fastjson", "JSON") }
}
/**
* A FastJson parse method. This is either `JSON.parse` or `JSON.parseObject`.
*/
class FastJsonParseMethod extends Method {
FastJsonParseMethod() {
this.getDeclaringType() instanceof FastJson and
this.hasName(["parse", "parseObject"])
}
}
/**
* A call to `ParserConfig.setSafeMode`.
*/
class FastJsonSetSafeMode extends MethodAccess {
FastJsonSetSafeMode() {
exists(Method m |
this.getMethod() = m and
m.hasName("setSafeMode") and
m.getDeclaringType().hasQualifiedName("com.alibaba.fastjson.parser", "ParserConfig")
)
}
关注公众号,后台发送“58白盒系列2”,获取Codeql教程下载链接