跳到主要内容

原生扩展:重要的 NativeCall 机制

SyntaxFlow 的高级关键特性之一是使用 NativeCall 函数。这些函数是预先定义的,可在语言内部提供各种实用功能。本教程将介绍 NativeCall 函数的概念,解释其用法,并提供可用函数的完整列表及其描述。

简介

SyntaxFlow 中,NativeCall(原生调用)是预封装的函数,允许用户在规则中执行各种高级操作。这些函数用于操作、检查和转换数据结构,促进高级代码分析和转换任务。通过 NativeCall,用户无需编写复杂的逻辑即可实现复杂的数据处理需求,大大提升了 SyntaxFlow 的灵活性和功能性。

NativeCall 语法定义

NativeCall 的语法结构如下:

<nativeCallName(arg1, argName="value", ...)>

其中:

  • <:标记 NativeCall 的开始。
  • nativeCallName:要使用的 NativeCall 函数名称。
  • (...):包含函数参数的圆括号。
  • >:标记 NativeCall 的结束。

完整的 eBNF 描述

nativeCall
: '<' useNativeCall '>'
;

useNativeCall
: identifier useDefCalcParams?
;

useDefCalcParams
: '{' nativeCallActualParams? '}'
| '(' nativeCallActualParams? ')'
;

nativeCallActualParams
: lines? nativeCallActualParam (',' lines? nativeCallActualParam)* ','? lines?
;

nativeCallActualParam
: (nativeCallActualParamKey (':' | '='))? nativeCallActualParamValue
;

nativeCallActualParamKey
: identifier
;

nativeCallActualParamValue
: identifier | numberLiteral | '`' ~'`'* '`' | '$' identifier | hereDoc
;

NativeCall 函数列表

下表列出了 SyntaxFlow 中所有可用的 NativeCall 函数,以及它们的描述:

函数名称描述
<getReturns>获取函数或方法的返回值。
<getFormalParams>检索函数或方法的形式参数。
<getFunc>查找包含特定指令或值的当前函数。
<getCall>获取一个值的调用,通常用于获取与操作码相关的调用。
<getCaller>查找包含特定值的调用指令。
<searchFunc>在整个程序中搜索对某个函数的调用。如果输入已经是调用,则搜索该方法(函数)的其他调用。
<getObject>获取与值关联的对象,通常用于面向对象的上下文中。
<getMembers>获取对象或类的成员变量或方法。
<getSiblings>检索代码结构中兄弟节点或值。
<typeName>获取值的类型名称(不含包路径)。
<fullTypeName>获取值的完整类型名称(包括包路径)。
<name>获取函数、方法、变量或类型的名称。
<string>将值转换为其字符串表示形式。
<include>包含一个外部文件或资源中的 SyntaxFlow 规则。
<eval>评估一个作为字符串提供的新 SyntaxFlow 规则。
<fuzztag>评估一个 Yaklang 的 fuzztag 模板,利用 SFFrameResult 中的变量。
<show>输出值而不执行任何操作(用于调试)。
<slice>从值中提取一个切片,类似于数组或字符串的切片操作。使用示例:<slice(start=0)>
<regexp>对字符串执行正则表达式匹配或提取。支持捕获组。示例:<regexp(pattern="\d+", group=1)>
<strlower>将字符串转换为小写。
<strupper>将字符串转换为大写。
<var>将值赋给变量以供后续使用。
<mybatisSink>在代码分析中识别 MyBatis 的 Sink(特定于 Java MyBatis 框架)。
<freeMarkerSink>识别 FreeMarker 的 Sink(特定于 FreeMarker 模板引擎)。
<opcodes>获取与值关联的操作码(opcode)。
<sourceCode>获取值的源代码表示形式。
<scanPrevious>扫描相对于给定值的前一个操作码或指令。
<scanNext>扫描给定值之后的下一个操作码或指令。
<delete>从当前上下文中删除变量或值。
<forbid>将值标记为禁止;如果该值存在,将报告严重错误。
<self>获取当前值本身(用于链式操作中)。
<dataflow>获取与值相关的数据流信息。在流操作符 -->#-> 之后使用。
<const>在代码中搜索常量值。
<versionIn>判断依赖版本是否在某个版本区间。

注意:上述函数列表是逐步演进的,可能并不包含全部的 NativeCall 函数。用户可以参考源码和相关规则了解更多未覆盖的 NativeCall 使用方法。在后续的案例中,我们会逐步为大家讲解 NativeCall 中的内容究竟都是如何使用的。

使用实例与解释

通过具体的代码案例,结合 SyntaxFlowNativeCall 函数,可以更直观地理解其应用。

案例一:获取函数的返回值

背景:在代码审计过程中,了解函数的返回值类型和具体返回值对于识别潜在漏洞至关重要。

审计代码示例

public class ReturnExample {
public String getUserInput() {
// 获取用户输入
return System.getProperty("user.input");
}

public void process() {
String input = getUserInput();
// 进一步处理输入
}
}

SyntaxFlow 规则案例

// 定义描述信息
desc(
"Title": "获取函数返回值示例",
"Fix": "确保返回值在使用前经过适当的验证和处理"
)

// 捕获所有函数的返回值
$returns = <getReturns()> as $ret;

// 检查返回值是否直接用于敏感操作
$vulnerable = $ret .*SensitiveOperation() as $vuln;

