跳到主要内容

代码审计:编写高质量 SyntaxFlow 规则的方法

· 阅读需 5 分钟
Yak Project
网络安全垂直语言团队

syntax-flow zero to hero 中,已经介绍了一部分syntax-flow 语法的编写规则以及关于基础的一些使用,比如#->#>-->等,当然,在之前的公众号中也介绍了一部分config的使用。

目前的yaklang中,也内置了一部分规则,但是毕竟编译的项目千差万别,我们无法保证,在某个项目中,通用规则一定起作用。所以本篇将着重从syntax-flow的基础语法实现进阶,向大家介绍如何去编写一条好用的syntax-flow规则。

https://github.com/yaklang/syntaxflow

Syntax-flow-rule语法

基础语法介绍

g4部分片段如下,在syntax-flow中,支持以多种方式来进行开头,其中,可以指定.开头,指定变量名开头,指定**nativeCall**开头,支持常量搜索。

filterItemFirst
: constSearchPrefix?(QuotedStringLiteral|hereDoc) # ConstFilter
| nameFilter # NamedFilter
| '.' lines? nameFilter # FieldCallFilter
| nativeCall # NativeCallFilter
....
....

filterItem
: filterItemFirst # First
| '...' lines? nameFilter # DeepChainFilter
| '(' lines? actualParam? ')' # FunctionCallFilter
| '[' sliceCallItem ']' # FieldIndexFilter
| '?{' conditionExpression '}' # OptionalFilter
| '->' # NextFilter
| '#>' # DefFilter
| '-->' # DeepNextFilter
| '-{' (config)? '}->' # DeepNextConfigFilter
| '#->' # TopDefFilter
| '#{' (config)? '}->' # TopDefConfigFilter
| '+' refVariable # MergeRefFilter
| '-' refVariable # RemoveRefFilter
| '&' refVariable # IntersectionRefFilter
....
....

在之前的**syntax-flow**中,学习到了**#->**是来进行搜索顶级定义的。那么,可以在之前的基础上进行一些横向拓展,在eval的过程中,我们可能会遇到匹配函数,或者匹配字段等问题。

过滤条件:

conditionExpression
: '(' conditionExpression ')' # ParenCondition
| filterExpr # FilterCondition // filter dot(.)Member and fields
| Opcode ':' opcodesCondition (',' opcodesCondition) * ','? # OpcodeTypeCondition // something like .(call, phi)
| Have ':' stringLiteralWithoutStarGroup # StringContainHaveCondition // something like .(have: 'a', 'b')
| HaveAny ':' stringLiteralWithoutStarGroup # StringContainAnyCondition // something like .(have: 'a', 'b')
| negativeCondition conditionExpression # NotCondition
| op = (
'>' | '<' | '=' | '==' | '>='
| '<=' | '!='
) (
numberLiteral | identifier | boolLiteral
) # FilterExpressionCompare
| op = ( '=~' | '!~') (stringLiteral | regexpLiteral) # FilterExpressionRegexpMatch
| conditionExpression '&&' conditionExpression # FilterExpressionAnd
| conditionExpression '||' conditionExpression # FilterExpressionOr
....

过滤条件通常是在变量中进行,用**?{}**来进行过滤。

have: 全部包含某个值,可以用,来进行分割。比如 ?{have: a,b}
any: 包含某一个值,?{any: a,b}
opcode: 过滤某些指令的类型为call或者const。比如?{opcode: call}
!: 对过滤条件进行取反
多条件过滤:?{opcode: call && have: filter}

案例1:

<?php

$b = new A();
$a = $b->autoload()->bb($dd);
$c = $b->cc($dd);

//sf

.autoload?{<getObject>?{have: b}} as $vuln

相交运算 & :

在向上寻找的过程中,可能希望某些函数是否参数可控,路径上是否经过某个点,就可以使用**&语法来做。如案例2:在寻找exec**参数顶级定义和$_GET超全局变量进行相交的点。

案例2:

<?php

$a = $_GET[1];
$b = str_replace($a);
$c = handler($b);
exec($c);

//

_GET.* as $param;
exec(* #{until: `* & $param`}-> as $sink)

进阶语法

常量搜索:

在上面的g4中,可以看到是支持常量搜索,常量搜索常常以模式来进行开头。支持stringheredoc进行衔接。适用于在代码中进行硬编码,想要快速匹配代码中的内容,比如,ip地址,密码等常见内容。

常量搜索支持,rge三种匹配模式,分别为正则匹配通配符匹配、**精确匹配。**支持:

r"" r<<<CODECODE

案例3:

搜索Ip地址

这是IP地址的一个正则通配,当在代码中写死一个IP地址的时候,可以通过正则来进行IP地址的搜索。比如。

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])$"

r<<<CODE
^((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])$
CODE

常用的nativeCall详解:

在**syntax-flow中,还适配了一些"内置方法"**,这里去简单的介绍一些nativeCall的使用,方便syntax-flow的编写。

通用:
  • :获取值中是函数调用的值。
<?php
$b = $_GET[a];
if($c){
$b = filter($b);
}
eval($b);

/*

//这里会hook到eval参数在向上寻找顶级定义中的所有值存到$info中
eval(* #{hook: `* as $info`}->)

//然后将$info中所有是call的存入到$param中
$info<getCaller> as $param

*/
  • :获取名称
  • :获取第几位之后的变量,从**0**开始,一般常用于函数调用获取参数。

注意⚠️:该内置方法只是获取到之后的所有变量,并不是固定到某个变量,如果需要指定获取某个变量可以通过f(,,* as $param)来进行获取(此次例子f(,,* as $param)获取的是第三个参数)

f(*<slice(start=1)> as $query) //获取第二个参数及其之后的所有变量

obj相关:

在数据流分析过程中,可能提前确定了一个obj中有**"操作空间"**,可以尝试从所有的sink点开始反推,反推到obj为某个类。

比如:

<?php
class A{
public $a;
public $b;
public function getA(){
return $this->a;
}
}
$a = new A();
eval($a->a);

//
.a<getObject><name>?{have: A} as $param

获取一个类中,所有的成员。

<?php
class A{
public $a;
public $b;
public function getA(){
return $this->a;
}
}
//
A<getMembers> as $param

获取一个类的全类名。

package com.example.demo1;

import java.io.File;
import java.io.IOException;

import java.io.FileOutputStream;

class A {
public int a = 0;

public static void test() throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream(new File("xxx.xx"));
}
}

//
fileOutputStream<fullTypeName>?{have: FileoutputStream} as $param

总结

在syntax-flow中,大部分还是关于rule规则的编写,但其实还有多个部分,比如descalertcheck等多个部分。等到后续开放导入自定义规则后,再进行详细讲解。


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