上一章节介绍了Semgrep使用及相关原理,传送门:Semgrep 之初识
下面这章就开始介绍Semgrep的模式语法,如何检测想要的代码。心急的同学,可以直接使用交互式的 Semgrep 规则教程,很快就能熟悉上手,https://semgrep.dev/learn。
一、Semgrep 规则可以做什么?
工欲善其事,必先利其器,首先要理解规则能做什么,才能更好的编写规则。
1.1 自动化的 Code Review 并在 PR 时评论
1.2 识别违反安全编码规范的代码
例如,检测 React 的规则 dangerouslySetInnerHTML
如下所示。dangerouslySetInnerHTML
是 React 用于代替在浏览器 DOM 中使用 innerHTML
,类似 Vue 的 v-html。一般来说,从代码设置 HTML 是有风险的,因为很容易将用户暴露于跨站点脚本(XSS) 攻击。Semgrep 检测识别该代码可以提醒其进行整改,或者接入 js-xss 库来进行防御。
1.3 扫描配置文件
Semgrep 原生支持 JSON 和 YAML,可用于为配置文件编写规则。此规则检查 Kubernetes 集群中跳过的 TLS 验证。
Semgrep 规则还可以用于更多事情。例如检测即将弃用的 API、强制执行身份验证等等。具体可以参考更多的官方用例:https://semgrep.dev/docs/writing-rules/rule-ideas/
二、模式语法(Pattern syntax)
模式语法介绍了 Semgrep 模式可以做什么,并提供省略号运算符、元变量等的示例用例。
在命令行中,模式是用标志 --pattern
(或 -e
)指定的。可以在配置文件中指定多个同等模式。
2.1 模式匹配(Pattern matching)
模式匹配搜索给定模式的代码。例如,表达式 1 + func(42)
可以匹配完整的表达式或者是子表达式的一部分:
foo(1 + func(42)) + bar()
同样,return 42
可以匹配函数中的顶部语句或任何嵌套语句:
def foo(x):
if x > 1:
if x > 2:
return 42
return 42
2.2 省略号运算符
省略号运算符 ( …) 抽象出一系列零个或多个参数、语句、参数、字段、字符串等。
2.2.1 函数调用(Function calls)
使用省略号运算符搜索函数调用或具有特定参数的函数调用。例如,该模式 insecure_function(...)
查找调用而不管其参数如何。
insecure_function("MALICIOUS_STRING", arg1, arg2)
函数和类可以通过它们的名称来引用,例如
-
django.utils.safestring.mark_safe(...)
或者mark_safe(...)
-
System.out.println(...)
或者println(...)
还可以在匹配后搜索带参数的调用。该模式 func(1, ...)
将匹配两者:
func(1, "extra stuff", False)
func(1) *# Matches no arguments as well*
或者在匹配之前查找带有参数的调用 func(..., 1)
:
func("extra stuff", False, 1)
func(1) *# Matches no arguments as well*
该模式 requests.get(..., verify=False, ...)
查找参数出现在任何地方的调用:
requests.get(verify=False, url=URL)
requests.get(URL, verify=False, timeout=3)
requests.get(URL, verify=False)
将关键字参数值与模式匹配 $FUNC(..., $KEY=$VALUE, ...)
。
2.2.2 方法调用(Method calls)
省略号运算符也可用于搜索方法调用。例如,模式 $OBJECT.extractall(...)
匹配:
tarball.extractall('/path/to/directory') *# 潜在的任意文件覆盖问题*
还可以在方法调用链中使用省略号。例如,模式 $O.foo(). ... .bar()
将匹配:
obj = MakeObject()
obj.foo().other_method(1,2).again(3,4).bar()
2.2.3 函数定义(Function definitions)
省略号运算符可用于函数参数列表或函数体中。要查找具有可变默认参数的函数定义:
pattern: |
def $FUNC(..., $ARG={}, ...):
...
def parse_data(parser, data={}): *# Oops, mutable default arguments*
pass
YAML 文件 |
运算符允许多行字符串。
省略号运算符也可用于函数名。实际上,在某些情况下,可能想要匹配任意的函数定义:例如常规函数、方法,还有匿名函数( lambda)。在这种情况下,可以使用省略号代替函数名称来匹配命名或匿名函数。例如,在 Javascript 中,模式 function ...($X) { ... }
将匹配具有一个参数的任何函数:
function foo(a) {
return a;
}
var bar = function (a) {
return a;
};
2.2.4 类定义(Class definitions)
省略号运算符可用于类定义。例如当要查找从某个父级继承的类:
pattern: |
class $CLASS(InsecureBaseClass):
...
class DataRetriever(InsecureBaseClass):
def __init__(self):
pass
2.2.5 字符串(Strings)
省略号运算符可用于搜索包含任何数据的字符串。例如使用 crypto.set_secret_key("...")
匹配:
crypto.set_secret_key("HARDCODED SECRET")
2.2.6 二元运算(Binary operations)
省略号运算符可以将任意数量的参数匹配到二元运算。模式 $X = 1 + 2 + ...
匹配:
foo = 1 + 2 + 3 + 4
2.2.7 容器(Containers)
省略号运算符可以匹配内部容器内的数据结构,如列表、数组和键值存储。
-
模式 user_list = [..., 10]
匹配:
user_list = [8, 9, 10]
-
模式 user_dict = {...}
匹配:
user_dict = {'username': 'password'}
-
该模式 user_dict = {..., $KEY: $VALUE, ...}
匹配以下内容并允许进一步的元变量查询:
user_dict = {'username': 'password', 'address': 'zipcode'}
-
还可以仅匹配字典中的键值对,例如在 JSON 中,模式 "foo": $X
仅匹配以下中的一行:
{ "bar": True,
"name": "self",
"foo": 42
}
2.2.8 条件和循环(Conditionals and loops)
省略号运算符可以在条件或循环中使用。例如:
pattern: |
if $CONDITION:
...
将会匹配如下代码:
if can_make_request:
check_status()
make_request()
return
如果后续重用主体语句信息,则元变量可以匹配条件或循环主体。例如:
pattern: |
if $CONDITION:
$BODY
将会匹配下面代码:
if can_make_request:
single_request_statement()
2.3 元变量(Metavariables)
元变量是一种抽象表达。用于在不知道值或内容时匹配代码,类似于正则表达式中的捕获组。
元变量可用于跟踪特定代码范围内的值。包括变量、函数、参数、类、对象方法、导入、异常等。
元变量一般看起来像 $X
、$WIDGET
或 $USERS_2
。都是以x 或
$some_value` 的名称无效。
2.3.1 表达式元变量(Expression metavariables)
例如 $X + $Y
将会与以下代码示例匹配:
foo() + bar()
current + total
2.3.2 导入元变量(Import metavariables)
元变量也可用于匹配导入。例如,import $X
匹配如下示例:
import random
2.3.3 重复出现元变量(Reoccuring metavariables)
重用元变量显示了它的真正威力。例如检测无用的分配:
pattern: |
$X = $Y
$X = $Z
检测到下面代码无用的分配:
initial_value = 10 *# Oops, useless assignment*
initial_value = get_initial_value()
2.3.4 字面元变量(Literal Metavariables)
可以使用 "$X"
匹配任何字符串文字。这与 前文介绍的省略号运算符类似。但字符串的内容存储在元变量 $X
中,可以用于在 message 或 metavariable-regex
。还可以使用 /$X/
and :$X
来分别匹配任何正则表达式或原子(语言需要支持这些结构,例如 Ruby)。
2.3.5 类型化元变量(Typed Metavariables)
类型化元变量仅在元变量被声明为特定类型时才匹配。例如,你可能想要专门检查 ==
从未用于字符串的那个。
Java:
pattern: $X == (String $Y)
public class Example {
public int foo(String a, int b) {
*// Matched*
if (a == "hello") {
return 1;
}
*// Not matched*
if (b == 2) {
return -1;
}
}
}
C :
pattern: $X == (char *$Y)
int main() {
char *a = "Hello";
int b = 1;
*// Matched*
if (a == "world") {
return 1;
}
*// Not matched*
if (b == 2) {
return -1;
}
return 0;
}
Go:
pattern: "$X == ($Y : string)"
func main() {
var x string
var y string
var a int
var b int
*// Matched*
if x == y {
x = y
}
*// Not matched*
if a == b {
a = b
}
}
对于 Go 语言,Semgrep 目前无法识别在同一行声明的所有变量的类型。也就是说,以下不会同时取 a
和 b
作为 int
s:var a, b = 1, 2
TypeScript:
pattern: $X == ($Y : string)
function foo(a: string, b: number) {
*// Matched*
if (a == "hello") {
return 1;
}
*// Not matched*
if (b == 1) {
return -1;
}
}
使用类型元变量(Using Typed Metavariables),类型推断适用于整个文件!常见方法是检查在特定类型的对象上调用的函数。例如,假设要在这样的类中寻找对潜在不安全记录器的调用:
class Test {
static Logger logger;
public static void run_test(String input, int num) {
logger.log("Running a test with " + input);
test(input, Math.log(num));
}
}
如果你搜索 $X.log(...)
,将会匹配 Math.log(num)
。相反,可以搜索
(Logger $X).log(...)
这只会给出 logger
结果
由于 Semgrep 只能匹配单个文件,因此只能保证对局部变量和参数有效。此外,Semgrep 目前对类型的理解很浅。例如,如果有 int[] A
类型,它将无法识别 A[0]
为整数。如果有一个带有字段的类,将无法对字段访问使用类型检查,并且它不会将该类的字段识别为预期类型。Literal 类型仅在有限程度上被理解。
2.3.6 省略号元变量(Ellipsis Metavariables)
可以结合省略号和元变量来匹配参数序列,并将匹配的序列存储在元变量中。例如,foo(…arg)将匹配:
foo(1,2,3,1,2)
2.4 等价(Equivalences)
Semgrep 会自动搜索语义等价的代码。
2.4.1 import 导入
使用别名或子模块的等价导入将会被匹配。例如模式 subprocess.Popen(...)
匹配:
import subprocess.Popen as sub_popen
sub_popen('ls')
模式 foo.bar.baz.qux(...)
匹配:
from foo.bar import baz
baz.qux()
2.4.2 常数等价
Semgrep 会检测常数是否等价。例如 set_password("password")
匹配:
HARDCODED_PASSWORD = "password"
def update_system():
set_password(HARDCODED_PASSWORD)
2.4.3 结合和交换运算符(Associative and Commutative operators)
Semgrep 执行关联交换 (AC) 匹配。
例如,... && B && C
将同时匹配 B && C
and (A && B) && C
(即 &&
是关联的)。
此外,A | B | C
将匹配 A | B | C
, and B | C | A
, and C | B | A
, 和任何其他排列(即,|
是关联的和可交换的)。
在 AC 匹配下,元变量的行为类似于 ...
。例如,可以通过四种不同的方式 A | $X
进行匹配($X
可以绑定到 B
, 或者 C
, 或者 B | C
)。为了避免组合过多,Semgrep 只会在潜在匹配的数量很小的情况下对元变量执行 AC 匹配,否则它将只产生一个匹配,其中每个元变量都绑定到单个操作数。
使用 options
它可以完全禁用 AC 匹配。也可以将布尔 AND 和 OR 运算符(例如,&&
在 ||
C 系列语言中)视为可交换的,尽管在语义上不准确,但它还是很有用的。
2.5 深度表达式运算符(Deep expression operator)
使用深度表达式运算符 <... [your_pattern] ...>
来匹配可以深度嵌套在另一个表达式中的表达式。例如,下面的 Pattern:
pattern: |
if <... $USER.is_admin() ...>:
...
匹配如下代码:
if user.authenticated() and user.is_admin() and user.has_group(gid):
[ CONDITIONAL BODY ]
深度表达式运算符适用于:
-
if
声明:if <... $X ...>:
-
嵌套调用: sql.query(<... $X ...>)
-
二元表达式的操作: "..." + <... $X ...>
-
任何其他表达式上下文
三、局限(Limitations)
3.1 语句类型(Statements types)
Semgrep 处理某些语句类型与其他语句方式不同,尤其是在代码语句中搜索片段时。例如,该模式 foo
将匹配以下语句:
x += foo()
return bar + foo
foo(1, 2)
但 foo
不会匹配以下语句
import foo
许多编程语言区分表达式和语句。表达式可以出现在 if 条件、函数调用参数中。而语句不能;它们是一系列的操作(在许多语言中 ;
用作分隔符/终止符)或特殊的控制流构造(if、while 等)。
foo()
是一个表达式(在大多数语言中)。
foo();
是一个陈述(在大多数语言中)。
如果你的搜索模式是语句,Semgrep 将自动尝试将其作为表达式和语句进行搜索。
当你在模式中编写表达式 foo()
时,Semgrep 将访问程序中的每个表达式和子表达式并尝试找到匹配项。
许多程序员并没有看到 foo()
和 foo();
之间的区别。这就是为什么当人们寻找 foo()
; Semgrep 认为用户想要匹配诸如 a = foo();
, 或之类的语句 print(foo());
。
在某些编程语言中,例如 Python,它不使用分号作为分隔符或终止符,表达式和语句之间的区别更加令人困惑。Python 中的缩进很重要,foo()
后面的换行符实际上与 foo();
等 C 等其他编程语言相同
3.2 部分表达式(Partial expressions)
部分表达式不是有效的模式。例如,以下内容无效:
pattern: 1+
需要一个完整的表达式(如 1 + $X
)
3.3 省略号和语句块(Ellipses and statement blocks)
省略号运算符不会从内部语句块跳转到外部语句块。
例如,这种 Pattern:
foo()
...
bar()
匹配如下代码:
foo()
baz()
bar()
并且还匹配下面代码:
foo()
baz()
if cond:
bar()
但它不匹配这个代码:
if cond:
foo()
baz()
bar()
因为 ...
不能从内部语句块 foo()
跳转到外部块 bar()
。
3.3 部分语句(Partial statements)
不完全地支持部分语句。例如,可以仅将条件的 header 与 匹配 if ($E)
,或者仅将异常语句的 try 部分与匹配 try { ... }
。这在用于 模式内部以限制搜索其他事物的上下文时特别有用。
3.4 其他部分结构体(Other partial constructs)
可以只匹配函数的头部(没有函数体),例如 int foo(...)
只匹配函数的头部部分 foo
。同样,您可以只匹配一个类头(例如,with class $A
)。
原文始发于微信公众号(洋洋自语):Semgrep 之模式语法(二)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/272935.html