164 lines
3.5 KiB
Markdown
164 lines
3.5 KiB
Markdown
# Logging Security Rules
|
|
|
|
Logging sensitive information can lead to data exposure and compliance violations.
|
|
|
|
**Rules:**
|
|
|
|
1. PII MUST NEVER be logged — filter passwords, tokens, emails, and personal data.
|
|
2. Log injection MUST be prevented — sanitize user input before logging.
|
|
3. Error messages MUST NOT expose internals to users — log details server-side, return generic messages.
|
|
|
|
---
|
|
|
|
## Sensitive Data in Logs — Medium
|
|
|
|
**Bad:**
|
|
|
|
```go
|
|
type User struct {
|
|
ID string
|
|
Username string
|
|
Password string
|
|
Token string
|
|
}
|
|
|
|
func logUserLogin(user *User) {
|
|
log.Printf("User logged in: %+v\n", user) // DON'T: Logs password, token
|
|
}
|
|
```
|
|
|
|
**Good:**
|
|
|
|
```go
|
|
import "log/slog"
|
|
|
|
func logUserLogin(logger *slog.Logger, user *User) {
|
|
logger.Info("user_login",
|
|
"user_id", user.ID,
|
|
"username", user.Username,
|
|
// Don't log: password, token
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Log Injection — Low
|
|
|
|
User input in logs can lead to log injection attacks.
|
|
|
|
**Bad:**
|
|
|
|
```go
|
|
log.Printf("User logged in: %s\n", username) // DON'T: No sanitization
|
|
```
|
|
|
|
**Good:**
|
|
|
|
```go
|
|
import "log/slog"
|
|
|
|
// Sanitize user input before logging
|
|
func sanitizeLogInput(input string) string {
|
|
// Remove control characters
|
|
var result strings.Builder
|
|
for _, r := range input {
|
|
if !unicode.IsControl(r) || r == '\n' || r == '\t' {
|
|
result.WriteRune(r)
|
|
}
|
|
}
|
|
return result.String()
|
|
}
|
|
|
|
func logUsername(logger *slog.Logger, username string) {
|
|
sanitized := sanitizeLogInput(username)
|
|
logger.Info("user_login", "username", sanitized)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Information Leakage in Error Messages — Medium
|
|
|
|
**Bad:**
|
|
|
|
```go
|
|
func handleDatabaseError(err error) error {
|
|
return fmt.Errorf("database error: %v", err) // DON'T: Leaks internal details
|
|
}
|
|
|
|
func dbErrorToHTTP(err error) {
|
|
http.Error(w, "Error: "+err.Error(), 500) // DON'T
|
|
}
|
|
```
|
|
|
|
**Good:**
|
|
|
|
```go
|
|
func handleDatabaseError(logger *slog.Logger, err error) error {
|
|
// Log detailed error for debugging
|
|
logger.Error("database_error", "error", err.Error())
|
|
// Return generic message to client
|
|
return errors.New("database operation failed")
|
|
}
|
|
|
|
func dbErrorToHTTP(w http.ResponseWriter, logger *slog.Logger, err error) {
|
|
logger.Error("database_error", "error", err.Error())
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## General Logger Security — Low
|
|
|
|
**Bad:**
|
|
|
|
```go
|
|
import "log"
|
|
log.Println("User logged in:", user.ID, password) // DON'T: Logs password
|
|
fmt.Printf("DEBUG: %+v\n", data) // DON'T: Raw data
|
|
```
|
|
|
|
**Good:**
|
|
|
|
```go
|
|
import "log/slog"
|
|
|
|
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
|
Level: slog.LevelInfo,
|
|
AddSource: false,
|
|
})
|
|
logger := slog.New(handler)
|
|
|
|
logger.Info("user_login",
|
|
"user_id", userID,
|
|
"ip_address", request.RemoteIP,
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## Log Security Checklist
|
|
|
|
- [ ] No passwords, tokens, or secrets in logs
|
|
- [ ] No PII/PHI in production logs
|
|
- [ ] Sanitize user input before logging
|
|
- [ ] Use structured logging (JSON)
|
|
- [ ] Implement log level strategy
|
|
- [ ] Separate access logs from error logs
|
|
- [ ] Log file permissions restricted (e.g., 600)
|
|
- [ ] Log rotation prevents disk exhaustion
|
|
- [ ] Generic error messages to clients
|
|
- [ ] Detailed errors only in internal logs
|
|
|
|
---
|
|
|
|
## CWE References
|
|
|
|
- **CWE-532**: Insertion of Sensitive Information into Log File
|
|
- **CWE-117**: Improper Output Neutralization for Logs
|
|
- **CWE-209**: Information Exposure Through an Error Message
|
|
- **CWE-200**: Exposure of Sensitive Information
|
|
- **CWE-312**: Cleartext Storage of Sensitive Information
|