├── img ├── image-20250815101023395.png └── image-20250815100838404-5223729.png ├── go.mod ├── examples ├── scan_example.sh └── custom_rule_example.json ├── go.sum ├── rules ├── LeakRule.json ├── PythonRule.json ├── PhpRule.json ├── DotNetRule.json └── JavaRule.json ├── rule └── rule.go ├── readme.md ├── main.go └── scanner └── scanner.go /img/image-20250815101023395.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guchangan1/CodeVulnScan/HEAD/img/image-20250815101023395.png -------------------------------------------------------------------------------- /img/image-20250815100838404-5223729.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guchangan1/CodeVulnScan/HEAD/img/image-20250815100838404-5223729.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/guchangan1/CodeVulnScan 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.7 6 | 7 | require github.com/fatih/color v1.18.0 8 | 9 | require ( 10 | github.com/mattn/go-colorable v0.1.14 // indirect 11 | github.com/mattn/go-isatty v0.0.20 // indirect 12 | golang.org/x/sys v0.35.0 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /examples/scan_example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 示例1:使用Java规则扫描src目录 4 | echo "示例1:使用Java规则扫描src目录" 5 | ./CodeVulnScan -T java -d ./src -v 6 | 7 | # 示例2:使用PHP规则扫描,排除vendor目录 8 | echo "\n示例2:使用PHP规则扫描,排除vendor目录" 9 | ./CodeVulnScan -T php -d ./src -nd ./src/vendor -v 10 | 11 | # 示例3:扫描配置文件中的敏感信息 12 | echo "\n示例3:扫描配置文件中的敏感信息" 13 | ./CodeVulnScan -T leak -d ./config -e yml,properties,xml,json -v -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 2 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 3 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 4 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 5 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 6 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 7 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 8 | golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= 9 | golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 10 | -------------------------------------------------------------------------------- /examples/custom_rule_example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "FileType": "java", 4 | "RegexRule": "Runtime\\.getRuntime\\(\\)\\.exec\\(", 5 | "Readme": "Java命令执行函数,可能存在命令注入漏洞", 6 | "VulName": "命令注入" 7 | }, 8 | { 9 | "FileType": "java", 10 | "RegexRule": "new\\s+ProcessBuilder\\(", 11 | "Readme": "Java进程构建函数,可能存在命令注入漏洞", 12 | "VulName": "命令注入" 13 | }, 14 | { 15 | "FileType": "java", 16 | "LikeName": "password", 17 | "Readme": "文件名中包含password,可能存在硬编码密码", 18 | "VulName": "硬编码密码" 19 | }, 20 | { 21 | "FileType": "php", 22 | "RegexRule": "eval\\(\\$_(?:GET|POST|REQUEST|COOKIE)", 23 | "Readme": "PHP eval函数直接执行用户输入,存在代码执行漏洞", 24 | "VulName": "代码执行" 25 | }, 26 | { 27 | "FileType": "cs", 28 | "RegexRule": "Process\\.Start\\(", 29 | "Readme": ".NET进程启动函数,可能存在命令注入漏洞", 30 | "VulName": "命令注入" 31 | } 32 | ] -------------------------------------------------------------------------------- /rules/LeakRule.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "FileType": "yml", 4 | "RegexRule": "(?:password|secret|key|token|credential)\\s*:\\s*[\\\"\\''][^\\\"\\']+[\\\"\\'']", 5 | "Readme": "配置文件中存在硬编码的敏感信息,可能导致安全风险。", 6 | "VulName": "敏感信息泄露" 7 | }, 8 | { 9 | "FileType": "yaml", 10 | "RegexRule": "(?:password|secret|key|token|credential)\\s*:\\s*[\\\"\\''][^\\\"\\']+[\\\"\\'']", 11 | "Readme": "配置文件中存在硬编码的敏感信息,可能导致安全风险。", 12 | "VulName": "敏感信息泄露" 13 | }, 14 | { 15 | "FileType": "properties", 16 | "RegexRule": "(?:password|secret|key|token|credential)\\s*=\\s*[^\\\"\\'\\s]+", 17 | "Readme": "配置文件中存在硬编码的敏感信息,可能导致安全风险。", 18 | "VulName": "敏感信息泄露" 19 | }, 20 | { 21 | "FileType": "xml", 22 | "RegexRule": "<(?:password|secret|key|token|credential)>([^<]+)", 23 | "Readme": "配置文件中存在硬编码的敏感信息,可能导致安全风险。", 24 | "VulName": "敏感信息泄露" 25 | }, 26 | { 27 | "FileType": "json", 28 | "RegexRule": "\"(?:password|secret|key|token|credential)\"\\s*:\\s*\"[^\"]+\"", 29 | "Readme": "配置文件中存在硬编码的敏感信息,可能导致安全风险。", 30 | "VulName": "敏感信息泄露" 31 | }, 32 | { 33 | "FileType": "config", 34 | "RegexRule": "(?:connectionString|password|secret|key|token|credential)\\s*=\\s*\"[^\"]+\"", 35 | "Readme": "配置文件中存在硬编码的敏感信息,可能导致安全风险。", 36 | "VulName": "敏感信息泄露" 37 | }, 38 | { 39 | "FileType": "java", 40 | "RegexRule": "(?:String|final)\\s+(?:password|secret|key|token|credential)\\s*=\\s*\"[^\"]+\"", 41 | "Readme": "代码中存在硬编码的敏感信息,可能导致安全风险。", 42 | "VulName": "敏感信息泄露" 43 | }, 44 | { 45 | "FileType": "cs", 46 | "RegexRule": "(?:string|const)\\s+(?:password|secret|key|token|credential)\\s*=\\s*\"[^\"]+\"", 47 | "Readme": "代码中存在硬编码的敏感信息,可能导致安全风险。", 48 | "VulName": "敏感信息泄露" 49 | }, 50 | { 51 | "FileType": "php", 52 | "RegexRule": "\\\\$(?:password|secret|key|token|credential)\\s*=\\s*[\\\"\\''][^\\\"\\']+[\\\"\\'']", 53 | "Readme": "代码中存在硬编码的敏感信息,可能导致安全风险。", 54 | "VulName": "敏感信息泄露" 55 | }, 56 | { 57 | "FileType": "py", 58 | "RegexRule": "(?:password|secret|key|token|credential)\\s*=\\s*['\"][^'\"]+['\"]", 59 | "Readme": "代码中存在硬编码的敏感信息,可能导致安全风险。", 60 | "VulName": "敏感信息泄露" 61 | } 62 | ] -------------------------------------------------------------------------------- /rules/PythonRule.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "FileType": "py", 4 | "RegexRule": "(?:os\\.system|subprocess\\.(?:call|Popen|run))\\(\\s*(?:(?!\")[^()]*)\\)", 5 | "Readme": "存在命令执行函数,其中包含变量,注意查看变量参数是否为用户可控。", 6 | "VulName": "命令注入漏洞" 7 | }, 8 | { 9 | "FileType": "py", 10 | "RegexRule": "eval\\(", 11 | "Readme": "在python代码中存在命令执行危险函数,需要关注其参数是否为可控。", 12 | "VulName": "命令注入漏洞" 13 | }, 14 | { 15 | "FileType": "py", 16 | "RegexRule": "(?:exec|eval)\\(\\s*(?:(?!\")[^()]*)\\)", 17 | "Readme": "存在代码执行函数,其中包含变量,注意查看变量参数是否为用户可控。", 18 | "VulName": "代码执行漏洞" 19 | }, 20 | { 21 | "FileType": "py", 22 | "RegexRule": "(?:pickle|marshal|yaml)\\.(?:loads|load)\\(\\s*(?:(?!\")[^()]*)\\)", 23 | "Readme": "存在不安全的反序列化函数,其中包含变量,注意查看变量参数是否为用户可控。", 24 | "VulName": "反序列化漏洞" 25 | }, 26 | { 27 | "FileType": "py", 28 | "RegexRule": "(?:request|requests)\\.(?:get|post|put|delete)\\(\\s*(?:(?!\")[^()]*)\\)", 29 | "Readme": "存在HTTP请求函数,其中包含变量,注意查看变量参数是否为用户可控,可能存在SSRF漏洞。", 30 | "VulName": "SSRF漏洞" 31 | }, 32 | { 33 | "FileType": "py", 34 | "RegexRule": "(?:open|file)\\(\\s*(?:(?!\")[^()]*)\\)", 35 | "Readme": "存在文件操作函数,其中包含变量,注意查看变量参数是否为用户可控,可能存在任意文件读取或写入漏洞。", 36 | "VulName": "文件操作漏洞" 37 | }, 38 | { 39 | "FileType": "py", 40 | "RegexRule": "(?:flask|django)\\.render\\(\\s*(?:(?!\")[^()]*)\\)", 41 | "Readme": "存在模板渲染函数,其中包含变量,注意查看变量参数是否为用户可控,可能存在模板注入漏洞。", 42 | "VulName": "模板注入漏洞" 43 | }, 44 | { 45 | "FileType": "py", 46 | "RegexRule": "(?:etree\\.parse|minidom\\.parse|parseString)\\(\\s*(?:(?!\")[^()]*)\\)", 47 | "Readme": "存在XML解析函数,其中包含变量,注意查看变量参数是否为用户可控,可能存在XXE漏洞。", 48 | "VulName": "XXE漏洞" 49 | }, 50 | { 51 | "FileType": "py", 52 | "RegexRule": "(?:cursor\\.execute|raw)\\(\\s*(?:(?!\")[^()]*)\\)", 53 | "Readme": "存在SQL执行函数,其中包含变量,注意查看变量参数是否为用户可控,可能存在SQL注入漏洞。", 54 | "VulName": "SQL注入漏洞" 55 | }, 56 | { 57 | "FileType": "py", 58 | "RegexRule": "mark_safe\\(\\s*(?:(?!\")[^()]*)\\)", 59 | "Readme": "存在Django的mark_safe函数,其中包含变量,注意查看变量参数是否为用户可控,可能存在XSS漏洞。", 60 | "VulName": "XSS漏洞" 61 | }, 62 | { 63 | "FileType": "py", 64 | "RegexRule": "xml\\.etree\\.ElementTree\\.(?:parse|iterparse|fromstring|XMLParser)\\(", 65 | "Readme": "存在python的xml解析危险函数,注意其参数是否为可控。", 66 | "VulName": "XXE漏洞" 67 | }, 68 | { 69 | "FileType": "py", 70 | "RegexRule": "xml\\.dom\\.expatbuilder\\.(parse|parseString)\\(", 71 | "Readme": "存在python的xml解析危险函数,注意其参数是否为可控。", 72 | "VulName": "XXE漏洞" 73 | }, 74 | { 75 | "FileType": "py", 76 | "RegexRule": "xml\\.sax\\.(parse|parseString|make_parser)\\(", 77 | "Readme": "存在python的xml解析危险函数,注意其参数是否为可控。", 78 | "VulName": "XXE漏洞" 79 | } 80 | ] -------------------------------------------------------------------------------- /rule/rule.go: -------------------------------------------------------------------------------- 1 | package rule 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | // Rule 定义了漏洞检测规则 12 | type Rule struct { 13 | FileType string `json:"FileType"` // 文件类型,如java、php、net等 14 | RegexRule string `json:"RegexRule"` // 正则表达式规则 15 | Readme string `json:"Readme"` // 规则说明 16 | VulName string `json:"VulName"` // 漏洞名称 17 | LikeName string `json:"likeName"` // 可选,用于模糊匹配文件名 18 | } 19 | 20 | // RuleManager 管理规则加载和访问 21 | type RuleManager struct { 22 | rules map[string][]Rule // 按语言分类的规则 23 | ruleFiles map[string]string // 语言对应的规则文件 24 | } 25 | 26 | // NewRuleManager 创建一个新的规则管理器 27 | func NewRuleManager() *RuleManager { 28 | return &RuleManager{ 29 | rules: make(map[string][]Rule), 30 | ruleFiles: map[string]string{ 31 | "java": "JavaRule.json", 32 | "php": "PhpRule.json", 33 | "net": "DotNetRule.json", 34 | "python": "PythonRule.json", 35 | "leak": "LeakRule.json", 36 | }, 37 | } 38 | } 39 | 40 | // LoadRules 加载指定语言的规则 41 | func (rm *RuleManager) LoadRules(language string) ([]Rule, error) { 42 | // 转换为小写以便统一处理 43 | language = strings.ToLower(language) 44 | 45 | // 检查是否已经加载过该语言的规则 46 | if rules, ok := rm.rules[language]; ok { 47 | return rules, nil 48 | } 49 | 50 | // 获取规则文件名 51 | ruleFile, ok := rm.ruleFiles[language] 52 | if !ok { 53 | return nil, fmt.Errorf("不支持的语言: %s", language) 54 | } 55 | 56 | // 构建规则文件路径 57 | rulePath := filepath.Join(getRulesDir(), ruleFile) 58 | 59 | // 检查规则文件是否存在 60 | if _, err := os.Stat(rulePath); os.IsNotExist(err) { 61 | return nil, fmt.Errorf("规则文件不存在: %s", rulePath) 62 | } 63 | 64 | // 读取规则文件 65 | data, err := os.ReadFile(rulePath) 66 | if err != nil { 67 | return nil, fmt.Errorf("读取规则文件失败: %v", err) 68 | } 69 | 70 | // 解析JSON 71 | var rules []Rule 72 | if err := json.Unmarshal(data, &rules); err != nil { 73 | return nil, fmt.Errorf("解析规则文件失败: %v", err) 74 | } 75 | 76 | // 缓存规则 77 | rm.rules[language] = rules 78 | 79 | return rules, nil 80 | } 81 | 82 | // getRulesDir 获取规则文件所在目录 83 | func getRulesDir() string { 84 | // 首先尝试从当前目录的rules子目录加载 85 | currentDir, err := os.Getwd() 86 | if err == nil { 87 | rulesDir := filepath.Join(currentDir, "rules") 88 | if _, err := os.Stat(rulesDir); err == nil { 89 | return rulesDir 90 | } 91 | } 92 | 93 | // 如果当前目录没有rules子目录,则尝试从可执行文件所在目录的rules子目录加载 94 | execPath, err := os.Executable() 95 | if err == nil { 96 | execDir := filepath.Dir(execPath) 97 | rulesDir := filepath.Join(execDir, "rules") 98 | if _, err := os.Stat(rulesDir); err == nil { 99 | return rulesDir 100 | } 101 | } 102 | 103 | // 如果都找不到,则返回当前目录 104 | fmt.Println("警告: 未找到规则目录,将使用当前目录") 105 | return currentDir 106 | } 107 | 108 | // GetRulesByLanguage 获取指定语言的所有规则 109 | func (rm *RuleManager) GetRulesByLanguage(language string) ([]Rule, error) { 110 | return rm.LoadRules(language) 111 | } 112 | 113 | // GetRulesByFileType 获取指定文件类型的所有规则 114 | func (rm *RuleManager) GetRulesByFileType(fileType string) ([]Rule, error) { 115 | // 加载所有支持的语言规则 116 | allRules := []Rule{} 117 | for lang := range rm.ruleFiles { 118 | rules, err := rm.LoadRules(lang) 119 | if err != nil { 120 | continue // 忽略加载失败的规则 121 | } 122 | allRules = append(allRules, rules...) 123 | } 124 | 125 | // 过滤出指定文件类型的规则 126 | result := []Rule{} 127 | for _, rule := range allRules { 128 | if strings.EqualFold(rule.FileType, fileType) { 129 | result = append(result, rule) 130 | } 131 | } 132 | 133 | return result, nil 134 | } 135 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # CodeVulnScan 3 | 4 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 5 | [![Language](https://img.shields.io/badge/language-Go-blue.svg)](https://golang.org/) 6 | [![Version](https://img.shields.io/badge/version-1.0.0-green.svg)]() 7 | 8 | ## 🔍 工具介绍 9 | 10 | CodeVulnScan 是一款基于正则表达式的代码安全审计工具,专为红队成员快速定位sink设计。它能够快速扫描目标代码库,定位潜在的漏洞 Sink 点,提升代码审计效率。 11 | 12 | > **注意**:工具发现的 Sink 点仅表示存在潜在风险,红队师傅们需要进一步分析 Source 点并验证漏洞是否可被利用。 13 | > 14 | > 开发于 2023.2,虽然技术实现上可能过时,但工具本身仍具有实用价值,开源出来给大家参考使用。 15 | 16 | 17 | 18 | ## ✨ 核心特性 19 | 20 | ### 🛡️ 多语言支持 21 | - **全面覆盖** - 支持 DotNet、Java、PHP、Python 等主流开发语言 22 | - **敏感信息检测** - 自动扫描配置文件中的密码、密钥、令牌等敏感信息 23 | 24 | ### ⚙️ 高度可定制 25 | - **自定义规则** - 轻松修改各语言的 rule.json 文件,定制专属规则库 26 | - **灵活配置** - 支持指定文件后缀、排除目录、设置最大扫描深度 27 | 28 | ### 🚀 高效性能 29 | - **并行扫描** - 可配置工作协程数量,充分利用多核性能 30 | - **精准定位** - 扫描结果包含文件路径、行号、匹配内容、规则名称及描述 31 | - **高亮显示** - 结果高亮展示,便于快速定位问题 32 | 33 | ### 📊 结果处理 34 | - **详细模式** - 可选输出更多扫描信息,便于深入分析 35 | - **CSV导出** - 支持将扫描结果导出为CSV文件,便于后续处理和团队协作 36 | 37 | 38 | 39 | ## 🚀 快速开始 40 | 41 | ### 安装 42 | 43 | ```bash 44 | # 下载最新版本 45 | git clone https://github.com/guchangan1/CodeVulnScan.git 46 | 47 | # 进入项目目录 48 | cd CodeVulnScan 49 | 50 | # 编译项目 51 | go build -o codevulnscan 52 | ``` 53 | 54 | ### 命令参数 55 | 56 | ```bash 57 | Usage: ./codevulnscan [options] 58 | ``` 59 | 60 | | 参数 | 说明 | 61 | |------|------| 62 | | `-T ` | **审计模式**:选择规则库类型
- 可选值:`java`、`net`、`php`、`python`、`leak`
- 默认文件类型:java→.java, net→.cs, php→.php
- 所有模式均自动包含敏感信息扫描 | 63 | | `-d ` | **扫描目录**:指定要扫描的目标目录 | 64 | | `-nd ` | **排除目录**:指定要排除的目录 | 65 | | `-e ` | **文件类型**:手动指定要扫描的文件后缀,如 `yml,json,xml` | 66 | | `-v` | **详细模式**:输出更多扫描信息 | 67 | | `-w ` | **并行数量**:工作协程数量,默认为0表示使用CPU核心数 | 68 | | `-m ` | **扫描深度**:最大扫描深度,默认为100 | 69 | | `-o ` | **结果导出**:将扫描结果导出到CSV文件 | 70 | 71 | ### 使用示例 72 | 73 | ```bash 74 | # .NET代码审计 75 | ./codevulnscan -T net -d /path/to/project 76 | 77 | # Java代码审计(排除特定目录) 78 | ./codevulnscan -T java -d /path/to/project -nd /path/to/project/vendor 79 | 80 | # PHP代码审计(详细模式) 81 | ./codevulnscan -T php -d /path/to/project -v 82 | 83 | # Python代码审计(自定义并行数) 84 | ./codevulnscan -T python -d /path/to/project -w 8 85 | 86 | # 专门扫描配置文件中的敏感信息 87 | ./codevulnscan -T leak -d /path/to/configs -e yml,json,xml,properties 88 | 89 | # 导出扫描结果到CSV 90 | ./codevulnscan -T java -d /path/to/project -o results.csv 91 | ``` 92 | 93 | > **提示**:所有扫描模式都会自动包含敏感信息扫描,无需额外指定。 94 | 95 | 96 | ## 📚 使用示例 97 | 98 | ### 🔧 自定义规则 99 | 100 | CodeVulnScan 支持自定义规则,只需创建或修改对应语言的规则文件即可。规则文件使用 JSON 格式,位于 `rules` 目录下。 101 | 102 | **规则文件结构:** 103 | 104 | ```json 105 | [ 106 | { 107 | "FileType": "java", 108 | "RegexRule": "Runtime\.getRuntime\(\)\.exec\(", 109 | "Readme": "Java命令执行函数,可能存在命令注入漏洞", 110 | "VulName": "命令注入" 111 | }, 112 | { 113 | "FileType": "php", 114 | "RegexRule": "eval\(\$_(?:GET|POST|REQUEST|COOKIE)", 115 | "Readme": "PHP eval函数直接执行用户输入,存在代码执行漏洞", 116 | "VulName": "代码执行" 117 | } 118 | ] 119 | ``` 120 | 121 | **规则字段说明:** 122 | 123 | | 字段 | 说明 | 124 | |------|------| 125 | | `FileType` | 文件类型,如 java、php、cs 等 | 126 | | `RegexRule` | 正则表达式规则,用于匹配代码中的漏洞点 | 127 | | `Readme` | 规则说明,描述潜在风险 | 128 | | `VulName` | 漏洞名称,用于分类和标识 | 129 | 130 | ### 📊 扫描结果示例 131 | 132 | 执行扫描后,CodeVulnScan 会以高亮方式显示扫描结果,包括文件路径、行号、匹配内容、规则名称和描述: 133 | 134 |
135 | 136 |

137 | 138 |
139 | 140 | ### 📁 项目示例 141 | 142 | 在 `examples` 目录下提供了完整示例: 143 | 144 | - `custom_rule_example.json` - 自定义规则示例文件 145 | - `scan_example.sh` - 常用扫描场景的脚本示例 146 | 147 | --- 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /rules/PhpRule.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "FileType": "php", 4 | "RegexRule": "(\\bheader\\s{0,5}\\(.{0,30}|window.location.href\\s{0,5}=\\s{0,5})\\$_(POST|GET|REQUEST|SERVER)", 5 | "Readme": "header函数或者js location有可控参数,存在任意跳转或http头污染漏洞", 6 | "VulName": "http头污染漏洞" 7 | }, 8 | { 9 | "FileType": "php", 10 | "RegexRule": "\\bmove_uploaded_file\\s{0,5}\\(", 11 | "Readme": "存在文件上传,注意上传类型是否可控", 12 | "VulName": "任意文件上传漏洞" 13 | }, 14 | { 15 | "FileType": "php", 16 | "RegexRule": "\\b(include|require)(_once){0,1}(\\s{1,5}|\\s{0,5}\\().{0,60}\\$(?!.*(this->))\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}", 17 | "Readme": "文件包含函数中存在变量,可能存在文件包含漏洞", 18 | "VulName": "文件包含" 19 | }, 20 | { 21 | "FileType": "php", 22 | "RegexRule": "\\bpreg_replace\\(\\s{0,5}.*/[is]{0,2}e[is]{0,2}[\"']\\s{0,5},(.*\\$.*,|.*,.*\\$)", 23 | "Readme": "preg_replace的/e模式,且有可控变量,可能存在代码执行漏洞", 24 | "VulName": "代码执行" 25 | }, 26 | { 27 | "FileType": "php", 28 | "RegexRule": "\\bcall_user_func(_array){0,1}\\(\\s{0,5}\\$\\w{1,15}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}", 29 | "Readme": "call_user_func函数参数包含变量,可能存在代码执行漏洞", 30 | "VulName": "代码执行" 31 | }, 32 | { 33 | "FileType": "php", 34 | "RegexRule": "\\b(system|passthru|pcntl_exec|shell_exec|escapeshellcmd|exec)\\s{0,10}\\(.{0,40}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}", 35 | "Readme": "命令执行函数中存在变量,可能存在任意命令执行漏洞", 36 | "VulName": "代码执行" 37 | }, 38 | { 39 | "FileType": "php", 40 | "RegexRule": ".*\\${0,1}\\$\\w{1,20}((\\[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}\\s{0,4}=\\s{0,4}.{0,20}\\$\\w{1,20}((\\[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}", 41 | "Readme": "可能存在代码执行漏洞,或者此处是后门", 42 | "VulName": "代码执行" 43 | }, 44 | { 45 | "FileType": "php", 46 | "RegexRule": "\\barray_map\\s{0,4}\\(\\s{0,4}.{0,20}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}\\s{0,4}.{0,20},", 47 | "Readme": "array_map参数包含变量,变量可控可能会导致代码执行漏洞", 48 | "VulName": "代码执行" 49 | }, 50 | { 51 | "FileType": "php", 52 | "RegexRule": "\\b(eval|assert)\\s{0,10}\\(.{0,60}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}", 53 | "Readme": "eval或者assertc函数中存在变量,可能存在代码执行漏洞", 54 | "VulName": "代码执行" 55 | }, 56 | { 57 | "FileType": "php", 58 | "RegexRule": "\\bphpinfo\\s{0,5}\\(\\s{0,5}\\)", 59 | "Readme": "phpinfo()函数,可能存在敏感信息泄露漏洞", 60 | "VulName": "phpinfo信息泄露" 61 | }, 62 | { 63 | "FileType": "php", 64 | "RegexRule": "createTemplate\\(\\s*\\$\\w+\\([^()]*\\)", 65 | "Readme": "createTemplate()函数,中存在变量,注意是否为用户可控,可能存在SSTI模版注入漏洞", 66 | "VulName": "ssti模版注入" 67 | }, 68 | { 69 | "FileType": "php", 70 | "RegexRule": "\\b(file_get_contents|fopen|readfile|fgets|fread|parse_ini_file|highlight_file|fgetss|show_source)\\s{0,5}\\(.{0,40}\\$\\w{1,15}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}", 71 | "Readme": "读取文件函数中存在变量,可能存在任意文件读取漏洞", 72 | "VulName": "任意文件读取" 73 | }, 74 | { 75 | "FileType": "php", 76 | "RegexRule": "\\b(mb_){0,1}parse_str\\s{0,10}\\(.{0,40}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}", 77 | "Readme": "parse_str函数中存在变量,可能存在变量覆盖漏洞", 78 | "VulName": "变量覆盖漏洞" 79 | }, 80 | { 81 | "FileType": "php", 82 | "RegexRule": "\\$\\$\\w{1,20}((\\[[\"']|\\[)\\$\\$[\\w\\[\\]\"']{0,30}){0,1}\\s{0,4}=\\s{0,4}.{0,20}\\$\\$\\w{1,20}((\\[\\[\"'\\]|\\[)\\$\\$\\[\\w\\[\\]\"'\\]{0,30}){0,1}", 83 | "Readme": "双$$符号可能存在变量覆盖漏洞", 84 | "VulName": "变量覆盖漏洞" 85 | }, 86 | { 87 | "FileType": "php", 88 | "RegexRule": "\\b(extract)\\s{0,5}\\(.{0,30}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}\\s{0,5},{0,1}\\s{0,5}(EXTR_OVERWRITE){0,1}\\s{0,5}\\)", 89 | "Readme": "extract函数中存在变量,可能存在变量覆盖漏洞", 90 | "VulName": "变量覆盖漏洞" 91 | }, 92 | { 93 | "FileType": "php", 94 | "RegexRule": "[\"'](HTTP_CLIENT_IP|HTTP_X_FORWARDED_FOR|HTTP_REFERER)[\"']", 95 | "Readme": "获取IP地址方式可伪造,HTTP_REFERER可伪造,常见引发SQL注入等漏洞", 96 | "VulName": "IP伪造漏洞" 97 | }, 98 | { 99 | "FileType": "php", 100 | "RegexRule": "\\b(unlink|copy|fwrite|file_put_contents|bzopen)\\s{0,10}\\(.{0,40}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}", 101 | "Readme": "文件操作函数中存在变量,可能存在任意文件读取/删除/修改/写入等漏洞", 102 | "VulName": "文件操作漏洞" 103 | }, 104 | { 105 | "FileType": "php", 106 | "RegexRule": "`\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}`", 107 | "Readme": "``反引号中包含变量,变量可控会导致命令执行漏洞", 108 | "VulName": "命令执行漏洞" 109 | }, 110 | { 111 | "FileType": "php", 112 | "RegexRule": "\\b(echo|print|print_r)\\s{0,5}\\({0,1}.{0,60}\\$_(POST|GET|REQUEST|SERVER)", 113 | "Readme": "echo等输出中存在可控变量,可能存在XSS漏洞", 114 | "VulName": "XSS漏洞" 115 | }, 116 | { 117 | "FileType": "php", 118 | "RegexRule": "select\\s{1,4}.{1,60}from.{1,50}\\bwhere\\s{1,3}.{1,50}=[\"\\s\\.]{0,10}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}", 119 | "Readme": "SQL语句select中条件变量无单引号保护,可能存在SQL注入漏洞", 120 | "VulName": "SQL注入漏洞" 121 | }, 122 | { 123 | "FileType": "php", 124 | "RegexRule": "delete\\s{1,4}from.{1,20}\\bwhere\\s{1,3}.{1,30}=[\"\\s\\.]{0,10}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}", 125 | "Readme": "SQL语句delete中条件变量无单引号保护,可能存在SQL注入漏洞", 126 | "VulName": "SQL注入漏洞" 127 | }, 128 | { 129 | "FileType": "php", 130 | "RegexRule": "insert\\s{1,5}into\\s{1,5}.{1,60}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}", 131 | "Readme": "SQL语句insert中插入变量无单引号保护,可能存在SQL注入漏洞", 132 | "VulName": "SQL注入漏洞" 133 | }, 134 | { 135 | "FileType": "php", 136 | "RegexRule": "update\\s{1,4}.{1,30}\\s{1,3}set\\s{1,5}.{1,60}\\$\\w{1,20}((\\[[\"']|\\[)\\${0,1}[\\w\\[\\]\"']{0,30}){0,1}", 137 | "Readme": "SQL语句update中条件变量无单引号保护,可能存在SQL注入漏洞", 138 | "VulName": "SQL注入漏洞" 139 | } 140 | ] -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/guchangan1/CodeVulnScan/rule" 7 | "github.com/guchangan1/CodeVulnScan/scanner" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | // 版本信息 13 | const version = "1.0.0" 14 | 15 | // 命令行参数 16 | type Options struct { 17 | Language string 18 | ScanDir string 19 | ExcludeDir string 20 | FileExtension string 21 | Verbose bool 22 | WorkerCount int 23 | MaxDepth int 24 | OutputCSV string // CSV输出文件路径 25 | } 26 | 27 | func main() { 28 | // 打印banner 29 | printBanner() 30 | 31 | // 解析命令行参数 32 | opts := parseOptions() 33 | 34 | // 验证命令行参数 35 | if err := validateOptions(opts); err != nil { 36 | fmt.Println(err) 37 | os.Exit(1) 38 | } 39 | 40 | // 准备扫描参数 41 | var fileExtensions []string 42 | 43 | // 设置文件扩展名 44 | if opts.FileExtension != "" { 45 | fileExtensions = strings.Split(opts.FileExtension, ",") 46 | // 确保每个扩展名都以.开头 47 | for i, ext := range fileExtensions { 48 | if !strings.HasPrefix(ext, ".") { 49 | fileExtensions[i] = "." + ext 50 | } 51 | } 52 | } else { 53 | // 根据语言确定默认扩展名 54 | fileExtensions = getDefaultExtensions(opts.Language) 55 | } 56 | 57 | // 加载规则 58 | rules, err := loadRules(opts.Language) 59 | if err != nil { 60 | fmt.Printf("加载规则失败: %v\n", err) 61 | os.Exit(1) 62 | } 63 | 64 | // 设置最大深度,如果未指定则使用默认值100 65 | maxDepth := 100 66 | if opts.MaxDepth > 0 { 67 | maxDepth = opts.MaxDepth 68 | } 69 | 70 | // 开始扫描 71 | results, err := scanFiles(opts.ScanDir, opts.ExcludeDir, fileExtensions, rules, opts.Verbose, maxDepth, opts.WorkerCount) 72 | if err != nil { 73 | fmt.Printf("扫描失败: %v\n", err) 74 | os.Exit(1) 75 | } 76 | 77 | // 输出结果 78 | printResults(results) 79 | 80 | // 如果指定了CSV输出文件,则导出结果 81 | if opts.OutputCSV != "" { 82 | err = scanner.ExportToCSV(results, opts.OutputCSV) 83 | if err != nil { 84 | fmt.Printf("导出CSV失败: %v\n", err) 85 | os.Exit(1) 86 | } 87 | } 88 | } 89 | 90 | // 打印banner 91 | func printBanner() { 92 | banner := ` 93 | _____ _ _ _ _ _____ 94 | / ____| | | | \ | | | | / ____| 95 | | | ___ __| | ___| \| |_ _| |_ __| (___ ___ __ _ _ __ 96 | | | / _ \ / _' |/ _ \ . ' | | | | | '_ \\___ \ / __/ _' | '_ \ 97 | | |___| (_) | (_| | __/ |\ | |_| | | | | |___) | (_| (_| | | | | 98 | \_____\___/ \__,_|\___|_| \_|\__,_|_|_| |_|_____/ \___\__,_|_| |_| 99 | 100 | Version: ` + version + ` 101 | ` 102 | fmt.Println(banner) 103 | fmt.Println("一款使用正则语法进行匹配目标代码漏洞Sink点的代码审计扫描器,用于红队快速定位漏洞点。") 104 | fmt.Println("注:发现sink点不代表目标存在漏洞,需要红队师傅自行跟踪Source点。\n") 105 | } 106 | 107 | // 解析命令行参数 108 | func parseOptions() *Options { 109 | opts := &Options{} 110 | 111 | flag.StringVar(&opts.Language, "T", "", "审计模式,根据模式选择对应的规则库。\n可选值:java、net、php、python、leak\n注:java默认指定.java后缀,net默认指定.cs,php默认指定.php,当使用-e时可以进行主动指定后缀\n无论选择哪种语言,都会自动加载敏感信息扫描规则,扫描配置文件中的敏感信息\n也可以单独选择leak模式,只扫描敏感信息") 112 | flag.StringVar(&opts.ScanDir, "d", "", "要扫描的目录") 113 | flag.StringVar(&opts.ExcludeDir, "nd", "", "排除目录") 114 | flag.StringVar(&opts.FileExtension, "e", "", "主动指定扫描的文件后缀,多个后缀用逗号分隔") 115 | flag.BoolVar(&opts.Verbose, "v", false, "输出详细信息") 116 | flag.IntVar(&opts.WorkerCount, "w", 0, "工作协程数量,默认为0表示使用CPU核心数") 117 | flag.IntVar(&opts.MaxDepth, "m", 100, "最大扫描深度,默认为100") 118 | flag.StringVar(&opts.OutputCSV, "o", "", "将扫描结果导出到CSV文件") 119 | 120 | // 自定义帮助信息 121 | flag.Usage = func() { 122 | fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) 123 | flag.PrintDefaults() 124 | fmt.Fprintf(os.Stderr, "\nExample:\n") 125 | fmt.Fprintf(os.Stderr, "//使用net语言的规则进行扫描\n") 126 | fmt.Fprintf(os.Stderr, "%s -T net -d /home/xxxx\n", os.Args[0]) 127 | fmt.Fprintf(os.Stderr, "//使用java语言的规则进行扫描\n") 128 | fmt.Fprintf(os.Stderr, "%s -T java -d /home/xxxx\n", os.Args[0]) 129 | fmt.Fprintf(os.Stderr, "//使用php语言的规则进行扫描,排除resource目录\n") 130 | fmt.Fprintf(os.Stderr, "%s -T php -d /home/xxxx -nd /home/xxxx/resource\n", os.Args[0]) 131 | fmt.Fprintf(os.Stderr, "//使用python语言的规则进行扫描,排除resource目录\n") 132 | fmt.Fprintf(os.Stderr, "%s -T python -d /home/xxxx -nd /home/xxxx/resource\n", os.Args[0]) 133 | fmt.Fprintf(os.Stderr, "//专门扫描配置文件敏感信息(硬编码等)\n") 134 | fmt.Fprintf(os.Stderr, "%s -T leak -d /home/xxxx -e yml,java\n", os.Args[0]) 135 | fmt.Fprintf(os.Stderr, "//将扫描结果导出到CSV文件\n") 136 | fmt.Fprintf(os.Stderr, "%s -T java -d /home/xxxx -o results.csv\n", os.Args[0]) 137 | fmt.Fprintf(os.Stderr, "//注意:所有扫描都会自动包含敏感信息扫描,无需额外指定\n") 138 | } 139 | 140 | flag.Parse() 141 | return opts 142 | } 143 | 144 | // 验证命令行参数 145 | func validateOptions(opts *Options) error { 146 | if opts.Language == "" { 147 | return fmt.Errorf("请指定审计语言 (-T)") 148 | } 149 | 150 | if opts.ScanDir == "" { 151 | return fmt.Errorf("请指定要扫描的目录 (-d)") 152 | } 153 | 154 | // 检查目录是否存在 155 | if _, err := os.Stat(opts.ScanDir); os.IsNotExist(err) { 156 | return fmt.Errorf("扫描目录不存在: %s", opts.ScanDir) 157 | } 158 | 159 | // 检查排除目录是否存在 160 | if opts.ExcludeDir != "" { 161 | if _, err := os.Stat(opts.ExcludeDir); os.IsNotExist(err) { 162 | return fmt.Errorf("排除目录不存在: %s", opts.ExcludeDir) 163 | } 164 | } 165 | 166 | return nil 167 | } 168 | 169 | // 根据语言确定默认的文件扩展名 170 | func getDefaultExtensions(language string) []string { 171 | // 根据语言确定默认扩展名 172 | var extensions []string 173 | 174 | switch strings.ToLower(language) { 175 | case "java": 176 | extensions = []string{".java"} 177 | case "net": 178 | extensions = []string{".cs"} 179 | case "php": 180 | extensions = []string{".php"} 181 | case "python": 182 | extensions = []string{".py"} 183 | case "leak": 184 | // 对于敏感信息扫描,默认扫描多种配置文件 185 | return []string{".yml", ".yaml", ".xml", ".properties", ".config", ".json"} 186 | default: 187 | // 默认情况下,使用与语言名称相同的扩展名 188 | extensions = []string{"." + language} 189 | } 190 | 191 | // 如果不是专门的敏感信息扫描,则添加配置文件扩展名 192 | if strings.ToLower(language) != "leak" { 193 | // 添加常见配置文件扩展名用于敏感信息扫描 194 | configExtensions := []string{".yml", ".yaml", ".xml", ".properties", ".config", ".json"} 195 | extensions = append(extensions, configExtensions...) 196 | } 197 | 198 | return extensions 199 | } 200 | 201 | // 加载规则 202 | func loadRules(language string) ([]rule.Rule, error) { 203 | rm := rule.NewRuleManager() 204 | 205 | // 加载指定语言的规则 206 | langRules, err := rm.LoadRules(language) 207 | if err != nil { 208 | return nil, err 209 | } 210 | 211 | // 如果指定的语言不是leak(敏感信息扫描),则额外加载敏感信息扫描规则 212 | if strings.ToLower(language) != "leak" { 213 | leakRules, err := rm.LoadRules("leak") 214 | if err != nil { 215 | // 如果加载敏感信息规则失败,仅打印警告,不影响主要扫描 216 | fmt.Printf("警告: 加载敏感信息扫描规则失败: %v\n", err) 217 | } else { 218 | // 合并规则 219 | langRules = append(langRules, leakRules...) 220 | fmt.Println("已自动加载敏感信息扫描规则") 221 | } 222 | } 223 | 224 | return langRules, nil 225 | } 226 | 227 | // 扫描文件 228 | func scanFiles(scanDir, excludeDir string, extensions []string, rules []rule.Rule, verbose bool, maxDepth int, workerCount int) ([]scanner.ScanResult, error) { 229 | // 创建扫描器 230 | // 将 rule.Rule 类型转换为 scanner.Rule 类型 231 | scannerRules := make([]scanner.Rule, len(rules)) 232 | for i, r := range rules { 233 | scannerRules[i] = scanner.Rule(r) 234 | } 235 | s := scanner.NewScanner(scanDir, excludeDir, extensions, scannerRules, verbose, maxDepth, workerCount) 236 | return s.Scan() 237 | } 238 | 239 | // 输出结果 240 | func printResults(results []scanner.ScanResult) { 241 | scanner.PrintResults(results) 242 | } 243 | -------------------------------------------------------------------------------- /rules/DotNetRule.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | { 4 | "VulName": "命令执行漏洞 (双平台)", 5 | "RegexRule": "(?i)(Process\\.Start|ProcessStartInfo\\s*\\()", 6 | "Readme": "Process类执行系统命令,参数外部可控导致RCE风险", 7 | "FileType": "net" 8 | }, 9 | { 10 | "VulName": "命令执行漏洞 (双平台)", 11 | "RegexRule": "(?i)(PowerShell\\.(Create|Invoke)|RunspaceFactory\\.CreateRunspace)", 12 | "Readme": "PowerShell管道执行未过滤用户输入", 13 | "FileType": "net" 14 | }, 15 | 16 | 17 | { 18 | "VulName": "SQL注入漏洞 (双平台)", 19 | "RegexRule": "(?i)(ExecuteSql(Raw|Interpolated)\\s*\\(\\s*@?\\\"[^\\\"]*\\+)", 20 | "Readme": "EF Core动态SQL拼接风险", 21 | "FileType": "net" 22 | }, 23 | { 24 | "VulName": "SQL注入漏洞 (双平台)", 25 | "RegexRule": "(?i)(SqlCommand\\s*\\([^\\)]*\\+)|(cmd\\.CommandText\\s*=[^;]*\\+)", 26 | "Readme": "ADO.NET SQL命令拼接风险", 27 | "FileType": "net" 28 | }, 29 | { 30 | "FileType": "net", 31 | "RegexRule": "(?:select|delete)\\s{1,5}.{1,60}from\\s.*where\\s.*[\\+]", 32 | "Readme": "在进行select查询或delete删除时,存在where拼接行为,疑似存在sql注入;需研判该调用点是否为用户可控。", 33 | "VulName": "SQL注入漏洞" 34 | }, 35 | 36 | 37 | { 38 | "VulName": "XSS漏洞 (双平台)", 39 | "RegexRule": "(?i)(Html\\.Raw\\s*\\(|Response\\.Write\\s*\\([^\\)]*\\+)", 40 | "Readme": "未编码输出用户可控HTML内容", 41 | "FileType": "net" 42 | }, 43 | { 44 | "VulName": "XSS漏洞 (双平台)", 45 | "RegexRule": "(?i)<%=\\s*[\\w\\.]+\\s*%>", 46 | "Readme": "ASPX内联表达式未使用Html.Encode", 47 | "FileType": "net" 48 | }, 49 | 50 | 51 | { 52 | "VulName": "文件上传漏洞 (双平台)", 53 | "RegexRule": "(?i)\\.CopyTo\\s*\\(\\s*Path\\.Combine\\s*\\([^,]+,\\s*[\\w\\.]+\\.FileName\\)", 54 | "Readme": "使用上传文件名拼接路径导致路径遍历", 55 | "FileType": "net" 56 | }, 57 | { 58 | "VulName": "文件上传漏洞 (双平台)", 59 | "RegexRule": "(?i)File\\.WriteAll(Bytes|Text)\\s*\\([^,]+,\\s*[\\w\\.]+\\.OpenReadStream", 60 | "Readme": "未校验文件内容签名和扩展名", 61 | "FileType": "net" 62 | }, 63 | 64 | 65 | { 66 | "VulName": "SSRF漏洞 (双平台)", 67 | "RegexRule": "(?i)(HttpClient|WebRequest)\\.[\\s\\n]*Get\\w*Async\\s*\\(\\s*[\\w\\.]+\\.[\\w]+\\[", 68 | "Readme": "HTTP客户端使用未过滤的用户输入URL", 69 | "FileType": "net" 70 | }, 71 | { 72 | "VulName": "SSRF漏洞 (双平台)", 73 | "RegexRule": "(?i)new\\s+Uri\\s*\\(\\s*(Request\\.Query|FormCollection)", 74 | "Readme": "未校验URL协议和白名单", 75 | "FileType": "net" 76 | }, 77 | 78 | 79 | { 80 | "VulName": "XXE漏洞 (双平台)", 81 | "RegexRule": "(?i)(new\\s+XmlDocument\\s*\\([^)]*\\)|XmlReader\\.[\\s\\n]*Create)\\.[^\\)]*Load\\s*\\([^,]*\\.(Input|Form)", 82 | "Readme": "XML解析器处理未过滤的用户输入", 83 | "FileType": "net" 84 | }, 85 | { 86 | "VulName": "XXE漏洞 (双平台)", 87 | "RegexRule": "(?i)(XmlReaderSettings\\s*{[^}]*ProhibitDtd\\s*=\\s*false)|(DtdProcessing\\s*=\\s*DtdProcessing\\.Parse)", 88 | "Readme": "XML解析配置显式启用外部实体", 89 | "FileType": "net" 90 | }, 91 | 92 | 93 | { 94 | "VulName": "反序列化漏洞 (双平台)", 95 | "RegexRule": "(?i)JsonConvert\\.DeserializeObject\\s*<[^>]+>\\s*\\([^,]+,.*(TypeNameHandling\\s*=\\s*(TypeNameHandling\\.)?(Objects|Auto|All))", 96 | "Readme": "Newtonsoft.Json启用危险TypeNameHandling配置", 97 | "FileType": "net" 98 | }, 99 | { 100 | "VulName": "反序列化漏洞 (.NET Framework高危)", 101 | "RegexRule": "(?i)(BinaryFormatter|LosFormatter|SoapFormatter)\\.Deserialize\\s*\\(", 102 | "Readme": ".NET Framework原生危险反序列化器,可导致RCE", 103 | "FileType": "net" 104 | }, 105 | 106 | 107 | { 108 | "VulName": "ViewState未加密 (.NET Framework)", 109 | "RegexRule": "(?i)]+viewStateEncryptionMode=\"Never\"", 110 | "Readme": "ViewState启用MAC但禁用加密,可能被篡改", 111 | "FileType": "web.config" 112 | }, 113 | { 114 | "VulName": "ViewState MAC禁用 (.NET Framework)", 115 | "RegexRule": "(?i)]*debug\\s*=\\s*\"true\"", 150 | "Readme": "生产环境启用调试模式,泄露堆栈信息", 151 | "FileType": "web.config" 152 | }, 153 | { 154 | "VulName": "详细错误暴露 (.NET Core)", 155 | "RegexRule": "(?i)app\\.UseDeveloperExceptionPage\\s*\\([^)]*\\)[^;]*!env\\.IsDevelopment", 156 | "Readme": "生产环境启用开发异常页面", 157 | "FileType": "net" 158 | }, 159 | 160 | 161 | { 162 | "VulName": "未授权访问 (双平台)", 163 | "RegexRule": "(?i)\\[Authorize\\][^\\]]*\\n\\s*public\\s+[^{]*\\{", 164 | "whiteRegex": "\\[AllowAnonymous\\]", 165 | "Readme": "控制器方法未添加授权特性", 166 | "FileType": "net" 167 | }, 168 | { 169 | "VulName": "水平越权 (.NET Framework)", 170 | "RegexRule": "(?i)User\\.Identity\\.Name\\s*!=\\s*[\\w\\.]+\\.UserId", 171 | "Readme": "直接比较用户名而未验证资源所有权", 172 | "FileType": "net" 173 | }, 174 | 175 | { 176 | "VulName": "不安全的反射 (.NET Framework高危)", 177 | "RegexRule": "(?i)Assembly\\.Load\\s*\\([^)]*\\.(Query|Form)\\b", 178 | "Readme": "动态加载用户可控程序集", 179 | "FileType": "net" 180 | }, 181 | { 182 | "VulName": "Cookie安全配置缺陷 (双平台)", 183 | "RegexRule": "(?i)new\\s+HttpCookie\\s*\\([^)]*\\)\\s*{\\s*HttpOnly\\s*=\\s*false", 184 | "Readme": "Cookie未设置HttpOnly标志", 185 | "FileType": "net" 186 | }, 187 | { 188 | "VulName": "CSRF防护缺失 (双平台)", 189 | "RegexRule": "(?i)\\[(HttpPost|HttpPut)\\]\\s*public\\s+[^{]*\\{", 190 | "whiteRegex": "\\[ValidateAntiForgeryToken\\]", 191 | "Readme": "表单操作未验证防伪令牌", 192 | "FileType": "net" 193 | }, 194 | { 195 | "FileType": "net", 196 | "RegexRule": "new\\s+XmlSerializer\\(", 197 | "Readme": "存在实例化XmlSerializer,可能存在xml反序列化漏洞;注意其Type类型是否可控。", 198 | "VulName": "反序列化漏洞" 199 | }, 200 | { 201 | "FileType": "net", 202 | "RegexRule": "(?:JavaScriptSerializer\\s+\\w+\\s+=\\s+)?new\\s+JavaScriptSerializer\\(", 203 | "Readme": "存在实例化JavaScriptSerializer,可能存在反序列漏洞,需判断DeserializeObject()中参数是否为可控。", 204 | "VulName": "反序列化漏洞" 205 | }, 206 | { 207 | "FileType": "net", 208 | "RegexRule": "(?:JsonSerializerSettings\\s+\\w+\\s+=\\s+)?new\\s+JsonSerializerSettings\\(", 209 | "Readme": "存在实例化JsonSerializerSettings,可能存在反序列化漏洞,需判断DeserializeObject()中参数是否为可控。", 210 | "VulName": "反序列化漏洞" 211 | }, 212 | { 213 | "FileType": "net", 214 | "RegexRule": "(?:SoapFormatter\\s+\\w+\\s+=\\s+)?new\\s+SoapFormatter\\(", 215 | "Readme": "存在实例化SoapFormatter,可能存在反序列化漏洞,需判断Deserialize()中参数是否为可控。", 216 | "VulName": "反序列化漏洞" 217 | }, 218 | { 219 | "FileType": "net", 220 | "RegexRule": "(?:IFormatter\\s+\\w+\\s+=\\s+)?new\\s+BinaryFormatter\\(", 221 | "Readme": "存在实例化BinaryFormatter,可能存在反序列化漏洞,需判断Deserialize()中参数是否为可控。", 222 | "VulName": "反序列化漏洞" 223 | }, 224 | { 225 | "FileType": "net", 226 | "RegexRule": "TypeFilterLevel\\s*=\\s*TypeFilterLevel\\.Full", 227 | "Readme": "存在将TypeFilterLevel设置为Full,可能存在反序列化漏洞。", 228 | "VulName": "反序列化漏洞" 229 | }, 230 | { 231 | "FileType": "net", 232 | "RegexRule": "(?:NetDataContractSerializer\\s+\\w+\\s+=\\s+)?new\\s+NetDataContractSerializer\\(", 233 | "Readme": "存在实例化NetDataContractSerializer,可能存在反序列化漏洞,需判断Deserialize()中参数是否为可控。", 234 | "VulName": "反序列化漏洞" 235 | }, 236 | { 237 | "FileType": "net", 238 | "RegexRule": "(?:LosFormatter\\s+\\w+\\s+=\\s+)?new\\s+LosFormatter\\(", 239 | "Readme": "存在实例化LosFormatter,可能存在反序列化漏洞,需判断Deserialize()中参数是否为可控。", 240 | "VulName": "反序列化漏洞" 241 | } 242 | ] -------------------------------------------------------------------------------- /rules/JavaRule.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | { 4 | "FileType": "java", 5 | "RegexRule": "File\\.write\\([^\"]*\\)", 6 | "Readme": "存在调用java文件写入方法,且其中存在变量,需判断变量是否为用户可控。", 7 | "VulName": "Java任意文件写入" 8 | }, 9 | { 10 | "FileType": "java", 11 | "RegexRule": "new\\s+XMLDecoder\\(", 12 | "Readme": "在Servlet中存在通过请求接收参数并置入请求体中,疑似存在逻辑漏洞。", 13 | "VulName": "Java逻辑漏洞" 14 | },{ 15 | "FileType": "java", 16 | "RegexRule": "@(?:Request|Get|Post|Delete|Put)Mapping\\(.*?\\)", 17 | "Readme": "spring 路由提取", 18 | "VulName": "Spring框架 Servlet" 19 | }, 20 | { 21 | "FileType": "java", 22 | "RegexRule": "(getOriginalFilename|MultipartFile)", 23 | "Readme": "文件操作函数,可能存在文件上传操作", 24 | "VulName": "文件上传" 25 | }, 26 | 27 | { 28 | "FileType": "java", 29 | "RegexRule": "(?:Class<[^>]*>\\s+\\w+\\s+=\\s+)?\\w+\\.parseClass\\([^\"]*\\)", 30 | "Readme": "Groovy代码执行危险函数.parseClass其中存在变量,注意查看变量是否为用户可控。", 31 | "VulName": "代码注入漏洞" 32 | }, 33 | { 34 | "FileType": "java", 35 | "RegexRule": "\\s+Class\\.forName\\([^\"]*\\)", 36 | "Readme": "存在Java反射调用点,该处可导致代码执行,其中存在变量,注意查看变量参数是否为用户可控。", 37 | "VulName": "代码注入漏洞" 38 | }, 39 | { 40 | "FileType": "java", 41 | "RegexRule": "(?:groovyShell)?\\w+\\.evaluate\\([^\"]*\\)", 42 | "Readme": "存在GroovyShell代码执行危险函数.evaluate,其中存在变量,注意查看变量参数是否为用户可控。", 43 | "VulName": "代码注入漏洞" 44 | }, 45 | { 46 | "FileType": "java", 47 | "RegexRule": "(?:compiledScript|scriptEngine)?\\w+\\.eval\\([^\"]*\\)", 48 | "Readme": "存在ScriptEngine或CompiledScript代码执行危险函数.eval,其中包含变量,注意查看变量参数是否为用户可控。", 49 | "VulName": "代码注入漏洞" 50 | }, 51 | { 52 | "FileType": "java", 53 | "RegexRule": "(?:Connection\\s+\\w+\\s+=\\s+)?DriverManager\\.getConnection\\(\\s*[^\"']*\\)", 54 | "Readme": "在JDBC连接池配置中存在变量,疑似存在JDBC反序列漏洞,可关注getConnection中参数是否可控。", 55 | "VulName": "JDBC反序列化漏洞" 56 | }, 57 | { 58 | "FileType": "java", 59 | "RegexRule": "(?:JSON|JSONObject|JSONArray)\\.(?:parse|parseObject|parseArray)\\(\\s*[^\"']*\\)", 60 | "Readme": "存在fastjson反序列化危险函数,其中包含变量,关注危险函数中参数变量是否可控。", 61 | "VulName": "fastjson反序列化漏洞" 62 | }, 63 | { 64 | "FileType": "java", 65 | "RegexRule": "(?:xstream)?\\w+\\.fromXML\\(\\s*[^\"']*\\)", 66 | "Readme": "存在XML反序列化危险函数XStream.fromXML(),其中存在变量,关注危险函数中变量参数是否可控。", 67 | "VulName": "XML反序列化漏洞" 68 | }, 69 | { 70 | "FileType": "java", 71 | "RegexRule": "new\\s+XMLDecoder\\(\\s*[^\"']*\\)", 72 | "Readme": "存在实例化XML反序列化危险函数XMLDecoder,且其中存在变量,关注其变量是否为用户可控及是否调用readObject危险函数。", 73 | "VulName": "XML反序列化漏洞" 74 | }, 75 | { 76 | "FileType": "java", 77 | "RegexRule": "(?:yaml)?\\.(load|loadAs)\\(\\s*[^\"']*\\)", 78 | "Readme": "疑似存在snakeyaml反序列化危险函数load或loadAS,且其中存在变量;需关注变量是否可控且snakeyaml版本应小于2.0。", 79 | "VulName": "XML反序列化漏洞" 80 | }, 81 | { 82 | "FileType": "java", 83 | "RegexRule": "\\.parseExpression\\(\\w+\\)\\.getValue\\(\\s*[^\"']*\\)", 84 | "Readme": "存在Java表达式注入危险函数-SpelExpressionParser.parseExpression().getValue,且其中存在变量,需关注变量是否为用户可控。", 85 | "VulName": "表达式注入漏洞" 86 | }, 87 | { 88 | "FileType": "java", 89 | "RegexRule": "(?:JexlExpression\\s+\\w+\\s+=\\s+)?(?:jexlEngine)?\\.createExpression\\(\\s*[^\"']*\\)", 90 | "Readme": "存在Java表达式注入危险函数-JexlExpression.createExpression(),且其中存在变量,需关注变量是否为用户可控。", 91 | "VulName": "表达式注入漏洞" 92 | }, 93 | { 94 | "FileType": "java", 95 | "RegexRule": "Ognl\\.parseExpression\\(\\s*[^\"']*\\)", 96 | "Readme": "存在Java表达式注入危险函数-Ognl.parseExpression(),且其中存在变量,需关注变量是否为用户可控。", 97 | "VulName": "表达式注入漏洞" 98 | }, 99 | { 100 | "FileType": "java", 101 | "RegexRule": "(?:objectMapper|mapper)?\\.enableDefaultTyping\\(", 102 | "Readme": "在java代码中,存在Jackjson开启了ObjectMapper.enableDefaultTyping,当在系统中Controller中将Target直接进行返回时,可导致反序列化漏洞。漏洞版本小于2.10.x", 103 | "VulName": "jackjson反序列化漏洞" 104 | }, 105 | { 106 | "FileType": "java", 107 | "RegexRule": "\\s*\\.validate\\(", 108 | "Readme": "存在validation中validate危险函数,需关注其中资源文件是否为外部可控。", 109 | "VulName": "XML外部实体注入漏洞" 110 | }, 111 | { 112 | "FileType": "java", 113 | "RegexRule": "(?:MyObject)?\\w+\\.unmarshal\\(", 114 | "Readme": "存在Unmarshaller危险函数,需关注其参数是否为外部可控。", 115 | "VulName": "XML外部实体注入漏洞" 116 | }, 117 | { 118 | "FileType": "java", 119 | "RegexRule": "(?i)(?:Document\\s+\\w+\\s+=\\s+)\\w+\\.\\s*parse\\(\\s*[^\"'()]*\\)", 120 | "Readme": "存在DocumentBuilder中parse危险函数,且其中存在变量,需关注危险函数中变量参数是否可控。", 121 | "VulName": "XML外部实体注入漏洞" 122 | }, 123 | { 124 | "FileType": "java", 125 | "RegexRule": "(?:XMLStreamReader\\s+\\w+\\s+=\\s+)?\\w+\\.\\s*createXMLStreamReader\\(", 126 | "Readme": "存在XMLStreamReader.createXMLStreamReader方法,需要查看该类中是否存在Unmarshaller.unmarshal()处理xml流且关注危险函数中参数是否可控。", 127 | "VulName": "XML外部实体注入漏洞" 128 | }, 129 | { 130 | "FileType": "java", 131 | "RegexRule": "new\\s+SAXReader\\(", 132 | "Readme": "存在实例化SAXReader对象,需要关注其read()方法中内容是否为外部可控。", 133 | "VulName": "XML外部实体注入漏洞" 134 | }, 135 | { 136 | "FileType": "java", 137 | "RegexRule": "(?:XMLReader\\s+\\w+\\s+=\\s+)?XMLReaderFactory\\.createXMLReader\\(", 138 | "Readme": "存在实例化XMLReader对象,需要关注其parse()方法中内容是否为外部可控。", 139 | "VulName": "XML外部实体注入漏洞" 140 | }, 141 | { 142 | "FileType": "java", 143 | "RegexRule": "Runtime\\.getRuntime\\(\\)(?:\\.exec\\()?", 144 | "Readme": "存在Java命令执行调用点,需要关注是否存在高危方法exec(),且其中参数是否可控。", 145 | "VulName": "命令执行漏洞" 146 | }, 147 | { 148 | "FileType": "java", 149 | "RegexRule": "Runtime\\.getRuntime\\(\\)\\.exec\\(\\s*[^\"']*\\)", 150 | "Readme": "存在Java命令执行调用点,且其中存在变量,需关注其中变量参数是否可控。", 151 | "VulName": "命令执行漏洞" 152 | }, 153 | { 154 | "FileType": "java", 155 | "RegexRule": "(?:ProcessBuilder\\s+\\w+\\s+=\\s+)?new\\s+ProcessBuilder\\(\\s*[^\"']*\\)", 156 | "Readme": "存在Java命令执行调用点,且在实例化ProcessBuilder()中存在变量,需关注变量参数是否为用户可控。", 157 | "VulName": "命令执行漏洞" 158 | }, 159 | { 160 | "FileType": "java", 161 | "RegexRule": "(?:VelocityContext\\s+\\w+\\s+=\\s+)?new\\s+VelocityContext\\(", 162 | "Readme": "存在实例化VelocityContext(),当参数可控时,其evaluate()方法存在模版注入漏洞;需要关注是否存在高危方法evaluate(),且其中参数是否可控。", 163 | "VulName": "模版注入漏洞" 164 | }, 165 | { 166 | "FileType": "java", 167 | "RegexRule": "(?:Template)?\\w+\\.getTemplate\\(", 168 | "Readme": "存在org.freemarker引入,需判断是否存在process()危险函数,且参数可控。", 169 | "VulName": "模版注入漏洞" 170 | }, 171 | { 172 | "FileType": "java", 173 | "RegexRule": "(?:JasperReport)?\\w+\\.compileReport\\(", 174 | "Readme": "存在Jasper引入,疑似存在ssti模版注入,需判断是否存在compileReport()中文件是否为用户可控。", 175 | "VulName": "模版注入漏洞" 176 | }, 177 | { 178 | "FileType": "java", 179 | "RegexRule": "(?:objectInputStream)?\\w+\\.readObject\\(", 180 | "Readme": "疑似存在ObjectInputStream.readObject()危险方法,需要关注对应实体类是否实现Serializable对应接口。", 181 | "VulName": "java原生反序列化漏洞" 182 | }, 183 | { 184 | "FileType": "java", 185 | "RegexRule": "\\s*\\.readUnshared\\(\\)", 186 | "Readme": "存在readUnshared()危险方法,注意其流是否可控,当可控时可能存在反序列化漏洞。", 187 | "VulName": "java原生反序列化漏洞" 188 | }, 189 | { 190 | "FileType": "java", 191 | "RegexRule": "(?:URLClassLoader\\s+\\w+\\s+=\\s+)?new\\s+URLClassLoader\\(", 192 | "Readme": "存在URLClassLoader远程方法调用,需注意实例化中参数是否为可控。", 193 | "VulName": "java-RPC漏洞" 194 | }, 195 | { 196 | "FileType": "java", 197 | "RegexRule": "\\.lookup\\(", 198 | "Readme": "疑似存在jndi中lookup()远程方法调用,注意其调用方法是否可控,当可控时可能存在rce漏洞。", 199 | "VulName": "java-RPC漏洞" 200 | }, 201 | { 202 | "likeName": "mapper", 203 | "FileType": "xml", 204 | "RegexRule": "\\$\\{", 205 | "Readme": "存在非预编译sql语句,疑似存在sql注入;需研判该调用点是否为用户可控。【改规则模糊匹配文件名中携带mapper,且文件类型为xml格式。】", 206 | "VulName": "Mybatis-SQL注入" 207 | }, 208 | { 209 | "FileType": "java", 210 | "RegexRule": "\\.getOriginalFilename\\(|MultipartFile\\s", 211 | "Readme": "使用MultipartFile上传文件,判断是否有文件类型过滤。", 212 | "VulName": "文件上传" 213 | }, 214 | { 215 | "FileType": "java", 216 | "RegexRule": "(?:FileWriter\\s+\\w+\\s+=\\s+)?new\\s+FileWriter\\(\\s*[^\\\"']*", 217 | "Readme": "存在实例化FileWriter,且其中存在变量,需判断变量是否为用户可控,当可控时,则存在任意文件上传。", 218 | "VulName": "文件上传" 219 | }, 220 | { 221 | "FileType": "java", 222 | "RegexRule": "(?:select|delete)\\s{1,5}.{1,60}from\\s.*where\\s.*[\\+]", 223 | "Readme": "在进行select查询或delete删除时,存在where拼接行为,疑似存在sql注入;需研判该调用点是否为用户可控。", 224 | "VulName": "SQL注入漏洞" 225 | }, 226 | { 227 | "FileType": "java", 228 | "RegexRule": "insert\\s{1,10}into\\s{1,5}.{1,60}\\s{1,10}values\\([\\s+]", 229 | "Readme": "在进行insert添加时,存在变量赋值行为,疑似存在sql注入;需研判该调用点是否为用户可控。", 230 | "VulName": "SQL注入漏洞" 231 | }, 232 | { 233 | "FileType": "java", 234 | "RegexRule": "update\\s{1,5}.{1,60}set\\s.*where\\s.*[\\+]", 235 | "Readme": "在进行update更新时,存在变量赋值行为,疑似存在sql注入;需研判该调用点是否为用户可控。", 236 | "VulName": "SQL注入漏洞" 237 | }, 238 | { 239 | "FileType": "java", 240 | "RegexRule": "(?:URLConnection\\s+\\w+\\s+=\\s+)?\\s*.openConnection\\(", 241 | "Readme": "存在URL.openConnection()危险函数,疑似存在SSRF漏洞,需要关注实例化URL时,参数是否为用户可控。", 242 | "VulName": "SQL注入漏洞" 243 | }, 244 | { 245 | "FileType": "java", 246 | "RegexRule": "(?:(?:HttpGet|HttpPost)\\s+\\w+\\s+=\\s+)?\\s*.(?:HttpGet|HttpPost)\\(", 247 | "Readme": "存在HttpClient中HttpGet或HttpPost实例化,疑似存在SSRF漏洞,需要关注实例化时,传入的参数是否为用户可控。", 248 | "VulName": "服务器端请求伪造-SSRF" 249 | }, 250 | { 251 | "FileType": "java", 252 | "RegexRule": "Request\\.(?:Get|Post)\\s*\\(\\s*[^\\)]*\\s*\\)\\.execute\\s*\\(\\s*", 253 | "Readme": "存在Request调用get或post请求,疑似存在SSRF漏洞,需要关注传入的参数是否为用户可控。", 254 | "VulName": "服务器端请求伪造-SSRF" 255 | }, 256 | { 257 | "FileType": "java", 258 | "RegexRule": "(?:OkHttpClient\\s+\\w+\\s+=\\s+)?new\\s+OkHttpClient\\(", 259 | "Readme": "存在实例化OkHttpClient,需关注是否存在Request.Builder创建链接,且参数是否为用户可控。", 260 | "VulName": "服务器端请求伪造-SSRF" 261 | }, 262 | { 263 | "FileType": "java", 264 | "RegexRule": "ImageIO\\.read\\(\\s*[^\\\"']*", 265 | "Readme": "存在调用ImageIO.read()方法,且其中存在变量,需关注变量是否为用户可控。", 266 | "VulName": "服务器端请求伪造-SSRF" 267 | }, 268 | { 269 | "FileType": "java", 270 | "RegexRule": "response\\s*\\.getWriter\\s*\\(\\s*\\)", 271 | "Readme": "直接使用response.getWriter向浏览器写数据,可能导致XSS。", 272 | "VulName": "跨站脚本攻击-XSS" 273 | }, 274 | { 275 | "FileType": "java", 276 | "RegexRule": "response\\.getOutputStream*", 277 | "Readme": "直接使用response.getOutputStream向浏览器写数据,可能导致XSS。", 278 | "VulName": "跨站脚本攻击-XSS" 279 | }, 280 | { 281 | "FileType": "java", 282 | "RegexRule": "response\\s*\\.\\s*(?:setContentType|setHeader|addHeader|setStatus|sendRedirect)\\s*\\(\\s*[^\\\"']*", 283 | "Readme": "在response设置中,存在变量,直接向浏览器中写入数据,可能存在xss漏洞。", 284 | "VulName": "跨站脚本攻击-XSS" 285 | } 286 | ] -------------------------------------------------------------------------------- /scanner/scanner.go: -------------------------------------------------------------------------------- 1 | package scanner 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/fatih/color" 7 | "os" 8 | "path/filepath" 9 | "regexp" 10 | "runtime" 11 | "strings" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | // Rule 定义了漏洞检测规则 17 | type Rule struct { 18 | FileType string // 文件类型,如java、php、net等 19 | RegexRule string // 正则表达式规则 20 | Readme string // 规则说明 21 | VulName string // 漏洞名称 22 | LikeName string // 可选,用于模糊匹配文件名 23 | } 24 | 25 | // ScanResult 表示扫描结果 26 | type ScanResult struct { 27 | FilePath string // 文件路径 28 | LineNumber int // 行号 29 | MatchedLine string // 匹配的行内容 30 | Rule Rule // 匹配的规则 31 | } 32 | 33 | // Scanner 结构体用于文件扫描 34 | type Scanner struct { 35 | ScanDir string // 扫描目录 36 | ExcludeDir string // 排除目录 37 | Extensions []string // 文件扩展名 38 | Rules []Rule // 规则列表 39 | ScanResults []ScanResult 40 | Verbose bool // 详细输出模式 41 | MaxDepth int // 最大扫描深度 42 | WorkerCount int // 工作协程数量 43 | } 44 | 45 | // NewScanner 创建一个新的扫描器 46 | func NewScanner(scanDir, excludeDir string, extensions []string, rules []Rule, verbose bool, maxDepth int, workerCount int) *Scanner { 47 | // 如果未指定最大深度,则使用默认值100 48 | if maxDepth <= 0 { 49 | maxDepth = 100 50 | } 51 | 52 | // 如果未指定工作协程数量,则使用CPU核心数 53 | if workerCount <= 0 { 54 | workerCount = runtime.NumCPU() 55 | } 56 | 57 | return &Scanner{ 58 | ScanDir: scanDir, 59 | ExcludeDir: excludeDir, 60 | Extensions: extensions, 61 | Rules: rules, 62 | Verbose: verbose, 63 | MaxDepth: maxDepth, 64 | WorkerCount: workerCount, 65 | } 66 | } 67 | 68 | // Scan 开始扫描文件 69 | func (s *Scanner) Scan() ([]ScanResult, error) { 70 | // 编译所有正则表达式 71 | compiledRules := make(map[string]*regexp.Regexp) 72 | for _, rule := range s.Rules { 73 | re, err := regexp.Compile(rule.RegexRule) 74 | if err != nil { 75 | fmt.Printf("警告: 规则 '%s' 的正则表达式编译失败: %v\n", rule.VulName, err) 76 | continue 77 | } 78 | compiledRules[rule.RegexRule] = re 79 | } 80 | 81 | // 计算排除目录的绝对路径 82 | excludePath := "" 83 | if s.ExcludeDir != "" { 84 | absExcludePath, err := filepath.Abs(s.ExcludeDir) 85 | if err == nil { 86 | excludePath = absExcludePath 87 | if s.Verbose { 88 | fmt.Printf("排除目录: %s\n", excludePath) 89 | } 90 | } 91 | } 92 | 93 | // 打印扫描信息 94 | fmt.Printf("开始扫描目录: %s\n", s.ScanDir) 95 | fmt.Printf("扫描文件类型: %s\n", strings.Join(s.Extensions, ", ")) 96 | 97 | // 初始化计数器和结果 98 | var results []ScanResult 99 | var scannedFiles, matchedFiles int 100 | startTime := time.Now() 101 | 102 | // 创建工作池 103 | workerCount := s.WorkerCount // 使用配置的工作协程数 104 | fileChan := make(chan string, 100) 105 | resultChan := make(chan []ScanResult, 100) 106 | errChan := make(chan error, 100) 107 | var wg sync.WaitGroup 108 | 109 | // 启动工作协程 110 | for i := 0; i < workerCount; i++ { 111 | wg.Add(1) 112 | go func() { 113 | defer wg.Done() 114 | for path := range fileChan { 115 | // 扫描文件 116 | fileResults, err := s.scanFile(path, compiledRules) 117 | if err != nil { 118 | errChan <- fmt.Errorf("警告: 扫描文件 '%s' 失败: %v", path, err) 119 | continue 120 | } 121 | resultChan <- fileResults 122 | } 123 | }() 124 | } 125 | 126 | // 启动结果收集协程 127 | done := make(chan struct{}) 128 | go func() { 129 | for fileResults := range resultChan { 130 | results = append(results, fileResults...) 131 | if len(fileResults) > 0 { 132 | matchedFiles++ 133 | if s.Verbose { 134 | fmt.Printf("发现 %d 个潜在漏洞\n", len(fileResults)) 135 | } 136 | } 137 | } 138 | close(done) 139 | }() 140 | 141 | // 启动错误收集协程 142 | errDone := make(chan struct{}) 143 | go func() { 144 | for err := range errChan { 145 | if s.Verbose { 146 | fmt.Println(err) 147 | } 148 | } 149 | close(errDone) 150 | }() 151 | 152 | // 遍历文件 153 | err := filepath.Walk(s.ScanDir, func(path string, info os.FileInfo, err error) error { 154 | // 检查目录深度 155 | if info.IsDir() && path != s.ScanDir { 156 | relPath, err := filepath.Rel(s.ScanDir, path) 157 | if err != nil { 158 | return nil 159 | } 160 | 161 | // 计算目录深度 162 | depth := len(strings.Split(relPath, string(os.PathSeparator))) 163 | if depth > s.MaxDepth { 164 | if s.Verbose { 165 | fmt.Printf("跳过超过最大深度的目录: %s (深度: %d)\n", path, depth) 166 | } 167 | return filepath.SkipDir 168 | } 169 | } 170 | if err != nil { 171 | if s.Verbose { 172 | fmt.Printf("无法访问: %s, 错误: %v\n", path, err) 173 | } 174 | return nil // 忽略无法访问的文件或目录 175 | } 176 | 177 | // 跳过目录 178 | if info.IsDir() { 179 | // 检查是否是排除目录 180 | if excludePath != "" { 181 | absPath, err := filepath.Abs(path) 182 | if err == nil && (absPath == excludePath || strings.HasPrefix(absPath, excludePath+string(os.PathSeparator))) { 183 | if s.Verbose { 184 | fmt.Printf("跳过目录: %s\n", path) 185 | } 186 | return filepath.SkipDir 187 | } 188 | } 189 | return nil 190 | } 191 | 192 | // 检查文件扩展名 193 | extMatch := false 194 | for _, ext := range s.Extensions { 195 | if strings.HasSuffix(strings.ToLower(path), strings.ToLower(ext)) { 196 | extMatch = true 197 | break 198 | } 199 | } 200 | 201 | if !extMatch { 202 | if s.Verbose { 203 | fmt.Printf("跳过不匹配的文件: %s\n", path) 204 | } 205 | return nil // 跳过不匹配扩展名的文件 206 | } 207 | 208 | // 更新扫描计数 209 | scannedFiles++ 210 | if s.Verbose && scannedFiles%100 == 0 { 211 | fmt.Printf("已扫描 %d 个文件...\r", scannedFiles) 212 | } 213 | 214 | // 将文件路径发送到通道 215 | fileChan <- path 216 | 217 | return nil 218 | }) 219 | 220 | // 关闭文件通道,等待所有工作协程完成 221 | close(fileChan) 222 | wg.Wait() 223 | 224 | // 关闭结果通道和错误通道,等待收集协程完成 225 | close(resultChan) 226 | close(errChan) 227 | <-done 228 | <-errDone 229 | 230 | // 打印扫描统计信息 231 | elapsedTime := time.Since(startTime) 232 | fmt.Printf("\n扫描完成! 耗时: %.2f秒\n", elapsedTime.Seconds()) 233 | fmt.Printf("共扫描 %d 个文件, 发现 %d 个包含潜在漏洞的文件, 共 %d 个漏洞点\n\n", 234 | scannedFiles, matchedFiles, len(results)) 235 | 236 | return results, err 237 | } 238 | 239 | // scanFile 扫描单个文件 240 | func (s *Scanner) scanFile(filePath string, compiledRules map[string]*regexp.Regexp) ([]ScanResult, error) { 241 | var results []ScanResult 242 | 243 | // 打开文件 244 | file, err := os.Open(filePath) 245 | if err != nil { 246 | return nil, err 247 | } 248 | defer file.Close() 249 | 250 | // 获取文件名(用于likeName匹配) 251 | fileName := filepath.Base(filePath) 252 | 253 | // 获取文件扩展名(不带点) 254 | ext := strings.TrimPrefix(filepath.Ext(filePath), ".") 255 | 256 | // 预先筛选适用于此文件的规则,提高性能 257 | var applicableRules []Rule 258 | var applicableRegexps []*regexp.Regexp 259 | for _, rule := range s.Rules { 260 | // 检查文件类型是否匹配 261 | if rule.FileType != "" && !strings.EqualFold(rule.FileType, ext) { 262 | continue 263 | } 264 | 265 | // 检查likeName是否匹配(如果有) 266 | if rule.LikeName != "" && !strings.Contains(strings.ToLower(fileName), strings.ToLower(rule.LikeName)) { 267 | continue 268 | } 269 | 270 | // 获取编译好的正则表达式 271 | re, ok := compiledRules[rule.RegexRule] 272 | if !ok { 273 | continue // 跳过编译失败的规则 274 | } 275 | 276 | applicableRules = append(applicableRules, rule) 277 | applicableRegexps = append(applicableRegexps, re) 278 | } 279 | 280 | // 如果没有适用的规则,直接返回 281 | if len(applicableRules) == 0 { 282 | return results, nil 283 | } 284 | 285 | // 创建扫描器 286 | scanner := bufio.NewScanner(file) 287 | lineNum := 0 288 | 289 | // 逐行扫描文件 290 | for scanner.Scan() { 291 | lineNum++ 292 | line := scanner.Text() 293 | 294 | // 对每个适用的规则进行匹配 295 | for i, re := range applicableRegexps { 296 | // 执行正则匹配 297 | if re.MatchString(line) { 298 | results = append(results, ScanResult{ 299 | FilePath: filePath, 300 | LineNumber: lineNum, 301 | MatchedLine: line, 302 | Rule: applicableRules[i], 303 | }) 304 | } 305 | } 306 | } 307 | 308 | if err := scanner.Err(); err != nil { 309 | return nil, err 310 | } 311 | 312 | return results, nil 313 | } 314 | 315 | // PrintResults 打印扫描结果 316 | func PrintResults(results []ScanResult) { 317 | if len(results) == 0 { 318 | fmt.Println("未发现任何漏洞点") 319 | return 320 | } 321 | 322 | fmt.Printf("共发现 %d 个潜在漏洞点\n\n", len(results)) 323 | 324 | // 按漏洞类型分组 325 | vulnGroups := make(map[string][]ScanResult) 326 | for _, result := range results { 327 | vulnGroups[result.Rule.VulName] = append(vulnGroups[result.Rule.VulName], result) 328 | } 329 | 330 | // 打印分组结果 331 | for vulnName, groupResults := range vulnGroups { 332 | // 打印漏洞类型 333 | color.New(color.FgRed, color.Bold).Printf("[%s] 发现 %d 处\n", vulnName, len(groupResults)) 334 | 335 | // 打印每个结果 336 | for i, result := range groupResults { 337 | // 打印文件路径和行号 338 | color.New(color.FgYellow).Printf("[%d] %s:%d\n", i+1, result.FilePath, result.LineNumber) 339 | 340 | // 高亮显示匹配的内容 341 | highlightMatchedLine(result.MatchedLine, result.Rule.RegexRule) 342 | 343 | // 打印规则说明 344 | color.New(color.FgCyan).Printf(" 说明: %s\n\n", result.Rule.Readme) 345 | } 346 | } 347 | } 348 | 349 | // ExportToCSV 将扫描结果导出为CSV文件,支持自适应列宽 350 | func ExportToCSV(results []ScanResult, filePath string) error { 351 | // 如果没有结果,创建一个空的CSV文件 352 | if len(results) == 0 { 353 | file, err := os.Create(filePath) 354 | if err != nil { 355 | return fmt.Errorf("创建CSV文件失败: %v", err) 356 | } 357 | defer file.Close() 358 | 359 | writer := bufio.NewWriter(file) 360 | _, err = writer.WriteString("漏洞类型,文件路径,行号,匹配内容,规则说明\n") 361 | if err != nil { 362 | return fmt.Errorf("写入CSV头失败: %v", err) 363 | } 364 | 365 | err = writer.Flush() 366 | if err != nil { 367 | return fmt.Errorf("刷新CSV缓冲区失败: %v", err) 368 | } 369 | 370 | fmt.Printf("扫描结果已成功导出到: %s\n", filePath) 371 | return nil 372 | } 373 | 374 | // 计算每列的最大宽度 375 | colWidths := calculateColumnWidths(results) 376 | 377 | // 创建CSV文件 378 | file, err := os.Create(filePath) 379 | if err != nil { 380 | return fmt.Errorf("创建CSV文件失败: %v", err) 381 | } 382 | defer file.Close() 383 | 384 | // 写入CSV头 - 使用计算的列宽 385 | writer := bufio.NewWriter(file) 386 | header := fmt.Sprintf("%s,%s,%s,%s,%s\n", 387 | padString("漏洞类型", colWidths[0]), 388 | padString("文件路径", colWidths[1]), 389 | padString("行号", colWidths[2]), 390 | padString("匹配内容", colWidths[3]), 391 | padString("规则说明", colWidths[4])) 392 | _, err = writer.WriteString(header) 393 | if err != nil { 394 | return fmt.Errorf("写入CSV头失败: %v", err) 395 | } 396 | 397 | // 写入每一行结果 398 | for _, result := range results { 399 | // 处理CSV中的特殊字符 400 | vulName := strings.ReplaceAll(result.Rule.VulName, "\"", "\"\"") 401 | filePath := strings.ReplaceAll(result.FilePath, "\"", "\"\"") 402 | matchedLine := strings.ReplaceAll(result.MatchedLine, "\"", "\"\"") 403 | readme := strings.ReplaceAll(result.Rule.Readme, "\"", "\"\"") 404 | 405 | // 使用计算的列宽格式化数据 406 | lineNumStr := fmt.Sprintf("%d", result.LineNumber) 407 | line := fmt.Sprintf("\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n", 408 | padString(vulName, colWidths[0]), 409 | padString(filePath, colWidths[1]), 410 | padString(lineNumStr, colWidths[2]), 411 | padString(matchedLine, colWidths[3]), 412 | padString(readme, colWidths[4])) 413 | _, err = writer.WriteString(line) 414 | if err != nil { 415 | return fmt.Errorf("写入CSV数据失败: %v", err) 416 | } 417 | } 418 | 419 | // 刷新缓冲区 420 | err = writer.Flush() 421 | if err != nil { 422 | return fmt.Errorf("刷新CSV缓冲区失败: %v", err) 423 | } 424 | 425 | fmt.Printf("扫描结果已成功导出到: %s\n", filePath) 426 | return nil 427 | } 428 | 429 | // padString 根据指定的宽度填充字符串 430 | func padString(s string, width int) string { 431 | // 如果字符串长度已经大于或等于宽度,直接返回 432 | if len(s) >= width { 433 | return s 434 | } 435 | 436 | // 填充空格到指定宽度 437 | return s + strings.Repeat(" ", width-len(s)) 438 | } 439 | 440 | // calculateColumnWidths 计算CSV文件每列的最大宽度 441 | func calculateColumnWidths(results []ScanResult) []int { 442 | // 初始化列宽数组,对应:漏洞类型,文件路径,行号,匹配内容,规则说明 443 | colWidths := []int{10, 10, 5, 15, 20} // 默认最小宽度 444 | 445 | // 计算每列的最大宽度 446 | for _, result := range results { 447 | // 漏洞类型 448 | if len(result.Rule.VulName) > colWidths[0] { 449 | colWidths[0] = len(result.Rule.VulName) 450 | } 451 | 452 | // 文件路径 453 | if len(result.FilePath) > colWidths[1] { 454 | colWidths[1] = len(result.FilePath) 455 | } 456 | 457 | // 行号 - 转换为字符串计算长度 458 | lineNumStr := fmt.Sprintf("%d", result.LineNumber) 459 | if len(lineNumStr) > colWidths[2] { 460 | colWidths[2] = len(lineNumStr) 461 | } 462 | 463 | // 匹配内容 464 | if len(result.MatchedLine) > colWidths[3] { 465 | colWidths[3] = len(result.MatchedLine) 466 | } 467 | 468 | // 规则说明 469 | if len(result.Rule.Readme) > colWidths[4] { 470 | colWidths[4] = len(result.Rule.Readme) 471 | } 472 | } 473 | 474 | return colWidths 475 | } 476 | 477 | // highlightMatchedLine 高亮显示匹配的行内容 478 | func highlightMatchedLine(line, regexPattern string) { 479 | // 编译正则表达式 480 | re, err := regexp.Compile(regexPattern) 481 | if err != nil { 482 | // 如果正则表达式编译失败,直接打印原始行 483 | fmt.Printf(" %s\n", line) 484 | return 485 | } 486 | 487 | // 查找匹配项 488 | indices := re.FindStringIndex(line) 489 | if indices == nil { 490 | // 如果没有找到匹配项,直接打印原始行 491 | fmt.Printf(" %s\n", line) 492 | return 493 | } 494 | 495 | // 分割行内容,以便高亮显示匹配部分 496 | preMatch := line[:indices[0]] 497 | match := line[indices[0]:indices[1]] 498 | postMatch := line[indices[1]:] 499 | 500 | // 打印高亮的行内容 501 | fmt.Printf(" %s", preMatch) 502 | color.New(color.FgRed, color.Bold).Printf("%s", match) 503 | fmt.Printf("%s\n", postMatch) 504 | } 505 | --------------------------------------------------------------------------------