最近公司在搞静态代码扫描,学一下相关工具和规则写法
semgrep对于静态扫描能力较好,且无需编译,也有许多开源规则,就算是pro规则也只要登陆之后即可使用,但对于数据流跟踪的实现较差,只能做到同文件内的跟踪;
数据流跟踪的效果最好的还是codeql,能够自动识别也能够手动拼接数据流;当然,codeql也有一定自身的局限性,如大项目的编译问题、断流问题等。
semgrep
安装和使用
安装其实很简单,linux上直接pip install -m semgrep即可。
使用时如果需要使用pro规则,则需要使用semgrep login;扫描时,利用``
semgrep目前探索到的能力边界
- semgrep的数据流跟踪能力并不好。
- semgrep无法对
c/c++的宏定义进行扫描,导致如果宏定义中使用了危险函数则无法对其进行扫描(该内容并不会在verbose选项中展示) - semgrep对部分情况存在文件跳过问题,当semgrep无法构建语法树时则会直接跳过,使用
--verbose选项会直接看到跳过的文件名和文件内容 - semgrep对于C/C++中的宏定义内代码的扫描效果近乎没有。
规则编写
semgrep的规则编写很简单,主要是这里了解一些语法https://semgrep.dev/learn/。
然后再是一个官方的在线的测试平台https://semgrep.dev/playground/new。
简单解释一个官方规则就可以很快上手了。首先是1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126rules:
- id: gin-command-injection-taint
message: "xxxxxxx"
severity: ERROR
metadata:
likelihood: MEDIUM
impact: HIGH
confidence: HIGH
interfile: true
category: security
subcategory:
- vuln
cwe:
- "CWE-78: Improper Neutralization of Special Elements used in an OS
Command ('OS Command Injection')"
cwe2020-top25: true
cwe2021-top25: true
cwe2022-top25: true
functional-categories:
- os::sink::os-command-or-thread::io
- os::sink::os-command-or-thread::os
- os::sink::os-command-or-thread::syscall
- web::source::cookie::gin
- web::source::form-data::gin
- web::source::header::gin
- web::source::http-body::gin
- web::source::http-params::gin
- web::source::url-path-params::gin
owasp:
- A01:2017 - Injection
- A03:2021 - Injection
references:
- https://owasp.org/Top10/A03_2021-Injection
- https://pkg.go.dev/os/exec
- https://pkg.go.dev/syscall#Exec
- https://semgrep.dev/docs/cheat-sheets/go-command-injection/
technology:
- gin
- go
license: Copyright 2023 Semgrep, Inc.
vulnerability_class:
- Command Injection
languages:
- go
mode: taint
pattern-sources:
- patterns:
- pattern-inside: |
import "github.com/gin-gonic/gin"
...
- pattern-either:
- patterns:
- pattern-either:
- pattern: |
($CONTEXT : gin.Context).$FIELD
- pattern: |
($CONTEXT : *gin.Context).$FIELD
- metavariable-regex:
metavariable: $FIELD
regex: ^(Cookie|DefaultPostForm|DefaultQuery|FormFile|GetHeader|GetPostForm|GetPostFormArray|GetPostFormMap|GetQuery|GetQueryArray|GetQueryMap|GetRawData|MultipartForm|Param|Params|PostForm|PostFormArray|PostFormMap|Query|QueryArray|QueryMap)$
- pattern-either:
- pattern: |
($CONTEXT : gin.Context).Request.URL.Query().Get(...)
- pattern: |
($CONTEXT : *gin.Context).Request.URL.Query().Get(...)
pattern-sinks:
- patterns:
- pattern-either:
- patterns:
- pattern-inside: |
$CMD_STRUCT := exec.Command("$SHELL")
...
- pattern-inside: |
$WRITER, ... := $CMD_STRUCT.StdinPipe()
...
- pattern-either:
- pattern: $WRITER.Write(...)
- patterns:
- pattern: io.WriteString($WRITER, $SHELLCMD, ...)
- focus-metavariable: $SHELLCMD
- patterns:
- pattern-inside: |
&exec.Cmd { Path: "$SHELL", ... }
- pattern-either:
- pattern: |
Args: { "$SHELL", "-c", $SHELLCMD, ...}
- pattern: |
Args: $TYPE{ "$SHELL", "-c", $SHELLCMD, ...}
- focus-metavariable: $SHELLCMD
- patterns:
- pattern-either:
- pattern: exec.Command("$SHELL", "-c", $SHELLCMD, ...)
- pattern: exec.CommandContext($CTX, "$SHELL", "-c", $SHELLCMD, ...)
- focus-metavariable: $SHELLCMD
- metavariable-regex:
metavariable: $SHELL
regex: (bash|csh|dash|fish|ksh|tcsh|sh|zsh)$
- patterns:
- pattern-either:
- pattern-inside: syscall.Exec($PATH, $ARGS, ...)
- pattern-inside: syscall.ForkExec($PATH, $ARGS, ...)
- pattern-inside: "&exec.Cmd {$PATH, $ARGS, ...}"
- patterns:
- pattern-inside: "&exec.Cmd { ... }"
- pattern-either:
- pattern-inside: |
Path: $INPUT
- pattern-inside: |
Args: $INPUT
- focus-metavariable: $INPUT
- patterns:
- pattern-either:
- pattern: exec.Command($PATH, ...)
- pattern: exec.CommandContext($CTX, $PATH, ...)
- focus-metavariable: $PATH
pattern-sanitizers:
- patterns:
- pattern-either:
- pattern-inside: |
import "gopkg.in/alessio/shellescape.v1"
...
- pattern-inside: |
import "github.com/alessio/shellescape"
...
- pattern: shellescape.Quote(...)id,这是规则的唯一标识。
其次是message,这里是在规则匹配到之后发出的告警,而且可以看到,message中可以使用变量进行表示。
metadata,这部分可进行自定义操作。language,需要扫描的语言,可同时支持多种语言,如c和c++mode,这部分我一般直接删去,因为semgrep的数据流跟踪真的并不好用,但这里配置了那就说一下吧。配置了之后就进入了数据流跟踪模式,就是现在的规则。需要配置sink点和source点.1
2
3source是指漏洞污染链条的输入点。比如获取http请求的参数部分,就是非常明显的Source。
sink是指漏洞污染链条的执行点,比如SQL注入漏洞,最终执行SQL语句的函数就是sink(这个函数可能叫query或者exeSql,或者其它)。
sanitizer又叫净化函数,是指在整个的漏洞链条当中,如果存在一个方法阻断了整个传递链,那么这个方法就叫sanitizer。规则语法
这里就是重头戏了,在pattern中,需要配置匹配的内容,这里涉及到一些semgrep的语法。
默认的mode是不带sink和sourc的,而如果配置了mode则需要带sink和source。
semgrep的语法有下面几个:...,省略号可以模糊匹配任何变量或内容,如a(...)可以匹配到a(a,b,c),也可以匹配到a("test",123),如果要匹配常量,则可以使用"...",例如a("...")可以匹配到a("test")。
元变量,元变量可以当作变量来看。可以自定义也可以直接用代码中来指代。自定义下面会有专门的语法说明,直接看指代。1
a=
patterns,多个匹配项同时匹配才满足要求。pattern,当前匹配项匹配即可触发pattern-either,满足其中一个匹配项目即可。pattern-inside,在满足的内容中metavariable-regex,设置元变量,这里涉及两个内容1
metavariable-patternsemgrep宏定义跳过测试
semgrep原理分析
只看了一点点内容,semgrep是用OCaml语言进行编写的,构建语法树用的是tree-sitter
codeql
- 本文作者: Sn1pEr
- 本文链接: https://sn1per-ssd.github.io/2024/09/09/代码扫描工具学习/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!
