现在终于到我们的重头戏了,学习 Semgrep 的规则语法。 Semgrep 的规则包含了一些字段,其中有些字段是必填字段、可选字段,每个字段代表的意义不同
一、必填字段
字段名 | 类型 | 描述 |
---|---|---|
id | string | id必须唯一, 描述字符,例如使用no-unused-variable |
message | string | 一般描述命中此规则的原因以及如何解决该问题单元格 |
severity | string | INFO、WARNING、ERROR之一 |
languages | array | 语言的扩展标签,例如.py, .go等,具体可查看附录一 |
pattern* | string | 查找匹配此表达式的代码 |
patterns* | array | 多个模式的逻辑与 |
patterns* | array | 多个模式的逻辑或 |
pattern-regex* | string | 在多行模式下查找与此PCRE兼容模式匹配的代码 |
pattern、patterns、pattern-either、pattern-regex在规则里仅需要其中之一即可
二、可选字段
字段 | 类型 | 描述 |
---|---|---|
options | object | 启用/禁用某些匹配功能的选项对象 |
fix | object | 简单的搜索和替换,自动修复功能 |
metadata | object | 任意用户提供的数据;将数据附加到规则而不影响 Semgrep 的行为 |
paths | object | 运行此规则时要包含或排除的路径 |
pattern-inside 字段必须位于 patterns
or pattern-either
字段下方。
字段 | 类型 | 描述 |
---|---|---|
pattern-inside | string | 保留此模式内的发现 |
以下可选字段必须位于 patterns
字段下方。
字段 | 类型 | 描述 |
---|---|---|
metavariable-regex | map | 通过Python的re模块搜索元变量;正则表达式匹配未锚定 |
metavariable-pattern | map | 将元变量与模式公式匹配 |
metavariable-comparison | map | 将元变量与基本Python 表达式进行比较 |
pattern-not | string | 逻辑非 – 删除匹配此表达式的结果 |
pattern-not-inside | string | 保留不属于此模式的发现 |
pattern-not-regex | string | 在多行模式下使用PCRE兼容模式过滤结果 |
三、操作方式
下面将介绍每个字段的使用方式
3.1 pattern
pattern
运算符查找与其表达式匹配的代码。可以是基本表达式如 $X == $X
或者查找不想要的函数调用,例如 hashlib.md5(...)
。
3.2 patterns
运算符 patterns
是对一个或多个子模式执行逻辑与运算。这对于将多个 pattern 结合在一起非常有用,所有 pattern 都必须为 true。
注意,在运算符中声明子 pattern 的顺序,对 patterns
最终结果没有影响。patterns
运算符始终以相同的方式进行评估。
3.3 pattern-either
运算符 pattern-either
对一个或多个子模式执行逻辑或运算。对于在任何可能为 true 的情况下将多个模式链接在一起很有用。
下面的规则是查找 Python 标准库函数 hashlib.md5
或 hashlib.sha1
. 这两个散列函数被认为是不安全的。
3.4 pattern-regex
pattern-regex
运算符在文件中搜索给定 PCRE 模式匹配的子字符串。这对于将现有的正则表达式代码搜索功能迁移到 Semgrep 很有用。PCRE “Perl-Compatible Regular Expressions” 是一个功能齐全的正则表达式库,它当然与 Perl 广泛兼容,也与 Python、JavaScript、Go、Ruby 和 Java 各自的正则表达式库兼容。该模式以多行模式编译,即除了输入的开头 ^
,和 $
结尾之外。还将分别在行的开头和结尾匹配(自 Semgrep 0.95.0 起)。
PCRE 仅支持有限数量的 Unicode 字符属性。例如,p{Egyptian_Hieroglyphs}
支持但 p{Bidi_Control}
不支持。
该 pattern-regex
运算符可以与其他模式运算符组合:
单 ( '
) 和双 ( "
) 引号在 YAML 语法中的行为不同。当使用 pattern-regex
与反斜杠 一起使用时,通常首选单引号。
如果正则表达式使用组,元变量 $1
,$2
等将绑定到捕获组的内容。
3.5 pattern-not-regex
pattern-not-regex
运算符在多行模式下使用 PCRE 正则表达式过滤结果。当与正则表达式规则结合使用时,非常有用。它提供了一种简单的方法来过滤结果,而无需使用 negative lookahead(零宽度负预测先行断言)。pattern-not-regex
也可以与常规 pattern
子句一起使用。
pattern-not-regex
运算符的语法与 pattern-regex
很像,但是该运算符将过滤与提供的正则表达式有任何重叠的结果。例如,如果您使用 pattern-regex
检测 Foo==1.1.1
并且它也检测 Foo-Bar==3.0.8
和 Bar-Foo==3.0.8
,你可以使用 pattern-not-regex
过滤不需要的发现。
3.6 focus-metavariable
focus-metavariable
将焦点放大在与元变量匹配的代码区域上。例如,要查找使用类型是 bad
的所有函数参数,可以编写以下模式:
pattern: |
def $FUNC(..., $ARG : bad, ...):
...
这个规则是有效的,但它会匹配整个函数定义。这是不可取的。如果定义跨越数百行,都将会匹配到。特别是,如果你正在使用 Semgrep 应用程序并且您已对由此模式生成的结果进行分类,如果您对函数的定义进行任何更改,相同的结果将再次显示为新结果!
需要指定只对特定元变量匹配的代码感兴趣,例如示例 $ARG
中,使用 focus-metavariable
.注意,
focus-metavariable: $ARG
这与 pattern: $ARG
不一样。 使用 pattern: $ARG
会找到使用该参数的用法,x
这不是我们想要的!(pattern: $ARG
不匹配形式参数声明,因为在这个上下文中 $ARG
只匹配表达式。)
简而言之,focus-metavariable: $X
它本身不是一个 pattern,它不执行任何匹配,它只将匹配重点放在已经 $X
被其他模式绑定的代码上。而 pattern: $X
将与代码匹配(在这种情况下,$X
仅匹配表达式)
3.7 metavariable-regex
metavariable-regex
运算符在元变量中搜索 PCRE 正则表达式。这对于根据元变量的值过滤结果很有用。它需要 metavariable和regex
,并且可以与其他模式运算符结合使用。
正则表达式匹配是未锚定的。对于锚定匹配,A
用于字符串开始锚定、Z
字符串结尾锚定。下面示例,使用与上面相同的表达式但指定锚定,将找不到匹配项:
metavariable-regex
在用于搜索字符串文字时,需要在正则表达式中包含引号。
3.8 metavariable-pattern
metavariable-pattern
运算符将元变量与模式公式匹配。这对于根据元变量的值过滤结果很有用。它需要 metavariable
的 key,并且需要是 pattern
、patterns
、pattern-either pattern-regex
其中一个。该运算符可以嵌套以及与其他运算符组合。
3.9 metavariable-comparison
metavariable-comparison
运算符将元变量与基本的 Python 表达式进行比较。这对于根据元变量的数值过滤结果很有用。
metavariable-comparison
运算符是一个需要 metavariable
和 comparison
key 的映射。它可以与其他模式运算符结合使用:
这将捕获到 set_port(443)
,而不是 set_port(8080)
。
比较表达式支持简单的算术运算以及与布尔运算符的组合用以允许更复杂的匹配。这对于检查元变量是否可以被特定值整除特别有用,例如强制特定值是偶数还是奇数:
在前面的示例的基础上,会捕获到 set_port(80)
但不会再捕获 set_port(443)
或 set_port(8080)
。
comparison
key 使用以下命令接受 Python 表达式:
-
布尔、字符串、整数和浮点文字。 -
布尔运算符 not
,or
, 和and
. -
算术运算符 +
,-
,*
,/
, 和%
. -
比较运算符 ==
,!=
,<
,<=
,>
, 和>=
. -
int()
将字符串转换为整数的函数。 -
str()
将数字转换为字符串的函数。 -
判断是否在列表中存在, in
运算符。 -
re.match()
匹配正则表达式的函数(没有可选flags
参数)。
你可以使用 Semgrep 元变量,例如 $MVAR
,Semgrep 评估检测如下:
-
如果 $MVAR
绑定到文字,则该文字是分配给的值$MVAR
。 -
如果 $MVAR
绑定到作为常量的代码变量,并且启用了常量传播(默认情况下),那么该常量就是分配给$MVAR
. -
否则,绑定到 的代码将 $MVAR
保持未评估,并且可以使用str()
函数获取其字符串表示形式,如str($MVAR)
. 例如,如果$MVAR
绑定到代码变量x
,则str($MVAR)
计算为字符串字面量"x"
。
3.10 pattern-not
pattern-not和pattern
是相反的。它找到与其表达式不匹配的代码。这对于消除常见的误报很有用。
3.11 pattern-inside
pattern-inside
运算符保留其表达式中的匹配结果。这对于在其他代码段(如函数或 if 块)中查找代码很有用。
3.12 pattern-not-inside
pattern-not-inside
运算符保留不存在于其表达式中的匹配结果。它和 pattern-inside
是相反的。这对于查找缺少相应清理操作(如断开、关闭或关闭)的代码很有用、对于查找不在缓解问题的代码中的有问题的代码也很有用。
上述规则会查找已打开但从未关闭的文件,这可能会导致资源耗尽。它寻找 open(...)
且后续没有 close()
的模式。
需要确保在 open和close
调用中使用相同的变量名 $F
。省略号运算符允许将任何参数传递 open
调用。该规则忽略了如何被调用或调用发生了什么——它只需要确保 close
被调用。
四、元变量匹配
对于逻辑 与 ( patterns
) 和逻辑 或( pattern-either
) 运算符,元变量匹配的操作方式不同。所有子运算符的行为都是一致的:pattern
, pattern-not
, pattern-regex
, pattern-inside
, pattern-not-inside
。
4.1 逻辑与中的元变量
使用运算符执行逻辑与运算时,元变量值必须在子模式中相同 patterns
。
例子:
rules:
- id: function-args-to-open
patterns:
- pattern-inside: |
def $F($X):
...
- pattern: open($X)
message: "Function argument passed to open() builtin"
languages: [python]
severity: ERROR
此规则与以下代码匹配:
def foo(path):
open(path)
示例规则与此代码不匹配:
def foo(path):
open(something_else)
4.2 逻辑或中的元变量
元变量匹配不影响逻辑或 pattern-either
运算符的匹配。
例子:
rules:
- id: insecure-function-call
pattern-either:
- pattern: insecure_func1($X)
- pattern: insecure_func2($X)
message: "Insecure function use"
languages: [python]
severity: ERROR
上述规则匹配以下两个示例:
insecure_func1(something)
insecure_func2(something)
insecure_func1(something)
insecure_func2(something_else)
4.3 复杂的元变量
如果父项是逻辑 与,元变量匹配仍会影响后续的逻 或。
例子:
patterns:
- pattern-inside: |
def $F($X):
...
- pattern-either:
- pattern: bar($X)
- pattern: baz($X)
上述规则匹配以下两个示例:
def foo(something):
bar(something)
def foo(something):
baz(something)
示例规则与此代码不匹配:
def foo(something):
bar(something_else)
五、其他功能
5.1 fix
fix
key 允许为每个匹配建议自动修复。运行 semgrep
以 --autofix
将更改应用到文件。
例子:
rules:
- id: use-dict-get
patterns:
- pattern: $DICT[$KEY]
fix: $DICT.get($KEY)
message: "Use `.get()` method to avoid a KeyNotFound error"
languages: [python]
severity: ERROR
5.2 metadata
记录有关规则的额外信息,例如相关的 CVE 或编写规则的安全工程师的姓名,这时可以使用 metadata:
key。
例子:
rules:
- id: eqeq-is-bad
patterns:
- [...]
message: "useless comparison operation `$X == $X` or `$X != $X`"
metadata:
cve: CVE-2077-1234
discovered-by: Ikwa L'equale
5.3 Paths
5.3.1 排除路径
要忽略特定文件的特定规则,paths:
使用一个或多个过滤器设置键。
例子:
rules:
- id: eqeq-is-bad
pattern: $X == $X
paths:
exclude:
- "*.jinja2"
- "*_test.go"
- "project/tests"
- project/static/*.js
当使用 调用时 semgrep -f rule.yaml project/
,上述规则将在 内部的文件上运行 project/
,但不会返回任何结果:
-
任何带有 .jinja2
文件扩展名的文件 -
任何名称以 结尾的文件 _test.go
,例如project/backend/server_test.go
-
里面的任何文件 project/tests
或其子目录 -
任何匹配 project/static/*.js
glob 模式的文件
5.3.2 指定路径
相反,要针对特定文件运行规则 paths:
使用以下一个或多个过滤器设置键:
rules:
- id: eqeq-is-bad
pattern: $X == $X
paths:
include:
- "*_test.go"
- "project/server"
- "project/schemata"
- "project/static/*.js"
- "tests/**/*.js"
当使用 调用时 semgrep -f rule.yaml project/
,此规则将在 内部的文件上运行 project/
,但结果将仅返回:
-
名称以 结尾的文件 _test.go
,例如project/backend/server_test.go
-
project/server
,project/schemata
或其子目录中的文件 -
匹配 project/static/*.js
glob 模式的文件 -
.js
扩展名的所有文件,测试文件夹内的任意深度
如果正在为规则编写测试,则还需要将任何测试文件或目录添加到包含的路径中。
当同时包含过滤器和排除过滤器时,排除过滤器优先。
例子:
paths:
include: "project/schemata"
exclude: "*_internal.py"
上述规则返回结果来自 /schemata/scan.py
,但不会来自 project/schemata/scan_internal.py
。
六、编写规则
当到这一步的时候,相信你已经对 Semgrep 的规则语法比较熟悉了,下面写一个属于自己的规则。
需求时,当检测 Python 语言进行 csv 文件导出的时候,即使用 csv.writer 的时候,判断是否使用 defusedcsv.writer,如果未使用 defusedcsv.writer,可能会有 CSV 注入,这时我们把 csv 替换成 defusedcsv,对于 CSV 注入漏洞感兴趣的可以参考该文章 CSV 注入原理和预防
rules:
- id: use-defusedcsv
patterns:
- pattern: csv.writer(...)
- pattern-not: defusedcsv.writer(...)
message: >-
Detected the generation of a CSV file using the built-in `csv` module.
If user data is used to generate the data in this file, it is possible that
an attacker could inject a formula when the CSV is imported into a spreadsheet
application that runs an attacker script, which could steal data from the importing
user or, at worst, install malware on the user's computer. `defusedcsv` is a
drop-in replacement with the same API that will attempt to mitigate formula
injection attempts. You can use `defusedcsv` instead of `csv` to safely generate CSVs.
metadata:
cwe: "CWE-1236: Improper Neutralization of Formula Elements in a CSV File"
owasp:
- "A01:2017 - Injection"
- "A03:2021 - Injection"
references:
- https://github.com/raphaelm/defusedcsv
- https://owasp.org/www-community/attacks/CSV_Injection
- https://web.archive.org/web/20220516052229/https://www.contextis.com/us/blog/comma-separated-vulnerabilities
category: security
technology:
- python
confidence: LOW
fix-regex:
regex: csv
replacement: defusedcsv
languages: [python]
severity: INFO
附录 1、各语言的扩展和标签
语言 | 扩展 | 标签 |
---|---|---|
Bash | .sh | bash |
C | .c | c |
C++ | .cpp, .h | cpp |
C# | .cs | csharp, cs, C# |
Generic | generic | |
Go | .go | go, golang |
Hack | .h, .hack | hack |
HTML | .htm, .html | html |
Java | .java | java |
JavaScript | .js, .jsx | js, jsx, javascript |
JSON | .json | json, JSON, Json |
JSX | .js, .jsx | js, jsx, javascript |
Kotlin | .kt, .kts, .ktm | kotlin |
Lua | .lua | lua |
OCaml | .ml, .mli | ocaml, ml |
PHP | .php | php |
Python | .py, .pyi | python, python2, python3, py |
R | .r, .rda, rds | r |
Ruby | .rb | ruby, rb |
Rust | .rs | rust, Rust, rs |
Scala | .scala, .sc | scala |
Solidity | .sol | solidity |
Terraform | .tf | hcl |
TypeScript | .ts, .tsx | ts, tsx, typescript |
TSX | .ts, .tsx | ts, tsx, typescript |
YAML | .yaml | yaml |
原文始发于微信公众号(洋洋自语):Semgrep 之规则语法(三)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/272917.html