常见 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;
注释:
.username
- 配置对象的username成员<getSiblings>
- 获取username的兄弟成员- 结果包含 password、database、host 等其他成员
- 用于分析对象结构的完整性
2. 安全配置检查
.ssl_enabled<getSiblings>?{<name>?{have:"password"|"key"|"secret"}} as $sensitiveConfig;
注释:
.ssl_enabled
- SSL配置成员<getSiblings>
- 获取同级的其他配置项?{<name>?{have:"password"|"key"|"secret"}}
- 筛选敏感配置项- 用于检查SSL配置附近是否有敏感信息泄露
3. 数据结构完整性验证
.required_field<getSiblings>?{!have:optional} as $mandatoryFields;
注释:
.required_field
- 必填字段<getSiblings>
- 获取同级字段?{!have:optional}
- 筛选非可选字段- 用于验证数据结构的必填字段完整性
由于getSiblings在实际项目中使用较少,上述示例主要用于说明概念。实际使用时,getMembers通常能满足大部分获取对象成员的需求。
name
作用
获取输入指令的名称表示,例如函数名、变量名、方法名或字段名等。
示例
1. 基础函数名提取 :
测试代码:
funcA = () => {
return "abc";
}
SyntaxFlow规则:
funcA<name> as $sink;
注释:直接从函数定义中提取函数名,结果为"funcA"
**2. 链式调用中的方法名提取 **
测试代码:
public String one() throws Exception {
var aArgs = new String[]{"aaaaaaa"};
xxeController.yourMethod(aArgs); // 这里调用了yourMethod方法
}
SyntaxFlow规则:
aArgs<getCall><getCallee><name> as $sink
注释:
aArgs<getCall>
- 找到使用aArgs变量的调用指令<getCallee>
- 获取被调用的函数(yourMethod)<name>
- 提取函数名称,结果为"yourMethod"
3. SQL注入检测中的函数名匹配 :
SyntaxFlow规则:
$params<getCallee>?{<name>?{have:toString}}<getObject>.append(,* as $appendParams)
注释:
$params<getCallee>
- 从参数获取被调用的方法?{<name>?{have:toString}}
- 检查方法名是否包含"toString"<getObject>.append
- 获取调用对象并查找append方法- 用于检测通过toString()转换后进行字符串拼接的SQL注入模式
4. XSS过滤函数检测 :
SyntaxFlow规则:
$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
注释:
$calls?{<getCallee><name>
- 获取调用指令的被调用函数名?{have: /(?i)(...)/}
- 使用正则表达式匹配常见的安全过滤函数名- 正则包含sanitize、encode、escape、replace等安全函数的变体
- 用于识别XSS防护中的过滤函数调用
string
作用
获取输入指令的字符串表示,返回该指令在代码中的文本形式。
示例
**1. 版本检测中的字符串提取 **
测试代码:
public class VersionTest {
String version = "1.2.3";
String config = "app.version=2.1.0";
}
SyntaxFlow规则:
version<string> as $versionString;
注释:提取version变量的字符串值,用于后续版本比较分析
2. 常量字符串处理:
"127.0.0.1"<string> as $ipString;
注释:获取IP地址常量的字符串表示,结果为"127.0.0.1"
eval
作用
动态执行 SyntaxFlow 代码,支持在运行时构建和执行规则。
示例
1. 动态执行简单规则
测试代码:
public String one() throws Exception {
var aArgs = new String[]{"aaaaaaa"};
xxeController.yourMethod(aArgs);
}
<eval('aArgs<getCall><getCallee><name> as $sink')>
注释:
- 动态执行包含在字符串中的SyntaxFlow规则
- 该规则获取aArgs的调用、被调用者的名称
- 结果找到"yourMethod"
2. 配合show调试
<eval('aArgs<getCall><getCallee><show><name> as $sink')>
注释:
- 在动态执行的规则中加入show进行调试
- 可以查看中间步骤的执行结果
3. 执行变量中的代码
<fuzztag('aArgs<getCall>{{getCallee}}<name> as $sink')> as $code;
<eval($code)><show>
注释:
- 先用fuzztag生成动态代码存储在$code变量中
- 再用eval执行$code变量中的SyntaxFlow规则
- 实现更灵活的动态规则构建
fuzztag
作用
执行 yaklang fuzztag 模板,支持动态生成SyntaxFlow规则代码,变量在 SFFrameResult 中可用。
示例
1. 动态模板生成
测试代码:
public String one() throws Exception {
var aArgs = new String[]{"aaaaaaa"};
xxeController.yourMethod(aArgs);
}
<fuzztag("<getCallee>")> as $templateVar;
<fuzztag('aArgs<getCall>{{templateVar}}<name> as $sink')> as $code;
<eval($code)><show>
check $sink;
注释:
- 第一步:
<fuzztag("<getCallee>")>
生成模板片段 - 第二步:使用
{{templateVar}}
将片段插入到新的模板中 - 第三步:用eval执行生成的完整规则
- 实现了SyntaxFlow规则的动态组装
2. 批量变量生成
测试代码:
a1=1;a2=2;a3=3;
<fuzztag('a{{int(1-3)}} as $sink')><eval><show>;
check $sink;
注释:
{{int(1-3)}}
生成1到3的整数序列- 生成多个规则:a1 as $sink, a2 as $sink, a3 as $sink
- 批量匹配多个相似变量
show
作用
显示中间过程的值,主要用于调试和开发阶段,不对数据流产生影响。
示例
1. 调试链式调用过程 :
SyntaxFlow规则:
aArgs<getCall><getCallee><show><name> as $sink
注释:
aArgs<getCall><getCallee>
- 获取被调用的函数<show>
- 在控制台显示函数对象的详细信息<name>
- 继续提取函数名- 用于调试时查看中间步骤的值
2. MyBatis注解值调试 :
SyntaxFlow规则:
.annotation.Select.value<show><regexp(\$\{\s*(\w+)\s*\}, group=1)> as $entry;
注释:
.annotation.Select.value
- 获取Select注解的value值<show>
- 显示注解的原始值,便于调试正则表达式<regexp(...)>
- 使用正则提取占位符中的变量名- 常用于调试MyBatis注解解析过程
slice
作用
对值进行切片操作,支持从数组、参数列表等容器类型中提取子 集或特定元素。
示例
1. 函数参数切片提取
hijackHTTPRequest<slice(index=0)> as $param0
hijackHTTPRequest<slice(index=1)> as $param1
hijackHTTPRequest<slice(index=2)> as $param2
注释:
<slice(index=0)>
- 获取第0个参数<slice(index=1)>
- 获取第1个参数- 用于从HTTP劫持函数中提取特定位置的参数
2. 密码相关参数提取
ldap_bind(*<slice(start=2)>?{opcode: const} as $pass)
注释:
*<slice(start=2)>
- 获取第2个参数及之后的所有参数?{opcode: const}
- 筛选常量类型的参数- 用于检测LDAP绑定中的硬编码密码
3. LDAP注入检测
ldap_search(*<slice(start=1)> as $query);
ldap_compare(*<slice(index=1)> as $query);
注释:
<slice(start=1)>
- 获取第1个参数及之后的参数(查询参数)<slice(index=1)>
- 获取第1个参数(比较查询)- 用于检测LDAP查询注入漏洞
4. 加密算法参数提取
AlgorithmParameters?{<typeName>?{have:"java.security.AlgorithmParameters"}}.getInstance(*<slice(index=1)> as $algorithm);
注释:
<slice(index=1)>
- 获取第1个参数(算法名称)- 用于检测弱加密算法的使用
- 分析加密配置的安全性
5. SQL注入参数提取
*sql*.append(*<slice(start=1)> as $params);
注释:
<slice(start=1)>
- 获取append方法的所有参数- 用于检测SQL字符串拼接注入
- 分析动态SQL构建的安全性
6. 文件操作参数提取
FileReader(*<slice(index=1)> as $fileReader);
注释:
<slice(index=1)>
- 获取第1个参数(文件路径)- 用于检测文件路径遍历漏洞
- 分析文件操作的安全性
regexp
作用
对字符串进行正则表达式匹配,支持分组提取和模式匹配。
示例
**1. MyBatis SQL 参数提取 **
@Select("select * from users where id = #{user_id} and name = #{user_name}")
public User getUserById(String userId, String userName);
.annotation.Select.value<regexp(\$\{\s*(\w+)\s*\}, group=1)> as $entry;
注释:
- 从Select注解的value值中提取MyBatis参数
- 正则表达式
\$\{\s*(\w+)\s*\}
匹配#{param}
格式 group=1
提取第一个分组,即参数名- 结果提取出 "user_id" 和 "user_name"
2. 基本字符串匹配
// 提取正则匹配的分组
"abc123def"<regexp(`(\d+)`, group: 1)> as $result;
注释:从字符串中提取数字部分,结果为 "123"
3. URL路径提取
"/api/users/123"<regexp(`/api/(\w+)/(\d+)`, group: 2)> as $userId;
注释:从URL路径中提取用户ID,结果为 "123"
strlower
作用
将字符串转换为小写形式。
示例
1. 基本字符串转换
"Hello World"<strlower> as $result;
注释:将字符串转换为小写,结果为 "hello world"
2. 方法名标准化处理
method<name><strlower> as $normalizedName;
注释:获取方法名并转换为小写,用于不区分大小写的匹配
strupper
作用
将字符串转换为大写形式。
示例
1. 基 本字符串转换
"hello world"<strupper> as $result;
注释:将字符串转换为大写,结果为 "HELLO WORLD"
2. 常量名规范化
constantValue<strupper> as $upperConstant;
注释:将常量值转换为大写,用于符合命名约定
var
作用
将值存储到变量表中,用于在SyntaxFlow规则中创建可重用的变量引用。
示例
1. 基本变量存储
result<var(name="myVar")> as $stored;
注释:将result值存储到名为"myVar"的变量中
2. 中间结果缓存
userInput<getFormalParams><var(name="params")> as $cachedParams;
注释:将函数参数缓存到变量中,便于后续规则引用
opcodes
作用
获取值所在函数中所有操作码的类型列表,用于分析函数的复杂度和结构特征。
示例
**1. 复杂函数分析 **
测试代码:
public int methodA(int a) {
try {
println(this.num);
b=6+6;
a = 2;
if (c){
a=22;
if(d){
bb = !true;
int[] myArray = {1, 2, 3, 4, 5};
}
}else {
a=33;
}
prinln(a);
for (int i=0;i<10;i++){
a+=i;
}
switch(a){}
return a;
}catch (Exception e){
return 0;
}
}
a<opcodes> as $allOpcodes;
注释:
- 获取函数methodA中所有指令的操作码类型
- 结果包含: Parameter, ParameterMember, Return, Loop, Function, Call, Phi, Undefined, ConstInst, Jump, If, UnOp, Switch, ErrorHandler, Make, BinOp
- 可以用于分析函数的控制流复杂度和语言特性使用情况
sourceCode
作用
获取指令对应的源代码文本,支持带上下文的代码提取。
示例
**1. 基本源代码提取 **
测试代码:
public static void main(String[] args) {
b=6+6;
a = 2;
if (c){
bb1;
}else {
bb2;
}
prinln(a);
}
bb1<sourceCode> as $code;
注释:获取bb1语句的源代码,结果为 "bb1;\n"
2. 带上下文的源代码提取
bb2<sourceCode(context=3)> as $contextCode;
注释:
- 获取bb2语句及其前后3行的上下文代码
- 结果包含完整的if-else结构和周围代码
- 用于代码审计时需要查看完整上下文的场景
3. 函数完整源码获取
method<sourceCode> as $functionCode;
注释:获取整个方法的源代码文本
scanNext
作用
扫描下一个指令,用于顺序分析代码执行流程。
示例
currentInstruction<scanNext> as $nextInstruction;
注释:
- 获取当前指令的下一个执行指令
- 用于控制流分析和代码执行路径追踪
- 在分析代码执行序列时非常有用
由于scanNext在实际项目中使用较少,主要用于底层的控制流分析。大多数情况下,数据流分析(#->
)能满足分析需求。
scanPrevious
作用
扫描前一个指令,用于逆向分析代码执行流程。
示例
currentInstruction<scanPrevious> as $previousInstruction;
注释:
- 获取当前指令的前一个执行指令
- 用于逆向控制流分析
- 在追踪指令来源时有用
scanPrevious主要用于专门的控制流分析场景。一般的数据流追踪建议使用getPredecessors
。
scanInstruction
作用
扫描当前基本块中的所有指令。
示例
value<scanInstruction> as $blockInstructions;
注释:
- 获取当前基本块内的所有指令列表
- 用于分析基本块的指令组成
- 在静态分析中用于理解代码块结构
scanInstruction主要用于编译器级别的分析。对于安全分析,通常使用foreach_function_inst
更为合适。
const
作用
搜索程序中的常量值,支持多种匹配模式:精确匹配、通配符匹配、正则表达式匹配等。
示例
**1. 通配符匹配 **
测试代码:
cc = "127.0.0.1"
<const(g="127*")> as $output
注释:
g="127*"
- 使用通配符模式匹配以"127"开头的常量- 结果找到 "127.0.0.1"
2. 正则表达式匹配
<const(r="^((0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])\.){3}(0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])$")> as $ipAddresses
注释:
r="..."
- 使用正则表达式匹配IP地址格式- 正则表达式验证IPv4地址的合法性
3. 精确匹配
<const(e="127.0.0.1")> as $exactMatch
注释:
e="127.0.0.1"
- 精确匹配指定的字符串常量- 只匹配完全相同的值
4. Here Document 匹配
<const(<<<CODE
"127.0.0.1"
CODE)> as $heredocMatch
注释:
- 支持多行字符串的常量匹配
- 适用于复杂的字符串模式
5. SSRF 过滤检测 :
$input<const(r="([?#]|[^?#:/\\\\][/\\\\])|^/$")> as $output;
注释:
- 检测可能绕过SSRF过滤的特殊字符模式
- 用于识别URL中的危险字符组合
6. 语法糖形式
"127*" as $globMatch // 等同于 <const(g="127*")>
e"127.0.0.1" as $exact // 等同于 <const(e="127.0.0.1")>
r"^\d+\.\d+\.\d+\.\d+$" as $regex // 等同于 <const(r="...")>
注释:SyntaxFlow提供了更简洁的语法糖形式来表达常量匹配
// 精确匹配
<const(e="127.0.0.1")> as $output;
// 全局匹配
<const(g="127*")> as $output;
// 正则匹配
<const(r=`^((0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])\.){3}(0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])$`)> as $output;
// 自动模式(语法糖)
"127*" as $output; // 全局匹配
"127.0.0.1" as $output; // 精确匹配
使用示例:
cc = "127.0.0.1"
<const(g="127*")> as $output;
// 结果: "127.0.0.1"
<const(r=`^((0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])\.){3}(0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])$`)> as $output;
// 结果: "127.0.0.1"
versionIn
作用
检查版本是否在指定范围内,用于依赖库漏洞检测和版本兼容性分析。
示例
1. Maven依赖版本检测
测试代码(pom.xml):
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
__dependency__.*fastjson.version as $ver;
$ver?{version_in:(0.1.0,1.3.0]} as $vulnVersion
注释:
__dependency__.*fastjson.version
- 获取fastjson依赖的版本version_in:(0.1.0,1.3.0]
- 检查版本是否在区间(0.1.0, 1.3.0]内- 用于检测存在漏洞的fastjson版本
2. 版本范围语法
// 开区间:不包含边界值
$ver?{version_in:(1.0.0,2.0.0)} as $result
// 闭区间:包含边界值
$ver?{version_in:[1.0.0,2.0.0]} as $result
// 混合区间:左开右闭
$ver?{version_in:(1.0.0,2.0.0]} as $result
// 开放边界:无上限
$ver?{version_in:[1.2.24,)} as $result
// 开放边界:无下限
$ver?{version_in:(,1.2.24]} as $result
3. 复杂版本条件
__dependency__.*fastjson.version as $ver;
$ver?{version_in:(0.1.0,1.3.0]||(1.1.0,2.3.0]} as $vulnVersion
注释:
- 使用
||
连接多个版本区间 - 检查版本是否在任一区间内
- 适用于有多个漏洞版本范围的情况
4. 语法糖形式
$ver in (0.1.0,1.3.0] as $vulnVersion
注释:使用in
关键字作为version_in
的简写形式
dataflow
dataflow
用于获取数据流。如果你想获取数据流,在 -->
或 #->
之后调用 <dataflow...>
。
// 获取数据流
$data<dataflow(<<<CODE
*?{opcode: call && <getCallee><name>?{name} }
CODE)>
self
self
用于获取自身值。
// 获取自身值
value<self> as $self;
forbid
forbid
用于禁止一个值,如果值存在,报告严重错误。
// 禁止特定变量
<forbid(var="variableName")>
// 禁止当前值
value<forbid>
使用示例:
a = (b) => {
return b + 1;
}
c = a();
b<show><forbid>
// 结果: 如果 b 存在,报告严重错误
delete
delete
用于删除变量。
// 删除变量
<delete(name="variableName")>
getUsers
作用
获取值的使用者(Users),即哪些指令使用了当前值,常用于数据流分析和未使用返回值检测。