跳到主要内容

代码审计:SyntaxFlow 数据流分析与高级过滤技巧

· 阅读需 8 分钟
Yak ProjectYak Project

在代码安全审计中,数据流分析和过滤是追踪漏洞路径的关键,但如何精确筛选想要审计的值、精确控制数据流分析常常困扰开发者。今天我们将深入解析 SyntaxFlow 的三大高阶能力,揭秘如何通过精细化配置提升分析效率与准确性。

本文将涵盖以下核心内容:

• SyntaxFlow 数据流分析各配置项的含义与应用。

• 一种更优雅的语法,用于根据方法参数筛选调用。

• 如何利用文件过滤语法,直接审计文件内容。

阅读本文文章需要的前置知识:

精细化控制 数据流分析

默认情况下,SyntaxFlow 的数据流分析(包括 Top-Def 和 Bottom-Use )会一直追踪到变量的最初定义或最终使用。 但在复杂场景中,我们往往需要更精细的控制,例如限制追踪深度或范围。为此,SyntaxFlow 提供了一系列配置项来控制分析行为。

追踪深度限制: depth

此配置项用于限制数据流的追踪深度,避免追踪过深导致性能问题。

  • 语法:{depth: N},其中 N 为正整数,代表最大追踪层数。
  • 示例:
// Top-Def限制追踪深度为 5
$sink #{depth: 5}-> $source;
// Bottom-Use限制追踪深度为 3
$source -{depth: 3}-> $sink;

捕获过程值: hook

在数据流追踪过程中,可以使用 hook 来记录中间状态或特定的函数调用。

  • 原理:hook 的本质是在数据流分析的每个节点上,启动一个新的 SyntaxFlow 虚拟机来运行其内部的规则。如果规则生成了新变量,该变量将在分析结束后可用。
  • 示例:查找HttpServletRequest 的使用点,并用 hook 捕获所有对 getParameter() 的调用,将其作为间接的污点源。
// 搜索 HttpServletRequest 的参数
HttpServletRequest?{opcode:param}?{<typeName>?{have:'javax.servlet.http.HttpServletRequest'}} as $req
// 使用Bottom-Use和hook记录getParameter()的调用
$req -{
hook: `*.getParameter() as $indirectParam`
}->;

包含或排除特定条件:

这两个配置项用于在数据流分析完成之后,对结果集进行过滤。

  • **原理:**分析结束后,SyntaxFlow 会深度优先遍历( DFS )所有结果路径。 在每个路径上,它会像 hook 一样启动一个新虚拟机来运行 includeexclude 的规则。 如果规则产生了新变量,则视为条件满足。
  • **include示例:检测密钥长度不足的弱加密。我们只关心密钥长度是常量的情况。
// 捕获RSAKeyGenParameterSpec构造函数的第二个参数(密钥长度)
RSAKeyGenParameterSpec?{<typeName>?{have:'java.security.spec.RSAKeyGenParameterSpec'}}(,* as $keySize);
// 向上追踪$keySize的定义,且只包含(include)定义是常量的情况
$keySize#{
include:`* ?{opcode:const}`
}-> as $size;
// 判断密钥长度是否小于2048$size in (,2048) as $risk;
  • **exclude 示例:**如果一个进程调用了 destroy 方法,我们则认为它是安全的,需要将其排除。
// 查找没有设置超时时间的Process.waitFor方法调用
Process.waitFor?{!(<getActualParams><slice(start=1)>)}(* as $process,);
// 检测$process,但排除那些调用了destroy或destroyForcibly的实例
$process?{*-{
exclude:`*.destroy*`,
}->} as $result;

具体流程可以参考下图:

设置终止条件: until

until 用于指定数据流追踪的终止条件,一旦满足,当前路径的分析就会停止。这对于从 sink 到 source 的污点分析尤其有用。

  • 原理: until 的检查分两个阶段。 分析时,在每个节点都会运行 until 规则,如果满足条件(即产生新变量),则停止当前路径的追踪。分析后,还会对所有结果路径进行一次最终检查,确保结果符合预期。
  • 示例: 检测 Java 中的命令注入。从执行命令的 sink 点 ($sink) 向上追踪,直到 ( until ) 遇到来自 Web 请求的参数 ( $source )。
// 定义source(Web参数)
<include('java-servlet-param')> as $source;
<include('java-spring-mvc-param')> as $source;
// 定义sink(命令执行函数)
<include('java-runtime-exec-sink')> as $sink;
// 从sink向上追踪,直到遇见source为止
$sink #{
until: "* & $source"
}-> as $controlled_source_site;

参数筛选的新语法:?(...)

在实战中,一个常见的审计需求是:找到某个方法调用,其参数还需满足特定条件。例如,检测 XXE 漏洞时,需要找到调用 XMLReader.setFeature 时,第一个参数为特定字符串,第二个参数为 false 的情况。