// 标记潜在的漏洞
check $vuln then "返回值未经验证直接用于敏感操作" else "返回值使用安全";

执行效果

保存上述规则为 return_example.sf 并执行审计:

yak ssa -t . --program return_example && yak sf --program return_example return_example.sf

输出示例

[INFO] 2024-07-01 10:15:30 [ssacli:272] syntax flow query result:
rule md5 hash: d4e5f6789012abcdef3456789abcdef3
rule preview: desc( "Title": "获取函数返回值示例",...
description: {Title: "获取函数返回值示例", Fix: "确保返回值在使用前经过适当的验证和处理", $vuln: "返回值未经验证直接用于敏感操作"}
Result Vars:
vuln:
t1327001: getUserInput().SensitiveOperation()
ReturnExample.java:10:25 - 10:50

解释

  • <getReturns()>:调用 NativeCall 函数 getReturns,获取所有函数的返回值。
  • $ret .*SensitiveOperation() as $vuln;:检查返回值是否被直接用于 SensitiveOperation 这一敏感操作。
  • check $vuln then "返回值未经验证直接用于敏感操作" else "返回值使用安全";:标记并报告潜在的漏洞。

案例二:字符串转换与正则匹配

背景:有时需要对字符串进行转换操作,或通过正则表达式提取特定信息。

审计代码示例

public class StringExample {
public void processInput(String input) {
// 转换为小写
String lowerInput = input.toLowerCase();
// 执行正则匹配
if (lowerInput.matches("\\d+")) {
// 处理数字字符串
}
}
}

SyntaxFlow 规则案例

// 定义描述信息
desc(
"Title": "字符串转换与正则匹配示例",
"Fix": "确保字符串转换和正则匹配逻辑的正确性,防止不当处理导致的漏洞"
)

// 捕获所有字符串转换为小写的操作
$lowerStrings = <strlower()> as $lower;

// 对转换后的字符串执行正则匹配
$matches = $lower .*<regexp(pattern="\\d+", group=0)> as $match;

// 检查匹配结果
check $match then "字符串包含仅数字" else "字符串不符合数字格式";

执行效果

保存规则为 string_example.sf 并执行审计:

yak ssa -t . --program string_example && yak sf --program string_example string_example.sf

输出示例

[INFO] 2024-07-01 11:30:45 [ssacli:272] syntax flow query result:
rule md5 hash: e5f6789012abcdef3456789abcdef4
rule preview: desc( "Title": "字符串转换与正则匹配示例",...
description: {Title: "字符串转换与正则匹配示例", Fix: "确保字符串转换和正则匹配逻辑的正确性,防止不当处理导致的漏洞", $match: "字符串包含仅数字"}
Result Vars:
match:
t1328001: input.toLowerCase().matches("\\d+")
StringExample.java:6:25 - 6:55

解释

  • <strlower()>:调用 NativeCall 函数 strlower,将字符串转换为小写。
  • <regexp(pattern="\\d+", group=0)>:调用 NativeCall 函数 regexp,执行匹配仅包含数字的正则表达式。
  • check $match then "字符串包含仅数字" else "字符串不符合数字格式";:根据匹配结果输出相应的信息。

实战中的注意事项

在实际应用中,使用 NativeCall 函数时需要注意以下几点:

1. 理解函数功能

每个 NativeCall 函数都有其特定的功能和用途。使用前,务必详细阅读函数描述,确保其适用于当前的分析需求。

2. 参数正确性

确保传递给 NativeCall 函数的参数正确且符合预期。例如,正则表达式的语法错误可能导致规则失效或产生错误结果。

3. 性能考虑

某些 NativeCall 函数可能涉及复杂的计算或大量的数据处理。在处理大型项目时,注意可能的性能开销,必要时优化规则或限制函数的使用范围。

4. 调试与验证

在编写和调整 NativeCall 规则时,使用 <show> 函数等调试手段输出中间结果,以验证规则的正确性和效果。

最佳实践

为了充分发挥 NativeCall 机制的优势,建议遵循以下最佳实践:

1. 模块化编写规则

将复杂的分析任务分解为多个模块,每个模块使用不同的 NativeCall 函数处理特定的需求,提升规则的可读性和可维护性。

2. 结合使用多种函数

NativeCall 函数可以组合使用,以实现更复杂的分析。例如,先使用 <getReturns> 获取返回值,再用 <regexp> 提取特定信息。

3. 定期更新与优化

随着 SyntaxFlow 的发展和项目代码的变化,定期审查和更新 NativeCall 规则,确保其适应最新的代码结构和分析需求。

4. 利用调试功能

使用 <show> 等调试函数输出中间变量和结果,帮助识别和解决规则中的问题,确保规则的准确性和有效性。

5. 参考官方文档与源码

当遇到未覆盖的 NativeCall 函数或复杂的使用场景时,参考 SyntaxFlow 的官方文档和源码,深入了解函数的实现和使用方法。

总结

NativeCall 机制为 SyntaxFlow 提供了强大的扩展能力,使用户能够在规则中执行多种高级操作。通过预定义的 NativeCall 函数,用户可以高效地操作、检查和转换数据结构,完成复杂的代码分析任务。掌握 NativeCall 的使用方法和最佳实践,将显著提升代码审计和安全分析的效率与准确性。