Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ services:
network_mode: host
restart: unless-stopped
environment:
- PORT=5001
- PORT=5000
- REDIS_ADDR=localhost:6379
depends_on:
redis:
condition: service_healthy
Expand All @@ -21,9 +22,8 @@ services:
ports:
- "6379:6379"
healthcheck:
test: ["executable", "arg"]
interval: 1m30s
timeout: 30s
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s

start_period: 10s
9 changes: 9 additions & 0 deletions go_security_app/blocker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ func TestAuthCheckHandler(t *testing.T) {
{"Allowed Path GET with Query", "GET", "/some/valid/path?user=1", http.StatusOK},
{"Allowed Path POST", "POST", "/api/submit", http.StatusOK},

// Legitimate traffic that must not be blocked.
{"Allowed category query", "GET", "/products?category=books", http.StatusOK},
{"Allowed selected query", "GET", "/list?selected=1&sort=updated", http.StatusOK},
{"Allowed word echo in path", "GET", "/blog/echolocation-in-bats", http.StatusOK},
{"Allowed empty parens", "GET", "/calc?formula=sum()", http.StatusOK},
{"Allowed concatenate param", "GET", "/api?op=concatenate&drop=false", http.StatusOK},
{"Allowed dropdown param", "GET", "/ui?dropdown=open", http.StatusOK},
{"Allowed cat substring", "GET", "/vacation-rentals?location=france", http.StatusOK},

// Blocked Paths
{"Blocked PHP Suffix", "GET", "/index.php", http.StatusForbidden},
{"Blocked WP-Admin Prefix", "GET", "/wp-admin/options.php", http.StatusForbidden},
Expand Down
105 changes: 46 additions & 59 deletions go_security_app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,46 +20,42 @@ import (
var blockedPathSuffixes = []string{
".php", ".aspx", ".jsp", ".htaccess", ".htpasswd", ".git/", ".env",
}
var blockedPathPrefixes = []string{
"/wp-admin/", "/wp-includes/", "/admin/", "/remote/", "/manager/",
}
var blockedExactPaths = []string{
"/xmlrpc.php", "/config.json", "/configuration.php",
}
var blockedPathContains = []string{
".git/", "wp-includes/", "wp-admin/", "admin/", "remote/", "manager/", "config.json", "configuration.php",
".git/", "wp-includes/", "wp-admin/", "admin/", "remote/", "manager/",
"xmlrpc.php", "config.json", "configuration.php",
}

// Encoded markers matched against the raw query before decoding, used as a
// fallback when the query has malformed encoding. Plain keywords are left to
// suspiciousDecodedQueryRegex so values like ?category= are not blocked.
var suspiciousRawQuerySubstrings = []string{
"%24%7B", "%3Cscript", "%27%20OR%20", "UNION%20SELECT", "%2E%2E%2F", "../", "etc/passwd", "eval(", "base64_decode", "()",
"cat", "passwd", "%2Fetc%2F", "%2F..%2F", "..%2F", "%2F..", "phpinfo", "%28%29",
"SELECT", "INSERT", "UPDATE", "DELETE", "DROP", "UNION", "alert(", "onerror=", "onload=", "'OR", "OR'", "'AND", "AND'",
"%24%7b", // ${
"%3cscript",
"%2e%2e%2f",
"..%2f",
"%2f..",
"../",
"%2fetc%2f",
"etc/passwd",
}

var commandInjectionPatterns = []string{
`\|\s*\w+`,
`echo\s+['"]?.*['"]?\s*\|`,
`[;&\|\\` + "`" + `]\s*\w+`,
`\$\(\w+`,
`\` + "`" + `\w+`,
`echo ['"].*?['"].*?\|.*?grep`, // Specifically match "echo 'evil' | grep e"
`%7C`, // URL encoded pipe character |
`echo`, // detects the "echo" command
}
// Checked against both the decoded path and query.
var commandInjectionRegex = regexp.MustCompile(
"(?i)(&&|\\|\\||;\\s*\\w|\\s\\|\\s|`|\\$\\(|\\bcat\\s+/|\\beval\\(|base64_decode|phpinfo\\(\\)|system\\(\\)|exec\\(\\)|shell_exec\\(\\)|passthru\\()")

var suspiciousDecodedQueryRegex = []*regexp.Regexp{
regexp.MustCompile(`(?i)(\s(SELECT|UNION|INSERT|UPDATE|DELETE)\s|\s(OR|AND)\s*['"]?1['"]?\s*=\s*['"]?1|--|#|\/\*.*\*\/)`),
regexp.MustCompile(`(?i)(<script|onerror=|onload=|javascript:|alert\()`),
regexp.MustCompile(`\${`),
regexp.MustCompile(`(&&|\s*;\s*|\s*\|\s*|\s*` + "``" + `)`),

regexp.MustCompile(`(?i)(\.\.\/|\.\.\\|etc\/passwd)`),
regexp.MustCompile(`(?i)(cat\s+.*passwd|ls\s+|rm\s+)`),
regexp.MustCompile(`(?i)(phpinfo\(\)|system\(\)|exec\(\)|shell_exec\(\)|passthru\(\)|eval\()`),

regexp.MustCompile(`(?i)(\s+(SELECT|UNION|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE)\s+|'.*?(OR|AND)\s*['"]?[01]['"]?\s*=\s*['"]?[01]|--|#|\/\*.*?\*\/)`),
regexp.MustCompile(`(?i)(<script|<img|onerror=|onload=|javascript:|alert\()`),
regexp.MustCompile(`(?i)(\$\{|\s*\|\s*|\s*;\s*|\s*&&\s*|\s*\|\|\s*|\s*` + "`" + `)`),
regexp.MustCompile(`(?i)(UNION\s+SELECT|DROP\s+TABLE|INSERT\s+INTO|UPDATE\s+.*?\s+SET)`),
regexp.MustCompile(`(?i)\bunion\b\s+\bselect\b`),
regexp.MustCompile(`(?i)\bselect\b\s+.*\bfrom\b`),
regexp.MustCompile(`(?i)\binsert\b\s+\binto\b`),
regexp.MustCompile(`(?i)\bupdate\b\s+.+\bset\b`),
regexp.MustCompile(`(?i)\bdelete\b\s+\bfrom\b`),
regexp.MustCompile(`(?i)\bdrop\b\s+\b(table|database)\b`),
regexp.MustCompile(`(?i)\b(or|and)\b\s+['"]?\d+['"]?\s*=\s*['"]?\d+`),
regexp.MustCompile(`(?i)['"]\s*\b(or|and)\b\s+['"\d]`),
regexp.MustCompile(`(--|#|/\*.*?\*/)`),
regexp.MustCompile(`(?i)(<script|<img|<svg|onerror\s*=|onload\s*=|javascript:|alert\s*\()`),
regexp.MustCompile(`\$\{`),
regexp.MustCompile(`(?i)(\.\./|\.\.\\|etc/passwd)`),
}

var redisClient *redis.Client
Expand Down Expand Up @@ -186,31 +182,19 @@ func authCheckHandler(w http.ResponseWriter, r *http.Request) {

lowerPath := strings.ToLower(path)
type pathCheck struct {
patterns []string
checkFn func(path, pattern string) bool
isEqualFold bool
message string
patterns []string
checkFn func(path, pattern string) bool
message string
}

pathChecks := []pathCheck{
{blockedPathContains, strings.Contains, false, "Blocked path containing"},
{blockedPathSuffixes, strings.HasSuffix, false, "Blocked path suffix"},
{blockedPathPrefixes, strings.HasPrefix, false, "Blocked path prefix"},
{blockedExactPaths, strings.EqualFold, true, "Blocked exact path"},
{commandInjectionPatterns, strings.Contains, false, "Blocked command injection pattern"},
{blockedPathContains, strings.Contains, "Blocked path containing"},
{blockedPathSuffixes, strings.HasSuffix, "Blocked path suffix"},
}

for _, check := range pathChecks {
for _, pattern := range check.patterns {
patternLower := strings.ToLower(pattern)
var match bool
if check.isEqualFold {
match = check.checkFn(path, pattern)
} else {
match = check.checkFn(lowerPath, patternLower)
}

if match {
if check.checkFn(lowerPath, pattern) {
recordInvalidAttempt(clientIP)
sendAuthBlockedResponse(w, clientIP, originalURI,
fmt.Sprintf("%s: %s", check.message, pattern))
Expand All @@ -219,10 +203,16 @@ func authCheckHandler(w http.ResponseWriter, r *http.Request) {
}
}

if commandInjectionRegex.MatchString(path) {
recordInvalidAttempt(clientIP)
sendAuthBlockedResponse(w, clientIP, originalURI, "Blocked command injection in path")
return
}

if rawQuery != "" {
lowerRawQuery := strings.ToLower(rawQuery)
for _, pattern := range suspiciousRawQuerySubstrings {
if strings.Contains(lowerRawQuery, strings.ToLower(pattern)) {
if strings.Contains(lowerRawQuery, pattern) {
recordInvalidAttempt(clientIP)
sendAuthBlockedResponse(w, clientIP, originalURI, fmt.Sprintf("Blocked suspicious raw query substring: %s", pattern))
return
Expand All @@ -239,13 +229,10 @@ func authCheckHandler(w http.ResponseWriter, r *http.Request) {
}
}

for _, pattern := range commandInjectionPatterns {
if strings.Contains(strings.ToLower(decodedQuery), strings.ToLower(pattern)) {
recordInvalidAttempt(clientIP)
sendAuthBlockedResponse(w, clientIP, originalURI,
fmt.Sprintf("Blocked command injection in query: %s", pattern))
return
}
if commandInjectionRegex.MatchString(decodedQuery) {
recordInvalidAttempt(clientIP)
sendAuthBlockedResponse(w, clientIP, originalURI, "Blocked command injection in query")
return
}
} else {
log.Printf("AuthCheck: Warning: Query decode failed for '%s' from %s: %v", rawQuery, clientIP, err)
Expand Down
Loading