由于 SyntaxFlow 的线性语法特性,在括号 () 内对参数进行过滤,很难影响到括号外的方法调用结果。过去,虽然可以借助 nativeCall 来实现,但规则非常复杂,可读性和可维护性都很差。

// 旧方案:需用原生调用层层解构
XMLReader.setFeature()?{
<getActualParams><slice(index=1)>?{=="http://..."}
&& <slice(index=2)>?{==false}
} as $xxeRisk

为了解决这个痛点,SyntaxFlow 引入了新语法:?(...)。它允许你在方法参数列表中直接进行筛选。如果某个参数被过滤掉了,那么整个方法调用也会被过滤掉。

  • 新语法示例:method?(*?{...}) as $result
  • XXE检测示例**: 使用新语法可以轻松实现需求。
// 1. 直接内联参数条件
XMLReader.setFeature?(
,*?{=="http://..."}
,?{==false}
) as $xxeRisk

这个语法直观地表达了“查找 setFeature 方法,其第一个参数等于特定字符串,且第二个参数等于 false”的意图。

文件过滤: 直接审计文件内容

SyntaxFlow 不仅能分析代码结构,还能直接通过正则表达式、XPath、JSONPath 等方式对文件内容进行审计。 这使得发现硬编码密钥、错误配置等问题变得非常方便。

  • 语法:${``'文件名'}.方法(参数)

具体案例解析

1、使用正则表达式过滤文件内容

    ${*}.re("\bya29\.[0-9A-Za-z\-_]+") as $google_oauth_access_token

解释:

  • ${...} 里面填写要过滤的文件名,支持Glob形式。这里使用 * 表示匹配所有文件。
  • .re(...) 方法使用正则表达式过滤文件内容。
  • "\bya29\.[0-9A-Za-z\-_]+" 是要匹配的正则表达式,用于查找 Google OAuth 访问令牌。
  • as $google_oauth_access_token 将匹配到的结果存储到变量 $google_oauth_access_token 中,便于后续使用。

2、使用 JSONPath 过滤文件内容

    ${*.json}.jsonPath("$.users[*].name") as $userNames

解释:

  • ${*.json} 匹配所有 JSON 文件。
  • .jsonPath(...) 方法使用 JSONPath 表达式过滤文件内容。
  • "$.users[*].name" 是 JSONPath 表达式,用于提取所有用户的名称。
  • as $userNames 将匹配到的用户名称存储到变量 $userNames 中。

3、使用 XPath 过滤 XML 文件内容

    ${*.xml}.xpath("//user/name") as $xmlUserNames

解释:

  • ${*.xml} 匹配所有 XML 文件。
  • .xpath(...) 方法使用 XPath 表达式过滤文件内容。
  • "//user/name" 是 XPath 表达式,用于提取所有用户的名称。
  • as $xmlUserNames 将匹配到的用户名称存储到变量 $xmlUserNames 中。

4、使用 Xpath 提取 Json 和 Yaml 文件内容

由于 JsonPath 的表达力有限,并不能很好的匹配内容。因此 Xpath 也支持匹配Json 和 Yaml 文件内容。

5、使用 XPath 过滤 JSON 文件内容

    ${*.json}.json("$.auths.*.email") as $result

解释:

  • ${*.json} 匹配所有 JSON 文件。
  • .json(...) 方法使用 XPath 表达式过滤 JSON 文件内容。
  • "$.auths.*.email" 是 XPath 表达式,用于提取所有认证信息中的电子邮件地址。

6、使用 XPath 过滤 YAML 文件内容

   ${*.yml}.xpath(<<<XPATH
//*[contains(lower-case(local-name()), 'passwd') or contains(lower-case(local-name()), 'password')
]
XPATH) as $result

解释:

  • ${*.yml} 匹配所有 YAML 文件。
  • .xpath(...) 方法使用 XPath 表达式过滤 YAML 文件内容。
  • <<<XPATH ... XPATH 是多HereDoc语法,用于编写复杂的 XPath 表达式。
  • xpath规则匹配所有包含 passwdpassword 的元素,忽略大小写。

7、实战中注意事项

在实际应用中,使用文件过滤功能时需要注意以下几点:

  • 文件路径:确保文件路径正确,支持通配符和相对路径。
  • 正则表达式:编写正则表达式时,注意转义字符的使用,确保表达式能够正确匹配目标内容。
  • 性能考虑:在处理大文件或复杂的过滤条件时,注意性能开销,避免过于复杂的正则表达式或 XPath 表达式导致性能下降。
  • 结果处理:确保对过滤结果进行适当的处理和存储,以便后续分析和使用。

总结

通过今天的分享,我们深入了解了 SyntaxFlow 中控制数据流分析的多种配置项、根据参数筛选方法调用的优雅新语法、以及强大的文件内容过滤功能。熟练掌握这些高级技巧,能让我们的代码审计工作变得更加精准、高效和深入。


本文首发于 Yak Project 公众号,阅读原文