Domain 8 β€Ί Domain 8 Β· Lesson 2 of 5

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).

VULNERABLE β€” NEVER
// Go β€” string concatenation (NEVER)
db.Query(
  "SELECT * FROM users WHERE id = "
  + userInput,  // ← ATTACK VECTOR
)
SECURE β€” ALWAYS
// Go β€” parameterized query (ALWAYS)
db.Query(
  "SELECT * FROM users WHERE id = ?",
  userID,  // ← value never interpolated
)
VULNERABLE β€” Java Platform A
// Java JDBC β€” Statement (NEVER with user input)
Statement stmt = conn.createStatement();
stmt.execute("SELECT * FROM loans WHERE id="
  + loanId);  // ← SQL injection risk
SECURE β€” Java Platform A
// 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 InjectionRelational databasesParameterized queries / ORM
Command InjectionOS shell (exec.Command)Avoid user input in shell commands; use allowlists
LDAP InjectionDirectory servicesParameterized LDAP queries; escape special chars
NoSQL InjectionMongoDB, Redis, etc.Parameterize all query operators; avoid $where
XPath InjectionXML databasesParameterized 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

Parameterized Query

SQL query where user values are passed separately, never interpolated into the query string β€” prevents SQL injection

Allowlist

Accept ONLY known-good values/patterns; superior to blocklist because novel attacks are rejected by default

TOCTOU

Time of Check to Time of Use β€” race condition where state changes between security check and action; fix with atomic DB transactions

SSRF

Server-Side Request Forgery β€” server fetches attacker-controlled URL, potentially reaching internal services

crypto/rand vs math/rand

crypto/rand = cryptographically secure, unpredictable. math/rand = seeded, deterministic, NOT for security use

Memory Safety

Go prevents buffer overflow via bounds checking; C/C++ do not β€” buffer overflow was the root cause of many historical CVEs

Exam Tips β€” Lesson 02
  1. 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. 2. Client-side validation = UX only. Server-side validation = security. This is the most commonly tested concept β€” CISSP answer is always "server-side."
  3. 3. Allowlist > blocklist. "Allow only known-good" is stronger than "block known-bad" because attackers find novel inputs that bypass blocklists.
  4. 4. TOCTOU (Time of Check to Time of Use) is a race condition. Fix: atomic database transactions using SELECT FOR UPDATE or optimistic locking β€” NOT just re-checking the condition.
  5. 5. math/rand is predictable (seeded with a fixed seed). NEVER use for tokens, OTPs, session IDs, or cryptographic keys. Always crypto/rand.
Platform C/FinTech Company X Secure Coding Audit

(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?

A: Vulnerable because user input is interpolated directly into the SQL string β€” attacker can inject SQL operators. Fix: use parameterized query: db.Query("SELECT * FROM users WHERE id=?", userID)
OWASP A03 (Injection) is the most commonly exploited vulnerability class. Parameterized queries ensure the database engine treats user input as DATA, never as SQL SYNTAX β€” even if the input contains SQL keywords like 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.
This is one of the most commonly tested secure coding concepts. CISSP answer: client-side = UX, server-side = security. Both should exist (client-side for better user experience), but only server-side provides actual security.

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.
TOCTOU (Time of Check to Time of Use) is a classic concurrency vulnerability. In Go + PostgreSQL, the pattern is: 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?

A: 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.
OTP tokens are security-sensitive randomness. Using 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.
Information disclosure via error messages is OWASP A09 (Security Logging and Monitoring Failures) and also relates to A01 (Broken Access Control). Returning SQL errors tells an attacker exactly how the database query is structured β€” valuable for SQL injection attacks. Stack traces reveal code structure and technology stack.