Secure Coding Practices & OWASP Mapping
Thα»±c hΓ nh ViαΊΏt Code An toΓ n
Injection Prevention β OWASP A03
SQL injection occurs when user-controlled input is concatenated directly into a query string. The fix is universal: never concatenate, always use parameterized queries (prepared statements).
// Go β string concatenation (NEVER) db.Query( "SELECT * FROM users WHERE id = " + userInput, // β ATTACK VECTOR )
// Go β parameterized query (ALWAYS) db.Query( "SELECT * FROM users WHERE id = ?", userID, // β value never interpolated )
// Java JDBC β Statement (NEVER with user input) Statement stmt = conn.createStatement(); stmt.execute("SELECT * FROM loans WHERE id=" + loanId); // β SQL injection risk
// Java JDBC β PreparedStatement (ALWAYS) PreparedStatement ps = conn.prepareStatement( "SELECT * FROM loans WHERE id=?"); ps.setLong(1, loanId); // β parameterized ps.execute();
| Injection Type | Target | Mitigation |
|---|---|---|
| SQL Injection | Relational databases | Parameterized queries / ORM |
| Command Injection | OS shell (exec.Command) | Avoid user input in shell commands; use allowlists |
| LDAP Injection | Directory services | Parameterized LDAP queries; escape special chars |
| NoSQL Injection | MongoDB, Redis, etc. | Parameterize all query operators; avoid $where |
| XPath Injection | XML databases | Parameterized XPath; use XML schema validation |
Input Validation Principles
Allowlist vs Blocklist
ALLOWLIST (Preferred)
"Accept ONLY known-good input β phone number must match ^\+?[0-9]{10,15}$"
Attackers cannot bypass β novel malicious input is not in the allowed set
BLOCKLIST (Weaker)
"Block known-bad patterns like <script>, DROP TABLE, etc."
Attackers find novel bypasses β <ScRiPt>, Unicode variants, encoding tricks
Validate ALL Four Dimensions
- TYPE Is it the right data type? (integer, string, date) β reject if not
- LENGTH Maximum and minimum length β reject oversized input (buffer overflow prevention)
- FORMAT Does it match expected pattern? (phone: digits only, email: RFC 5321 format)
- RANGE Is the value within acceptable bounds? (loan amount: 1,000 to 50,000,000 VND)
Server-side = Security | Client-side = UX only
Client-side validation (JavaScript) can be bypassed by any attacker using Burp Suite, curl, or browser DevTools. Server-side validation is the actual security control.
Common Vulnerabilities in Go (Platform C Context)
| Vulnerability | Go-Specific Risk | Mitigation |
|---|---|---|
| SQL Injection | Low risk with database/sql (uses ? params) but possible with raw string queries |
Always use parameterized queries β never concatenate |
| Path Traversal | filepath.Clean() alone is not sufficient for security |
Validate against an allowlist of permitted directories; use filepath.Abs() + prefix check |
| SSRF | Service fetches user-supplied URL (e.g., webhook callback URL) | Allowlist permitted external domains; block private IP ranges (10.x, 172.16.x, 192.168.x) |
| TOCTOU Race Condition | Goroutines create race windows; time between check and use | Use sync primitives; SELECT FOR UPDATE in database transactions |
| Unsafe Package | Bypasses Go's memory safety guarantees β like writing C code | Never use unsafe in production security-sensitive code |
| Weak Randomness | math/rand is deterministic and seeded β predictable by attackers |
Always use crypto/rand for tokens, OTPs, session IDs, keys |
// WRONG β predictable OTP generation import "math/rand" otp := rand.Intn(999999) // β predictable // CORRECT β cryptographically secure import "crypto/rand" import "math/big" max := big.NewInt(999999) n, _ := rand.Int(rand.Reader, max) otp := n.Int64() // β unpredictable
Memory Safety in Go
Go is memory-safe by default β bounds checking, garbage collection, no manual memory management. This prevents buffer overflow, use-after-free, and double-free that are common in C/C++. The unsafe package removes these protections.
Error Handling β Never Expose Internals
Stack traces, SQL error messages, file paths in error responses = information disclosure. Log the full error internally; return only a generic message to API callers.
Secrets Management in Code
NEVER β Hard-coded secrets
dbPassword := "Prod@2024!" apiKey := "sk-abc123xyz"
Visible in source code, Git history, logs β permanent exposure
NEVER β Dockerfile/image ENV
ENV DB_PASSWORD=Prod@2024! # β baked into image layers
Visible in any layer inspection of the image β persistent in registry
ALWAYS β Vault at runtime
// Vault Agent injects secrets
// as files or env at pod start
secret := os.Getenv("DB_PASS")
// rotated without redeploy
Secret never in code, config, or image. Rotatable without redeployment.
Key Terms
SQL query where user values are passed separately, never interpolated into the query string β prevents SQL injection
Accept ONLY known-good values/patterns; superior to blocklist because novel attacks are rejected by default
Time of Check to Time of Use β race condition where state changes between security check and action; fix with atomic DB transactions
Server-Side Request Forgery β server fetches attacker-controlled URL, potentially reaching internal services
crypto/rand = cryptographically secure, unpredictable. math/rand = seeded, deterministic, NOT for security use
Go prevents buffer overflow via bounds checking; C/C++ do not β buffer overflow was the root cause of many historical CVEs
- 1. Parameterized queries PREVENT SQL injection. Input sanitization ALONE does NOT fully prevent it (encoding bypasses exist). Both are needed but parameterized queries are the non-negotiable control.
- 2. Client-side validation = UX only. Server-side validation = security. This is the most commonly tested concept β CISSP answer is always "server-side."
- 3. Allowlist > blocklist. "Allow only known-good" is stronger than "block known-bad" because attackers find novel inputs that bypass blocklists.
- 4. TOCTOU (Time of Check to Time of Use) is a race condition. Fix: atomic database transactions using
SELECT FOR UPDATEor optimistic locking β NOT just re-checking the condition. - 5.
math/randis predictable (seeded with a fixed seed). NEVER use for tokens, OTPs, session IDs, or cryptographic keys. Alwayscrypto/rand.
(1) Grep all Go files for raw SQL string concatenation:
grep -rn 'db\.Query.*+\|db\.Exec.*+' --include='*.go' .
Any match is a Critical finding β replace with parameterized query immediately.
(2) Grep for math/rand usage in security-sensitive code:
grep -rn 'math/rand' --include='*.go' .
Review each use β replace with crypto/rand for OTP generation, session tokens, nonces.
(3) Check error handling in API middleware:
Are stack traces or SQL error details being returned in HTTP responses? Add a recovery middleware that logs the full error and returns only a generic 500 message to callers.
(4) Platform A Java 8 audit (higher risk than Go):
grep -rn 'createStatement\(\)' --include='*.java' .
Any Statement using user-controlled input = SQL injection. Priority: Java Platform A audit before Platform C β Go's database/sql API makes parameterized queries the natural choice.
Root cause of PII incident: unencrypted data at rest. Secure coding principle: encryption must be applied at the code layer, not assumed from infrastructure.
Practice Questions
Q1: What makes db.Query("SELECT * FROM users WHERE id=" + userInput) vulnerable, and what is the correct fix?
db.Query("SELECT * FROM users WHERE id=?", userID)
DROP TABLE, it is safely escaped.Q2: A React form validates that loan amounts must be positive before submitting. Is this a security control?
A: No. Client-side (JavaScript) validation is a UX convenience only β it can be bypassed by any attacker using Burp Suite, curl, or browser DevTools to send raw HTTP requests directly to the API. The server-side API must independently validate all inputs.Q3: A TOCTOU race condition occurs in a credit limit check. What database feature prevents it?
A:SELECT FOR UPDATE β acquires a row-level lock at read time, preventing concurrent modification between the check and the use. The entire check-and-update must run in a single atomic transaction.
BEGIN; SELECT credit_limit FROM loans WHERE id=? FOR UPDATE; UPDATE loans...; COMMIT; β the lock prevents another goroutine from changing the credit limit between the check and the update.Q4: Should math/rand or crypto/rand be used for generating 6-digit OTP tokens sent to customers?
crypto/rand β always. math/rand is seeded with a predictable value and is deterministic. An attacker who knows the seed (or can observe enough OTPs) can predict future values. crypto/rand uses the OS's cryptographically secure random number generator.
math/rand for OTPs is a critical vulnerability β it was the root cause of several authentication bypasses in production systems. The CISSP principle: use cryptographically secure random number generators for all security-sensitive values (tokens, nonces, session IDs, keys).Q5: What information should NOT be returned to API callers in error responses?
A: Stack traces, database error messages (including SQL query text), internal file paths, server software versions, internal IP addresses, and any system internals. Return only a generic error code and message. Log the full details server-side.