Log Injection in Chi
How Log Injection Manifests in Chi
Log injection in Chi applications occurs when untrusted user input flows directly into log statements without proper sanitization. Chi's minimalist router design means developers often implement custom logging middleware or use standard library logging, creating multiple injection vectors.
The most common pattern involves request parameters being logged verbatim. Consider this middleware:
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Request from %s: %s %s %s", r.RemoteAddr, r.Method, r.URL.Path, r.URL.Query().Encode())
next.ServeHTTP(w, r)
})
}An attacker can inject newline characters and additional log entries by crafting requests like:
GET /api/resource?param=value%0AINFO%20New%20malicious%20entry%0AThis breaks the log structure, creating false entries that appear legitimate. The injection can include timestamps, log levels, and arbitrary content, making forensic analysis unreliable.
Another Chi-specific vector involves path parameters. Since Chi uses named parameters for routing:
router.Get("/user/{id}", func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
log.Printf("Fetching user %s", id)
// ... handler logic
})An attacker can inject newlines via URL encoding: /user/123%0AERROR%20Unauthorized%20access%20detected. This creates misleading security alerts in log monitoring systems.
Header injection represents another critical vector. Many Chi applications log request headers for debugging:
log.Printf("Headers: %v", r.Header)Attackers can craft headers containing newline sequences, breaking log integrity and potentially hiding malicious activity within legitimate log entries.
Structured logging doesn't automatically prevent injection. Consider this JSON logging approach:
logEntry := fmt.Sprintf(`{"timestamp":"%s","method":"%s","path":"%s","query":"%s"}`,
time.Now().Format(time.RFC3339), r.Method, r.URL.Path, r.URL.RawQuery)
log.Println(logEntry)If r.URL.RawQuery contains "}","malicious":true,"{", it breaks the JSON structure, potentially causing log parsing failures or creating malformed entries that evade detection.
Chi's context-based request-scoped values create another injection surface. Developers often log these values:
ctx := r.Context()
userID := ctx.Value("user_id").(string)
log.Printf("User %s performed action", userID)If an attacker manipulates how user_id is stored in context (through middleware vulnerabilities), they can inject arbitrary log content.
Rate limiting and authentication middleware that logs attempts are particularly vulnerable. An attacker can flood logs with injected content, causing log rotation issues, storage exhaustion, or masking actual attacks within the noise.
The impact extends beyond log integrity. Injected log entries can trigger false positives in SIEM systems, cause alert fatigue, or worse, hide actual security incidents within legitimate-looking log entries.
Chi-Specific Detection
Detecting log injection in Chi applications requires both static analysis and runtime monitoring. Start with code review focusing on logging patterns.
Static analysis should identify these anti-patterns:
// Vulnerable: direct interpolation without sanitization
log.Printf("User %s accessed %s", userID, resourceID)
// Vulnerable: logging raw query parameters
log.Printf("Query: %s", r.URL.RawQuery)
// Vulnerable: logging headers without validation
log.Printf("Headers: %v", r.Header)Automated scanning with middleBrick can detect these patterns by analyzing your Chi application's runtime behavior. middleBrick's black-box scanning tests for log injection by sending requests with encoded newline characters and special characters, then analyzing the actual log output for injection success.
middleBrick specifically tests for:
- Newline character injection (
%0A,%0D) - Log level injection (INFO, ERROR, DEBUG prefixes)
- JSON structure breaking characters
- Timestamp manipulation attempts
- Multi-line injection payloads
The scanner examines log files or log aggregation endpoints to verify if injected content appears in the logs, providing a severity score based on injection success and potential impact.
Runtime detection requires monitoring for anomalous log patterns:
func secureLoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Sanitize inputs before logging
sanitizedMethod := sanitizeLogInput(r.Method)
sanitizedPath := sanitizeLogInput(r.URL.Path)
sanitizedQuery := sanitizeLogInput(r.URL.RawQuery)
logEntry := fmt.Sprintf("Request: %s %s?%s from %s",
sanitizedMethod, sanitizedPath, sanitizedQuery, r.RemoteAddr)
// Detect potential injection attempts
if containsSuspiciousPatterns(logEntry) {
logWarn("Potential log injection attempt detected", r)
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
log.Println(logEntry)
next.ServeHTTP(w, r)
})
}middleBrick's continuous monitoring in Pro tier can alert you when new injection vulnerabilities are introduced in your codebase, comparing current scan results against historical baselines.
Integration testing should include log injection test cases:
func TestLogInjection(t *testing.T) {
router := chi.NewRouter()
router.Use(secureLoggingMiddleware)
// Test newline injection
req := httptest.NewRequest("GET", "/test?param=value%0AERROR%20Injected", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Verify log output doesn't contain injected content
logs := getCapturedLogs()
for _, entry := range logs {
if strings.Contains(entry, "ERROR Injected") {
t.Errorf("Log injection successful: %s", entry)
}
}
}Chi-Specific Remediation
Remediation requires a defense-in-depth approach combining input sanitization, structured logging, and secure middleware patterns.
First, implement comprehensive input sanitization for log entries:
func sanitizeLogInput(input string) string {
// Remove newline characters
input = strings.ReplaceAll(input, "\n", " ")
input = strings.ReplaceAll(input, "\r", " ")
// Escape special characters that could break log structure
input = strings.ReplaceAll(input, "\"", "'")
input = strings.ReplaceAll(input, "{", "[")
input = strings.ReplaceAll(input, "}", "]")
// Limit length to prevent log flooding
if len(input) > 1000 {
input = input[:997] + "..."
}
return input
}
// Secure logging middleware
func secureLoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Sanitize all user-controlled inputs
method := sanitizeLogInput(r.Method)
path := sanitizeLogInput(r.URL.Path)
query := sanitizeLogInput(r.URL.RawQuery)
remoteAddr := sanitizeLogInput(r.RemoteAddr)
// Use structured logging with consistent format
logEntry := fmt.Sprintf("method=%s path=%s query=%s remote=%s",
method, path, query, remoteAddr)
log.Println(logEntry)
next.ServeHTTP(w, r)
})
}Implement structured logging with JSON format to prevent injection from breaking log structure:
type logEntry struct {
Timestamp string `json:"timestamp"`
Method string `json:"method"`
Path string `json:"path"`
Query string `json:"query,omitempty"`
Remote string `json:"remote"`
Status int `json:"status"`
}
func jsonLoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Create response wrapper to capture status
rw := &responseWrapper{ResponseWriter: w, status: http.StatusOK}
// Sanitize inputs
method := sanitizeLogInput(r.Method)
path := sanitizeLogInput(r.URL.Path)
query := sanitizeLogInput(r.URL.RawQuery)
remote := sanitizeLogInput(r.RemoteAddr)
next.ServeHTTP(rw, r)
// Create structured log entry
entry := logEntry{
Timestamp: time.Now().Format(time.RFC3339),
Method: method,
Path: path,
Query: query,
Remote: remote,
Status: rw.status,
}
// Marshal to JSON - invalid characters are escaped
jsonLog, _ := json.Marshal(entry)
log.Println(string(jsonLog))
})
}
type responseWrapper struct {
http.ResponseWriter
status int
size int
}
func (rw *responseWrapper) WriteHeader(code int) {
rw.status = code
rw.ResponseWriter.WriteHeader(code)
}
func (rw *responseWrapper) Write(b []byte) (int, error) {
size, err := rw.ResponseWriter.Write(b)
rw.size += size
return size, err
}For Chi-specific patterns, use the router's built-in parameter handling safely:
router.Get("/user/{id}", func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
// Sanitize parameter before logging
sanitizedID := sanitizeLogInput(id)
log.Printf("Fetching user: %s", sanitizedID)
// Validate parameter format
if !isValidUserID(id) {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
// ... handler logic
})Implement rate limiting on logging to prevent log flooding attacks:
var logRateLimiter = rate.NewLimiter(rate.Every(time.Minute), 100)
func rateLimitedLoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !logRateLimiter.Allow() {
// Log rate limit exceeded, but don't include user data
log.Println("Log rate limit exceeded")
next.ServeHTTP(w, r)
return
}
// Normal logging logic
next.ServeHTTP(w, r)
})
}middleBrick's Pro plan includes continuous monitoring that can alert you when logging patterns change unexpectedly, potentially indicating injection attempts or other anomalies.
Finally, implement log integrity verification:
func verifyLogIntegrity() {
// Periodically check logs for injection patterns
logs, err := readRecentLogs()
if err != nil {
return
}
for _, line := range logs {
if containsInjectionPatterns(line) {
alertSecurityTeam("Suspected log injection detected")
break
}
}
}