Day 71 — Path Traversal & Command Injection
Learning Objectives
- Detect path traversal in all encoded forms: raw, URL-encoded, double-encoded, unicode
- Detect command injection operators
- Understand why SSRF is a path traversal variant at the URL level
Exercise: Path Traversal + Command Injection Rules
package waf
import (
"net/http"
"net/url"
"regexp"
"strings"
)
var (
// Path traversal in various encodings
pathTraversalPatterns = []struct{ pattern, name string }{
{`\.\./`, "dot-dot-slash"},
{`\.\.\\`, "dot-dot-backslash"},
{`%2e%2e%2f`, "url-encoded ../"},
{`%2e%2e/`, "partial url-encoded ../"},
{`\.%2e/`, "partial url-encoded ../"},
{`%252e%252e%252f`, "double url-encoded ../"},
{`%c0%ae%c0%ae/`, "unicode dot ../"},
{`\x00`, "null byte"},
}
// Command injection operators
cmdInjectionRe = regexp.MustCompile("(?i)(;|\\||&&|\\$\\(|`|\\$\\{|\\bexec\\b|\\bsystem\\b|\\bpassthru\\b|\\bshell_exec\\b|\\bpopen\\b|\\bproc_open\\b)")
// SSRF patterns — internal IP access via URL parameters
ssrfInternalIPRe = regexp.MustCompile(`(?:^|[^0-9])(?:127\.0\.0\.1|0\.0\.0\.0|localhost|169\.254\.\d+\.\d+|10\.\d+\.\d+\.\d+|172\.(?:1[6-9]|2\d|3[01])\.\d+\.\d+|192\.168\.\d+\.\d+)(?:[:/]|$)`)
)
type PathTraversalRule struct{}
func (r *PathTraversalRule) ID() string { return "TRAV-001" }
func (r *PathTraversalRule) Category() string { return "TRAVERSAL" }
func (r *PathTraversalRule) Match(req *http.Request, body []byte) (bool, int, string) {
// Check URL path + all query parameters + body
inputs := []string{req.URL.Path, req.URL.RawQuery}
inputs = append(inputs, extractInputs(req, body)...)
for _, input := range inputs {
normalized := normalizeTraversal(input)
for _, p := range pathTraversalPatterns {
re := regexp.MustCompile("(?i)" + p.pattern)
if re.MatchString(normalized) {
return true, 10, fmt.Sprintf("path traversal: %s in %q", p.name, input[:min(50, len(input))])
}
}
}
return false, 0, ""
}
func normalizeTraversal(input string) string {
// Multiple decode passes to catch double-encoding
result := input
for i := 0; i < 3; i++ {
decoded, err := url.PathUnescape(result)
if err != nil || decoded == result { break }
result = decoded
}
return strings.ToLower(result)
}
type CmdInjectionRule struct{}
func (r *CmdInjectionRule) ID() string { return "CMDINJ-001" }
func (r *CmdInjectionRule) Category() string { return "CMDINJ" }
func (r *CmdInjectionRule) Match(req *http.Request, body []byte) (bool, int, string) {
for _, input := range extractInputs(req, body) {
if m := cmdInjectionRe.FindString(input); m != "" {
return true, 9, fmt.Sprintf("command injection: %q", m)
}
}
return false, 0, ""
}
type SSRFRule struct{}
func (r *SSRFRule) ID() string { return "SSRF-001" }
func (r *SSRFRule) Category() string { return "SSRF" }
func (r *SSRFRule) Match(req *http.Request, body []byte) (bool, int, string) {
// Check URL parameters that might contain target URLs
for _, param := range []string{"url", "target", "redirect", "src", "href", "link"} {
val := req.URL.Query().Get(param)
if val == "" { continue }
if ssrfInternalIPRe.MatchString(val) {
return true, 9, fmt.Sprintf("SSRF: internal IP in %q param", param)
}
}
return false, 0, ""
}
Real-World Context
Path traversal (OWASP A01) was used in the 2021 Accellion FTA breach (100+ organizations). Command injection was the mechanism of the 2021 Log4Shell vulnerability (JNDI lookup injection). SSRF (OWASP A10) was used in the Capital One breach — attacker used metadata service (169.254.169.254) to steal AWS credentials.