- audit/events: 73.5% → 97.6% (+24.1%)
- Add tests for IsM013/M014/M015RelatedEvent
- Add tests for FormatSECURITYEvent
- Add comprehensive coverage for all CRED and SECURITY event functions
- security: 67.2% → 88.8% (+21.6%)
- Add tests for ValidateKeyID, DecryptionError.Error()
- Add tests for ValidateQueryParams, GetAllowedParamNames
- Add tests for isHexString, looksLikeAPIKey
- Fix test cases to match actual implementation behavior
- audit/sanitizer: Fix MaskMap []string handling bug
- Add maskSliceInterface for []interface{} type
- Tests now pass for string slice sensitive fields
All tests pass
237 lines
5.9 KiB
Go
237 lines
5.9 KiB
Go
package security
|
||
|
||
import (
|
||
"net/url"
|
||
"strings"
|
||
)
|
||
|
||
// ==================== P0-04 Query Key白名单检测 ====================
|
||
|
||
// AllowedQueryParam 白名单参数
|
||
type AllowedQueryParam struct {
|
||
Name string
|
||
Description string
|
||
}
|
||
|
||
// GetAllowedQueryParams 获取允许的query参数白名单
|
||
func GetAllowedQueryParams() []AllowedQueryParam {
|
||
return []AllowedQueryParam{
|
||
// 分页
|
||
{Name: "page", Description: "页码"},
|
||
{Name: "page_size", Description: "每页大小"},
|
||
{Name: "limit", Description: "限制数量"},
|
||
{Name: "offset", Description: "偏移量"},
|
||
|
||
// 排序
|
||
{Name: "sort", Description: "排序字段"},
|
||
{Name: "order", Description: "排序方向"},
|
||
|
||
// 过滤
|
||
{Name: "filter", Description: "过滤条件"},
|
||
{Name: "search", Description: "搜索关键词"},
|
||
|
||
// 时间范围
|
||
{Name: "start_date", Description: "开始日期"},
|
||
{Name: "end_date", Description: "结束日期"},
|
||
{Name: "from", Description: "起始时间"},
|
||
{Name: "to", Description: "结束时间"},
|
||
|
||
// 视图选项
|
||
{Name: "format", Description: "响应格式"},
|
||
{Name: "fields", Description: "字段选择"},
|
||
|
||
// 调试选项
|
||
{Name: "debug", Description: "调试模式"},
|
||
}
|
||
}
|
||
|
||
// GetAllowedParamNames 获取白名单参数名集合
|
||
func GetAllowedParamNames() map[string]bool {
|
||
params := GetAllowedQueryParams()
|
||
allowed := make(map[string]bool)
|
||
for _, p := range params {
|
||
allowed[strings.ToLower(p.Name)] = true
|
||
}
|
||
return allowed
|
||
}
|
||
|
||
// isQueryParamAllowed 检查参数是否在白名单中(大小写不敏感)
|
||
func isQueryParamAllowed(param string, whitelist []AllowedQueryParam) bool {
|
||
lowerParam := strings.ToLower(param)
|
||
lowerWhitelist := make(map[string]bool)
|
||
for _, p := range whitelist {
|
||
lowerWhitelist[strings.ToLower(p.Name)] = true
|
||
}
|
||
return lowerWhitelist[lowerParam]
|
||
}
|
||
|
||
// isQueryParamBlocked 检查参数是否被禁止(大小写不敏感)
|
||
func isQueryParamBlocked(param string, whitelist []AllowedQueryParam) bool {
|
||
return !isQueryParamAllowed(param, whitelist)
|
||
}
|
||
|
||
// blockedParamNames 禁止的参数名(包含各种变体)
|
||
var blockedParamNames = []string{
|
||
"key", "api_key", "apikey", "api-key",
|
||
"token", "access_token", "access-token",
|
||
"refresh_token", "refresh-token",
|
||
"secret", "secret_key", "secretkey",
|
||
"password", "passwd", "pwd",
|
||
"credential", "cred",
|
||
"auth", "authorization",
|
||
"session", "session_id", "sessionid",
|
||
"jwt", "jti",
|
||
"signature", "sig",
|
||
"private", "private_key", "privatekey",
|
||
}
|
||
|
||
// detectBlockedParams 检测是否有被禁止的参数(支持URL解码和大小写不敏感)
|
||
func detectBlockedParams(query url.Values, whitelist []AllowedQueryParam) bool {
|
||
whitelistMap := make(map[string]bool)
|
||
for _, p := range whitelist {
|
||
whitelistMap[strings.ToLower(p.Name)] = true
|
||
}
|
||
|
||
for param := range query {
|
||
// 1. 检查白名单
|
||
if whitelistMap[strings.ToLower(param)] {
|
||
continue
|
||
}
|
||
|
||
// 2. 检查是否包含敏感关键词(即使参数名不同)
|
||
lowerParam := strings.ToLower(param)
|
||
if containsSensitiveKeyword(lowerParam) {
|
||
return true
|
||
}
|
||
|
||
// 3. 检查参数值是否可疑
|
||
value := query.Get(param)
|
||
if isSuspiciousQueryValue(param, value) {
|
||
return true
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// containsSensitiveKeyword 检查是否包含敏感关键词
|
||
func containsSensitiveKeyword(param string) bool {
|
||
sensitiveKeywords := []string{
|
||
"key", "token", "secret", "password", "credential",
|
||
"auth", "jwt", "signature", "private",
|
||
}
|
||
|
||
for _, kw := range sensitiveKeywords {
|
||
if strings.Contains(param, kw) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// isSuspiciousQueryValue 检查query参数值是否可疑
|
||
// 可疑模式:值看起来像API key、Bearer token等
|
||
func isSuspiciousQueryValue(param, value string) bool {
|
||
if value == "" {
|
||
return false
|
||
}
|
||
|
||
// 1. 检查JWT格式(即使参数名不像token)
|
||
if strings.HasPrefix(value, "eyJ") && strings.Count(value, ".") == 2 {
|
||
return true
|
||
}
|
||
|
||
// 2. 检查Bearer token格式
|
||
if strings.HasPrefix(value, "Bearer ") || strings.HasPrefix(value, "bearer ") {
|
||
return true
|
||
}
|
||
|
||
// 3. 检查长度 - 可疑的API key通常较长
|
||
if len(value) > 20 && looksLikeAPIKey(value) {
|
||
return true
|
||
}
|
||
|
||
// 4. 检查参数名是否包含敏感关键词,且值较长
|
||
lowerParam := strings.ToLower(param)
|
||
if len(value) > 20 {
|
||
if containsSensitiveKeyword(lowerParam) {
|
||
return true
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// looksLikeAPIKey 检查值是否像API key
|
||
func looksLikeAPIKey(value string) bool {
|
||
// 常见的API key前缀
|
||
apiKeyPrefixes := []string{
|
||
"sk-", "sk_", // OpenAI
|
||
"ak-", "ak_", // AWS
|
||
"pk-", "pk_", // Stripe
|
||
"ghp_", "github_", // GitHub
|
||
"xoxb-", // Slack
|
||
"AIza", // Google API
|
||
}
|
||
|
||
lowerValue := strings.ToLower(value)
|
||
for _, prefix := range apiKeyPrefixes {
|
||
if strings.HasPrefix(lowerValue, prefix) {
|
||
return true
|
||
}
|
||
}
|
||
|
||
// 检查是否是长哈希值 (32+字符的十六进制)
|
||
if len(value) >= 32 && isHexString(value) {
|
||
return true
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// isHexString 检查字符串是否是十六进制
|
||
func isHexString(s string) bool {
|
||
for _, c := range s {
|
||
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
|
||
return false
|
||
}
|
||
}
|
||
return len(s) >= 32
|
||
}
|
||
|
||
// QueryKeyValidationResult Query Key验证结果
|
||
type QueryKeyValidationResult struct {
|
||
Allowed bool
|
||
BlockedParam string
|
||
Reason string
|
||
}
|
||
|
||
// ValidateQueryParams 验证query参数
|
||
func ValidateQueryParams(rawQuery string) *QueryKeyValidationResult {
|
||
parsed, err := url.ParseQuery(rawQuery)
|
||
if err != nil {
|
||
return &QueryKeyValidationResult{
|
||
Allowed: false,
|
||
Reason: "invalid query string",
|
||
}
|
||
}
|
||
|
||
whitelist := GetAllowedQueryParams()
|
||
if detectBlockedParams(parsed, whitelist) {
|
||
// 找出被阻止的参数
|
||
for param := range parsed {
|
||
if !isQueryParamAllowed(param, whitelist) {
|
||
return &QueryKeyValidationResult{
|
||
Allowed: false,
|
||
BlockedParam: param,
|
||
Reason: "query parameter not in whitelist or suspicious",
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return &QueryKeyValidationResult{
|
||
Allowed: true,
|
||
}
|
||
}
|