常见 NativeCall 以及用例
在 SyntaxFlow 中,NativeCall 是一种强大的机制,允许用户在规则中调用内置函数来实现复杂的分析功能。本章节将详细介绍一些常用的 NativeCall 及其实际应用场景。
- 什么是 NativeCall?
- 常用 NativeCall 详解
- include
- typeName
- fullTypeName
- getReturns
- getFormalParams
- getFunc
- getCall
- getCallee
- searchFunc
- getObject
- getMembers
- getMemberByKey
- getSiblings
- name
- string
- eval
- fuzztag
- show
- slice
- regexp
- strlower
- strupper
- var
- opcodes
- sourceCode
- scanNext
- scanPrevious
- scanInstruction
- const
- versionIn
- dataflow
- self
- forbid
- delete
- getUsers
- getPredecessors
- getActualParams
- getActualParamLen
- getCurrentBlueprint
- getBluePrint
- getParentsBlueprint
- getInterfaceBlueprint
- getRootParentBlueprint
- extendsBy
- len
- root
- FilenameByContent
- getFullFileName
- foreach_function_inst
- javaUnescapeOutput
- isSanitizeName
- mybatisSink
- 总结
什么是 NativeCall?
NativeCall 是 SyntaxFlow 提供的一种特殊机制,它允许用户在规则中直接调用一些内置的高级分析函数。这些函数通常封装了复杂的分析逻辑,使得用户可以更方便地实现高级分析功能,而无需手动编写复杂的逻辑。
- 简化复杂分析逻辑的实现
- 提供标准化的分析接口
- 提高规则的可维护性
- 确保分析的一致性和准确性
常用 NativeCall 详解
SyntaxFlow 提供了丰富的 NativeCall 功能,极大地扩展了规则编写的灵活性。在使用 IRify 编写规则时,输入 <
符号会自动提示可用的 NativeCall,无需记忆具体的函数名称。
include
include 是一个高频使用的 NativeCall,它允许在本规则中引用其他的规则,实现规则的复用与解耦。
比如,在编写 SpringBoot 的数据流分析规则时,通常会遇到用户可控输入点(Source)都是控制层的可控参数,而污染汇聚点(Sink)则是不固定的。为了复用 Source 的规则,我们可以将其单独写成一个规则,并在 desc
声明的时候添加 lib
字段,以便被其他规则调用。
以下为内置规则中,查找 Java Spring Boot 的用户可控输入的规则:
desc(
title: 'checking [spring controller source parameter]',
type: audit,
lib: 'java-spring-param',
desc: <<<TEXT
此规则旨在审计Spring框架中控制器方法的参数来源安全性。确保控制器方法中的参数来源清晰且安全至关重要,因为不当的参数处理可能导致安全漏洞,如SQL注入、跨站脚本攻击(XSS)等。通过检查控制器方法的参数是否明确指定了来源(如通过@RequestParam、@PathVariable等注解),可以防止潜在的参数篡改和注入攻击。此外,这也有助于维护代码的清晰性和可维护性。
TEXT
)
*Mapping.__ref__?{opcode: function} as $start;
// annotation method' formal params.
$start(*?{opcode: param && !have: this} as $formalParams);
// fetching getParameter
.getParameter()?{<getFunc>.annotation.*Mapping} as $dynamicParams;
// merge start and output
$formalParams + $dynamicParams as $output;
// output lib params
alert $output;
desc
中声明的 lib
为 java-spring-param
,其他规则要调用这个规则就使用这个名称。在规则的最后使用 alert $output
,将该规则分析的值传出。
在其他规则中,就可以使用 NativeCall 调用该 lib 规则了:
<include('java-spring-param')> as $source;
这样,我们就能够根据不同的情况组合各种 lib 规则,比如检测 Java Spring Boot SSRF 漏洞的规则:
desc(
title_zh: "检测Java SpringBoot 直接SSRF",
title: "Find Java SpringBoot SSRF Vuln",
type: vuln,
)
<include('java-spring-param')> as $source;
<include("java-http-sink")> as $sink;
$sink #{
include: `<self> & $source`,
exclude: `<self>?{opcode:call}?{!<self> & $source}?{!<self> & $sink}`,
}->as $mid;
alert $mid for {
message: "发现Java SpringBoot SSRF漏洞,且未进行数据流过滤。",
risk: ssrf,
level: mid,
}
比如 Java Spring Boot 与 Java Servlet 检测 RCE 的规则:
desc(
title: "Servlet & Springframework Paremeter Passed into Command Injection Sink",
title: 'Servlet & Springframework 基础参数命令注入',
type: vuln,
risk: rce,
desc: <<<TEXT
在Java中,如果用户输入的参数直接传递给命令执行函数,可能会导致命令注入漏洞。攻击者可以通过构造恶意输入,执行系统命令,进而控制服务器。为了防止命令注入漏洞,需要对用户输入的参数进行严格的过滤,包括验证参数的合法性、长度、字符等,并尽量避免直接将用户输入的参数传递给命令执行函数。
TEXT
)
<include('java-servlet-param')> as $source;
<include('java-spring-param')> as $source;
check $source;
<include('java-runtime-exec-sink')> as $sink;
<include('java-command-exec-sink')> as $sink;
check $sink;
$sink #{
include: `<self> & $source`,
exclude: `<self>?{opcode:call}?{!<self> & $source}`
}->as $high;
alert $high for {
message: "发现Java代码中存在命令注入漏洞,且未进行数据流过滤。",
level: high,
};
typeName
typeName
用于获取一个值的类型名。默认情况下,typeName
包含该值所属类的简单名称以及全名称。类型名是通过所导入包进行生成的,如以下例子:
package org.example.moudels.typename;
import com.alibaba.fastjson.JSON;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.*;
@Slf4j
@Api(value = "TypeNameController", tags = "Irify TypeName用例")
@Controller
@RequestMapping("/typename")
public class TypeNameController {
@GetMapping("/simple")
public ResponseEntity<Object> simple(@RequestParam(name = "id") String id) {
Object anyJSON = JSON.parse(id);
return ResponseEntity.ok(anyJSON);
}
}
JSON.parse
的 typeName
是根据 import 了 com.alibaba.fastjson.JSON
的包所确定的。这个导入的包名同时也是 JSON.parse
的全限定名称。以下是获取 typeName
的规则:
JSON.parse<typeName()> as $name
Import all 机制
一些情况下,有一些类的使用是通过 import all 实现的,比如以下例子:
package org.example.moudels.typename;
import com.alibaba.fastjson.JSON;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.*;
import org.springframework.web.cors.*;
@Slf4j
@Api(value = "TypeNameController", tags = "Irify TypeName用例")
@Controller
@RequestMapping("/typename")
public class TypeNameController {
@GetMapping("/importAll")
public void importAll(@ApiParam(name="url",value="请求参数",required = true)@RequestParam String url) {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin(url);
}
}
其中 CorsConfiguration
在源码中是根据 import org.springframework.web.cors.*;
导入的,该导入语句称作 import all 语句。import all 语句使得 CorsConfiguration
无法确定可以使用哪个语句进行生成 typeName
。因此没办法确定使用哪条 import 语句生成 typeName
的值,会使用以下方式生成 typeName
,以确保不会遗漏任何分析信息:
- 将自身名称与当前的包名拼接
- 将自身名称与所有 import all 语句拼接
- 与默认导入的
java.lang
包进行拼接
因此 CorsConfiguration
有 5 个 typeName
:
CorsConfiguration()<typeName()> as $name
左右类型合并
在语句 Object anyJSON = JSON.parse(id);
中,右值 JSON.parse
的 typeName
是确定的,左值的 anyJson
的类型是 Object
,而 Object
又没有进行显式导入,因而会通过 import all 机制生成 typeName
。同时左右值的 typeName
不一致,因而会将左右值的 typeName
进行合并,以确保不会遗漏分析信息:
anyJSON<typeName()> as $name
fullTypeName
fullTypeName
意为全限定名,即一个值类型的全名称。它本质上与 typeName
一样,不过少了简单名称,并且多了版本信息(如果导入的类有版本信息的话)。如下:
JSON.parse<fullTypeName()> as $name
getReturns
getReturns
用于获取函数的返回值。输入必须是一个函数指令。
实现原理
getReturns
的工作流程如下:
这个过程确保了数据流分析的完整性,使得可以追踪从函数返回值到其使用点的完整路径。
示例
public String HHHHH(@RequestParam(value = "xxx") String xxxFooBar) throws Exception {
return "Hello getReturns";
}
HHHHH <getReturns> as $sink;
// 结果: "Hello getReturns"
实际规则用法 :
Controller.__ref__<getMembers>?{.annotation.*Mapping && !.annotation.ResponseBody} as $entryMethods;
$entryMethods<getReturns>?{<typeName>?{have: String}}?{have:'redirect:'} as $sink;
这个规则用于检测Java URL重定向漏洞,它:
- 找到所有 Controller 类的成员方法
- 过滤出有 Mapping 注解但没有 ResponseBody 注解的方法
- 获取这些方法的返回值
- 筛选出类型为 String 且包含 'redirect:' 的返回值
FreeMarker 模板注入检测 :
*Mapping.__ref__<getFunc><getReturns>?{<typeName>?{have:'String'}}<freeMarkerSink> as $a
这个规则:
- 找到有 Mapping 注解的方法
- 获取包含这些方法的函数
- 获取函数的返回值
- 筛选出 String 类型的返回值
- 应用 FreeMarker sink 检测
getFormalParams
getFormalParams
用于获取函数的形参。输入必须是一个函数指令。
实现原理
getFormalParams
的工作流程如下:
- 输入验证: 接收函数值作为输入,递归遍历处理
- 操作码检查: 验证输入值的操作码是否为Function类型
- 函数转换: 将输入值转换为函数对象,获取函数的元数据
- 参数列表获取: 从函数定义中提取形式参数列表
- 参数值创建: 为每个形式参数创建对应的Value对象
- 关系建立: 建立参数值与原函数之间的前驱关系,用于数据流追踪
- 结果返回: 返回包含所有形式参数的值集合
这个机制使得可以从函数定义出发,找到所有可能的输入点,这对于安全分析中的污点源识别非常重要。
示例
public String HHHHH(@RequestParam(value = "xxx") String xxxFooBar) throws Exception {
return "Hello getReturns";
}
HHHHH <getFormalParams> as $sink;
// 结果: ["xxxFooBar", "this"]
Spring Controller 参数提取 :
$start<getFormalParams>?{opcode: param && !have: this} as $params;
这个规则用于获取 Spring MVC 控制器方法的所有非 this 参数,用于识别用户输入源。
Java Servlet 参数提取 :
/(do(Get|Post|Delete|Filter|[A-Z]\w+))|(service)/<getFormalParams>?{!have: this && opcode: param } as $req;
这个规则:
- 匹配 Servlet 的 doGet、doPost 等方法
- 获取这些方法的形参
- 过滤掉 this 参数,只保留方法参数
注解测试用法 :
*Mapping.__ref__<getFormalParams>?{opcode: param && !have: this} as $ref
这个测试:
- 找到有 Mapping 注解的方法
- 获取方法的形参
- 筛选出非 this 的参数
getFunc
getFunc
用于获取输入指令所在的函数。输入可以是任何指令。
实现原理
getFunc
的工作流程如下:
- 值遍历: 递归处理输入的所有值
- 函数查找: 对于每个值,向上查找包含该值的函数上下文
- 函数提取: 从SSA的函数结构中提取函数定义
- 对象创建: 为找到的函数创建新的Value对象
- 关系建立: 建立与原始值的前驱关系
- 结果聚合: 返回所有找到的函数值集合
这个NativeCall特别有用于上下文分析,可以帮助确定某个值或操作发生在哪个函数范围内。
项目中的真实用法
获取特定类型的控制器函数 :
$sink?{<getFunc><getCurrentBlueprint><fullTypeName>?{any: "Controller","controller"}} as $output
这个规则:
- 获取sink值所在的函数
- 获取该函数的当前类蓝图
- 检查类的全限定名是否包含"Controller"
- 用于识别ThinkPHP控制器方法
Spring Boot文件下载漏洞检测 :
$params?{<getFunc><getReturns><typeName>?{have: ResponseEntity}<show>} as $entry;
这个规则:
- 从参数出发获取所在函数
- 获取函数的返回值
- 检查返回值类型是否为ResponseEntity
- 用于识别文件下载相关的处理函数
getCall
getCall
用于获取输入指令的调用指令。输入必须是一个函数指令。
实现原理
getCall
的工作机制:
- 输入处理: 遍历输入的所有值
- 使用者查找: 查找每个值的使用者(Users)
- 调用过滤: 筛选出操作码为Call类型的使用者
- 调用收集: 收集所有符合条件的调用指令
- 关系建立: 为每个调用建立与原值的前驱关系
示例
测试用例 :
链式调用分析 :
$concatA<getCall><getCallee>(,* as $b) as $concatB;
$concatB<getCall><getCallee>(,* as $c);
这个测试展示了如何通过getCall追踪链式方法调用。
Golang数据库连接追踪 :
$entry.Open <getCall> as $db;
这个规则:
- 找到数据库库的Open方法
- 获取对Open方法的调用
- 将调用结果标记为数据库连接对象
getCallee
getCallee
用于获取调用指令中被调用的函数。输入必须是一个调用指令(call)。
实现原理
getCallee
的处理流程:
示例
函数名提取 :
SQL注入检测 :
$params<getCallee>?{<name>?{have:toString}}<getObject>.append(,* as $appendParams)
这个规则:
- 从参数获取被调用的函数
- 检查函数名是否包含toString
- 获取调用对象并查找append方法
- 用于检测SQL注入中的字符串拼接模式
XSS过滤检测 :
$calls?{<getCallee><name>?{have: /(?i)(sanitiz|encod(e|ing)|entit(y|ies)|escap(e|ing)|replace|regex|normaliz|canonical|anti|safe|purif|purg|pure|validate|strip|clean|clear|special|convert|remov)/}} as $__next
这个规则通过getCallee获取被调用函数,然后检查函数名是否匹配常见的过滤/编码函数模式。
searchFunc
searchFunc
用于搜索输入指令的调用指令。它是一个强大的函数搜索机制,可以处理多种不同类型的输入并找到相关的函数调用。
实现原理
searchFunc
的工作机制比较复杂,它能够处理多种不同的输入类型:
主要处理的情况包括:
- 参数成员: 从参数成员获取函数名进行搜索
- 普通参数: 从参数所属函数获取方法名进行搜索
- 调用指令: 提取被调用函数名,去除对象前缀后搜索
- 常量指令: 解析常量字符串作为函数名搜索
示例
数据流追踪示例:
// 从变量出发,找到调用它的地方,再搜索同名函数的其他调用
aArgs <getCall> <searchFunc> as $allCalls;
这个NativeCall特别适用于:
- 查找函数的所有调用点
- 进行跨函数的数据流分析
- 识别相同函数名的不同实现
getObject
作用
获取输入指令的父对象。如果指令是一个成员,可以通过这个指令获取成员的父对象。
示例
1. 基本对象成员访问
测试代码:
a = {"b": 111, "c": 222, "e": 333}
.b<getObject>.c as $sink;
注释:
.b
- 获取对象a的成员b<getObject>
- 获取成员b的父对象,即对象a.c
- 访问父对象的成员c- 结果为 222,通过父对象间接访问其他成员
2. Spring MVC参数检测
.getParameter()?{<getCallee><getObject><fullTypeName>?{have: servlet} && <getFunc><getObject>.annotation.*Mapping} as $dynamicParams
注释:
.getParameter()
- 获取参数方法<getCallee><getObject>
- 获取方法调用的父对象<fullTypeName>?{have: servlet}
- 检查父对象类型是否为Servlet<getFunc><getObject>.annotation.*Mapping
- 检查方法所在类是否有Mapping注解- 用于识别Spring MVC控制器中的动态参数获取
3. 反序列化安全检测
.readObject?{<typeName>?{have:'java.beans.XMLDecoder'}}<getObject()> as $decoder;
注释:
.readObject
- XML反序列化方法<typeName>?{have:'java.beans.XMLDecoder'}
- 确认是XMLDecoder类型<getObject()>
- 获取readObject方法的调用对象- 用于检测XML反序列化漏洞
4. SQL注入检测
$params<getCallee>?{<name>?{have:toString}}<getObject>.append(,* as $appendParams)
注释:
$params<getCallee>
- 获取被调用的方法?{<name>?{have:toString}}
- 检查方法名包含toString<getObject>
- 获取toString方法的调用对象.append
- 查找对象的append方法- 用于检测通过toString()后进行字符串拼接的SQL注入模式
getMembers
作用
获取输入指令的成员指令。如果指令是一个对象,可以通过这个指令获取对象的所有成员。
示例
1. Spring Controller方法获取
Controller.__ref__<getMembers>?{.annotation.*Mapping && !.annotation.ResponseBody} as $entryMethods;
注释:
Controller.__ref__
- 获取Controller类的引用<getMembers>
- 获取Controller类的所有成员方法?{.annotation.*Mapping}
- 筛选有Mapping注解的方法?{!.annotation.ResponseBody}
- 排除有ResponseBody注解的方法- 用于识别Spring MVC控制器的视图返回方法
2. 数据库连接对象分析
$db <getMembers> as $output;
注释:
$db
- 数据库连接对象<getMembers>
- 获取数据库对象的所有可用方法和字段- 结果包含Execute、Query、Prepare等数据库操作方法
- 用于数据库操作的安全分析
3. REST Controller API检测
RestController.__ref__<getMembers>?{.annotation.*Mapping} as $entryMethods;
注释:
RestController.__ref__
- 获取RestController类引用<getMembers>
- 获取类的所有成员方法?{.annotation.*Mapping}
- 筛选有HTTP请求映射注解的方法- 用于识别REST API端点方法
4. Golang数据库连接分析
$entry.Open() <getMembers> as $client;
注释:
$entry.Open()
- 数据库连接打开操作<getMembers>
- 获取连接对象的所有可用方法- 用于分析数据库ORM框架的操作方法
- 常见于Golang的数据库操作分析
getMemberByKey
作用
获取输入指令的特定成员。如果指令是一个对象,可以通过指定的key获取对象的某个特定成员。
示例
1. JSON敏感信息提取
$sink<getMemberByKey(key="password")> as $obj
注释:
$sink
- 目标对象(通常是JSON或数据结构)<getMemberByKey(key="password")>
- 获取名为"password"的成员- 用于检测JSON响应中的敏感信息泄露
- 常用于信息泄露漏洞检测
2. 配置对象特定字段提取
config<getMemberByKey(key="database_password")> as $dbPassword;
config<getMemberByKey(key="api_key")> as $apiKey;
config<getMemberByKey(key="secret_token")> as $token;
注释:
- 从配置对象中提取特定的敏感配置项
- 用于检测硬编码密码和API密钥
- 帮助识别配置安全问题
3. 用户对象敏感字段访问
userObject<getMemberByKey(key="creditCard")> as $sensitiveData;
userObject<getMemberByKey(key="ssn")> as $pii;
注释:
- 从用户对象中提取信用卡号码或社会安全号码
- 用于个人信息保护合规性检查
- 检测敏感数据的不当处理
getSiblings
作用
获取输入指令的兄弟指令。如果指令是一个对象的成员,可以通过这个指令获取该对象的其他成员。
示例
1. 基本兄弟成员获取
假设有如下结构:
config = {
username: "admin",
password: "secret",
database: "mydb",
host: "localhost"
}
.username<getSiblings> as $siblings;