Why Security Matters
Think of security like building a house. You wouldn't leave your doors unlocked or windows open when you go on vacation. Yet many developers do exactly this with their applications - leaving digital "doors and windows" wide open for attackers.
Security isn't optionalβone vulnerability can compromise your entire system, leak user data, or cost millions in damages. Go provides strong security features by default, but developers must use them correctly. This guide covers essential security practices for building production-ready Go applications.
Real-World Impact:
Equifax Breach - 147 million records stolen:
1// Vulnerable: Outdated Apache Struts
2// Fix: Regular dependency updates
3go get -u ./...
4go mod tidy
A single unpatched vulnerability led to one of the largest data breaches in history. Cost: $1.4 billion in settlements, massive reputation damage.
Capital One Breach - 100 million records exposed:
1// Vulnerable: SSRF via misconfigured WAF
2// Fix: Proper input validation and least privilege
3if !isValidURL(input) {
4 return errors.New("invalid URL")
5}
A hacker exploited a misconfigured firewall to access sensitive data. Cost: $80 million fine, criminal charges.
Log4Shell - Critical RCE in logging library:
1// Vulnerable: Unsafe deserialization
2// Fix: Input validation and safe parsing
3log.Printf("User: %s", sanitize(userInput))
A simple string in log messages allowed attackers to execute code remotely. Affected: Millions of applications worldwide, months of remediation.
π‘ Key Takeaway: Security vulnerabilities aren't just technical problems - they're business risks that can destroy customer trust, incur massive costs, and even put companies out of business.
OWASP Top 10 for Go
The OWASP Top 10 represents the most critical web application security risks. Think of this as the "Most Wanted" list for vulnerabilities that attackers actively exploit:
| Rank | Vulnerability | Go Relevance | Prevention |
|---|---|---|---|
| A01 | Broken Access Control | βββββ Critical | Authorization middleware, RBAC |
| A02 | Cryptographic Failures | ββββ High | Use crypto/*, avoid MD5/SHA1 |
| A03 | Injection | βββββ Critical | Parameterized queries, input validation |
| A04 | Insecure Design | βββ Medium | Threat modeling, security reviews |
| A05 | Security Misconfiguration | ββββ High | Secure defaults, hardening guides |
| A06 | Vulnerable Components | ββββ High | Dependency scanning, updates |
| A07 | Authentication Failures | βββββ Critical | MFA, secure session management |
| A08 | Data Integrity Failures | βββ Medium | Signed JWTs, integrity checks |
| A09 | Logging/Monitoring Failures | ββββ High | Structured logging, audit trails |
| A10 | SSRF | βββ Medium | URL validation, allowlists |
Real-World Examples of OWASP Top 10:
- A01 Broken Access Control: Facebook's 2018 bug that let users view private photos of anyone
- A03 Injection: The 2014 TalkTalk breach where SQL injection exposed customer data
- A07 Authentication Failures: The 2019 Twitter breach where employees were socially engineered
When to Prioritize Security
Think about security like insurance - you hope you never need it, but when you do, you're really glad you have it.
β Security is Critical For:
- Financial applications - Handle money, PCI compliance, trust is everything
- Healthcare systems - HIPAA, patient data, lives at stake
- Authentication services - OAuth, SSO providers, identity theft risks
- Public APIs - Exposed to internet threats, 24/7 attacks
- Enterprise software - Corporate data protection, intellectual property
- IoT/embedded systems - Physical security implications, device tampering
β οΈ Security Still Matters:
- Internal tools - Insider threats exist
- Prototypes - May become production code faster than expected
- Open source - Public scrutiny, reputation damage affects career
Security Decision Tree:
Does your app handle sensitive data?
ββ Yes β Does it handle PII?
β ββ Yes β β
MAXIMUM SECURITY
β ββ No β Does it handle auth credentials?
β ββ Yes β β
HIGH SECURITY
β ββ No β β
MEDIUM SECURITY
ββ No β Is it exposed to the internet?
ββ Yes β β
MEDIUM SECURITY
ββ No β β
BASIC SECURITY
Now let's dive into the most common and dangerous vulnerabilities, starting with input validation - the foundation of application security.
Input Validation and Sanitization
Rule #1 of Security: Never trust user input!
Think of input validation like airport security. They don't just trust that you're safe - they check your bags, scan you, and verify your documents. Your application should be just as careful about every piece of data it receives.
Real-World Consequences of Poor Input Validation:
- SQL Injection: Attackers can steal or destroy your entire database
- XSS: Hackers can steal user cookies and session data
- Command Injection: Attackers can execute system commands on your server
- Path Traversal: Hackers can access files they shouldn't see
Input Validation Strategies
The key principle is "whitelist over blacklist" - instead of blocking bad input, only allow good input.
1package main
2
3import (
4 "errors"
5 "fmt"
6 "net/mail"
7 "regexp"
8 "strconv"
9 "strings"
10 "unicode"
11)
12
13// Validator provides input validation
14type Validator struct {
15 errors []string
16}
17
18// NewValidator creates a validator
19func NewValidator() *Validator {
20 return &Validator{errors: make([]string, 0)}
21}
22
23// AddError adds a validation error
24func (v *Validator) AddError(msg string) {
25 v.errors = append(v.errors, msg)
26}
27
28// Valid returns true if no errors
29func (v *Validator) Valid() bool {
30 return len(v.errors) == 0
31}
32
33// Errors returns all validation errors
34func (v *Validator) Errors() []string {
35 return v.errors
36}
37
38// String validators
39
40// ValidateRequired checks if string is not empty
41func ValidateRequired(v *Validator, field, value string) {
42 if strings.TrimSpace(value) == "" {
43 v.AddError(fmt.Sprintf("%s is required", field))
44 }
45}
46
47// ValidateMinLength checks minimum length
48func ValidateMinLength(v *Validator, field, value string, min int) {
49 if len(value) < min {
50 v.AddError(fmt.Sprintf("%s must be at least %d characters", field, min))
51 }
52}
53
54// ValidateMaxLength checks maximum length
55func ValidateMaxLength(v *Validator, field, value string, max int) {
56 if len(value) > max {
57 v.AddError(fmt.Sprintf("%s must be at most %d characters", field, max))
58 }
59}
60
61// ValidateEmail checks if value is valid email
62func ValidateEmail(v *Validator, field, value string) {
63 if _, err := mail.ParseAddress(value); err != nil {
64 v.AddError(fmt.Sprintf("%s is not a valid email", field))
65 }
66}
67
68// ValidateAlphanumeric checks if only letters and numbers
69func ValidateAlphanumeric(v *Validator, field, value string) {
70 for _, r := range value {
71 if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
72 v.AddError(fmt.Sprintf("%s must be alphanumeric", field))
73 return
74 }
75 }
76}
77
78// ValidatePattern checks if value matches regex
79func ValidatePattern(v *Validator, field, value, pattern string) {
80 matched, err := regexp.MatchString(pattern, value)
81 if err != nil || !matched {
82 v.AddError(fmt.Sprintf("%s does not match required pattern", field))
83 }
84}
85
86// Number validators
87
88// ValidateIntRange checks if number is in range
89func ValidateIntRange(v *Validator, field string, value, min, max int) {
90 if value < min || value > max {
91 v.AddError(fmt.Sprintf("%s must be between %d and %d", field, min, max))
92 }
93}
94
95// ValidatePositive checks if number is positive
96func ValidatePositive(v *Validator, field string, value int) {
97 if value <= 0 {
98 v.AddError(fmt.Sprintf("%s must be positive", field))
99 }
100}
101
102// Example: User registration validation
103type UserRegistration struct {
104 Username string
105 Email string
106 Password string
107 Age int
108}
109
110func (u *UserRegistration) Validate() error {
111 v := NewValidator()
112
113 // Username validation
114 ValidateRequired(v, "username", u.Username)
115 ValidateMinLength(v, "username", u.Username, 3)
116 ValidateMaxLength(v, "username", u.Username, 20)
117 ValidateAlphanumeric(v, "username", u.Username)
118
119 // Email validation
120 ValidateRequired(v, "email", u.Email)
121 ValidateEmail(v, "email", u.Email)
122
123 // Password validation
124 ValidateRequired(v, "password", u.Password)
125 ValidateMinLength(v, "password", u.Password, 8)
126 ValidatePattern(v, "password", u.Password, `[A-Z]`) // At least one uppercase
127 ValidatePattern(v, "password", u.Password, `[a-z]`) // At least one lowercase
128 ValidatePattern(v, "password", u.Password, `[0-9]`) // At least one digit
129
130 // Age validation
131 ValidateIntRange(v, "age", u.Age, 13, 120)
132
133 if !v.Valid() {
134 return fmt.Errorf("validation failed: %s", strings.Join(v.Errors(), ", "))
135 }
136
137 return nil
138}
139
140func main() {
141 // Valid registration
142 user1 := &UserRegistration{
143 Username: "alice123",
144 Email: "alice@example.com",
145 Password: "SecurePass123",
146 Age: 25,
147 }
148
149 if err := user1.Validate(); err != nil {
150 fmt.Printf("Validation failed: %v\n", err)
151 } else {
152 fmt.Println("User 1: Valid!")
153 }
154
155 // Invalid registration
156 user2 := &UserRegistration{
157 Username: "ab", // Too short
158 Email: "invalid", // Invalid email
159 Password: "weak", // Too short, no uppercase/digit
160 Age: 10, // Too young
161 }
162
163 if err := user2.Validate(); err != nil {
164 fmt.Printf("User 2: %v\n", err)
165 }
166}
β οΈ Common Input Validation Mistakes:
- Client-side only validation: Users can bypass JavaScript validation
- Blacklisting dangerous characters: Attackers always find creative ways around
- Trusting headers: HTTP headers can be easily spoofed
- Not validating file uploads: Attackers can upload malicious files
Advanced Input Validation Patterns
In production systems, you need more sophisticated validation strategies that handle edge cases, internationalization, and business logic constraints.
1// run
2package main
3
4import (
5 "errors"
6 "fmt"
7 "net"
8 "net/url"
9 "path/filepath"
10 "regexp"
11 "strings"
12 "time"
13)
14
15// AdvancedValidator provides production-ready validation
16type AdvancedValidator struct {
17 errors map[string][]string
18}
19
20func NewAdvancedValidator() *AdvancedValidator {
21 return &AdvancedValidator{
22 errors: make(map[string][]string),
23 }
24}
25
26func (av *AdvancedValidator) AddError(field, message string) {
27 av.errors[field] = append(av.errors[field], message)
28}
29
30func (av *AdvancedValidator) HasErrors() bool {
31 return len(av.errors) > 0
32}
33
34func (av *AdvancedValidator) GetErrors() map[string][]string {
35 return av.errors
36}
37
38// ValidateURL checks if URL is valid and safe
39func (av *AdvancedValidator) ValidateURL(field, value string, allowedSchemes []string) {
40 parsed, err := url.Parse(value)
41 if err != nil {
42 av.AddError(field, "invalid URL format")
43 return
44 }
45
46 // Check scheme
47 schemeAllowed := false
48 for _, scheme := range allowedSchemes {
49 if parsed.Scheme == scheme {
50 schemeAllowed = true
51 break
52 }
53 }
54
55 if !schemeAllowed {
56 av.AddError(field, fmt.Sprintf("URL scheme must be one of: %v", allowedSchemes))
57 }
58
59 // Prevent SSRF attacks
60 if parsed.Host == "localhost" || parsed.Host == "127.0.0.1" || strings.HasPrefix(parsed.Host, "192.168.") {
61 av.AddError(field, "internal URLs are not allowed")
62 }
63}
64
65// ValidateIP checks if IP address is valid and not private
66func (av *AdvancedValidator) ValidateIP(field, value string) {
67 ip := net.ParseIP(value)
68 if ip == nil {
69 av.AddError(field, "invalid IP address")
70 return
71 }
72
73 // Check for private/internal IPs
74 if ip.IsLoopback() || ip.IsPrivate() {
75 av.AddError(field, "private IP addresses are not allowed")
76 }
77}
78
79// ValidateFilePath checks if file path is safe
80func (av *AdvancedValidator) ValidateFilePath(field, value, baseDir string) {
81 // Clean the path
82 cleaned := filepath.Clean(value)
83
84 // Check for path traversal
85 if strings.Contains(cleaned, "..") {
86 av.AddError(field, "path traversal detected")
87 return
88 }
89
90 // Ensure path is within base directory
91 absPath, err := filepath.Abs(cleaned)
92 if err != nil {
93 av.AddError(field, "invalid file path")
94 return
95 }
96
97 absBase, err := filepath.Abs(baseDir)
98 if err != nil {
99 av.AddError(field, "invalid base directory")
100 return
101 }
102
103 if !strings.HasPrefix(absPath, absBase) {
104 av.AddError(field, "path must be within base directory")
105 }
106}
107
108// ValidateDate checks if date is valid and within range
109func (av *AdvancedValidator) ValidateDate(field, value, layout string, minDate, maxDate time.Time) {
110 parsed, err := time.Parse(layout, value)
111 if err != nil {
112 av.AddError(field, "invalid date format")
113 return
114 }
115
116 if parsed.Before(minDate) {
117 av.AddError(field, fmt.Sprintf("date must be after %s", minDate.Format(layout)))
118 }
119
120 if parsed.After(maxDate) {
121 av.AddError(field, fmt.Sprintf("date must be before %s", maxDate.Format(layout)))
122 }
123}
124
125// ValidateJSON checks if string is valid JSON
126func (av *AdvancedValidator) ValidateJSON(field, value string) {
127 // Simple check for JSON structure
128 trimmed := strings.TrimSpace(value)
129 if !strings.HasPrefix(trimmed, "{") && !strings.HasPrefix(trimmed, "[") {
130 av.AddError(field, "value must be valid JSON")
131 }
132}
133
134// ValidateEnum checks if value is in allowed set
135func (av *AdvancedValidator) ValidateEnum(field, value string, allowed []string) {
136 for _, a := range allowed {
137 if value == a {
138 return
139 }
140 }
141 av.AddError(field, fmt.Sprintf("value must be one of: %v", allowed))
142}
143
144// Example: API request validation
145type APIRequest struct {
146 Endpoint string
147 IPAddress string
148 FilePath string
149 StartDate string
150 Status string
151 Metadata string
152}
153
154func (r *APIRequest) Validate() error {
155 validator := NewAdvancedValidator()
156
157 // Validate URL
158 validator.ValidateURL("endpoint", r.Endpoint, []string{"https"})
159
160 // Validate IP
161 validator.ValidateIP("ip_address", r.IPAddress)
162
163 // Validate file path
164 validator.ValidateFilePath("file_path", r.FilePath, "/var/uploads")
165
166 // Validate date
167 minDate := time.Now().AddDate(-1, 0, 0) // 1 year ago
168 maxDate := time.Now().AddDate(1, 0, 0) // 1 year from now
169 validator.ValidateDate("start_date", r.StartDate, "2006-01-02", minDate, maxDate)
170
171 // Validate enum
172 validator.ValidateEnum("status", r.Status, []string{"pending", "active", "completed", "failed"})
173
174 // Validate JSON
175 validator.ValidateJSON("metadata", r.Metadata)
176
177 if validator.HasErrors() {
178 return fmt.Errorf("validation errors: %v", validator.GetErrors())
179 }
180
181 return nil
182}
183
184func main() {
185 fmt.Println("=== Advanced Input Validation ===\n")
186
187 // Valid request
188 validReq := &APIRequest{
189 Endpoint: "https://api.example.com/data",
190 IPAddress: "8.8.8.8",
191 FilePath: "/var/uploads/file.txt",
192 StartDate: time.Now().Format("2006-01-02"),
193 Status: "active",
194 Metadata: `{"key": "value"}`,
195 }
196
197 if err := validReq.Validate(); err != nil {
198 fmt.Printf("Valid request failed: %v\n", err)
199 } else {
200 fmt.Println("β Valid request passed validation")
201 }
202
203 // Invalid request
204 fmt.Println("\n=== Testing Invalid Inputs ===\n")
205
206 invalidReq := &APIRequest{
207 Endpoint: "http://localhost:8080/admin", // Wrong scheme and localhost
208 IPAddress: "127.0.0.1", // Loopback
209 FilePath: "../../etc/passwd", // Path traversal
210 StartDate: "2030-01-01", // Future date beyond range
211 Status: "unknown", // Invalid enum
212 Metadata: "not json", // Invalid JSON
213 }
214
215 if err := invalidReq.Validate(); err != nil {
216 fmt.Printf("β Invalid request properly rejected:\n%v\n", err)
217 }
218}
Now let's look at Cross-Site Scripting prevention - one of the most common and dangerous web vulnerabilities.
HTML/JavaScript Sanitization
Cross-Site Scripting is like leaving your front door unlocked with a sign that says "come in and take whatever you want." Attackers can inject malicious JavaScript that steals user data, hijacks sessions, or even takes over user accounts.
Real-World XSS Examples:
- Twitter: The "onmouseover" worm that spread automatically when users hovered over tweets
- MySpace: The "Samy" worm that added over 1 million friends in just 20 hours
- Yahoo: Advertisers exploited XSS to serve malware through legitimate ads
Types of XSS:
- Stored XSS: Malicious code stored in database, affects all users who view it
- Reflected XSS: Malicious code in URL parameters, executes when link is clicked
- DOM-based XSS: Vulnerability exists in client-side JavaScript
1package main
2
3import (
4 "fmt"
5 "html"
6 "html/template"
7 "os"
8 "regexp"
9 "strings"
10)
11
12// Sanitizer provides XSS protection
13type Sanitizer struct{}
14
15// SanitizeHTML escapes HTML special characters
16func (s *Sanitizer) SanitizeHTML(input string) string {
17 return html.EscapeString(input)
18}
19
20// SanitizeJS escapes JavaScript special characters
21func (s *Sanitizer) SanitizeJS(input string) string {
22 // Escape quotes and backslashes
23 input = strings.ReplaceAll(input, "\\", "\\\\")
24 input = strings.ReplaceAll(input, "'", "\\'")
25 input = strings.ReplaceAll(input, "\"", "\\\"")
26 input = strings.ReplaceAll(input, "\n", "\\n")
27 input = strings.ReplaceAll(input, "\r", "\\r")
28 return input
29}
30
31// StripTags removes all HTML tags
32func (s *Sanitizer) StripTags(input string) string {
33 re := regexp.MustCompile(`<[^>]*>`)
34 return re.ReplaceAllString(input, "")
35}
36
37// AllowSafeTags allows only safe HTML tags
38func (s *Sanitizer) AllowSafeTags(input string) string {
39 // Whitelist of safe tags
40 safeTags := []string{"b", "i", "u", "em", "strong", "p", "br"}
41
42 // Remove all tags except safe ones
43 re := regexp.MustCompile(`</?([a-z]+)[^>]*>`)
44 return re.ReplaceAllStringFunc(input, func(match string) string {
45 tagMatch := regexp.MustCompile(`</?([a-z]+)`).FindStringSubmatch(match)
46 if len(tagMatch) > 1 {
47 tag := tagMatch[1]
48 for _, safeTag := range safeTags {
49 if tag == safeTag {
50 return match
51 }
52 }
53 }
54 return ""
55 })
56}
57
58// SanitizeURL validates and sanitizes URLs
59func (s *Sanitizer) SanitizeURL(input string) (string, error) {
60 // Remove javascript: protocol
61 if strings.HasPrefix(strings.ToLower(input), "javascript:") {
62 return "", fmt.Errorf("dangerous URL protocol")
63 }
64
65 // Remove data: protocol
66 if strings.HasPrefix(strings.ToLower(input), "data:") {
67 return "", fmt.Errorf("data URLs not allowed")
68 }
69
70 // Allow only http/https
71 if !strings.HasPrefix(input, "http://") && !strings.HasPrefix(input, "https://") {
72 return "", fmt.Errorf("only http/https URLs allowed")
73 }
74
75 return input, nil
76}
77
78func main() {
79 sanitizer := &Sanitizer{}
80
81 // XSS attempt
82 malicious := `<script>alert('XSS')</script>Hello`
83 safe := sanitizer.SanitizeHTML(malicious)
84 fmt.Printf("Original: %s\n", malicious)
85 fmt.Printf("Sanitized: %s\n", safe)
86
87 // Strip all tags
88 htmlInput := `<p>Hello <b>World</b></p>`
89 stripped := sanitizer.StripTags(htmlInput)
90 fmt.Printf("Stripped: %s\n", stripped)
91
92 // Allow safe tags only
93 mixedInput := `<p>Safe</p> <script>alert('XSS')</script> <b>Bold</b>`
94 safeTags := sanitizer.AllowSafeTags(mixedInput)
95 fmt.Printf("Safe tags only: %s\n", safeTags)
96
97 // Template rendering
98 tmpl := template.Must(template.New("test").Parse(`<div>{{.}}</div>`))
99 tmpl.Execute(os.Stdout, malicious)
100}
π‘ XSS Prevention Best Practices:
- Always escape output: Never trust data coming from users or databases
- Use Content Security Policy: Adds an extra layer of protection
- HttpOnly cookies: Prevents JavaScript from accessing sensitive cookies
- Validate input: Stop malicious input before it enters your system
β οΈ Common XSS Mistakes:
- Using innerHTML: Instead of textContent for setting element content
- DOM-based XSS: Assuming client-side code is safe
- User-generated content: Allowing users to upload HTML/JS files
- Third-party content: Not sanitizing content from external APIs
Now let's tackle SQL Injection - one of the most devastating vulnerabilities that can compromise your entire database.
SQL Injection Prevention
SQL injection is like giving a stranger the keys to your house and letting them rearrange the furniture however they want. Attackers can manipulate your database queries to steal data, delete records, or even take over your entire database.
Real-World SQL Injection Disasters:
- TalkTalk: Β£60 million fine after 157,000 customer records stolen
- Heartland Payment Systems: 130 million credit card numbers exposed
- Sony Pictures: 77 million accounts compromised, massive data breach
- Adult FriendFinder: 412 million accounts exposed
How SQL Injection Works:
1-- Normal query
2SELECT * FROM users WHERE username = 'alice'
3
4-- Malicious input: alice' OR '1'='1
5-- Becomes: SELECT * FROM users WHERE username = 'alice' OR '1'='1'
6-- Returns ALL users!
7
8-- Even worse: alice'; DROP TABLE users; --
9-- Becomes: SELECT * FROM users WHERE username = 'alice'; DROP TABLE users; --'
10-- Deletes the entire users table!
Parameterized Queries
Parameterized queries are like having a bouncer at your club who checks IDs carefully. No matter what someone shows them, they only look for the valid format and reject everything else.
1package main
2
3import (
4 "database/sql"
5 "fmt"
6 "log"
7
8 _ "github.com/mattn/go-sqlite3"
9)
10
11// Safe database operations
12type UserDB struct {
13 db *sql.DB
14}
15
16func NewUserDB(db *sql.DB) *UserDB {
17 return &UserDB{db: db}
18}
19
20// β
SAFE: Parameterized query
21func (udb *UserDB) GetUserSafe(username string) (*User, error) {
22 query := "SELECT id, username, email FROM users WHERE username = ?"
23
24 var user User
25 err := udb.db.QueryRow(query, username).Scan(&user.ID, &user.Username, &user.Email)
26 if err != nil {
27 return nil, err
28 }
29
30 return &user, nil
31}
32
33// β DANGEROUS: SQL injection vulnerable
34func (udb *UserDB) GetUserUnsafe(username string) (*User, error) {
35 // NEVER DO THIS!
36 query := fmt.Sprintf("SELECT id, username, email FROM users WHERE username = '%s'", username)
37
38 var user User
39 err := udb.db.QueryRow(query).Scan(&user.ID, &user.Username, &user.Email)
40 if err != nil {
41 return nil, err
42 }
43
44 return &user, nil
45}
46
47// β
SAFE: Multiple parameters
48func (udb *UserDB) SearchUsersSafe(username, email string, minAge int) ([]User, error) {
49 query := `
50 SELECT id, username, email, age
51 FROM users
52 WHERE username LIKE ?
53 AND email LIKE ?
54 AND age >= ?
55 `
56
57 rows, err := udb.db.Query(query, "%"+username+"%", "%"+email+"%", minAge)
58 if err != nil {
59 return nil, err
60 }
61 defer rows.Close()
62
63 var users []User
64 for rows.Next() {
65 var user User
66 if err := rows.Scan(&user.ID, &user.Username, &user.Email, &user.Age); err != nil {
67 return nil, err
68 }
69 users = append(users, user)
70 }
71
72 return users, nil
73}
74
75// β
SAFE: INSERT with parameters
76func (udb *UserDB) CreateUserSafe(username, email, password string) (int64, error) {
77 query := "INSERT INTO users (username, email, password) VALUES (?, ?, ?)"
78
79 result, err := udb.db.Exec(query, username, email, password)
80 if err != nil {
81 return 0, err
82 }
83
84 return result.LastInsertId()
85}
86
87// β
SAFE: UPDATE with parameters
88func (udb *UserDB) UpdateUserSafe(id int, username, email string) error {
89 query := "UPDATE users SET username = ?, email = ? WHERE id = ?"
90
91 _, err := udb.db.Exec(query, username, email, id)
92 return err
93}
94
95// β
SAFE: DELETE with parameters
96func (udb *UserDB) DeleteUserSafe(id int) error {
97 query := "DELETE FROM users WHERE id = ?"
98
99 _, err := udb.db.Exec(query, id)
100 return err
101}
102
103type User struct {
104 ID int
105 Username string
106 Email string
107 Age int
108}
109
110func main() {
111 // Create in-memory database
112 db, err := sql.Open("sqlite3", ":memory:")
113 if err != nil {
114 log.Fatal(err)
115 }
116 defer db.Close()
117
118 // Create table
119 _, err = db.Exec(`
120 CREATE TABLE users (
121 id INTEGER PRIMARY KEY AUTOINCREMENT,
122 username TEXT NOT NULL,
123 email TEXT NOT NULL,
124 password TEXT NOT NULL,
125 age INTEGER
126 )
127 `)
128 if err != nil {
129 log.Fatal(err)
130 }
131
132 userDB := NewUserDB(db)
133
134 // Insert test users
135 userDB.CreateUserSafe("alice", "alice@example.com", "hash123")
136 userDB.CreateUserSafe("bob", "bob@example.com", "hash456")
137
138 // Safe query
139 user, err := userDB.GetUserSafe("alice")
140 if err != nil {
141 log.Fatal(err)
142 }
143 fmt.Printf("Found user: %+v\n", user)
144
145 // SQL injection attempt
146 maliciousInput := "admin' OR '1'='1"
147 user, err = userDB.GetUserSafe(maliciousInput)
148 if err != nil {
149 fmt.Printf("Injection blocked: %v\n", err) // sql: no rows in result set
150 }
151}
π‘ SQL Injection Prevention Rules:
- NEVER concatenate user input into SQL queries
- ALWAYS use parameterized queries
- Use ORM libraries when possible
- Validate input before it reaches the database
- Use least privilege database accounts
β οΈ Common SQL Injection Mistakes:
- String formatting: Using fmt.Sprintf or + for queries
- ORM raw queries: Using raw SQL without parameters
- Dynamic column names: Not validating table/column names
- Stored procedures: Still vulnerable if not parameterized
Dynamic Queries
When you need dynamic column names or table names, use whitelisting:
1package main
2
3import (
4 "database/sql"
5 "errors"
6 "fmt"
7 "strings"
8)
9
10// QueryBuilder builds safe dynamic queries
11type QueryBuilder struct {
12 allowedTables map[string]bool
13 allowedColumns map[string]bool
14}
15
16func NewQueryBuilder() *QueryBuilder {
17 return &QueryBuilder{
18 allowedTables: map[string]bool{
19 "users": true,
20 "products": true,
21 "orders": true,
22 },
23 allowedColumns: map[string]bool{
24 "id": true,
25 "username": true,
26 "email": true,
27 "age": true,
28 "name": true,
29 "price": true,
30 },
31 }
32}
33
34// ValidateIdentifier checks if identifier is allowed
35func (qb *QueryBuilder) ValidateIdentifier(identifier string, allowed map[string]bool) error {
36 if !allowed[identifier] {
37 return fmt.Errorf("invalid identifier: %s", identifier)
38 }
39 return nil
40}
41
42// BuildSelect creates a safe SELECT query
43func (qb *QueryBuilder) BuildSelect(table string, columns []string, where map[string]interface{}) (string, []interface{}, error) {
44 // Validate table name
45 if err := qb.ValidateIdentifier(table, qb.allowedTables); err != nil {
46 return "", nil, err
47 }
48
49 // Validate column names
50 for _, col := range columns {
51 if err := qb.ValidateIdentifier(col, qb.allowedColumns); err != nil {
52 return "", nil, err
53 }
54 }
55
56 // Build column list
57 columnList := strings.Join(columns, ", ")
58
59 // Build WHERE clause
60 var whereClauses []string
61 var args []interface{}
62
63 for col, val := range where {
64 if err := qb.ValidateIdentifier(col, qb.allowedColumns); err != nil {
65 return "", nil, err
66 }
67 whereClauses = append(whereClauses, fmt.Sprintf("%s = ?", col))
68 args = append(args, val)
69 }
70
71 whereClause := ""
72 if len(whereClauses) > 0 {
73 whereClause = " WHERE " + strings.Join(whereClauses, " AND ")
74 }
75
76 query := fmt.Sprintf("SELECT %s FROM %s%s", columnList, table, whereClause)
77 return query, args, nil
78}
79
80func main() {
81 qb := NewQueryBuilder()
82
83 // Safe dynamic query
84 query, args, err := qb.BuildSelect(
85 "users",
86 []string{"id", "username", "email"},
87 map[string]interface{}{
88 "username": "alice",
89 "age": 25,
90 },
91 )
92
93 if err != nil {
94 fmt.Printf("Error: %v\n", err)
95 } else {
96 fmt.Printf("Query: %s\n", query)
97 fmt.Printf("Args: %v\n", args)
98 }
99
100 // Attempt with invalid table
101 _, _, err = qb.BuildSelect(
102 "admin_secrets", // Not in whitelist
103 []string{"password"},
104 nil,
105 )
106 fmt.Printf("Invalid table: %v\n", err)
107}
Authentication and Password Security
Authentication is like the front door to your application. If the lock is weak, attackers can easily break in and steal everything inside. Password security is critical because weak or poorly stored passwords are one of the most common ways attackers gain access.
Real-World Authentication Disasters:
- LinkedIn: 6.5 million passwords leaked because they used unsalted SHA1 hashes
- Adobe: 150 million passwords stored in plain text
- RockYou: 32 million passwords stored in plain text, showed "123456" was the most common password
Password Hashing with bcrypt
Think of password hashing like a one-way meat grinder. You can put meat in and get ground meat out, but you can never reverse the process to get the original meat back. bcrypt is designed to be slow and computationally expensive, making brute force attacks impractical.
Why bcrypt is superior to other methods:
- MD5/SHA1: Too fast, vulnerable to rainbow tables and GPU cracking
- Plain text: Obviously terrible - anyone who sees the database has all passwords
- bcrypt: Slow, includes salt, resistant to GPU/ASIC attacks
1package main
2
3import (
4 "errors"
5 "fmt"
6
7 "golang.org/x/crypto/bcrypt"
8)
9
10// AuthService handles authentication
11type AuthService struct {
12 cost int // bcrypt cost
13}
14
15func NewAuthService() *AuthService {
16 return &AuthService{cost: 12}
17}
18
19// HashPassword securely hashes a password
20func (as *AuthService) HashPassword(password string) (string, error) {
21 // Validate password strength
22 if len(password) < 8 {
23 return "", errors.New("password too short")
24 }
25
26 // Hash with bcrypt
27 hash, err := bcrypt.GenerateFromPassword([]byte(password), as.cost)
28 if err != nil {
29 return "", err
30 }
31
32 return string(hash), nil
33}
34
35// VerifyPassword checks if password matches hash
36func (as *AuthService) VerifyPassword(hash, password string) error {
37 return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
38}
39
40// β
SAFE: Registration
41func (as *AuthService) Register(username, password string) error {
42 // Hash password
43 hash, err := as.HashPassword(password)
44 if err != nil {
45 return err
46 }
47
48 // Store hash in database
49 fmt.Printf("Storing user %s with hash: %s\n", username, hash[:20]+"...")
50
51 return nil
52}
53
54// β
SAFE: Login
55func (as *AuthService) Login(username, password string) error {
56 // Retrieve hash from database
57 storedHash := "$2a$12$..." // From database
58
59 // Verify password
60 if err := as.VerifyPassword(storedHash, password); err != nil {
61 return errors.New("invalid credentials")
62 }
63
64 fmt.Printf("User %s logged in successfully\n", username)
65 return nil
66}
67
68func main() {
69 auth := NewAuthService()
70
71 // Register user
72 password := "SecurePassword123"
73 hash, err := auth.HashPassword(password)
74 if err != nil {
75 panic(err)
76 }
77
78 fmt.Printf("Password: %s\n", password)
79 fmt.Printf("Hash: %s\n", hash)
80
81 // Verify correct password
82 if err := auth.VerifyPassword(hash, password); err != nil {
83 fmt.Println("Verification failed")
84 } else {
85 fmt.Println("Password verified!")
86 }
87
88 // Verify incorrect password
89 if err := auth.VerifyPassword(hash, "WrongPassword"); err != nil {
90 fmt.Println("Wrong password rejected")
91 }
92}
π‘ Password Security Best Practices:
- Use bcrypt with cost factor 10-12 for production
- Never store plain text passwords - ever!
- Use unique salts (bcrypt handles this automatically)
- Implement rate limiting to prevent brute force attacks
- Require strong passwords
β οΈ Common Password Mistakes:
- Weak algorithms: MD5, SHA1, or custom hashing functions
- No salt: Same password = same hash
- Low cost factors: bcrypt cost < 10 is too fast for modern hardware
- Plain text storage: Shockingly common in legacy systems
- Password complexity rules: "Password123!" meets complexity but is still weak
Now let's explore token-based authentication with JWTs, which is essential for modern APIs and microservices.
JWT Authentication
1package main
2
3import (
4 "errors"
5 "fmt"
6 "time"
7
8 "github.com/golang-jwt/jwt/v5"
9)
10
11// JWTService handles JWT tokens
12type JWTService struct {
13 secret []byte
14 issuer string
15}
16
17func NewJWTService(secret, issuer string) *JWTService {
18 return &JWTService{
19 secret: []byte(secret),
20 issuer: issuer,
21 }
22}
23
24// Claims represents JWT claims
25type Claims struct {
26 UserID int `json:"user_id"`
27 Username string `json:"username"`
28 Role string `json:"role"`
29 jwt.RegisteredClaims
30}
31
32// GenerateToken creates a new JWT token
33func (js *JWTService) GenerateToken(userID int, username, role string) (string, error) {
34 claims := &Claims{
35 UserID: userID,
36 Username: username,
37 Role: role,
38 RegisteredClaims: jwt.RegisteredClaims{
39 ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
40 IssuedAt: jwt.NewNumericDate(time.Now()),
41 NotBefore: jwt.NewNumericDate(time.Now()),
42 Issuer: js.issuer,
43 },
44 }
45
46 token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
47 return token.SignedString(js.secret)
48}
49
50// ValidateToken verifies and parses a JWT token
51func (js *JWTService) ValidateToken(tokenString string) (*Claims, error) {
52 token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
53 // Verify signing method
54 if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
55 return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
56 }
57 return js.secret, nil
58 })
59
60 if err != nil {
61 return nil, err
62 }
63
64 if claims, ok := token.Claims.(*Claims); ok && token.Valid {
65 return claims, nil
66 }
67
68 return nil, errors.New("invalid token")
69}
70
71// RefreshToken generates a new token from an existing valid token
72func (js *JWTService) RefreshToken(tokenString string) (string, error) {
73 claims, err := js.ValidateToken(tokenString)
74 if err != nil {
75 return "", err
76 }
77
78 // Generate new token with same claims but new expiry
79 return js.GenerateToken(claims.UserID, claims.Username, claims.Role)
80}
81
82func main() {
83 jwtService := NewJWTService("your-secret-key", "myapp.com")
84
85 // Generate token
86 token, err := jwtService.GenerateToken(1, "alice", "admin")
87 if err != nil {
88 panic(err)
89 }
90
91 fmt.Printf("Token: %s\n", token)
92
93 // Validate token
94 claims, err := jwtService.ValidateToken(token)
95 if err != nil {
96 panic(err)
97 }
98
99 fmt.Printf("Claims: UserID=%d, Username=%s, Role=%s\n",
100 claims.UserID, claims.Username, claims.Role)
101
102 // Refresh token
103 newToken, err := jwtService.RefreshToken(token)
104 if err != nil {
105 panic(err)
106 }
107
108 fmt.Printf("Refreshed token: %s\n", newToken[:50]+"...")
109}
Secrets Management
NEVER hardcode secrets in source code!
Environment Variables
1package main
2
3import (
4 "fmt"
5 "log"
6 "os"
7
8 "github.com/joho/godotenv"
9)
10
11// Config holds application configuration
12type Config struct {
13 DatabaseURL string
14 APIKey string
15 JWTSecret string
16 Environment string
17}
18
19// LoadConfig loads configuration from environment
20func LoadConfig() (*Config, error) {
21 // Load .env file in development
22 if os.Getenv("ENVIRONMENT") != "production" {
23 if err := godotenv.Load(); err != nil {
24 log.Println("No .env file found")
25 }
26 }
27
28 config := &Config{
29 DatabaseURL: os.Getenv("DATABASE_URL"),
30 APIKey: os.Getenv("API_KEY"),
31 JWTSecret: os.Getenv("JWT_SECRET"),
32 Environment: os.Getenv("ENVIRONMENT"),
33 }
34
35 // Validate required secrets
36 if config.DatabaseURL == "" {
37 return nil, fmt.Errorf("DATABASE_URL is required")
38 }
39 if config.JWTSecret == "" {
40 return nil, fmt.Errorf("JWT_SECRET is required")
41 }
42
43 return config, nil
44}
45
46func main() {
47 config, err := LoadConfig()
48 if err != nil {
49 log.Fatal(err)
50 }
51
52 fmt.Printf("Environment: %s\n", config.Environment)
53 fmt.Printf("Database URL: %s\n", maskSecret(config.DatabaseURL))
54 fmt.Printf("JWT Secret: %s\n", maskSecret(config.JWTSecret))
55}
56
57func maskSecret(secret string) string {
58 if len(secret) <= 4 {
59 return "****"
60 }
61 return secret[:4] + "****"
62}
.env file:
DATABASE_URL=postgres://user:pass@localhost/db
API_KEY=sk_live_abcdef123456
JWT_SECRET=super-secret-key-change-me
ENVIRONMENT=development
.gitignore:
.env
.env.local
.env.production
*.pem
*.key
secrets/
HashiCorp Vault Integration
1package main
2
3import (
4 "context"
5 "fmt"
6 "log"
7
8 vault "github.com/hashicorp/vault/api"
9)
10
11// VaultClient wraps Vault API
12type VaultClient struct {
13 client *vault.Client
14}
15
16// NewVaultClient creates a Vault client
17func NewVaultClient(address, token string) (*VaultClient, error) {
18 config := vault.DefaultConfig()
19 config.Address = address
20
21 client, err := vault.NewClient(config)
22 if err != nil {
23 return nil, err
24 }
25
26 client.SetToken(token)
27
28 return &VaultClient{client: client}, nil
29}
30
31// GetSecret retrieves a secret from Vault
32func (vc *VaultClient) GetSecret(path, key string) (string, error) {
33 secret, err := vc.client.Logical().Read(path)
34 if err != nil {
35 return "", err
36 }
37
38 if secret == nil {
39 return "", fmt.Errorf("secret not found: %s", path)
40 }
41
42 value, ok := secret.Data[key].(string)
43 if !ok {
44 return "", fmt.Errorf("key not found: %s", key)
45 }
46
47 return value, nil
48}
49
50// SetSecret stores a secret in Vault
51func (vc *VaultClient) SetSecret(path string, data map[string]interface{}) error {
52 _, err := vc.client.Logical().Write(path, data)
53 return err
54}
55
56func main() {
57 // Connect to Vault
58 vaultClient, err := NewVaultClient("http://localhost:8200", "root-token")
59 if err != nil {
60 log.Fatal(err)
61 }
62
63 // Store secret
64 err = vaultClient.SetSecret("secret/myapp/db", map[string]interface{}{
65 "username": "dbuser",
66 "password": "dbpass123",
67 })
68 if err != nil {
69 log.Fatal(err)
70 }
71
72 // Retrieve secret
73 password, err := vaultClient.GetSecret("secret/myapp/db", "password")
74 if err != nil {
75 log.Fatal(err)
76 }
77
78 fmt.Printf("Retrieved password: %s\n", maskSecret(password))
79}
Security Headers
Essential HTTP security headers for web applications:
1package main
2
3import (
4 "net/http"
5)
6
7// SecurityHeaders middleware adds security headers
8func SecurityHeaders(next http.Handler) http.Handler {
9 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
10 // Prevent clickjacking
11 w.Header().Set("X-Frame-Options", "DENY")
12
13 // XSS protection
14 w.Header().Set("X-XSS-Protection", "1; mode=block")
15
16 // Content type sniffing protection
17 w.Header().Set("X-Content-Type-Options", "nosniff")
18
19 // Referrer policy
20 w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
21
22 // Content Security Policy
23 csp := "default-src 'self'; " +
24 "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " +
25 "style-src 'self' 'unsafe-inline'; " +
26 "img-src 'self' data: https:; " +
27 "font-src 'self'; " +
28 "connect-src 'self'; " +
29 "frame-ancestors 'none'"
30 w.Header().Set("Content-Security-Policy", csp)
31
32 // HSTS
33 w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
34
35 // Permissions policy
36 w.Header().Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()")
37
38 next.ServeHTTP(w, r)
39 })
40}
41
42// CORS middleware
43func CORS(allowedOrigins []string) func(http.Handler) http.Handler {
44 return func(next http.Handler) http.Handler {
45 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
46 origin := r.Header.Get("Origin")
47
48 // Check if origin is allowed
49 allowed := false
50 for _, allowedOrigin := range allowedOrigins {
51 if origin == allowedOrigin {
52 allowed = true
53 break
54 }
55 }
56
57 if allowed {
58 w.Header().Set("Access-Control-Allow-Origin", origin)
59 w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
60 w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
61 w.Header().Set("Access-Control-Max-Age", "3600")
62 }
63
64 // Handle preflight
65 if r.Method == "OPTIONS" {
66 w.WriteHeader(http.StatusNoContent)
67 return
68 }
69
70 next.ServeHTTP(w, r)
71 })
72 }
73}
74
75func main() {
76 mux := http.NewServeMux()
77
78 mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
79 w.Write([]byte("Secure response"))
80 })
81
82 // Apply security middleware
83 handler := SecurityHeaders(mux)
84 handler = CORS([]string{"https://example.com"})(handler)
85
86 http.ListenAndServe(":8080", handler)
87}
Threat Modeling
Threat modeling is the process of identifying potential security threats before they become real vulnerabilities. Think of it like a security review before launching a product.
STRIDE Framework
STRIDE is a widely-used threat modeling framework developed by Microsoft:
- Spoofing - Impersonating someone or something
- Tampering - Modifying data or code
- Repudiation - Claiming not to have performed an action
- Information Disclosure - Exposing information to unauthorized parties
- Denial of Service - Preventing legitimate users from accessing the system
- Elevation of Privilege - Gaining unauthorized capabilities
1// run
2package main
3
4import (
5 "fmt"
6 "strings"
7)
8
9// ThreatCategory represents a STRIDE category
10type ThreatCategory string
11
12const (
13 Spoofing ThreatCategory = "Spoofing"
14 Tampering ThreatCategory = "Tampering"
15 Repudiation ThreatCategory = "Repudiation"
16 InformationDisclosure ThreatCategory = "Information Disclosure"
17 DenialOfService ThreatCategory = "Denial of Service"
18 ElevationOfPrivilege ThreatCategory = "Elevation of Privilege"
19)
20
21// Threat represents a security threat
22type Threat struct {
23 Category ThreatCategory
24 Description string
25 Impact string
26 Likelihood string
27 Mitigation string
28}
29
30// ThreatModel represents a complete threat model
31type ThreatModel struct {
32 Component string
33 Threats []Threat
34}
35
36// NewThreatModel creates a new threat model
37func NewThreatModel(component string) *ThreatModel {
38 return &ThreatModel{
39 Component: component,
40 Threats: make([]Threat, 0),
41 }
42}
43
44// AddThreat adds a threat to the model
45func (tm *ThreatModel) AddThreat(threat Threat) {
46 tm.Threats = append(tm.Threats, threat)
47}
48
49// GenerateReport generates a threat model report
50func (tm *ThreatModel) GenerateReport() string {
51 var report strings.Builder
52
53 report.WriteString(fmt.Sprintf("=== Threat Model: %s ===\n\n", tm.Component))
54
55 for i, threat := range tm.Threats {
56 report.WriteString(fmt.Sprintf("Threat #%d: %s\n", i+1, threat.Category))
57 report.WriteString(fmt.Sprintf("Description: %s\n", threat.Description))
58 report.WriteString(fmt.Sprintf("Impact: %s\n", threat.Impact))
59 report.WriteString(fmt.Sprintf("Likelihood: %s\n", threat.Likelihood))
60 report.WriteString(fmt.Sprintf("Mitigation: %s\n", threat.Mitigation))
61 report.WriteString("\n")
62 }
63
64 return report.String()
65}
66
67func main() {
68 // Example: User Authentication System
69 authModel := NewThreatModel("User Authentication System")
70
71 // Spoofing threats
72 authModel.AddThreat(Threat{
73 Category: Spoofing,
74 Description: "Attacker impersonates legitimate user with stolen credentials",
75 Impact: "High - Unauthorized access to user account",
76 Likelihood: "Medium - Phishing and credential stuffing attacks common",
77 Mitigation: "Implement MFA, monitor for anomalous login patterns, use strong password policies",
78 })
79
80 // Tampering threats
81 authModel.AddThreat(Threat{
82 Category: Tampering,
83 Description: "JWT token modified to elevate privileges",
84 Impact: "Critical - Attacker gains admin access",
85 Likelihood: "Low - Requires knowledge of signing algorithm",
86 Mitigation: "Use signed JWTs with strong secrets, validate signature on every request",
87 })
88
89 // Repudiation threats
90 authModel.AddThreat(Threat{
91 Category: Repudiation,
92 Description: "User denies performing malicious actions",
93 Impact: "Medium - Unable to trace security incidents",
94 Likelihood: "Medium - Common in insider threat scenarios",
95 Mitigation: "Implement comprehensive audit logging with tamper-proof storage",
96 })
97
98 // Information Disclosure threats
99 authModel.AddThreat(Threat{
100 Category: InformationDisclosure,
101 Description: "Password hashes exposed through database breach",
102 Impact: "High - User passwords may be cracked",
103 Likelihood: "Medium - Database breaches occur regularly",
104 Mitigation: "Use bcrypt with high cost factor, implement encryption at rest",
105 })
106
107 // Denial of Service threats
108 authModel.AddThreat(Threat{
109 Category: DenialOfService,
110 Description: "Brute force attack overwhelms authentication service",
111 Impact: "High - Legitimate users unable to login",
112 Likelihood: "High - Automated tools make brute force easy",
113 Mitigation: "Implement rate limiting, account lockout after failed attempts, CAPTCHA",
114 })
115
116 // Elevation of Privilege threats
117 authModel.AddThreat(Threat{
118 Category: ElevationOfPrivilege,
119 Description: "Regular user exploits vulnerability to gain admin access",
120 Impact: "Critical - Full system compromise",
121 Likelihood: "Low - Requires finding specific vulnerability",
122 Mitigation: "Implement principle of least privilege, regular security audits, input validation",
123 })
124
125 // Generate and print report
126 fmt.Println(authModel.GenerateReport())
127
128 // Print summary statistics
129 fmt.Printf("Total threats identified: %d\n", len(authModel.Threats))
130
131 criticalCount := 0
132 for _, threat := range authModel.Threats {
133 if strings.Contains(threat.Impact, "Critical") {
134 criticalCount++
135 }
136 }
137 fmt.Printf("Critical threats: %d\n", criticalCount)
138}
Security Testing
Security testing should be integrated into your development workflow, not treated as an afterthought.
Static Analysis with gosec
1# Install gosec
2go install github.com/securego/gosec/v2/cmd/gosec@latest
3
4# Run security scan
5gosec ./...
6
7# Generate report
8gosec -fmt=json -out=security-report.json ./...
Dependency Scanning
1# Install nancy
2go install github.com/sonatype-nexus-community/nancy@latest
3
4# Scan dependencies
5go list -json -m all | nancy sleuth
6
7# Check for known vulnerabilities
8go mod download
9nancy sleuth
Penetration Testing Checklist
Before Production:
- Run automated security scanners (gosec, nancy)
- Review all authentication and authorization code
- Test all input validation thoroughly
- Verify secrets are not in source code
- Check security headers are properly set
- Test rate limiting on all endpoints
- Review error messages for information leakage
- Verify encryption is used for sensitive data
- Test session management and timeout
- Perform SQL injection testing
- Test XSS vulnerabilities
- Verify CSRF protection
- Check for path traversal vulnerabilities
- Test file upload security
- Verify API authentication
- Test privilege escalation scenarios
Compliance and Standards
Different industries have different compliance requirements:
PCI DSS (Payment Card Industry)
Key requirements:
- Encrypt transmission of cardholder data
- Maintain vulnerability management program
- Implement strong access control measures
- Regularly monitor and test networks
- Maintain information security policy
1// run
2package main
3
4import (
5 "crypto/aes"
6 "crypto/cipher"
7 "crypto/rand"
8 "encoding/base64"
9 "fmt"
10 "io"
11)
12
13// CreditCardEncryption handles PCI DSS compliant encryption
14type CreditCardEncryption struct {
15 key []byte
16}
17
18func NewCreditCardEncryption(key []byte) *CreditCardEncryption {
19 return &CreditCardEncryption{key: key}
20}
21
22// Encrypt encrypts credit card data
23func (cce *CreditCardEncryption) Encrypt(plaintext string) (string, error) {
24 block, err := aes.NewCipher(cce.key)
25 if err != nil {
26 return "", err
27 }
28
29 // Generate random IV
30 ciphertext := make([]byte, aes.BlockSize+len(plaintext))
31 iv := ciphertext[:aes.BlockSize]
32 if _, err := io.ReadFull(rand.Reader, iv); err != nil {
33 return "", err
34 }
35
36 stream := cipher.NewCFBEncrypter(block, iv)
37 stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(plaintext))
38
39 return base64.URLEncoding.EncodeToString(ciphertext), nil
40}
41
42// Decrypt decrypts credit card data
43func (cce *CreditCardEncryption) Decrypt(ciphertext string) (string, error) {
44 data, err := base64.URLEncoding.DecodeString(ciphertext)
45 if err != nil {
46 return "", err
47 }
48
49 block, err := aes.NewCipher(cce.key)
50 if err != nil {
51 return "", err
52 }
53
54 if len(data) < aes.BlockSize {
55 return "", fmt.Errorf("ciphertext too short")
56 }
57
58 iv := data[:aes.BlockSize]
59 data = data[aes.BlockSize:]
60
61 stream := cipher.NewCFBDecrypter(block, iv)
62 stream.XORKeyStream(data, data)
63
64 return string(data), nil
65}
66
67// MaskCardNumber masks credit card number for display
68func MaskCardNumber(cardNumber string) string {
69 if len(cardNumber) < 4 {
70 return "****"
71 }
72 return "************" + cardNumber[len(cardNumber)-4:]
73}
74
75func main() {
76 fmt.Println("=== PCI DSS Compliant Credit Card Handling ===\n")
77
78 // Generate or load encryption key (must be 32 bytes for AES-256)
79 key := make([]byte, 32)
80 if _, err := rand.Read(key); err != nil {
81 panic(err)
82 }
83
84 cce := NewCreditCardEncryption(key)
85
86 // Credit card number (example)
87 cardNumber := "4532015112830366"
88
89 // Encrypt for storage
90 encrypted, err := cce.Encrypt(cardNumber)
91 if err != nil {
92 panic(err)
93 }
94
95 fmt.Printf("Original: %s\n", cardNumber)
96 fmt.Printf("Encrypted: %s\n", encrypted[:20]+"...")
97 fmt.Printf("Masked for display: %s\n", MaskCardNumber(cardNumber))
98
99 // Decrypt when needed
100 decrypted, err := cce.Decrypt(encrypted)
101 if err != nil {
102 panic(err)
103 }
104
105 fmt.Printf("Decrypted: %s\n", decrypted)
106 fmt.Printf("\nβ Credit card data encrypted with AES-256\n")
107 fmt.Printf("β Only last 4 digits shown to users\n")
108 fmt.Printf("β Encryption key stored securely (not in code)\n")
109}
GDPR (General Data Protection Regulation)
Key principles:
- Lawfulness, fairness, and transparency
- Purpose limitation
- Data minimization
- Accuracy
- Storage limitation
- Integrity and confidentiality
- Accountability
1// run
2package main
3
4import (
5 "crypto/sha256"
6 "encoding/hex"
7 "fmt"
8 "time"
9)
10
11// PersonalData represents GDPR-regulated personal data
12type PersonalData struct {
13 UserID string
14 Email string
15 Name string
16 Address string
17 Phone string
18 CreatedAt time.Time
19 ConsentGiven bool
20 ConsentDate time.Time
21}
22
23// GDPRCompliance handles GDPR requirements
24type GDPRCompliance struct {
25 retentionPeriod time.Duration
26}
27
28func NewGDPRCompliance(retentionDays int) *GDPRCompliance {
29 return &GDPRCompliance{
30 retentionPeriod: time.Duration(retentionDays) * 24 * time.Hour,
31 }
32}
33
34// AnonymizeData pseudonymizes personal data
35func (gc *GDPRCompliance) AnonymizeData(data *PersonalData) *PersonalData {
36 // Hash identifiable information
37 emailHash := sha256.Sum256([]byte(data.Email))
38 phoneHash := sha256.Sum256([]byte(data.Phone))
39
40 return &PersonalData{
41 UserID: data.UserID,
42 Email: hex.EncodeToString(emailHash[:]),
43 Name: "REDACTED",
44 Address: "REDACTED",
45 Phone: hex.EncodeToString(phoneHash[:]),
46 CreatedAt: data.CreatedAt,
47 ConsentGiven: data.ConsentGiven,
48 ConsentDate: data.ConsentDate,
49 }
50}
51
52// ShouldDelete checks if data should be deleted per retention policy
53func (gc *GDPRCompliance) ShouldDelete(data *PersonalData) bool {
54 return time.Since(data.CreatedAt) > gc.retentionPeriod
55}
56
57// RightToErasure implements GDPR right to be forgotten
58func (gc *GDPRCompliance) RightToErasure(userID string) error {
59 fmt.Printf("Deleting all personal data for user %s\n", userID)
60 // In production: delete from all systems, databases, backups
61 return nil
62}
63
64// DataPortability exports user data per GDPR requirements
65func (gc *GDPRCompliance) DataPortability(userID string) (map[string]interface{}, error) {
66 // Export all user data in machine-readable format
67 export := map[string]interface{}{
68 "user_id": userID,
69 "email": "user@example.com",
70 "name": "John Doe",
71 "created_at": time.Now(),
72 "data": map[string]interface{}{
73 "preferences": "...",
74 "history": "...",
75 },
76 }
77 return export, nil
78}
79
80func main() {
81 fmt.Println("=== GDPR Compliance System ===\n")
82
83 gdpr := NewGDPRCompliance(30) // 30-day retention
84
85 // Original personal data
86 userData := &PersonalData{
87 UserID: "user123",
88 Email: "john@example.com",
89 Name: "John Doe",
90 Address: "123 Main St",
91 Phone: "+1234567890",
92 CreatedAt: time.Now().AddDate(0, -2, 0), // 2 months ago
93 ConsentGiven: true,
94 ConsentDate: time.Now().AddDate(0, -2, 0),
95 }
96
97 fmt.Println("Original Data:")
98 fmt.Printf("Email: %s\n", userData.Email)
99 fmt.Printf("Name: %s\n", userData.Name)
100 fmt.Printf("Phone: %s\n\n", userData.Phone)
101
102 // Anonymize data
103 anonymized := gdpr.AnonymizeData(userData)
104 fmt.Println("Anonymized Data:")
105 fmt.Printf("Email Hash: %s\n", anonymized.Email[:20]+"...")
106 fmt.Printf("Name: %s\n", anonymized.Name)
107 fmt.Printf("Phone Hash: %s\n\n", anonymized.Phone[:20]+"...")
108
109 // Check retention policy
110 if gdpr.ShouldDelete(userData) {
111 fmt.Println("β Data exceeds retention period - should be deleted")
112 }
113
114 // Export user data
115 export, _ := gdpr.DataPortability(userData.UserID)
116 fmt.Printf("\nβ Data export available for user: %v\n", export["user_id"])
117
118 // Right to erasure
119 gdpr.RightToErasure(userData.UserID)
120 fmt.Println("β User data deleted per GDPR right to erasure")
121}
HIPAA (Healthcare)
Key requirements:
- Protect electronic health information
- Implement physical safeguards
- Implement technical safeguards
- Ensure confidentiality, integrity, and availability
- Audit and monitor access
Further Reading
Standards
Go-Specific
Tools
Practice Exercises
Exercise 1: Build a Rate Limiter
Learning Objective: Design and implement robust rate limiting systems to protect APIs from brute-force attacks and abuse.
Context: Rate limiting is essential for protecting APIs from abuse and ensuring fair usage. Major breaches like the 2016 Dyn DDoS attack were exacerbated by insufficient rate limiting, causing widespread internet outages. Companies like Cloudflare process billions of requests daily through sophisticated rate limiting systems.
Difficulty: Intermediate | Time: 20-25 minutes
Implement a comprehensive rate limiting system to prevent brute-force attacks and protect API endpoints:
- Design token bucket algorithm with configurable rates
- Implement sliding window counters for precise rate control
- Handle IPv4 and IPv6 addresses correctly
- Support multiple rate limits per user/IP
- Include proper cleanup and memory management
- Provide metrics for monitoring rate limit effectiveness
Real-world application: This pattern protects login endpoints, API gateways, and public services from credential stuffing attacks, DDoS attempts, and resource abuse.
Solution
1package main
2
3import (
4 "fmt"
5 "net/http"
6 "sync"
7 "time"
8)
9
10// RateLimiter implements token bucket rate limiting
11type RateLimiter struct {
12 mu sync.Mutex
13 buckets map[string]*bucket
14 rate int // requests per window
15 window time.Duration
16 cleanup time.Duration
17}
18
19type bucket struct {
20 tokens int
21 lastRefill time.Time
22}
23
24func NewRateLimiter(rate int, window time.Duration) *RateLimiter {
25 rl := &RateLimiter{
26 buckets: make(map[string]*bucket),
27 rate: rate,
28 window: window,
29 cleanup: time.Minute,
30 }
31
32 go rl.cleanupBuckets()
33 return rl
34}
35
36func (rl *RateLimiter) Allow(key string) bool {
37 rl.mu.Lock()
38 defer rl.mu.Unlock()
39
40 b, exists := rl.buckets[key]
41 if !exists {
42 b = &bucket{
43 tokens: rl.rate,
44 lastRefill: time.Now(),
45 }
46 rl.buckets[key] = b
47 }
48
49 // Refill tokens based on elapsed time
50 now := time.Now()
51 elapsed := now.Sub(b.lastRefill)
52
53 if elapsed >= rl.window {
54 b.tokens = rl.rate
55 b.lastRefill = now
56 }
57
58 if b.tokens > 0 {
59 b.tokens--
60 return true
61 }
62
63 return false
64}
65
66func (rl *RateLimiter) cleanupBuckets() {
67 ticker := time.NewTicker(rl.cleanup)
68 defer ticker.Stop()
69
70 for range ticker.C {
71 rl.mu.Lock()
72 now := time.Now()
73
74 for key, b := range rl.buckets {
75 if now.Sub(b.lastRefill) > rl.window*2 {
76 delete(rl.buckets, key)
77 }
78 }
79
80 rl.mu.Unlock()
81 }
82}
83
84// Middleware
85func RateLimitMiddleware(limiter *RateLimiter) func(http.Handler) http.Handler {
86 return func(next http.Handler) http.Handler {
87 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
88 // Use IP address as key
89 key := r.RemoteAddr
90
91 if !limiter.Allow(key) {
92 http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
93 return
94 }
95
96 next.ServeHTTP(w, r)
97 })
98 }
99}
100
101func main() {
102 limiter := NewRateLimiter(5, time.Minute)
103
104 mux := http.NewServeMux()
105 mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
106 w.Write([]byte("Request successful"))
107 })
108
109 handler := RateLimitMiddleware(limiter)(mux)
110
111 fmt.Println("Server running on :8080")
112 http.ListenAndServe(":8080", handler)
113}
Exercise 2: Implement CSRF Protection
Learning Objective: Build comprehensive CSRF protection middleware to prevent state-changing requests from unauthorized websites.
Context: CSRF attacks have been responsible for major security breaches, including the 2009 Gmail CSRF vulnerability that allowed attackers to read user emails. The OWASP Top 10 consistently ranks CSRF as a critical web application vulnerability that every developer must understand and prevent.
Difficulty: Advanced | Time: 25-30 minutes
Build production-grade CSRF protection middleware that prevents malicious websites from making unauthorized requests on behalf of authenticated users:
- Generate cryptographically secure CSRF tokens
- Implement double submit cookie pattern
- Support both cookie-based and session-based token storage
- Handle AJAX requests with proper header validation
- Include token refresh and expiration mechanisms
- Provide proper error handling and logging
Real-world application: This protection is essential for banking applications, social media platforms, and any web application that handles user authentication and sensitive operations.
Solution
1package main
2
3import (
4 "crypto/rand"
5 "encoding/base64"
6 "fmt"
7 "net/http"
8 "sync"
9 "time"
10)
11
12// CSRFProtection implements CSRF token validation
13type CSRFProtection struct {
14 mu sync.RWMutex
15 tokens map[string]time.Time
16 maxAge time.Duration
17}
18
19func NewCSRFProtection(maxAge time.Duration) *CSRFProtection {
20 csrf := &CSRFProtection{
21 tokens: make(map[string]time.Time),
22 maxAge: maxAge,
23 }
24
25 go csrf.cleanup()
26 return csrf
27}
28
29// GenerateToken creates a new CSRF token
30func (csrf *CSRFProtection) GenerateToken() (string, error) {
31 bytes := make([]byte, 32)
32 if _, err := rand.Read(bytes); err != nil {
33 return "", err
34 }
35
36 token := base64.URLEncoding.EncodeToString(bytes)
37
38 csrf.mu.Lock()
39 csrf.tokens[token] = time.Now().Add(csrf.maxAge)
40 csrf.mu.Unlock()
41
42 return token, nil
43}
44
45// ValidateToken checks if token is valid
46func (csrf *CSRFProtection) ValidateToken(token string) bool {
47 csrf.mu.RLock()
48 defer csrf.mu.RUnlock()
49
50 expiry, exists := csrf.tokens[token]
51 if !exists {
52 return false
53 }
54
55 return time.Now().Before(expiry)
56}
57
58// DeleteToken removes a token
59func (csrf *CSRFProtection) DeleteToken(token string) {
60 csrf.mu.Lock()
61 delete(csrf.tokens, token)
62 csrf.mu.Unlock()
63}
64
65func (csrf *CSRFProtection) cleanup() {
66 ticker := time.NewTicker(time.Minute)
67 defer ticker.Stop()
68
69 for range ticker.C {
70 csrf.mu.Lock()
71 now := time.Now()
72
73 for token, expiry := range csrf.tokens {
74 if now.After(expiry) {
75 delete(csrf.tokens, token)
76 }
77 }
78
79 csrf.mu.Unlock()
80 }
81}
82
83// Middleware
84func CSRFMiddleware(csrf *CSRFProtection) func(http.Handler) http.Handler {
85 return func(next http.Handler) http.Handler {
86 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
87 // Skip CSRF for safe methods
88 if r.Method == "GET" || r.Method == "HEAD" || r.Method == "OPTIONS" {
89 next.ServeHTTP(w, r)
90 return
91 }
92
93 // Validate CSRF token
94 token := r.Header.Get("X-CSRF-Token")
95 if token == "" {
96 token = r.FormValue("csrf_token")
97 }
98
99 if !csrf.ValidateToken(token) {
100 http.Error(w, "Invalid CSRF token", http.StatusForbidden)
101 return
102 }
103
104 next.ServeHTTP(w, r)
105 })
106 }
107}
108
109func main() {
110 csrf := NewCSRFProtection(1 * time.Hour)
111
112 mux := http.NewServeMux()
113
114 // GET endpoint
115 mux.HandleFunc("/form", func(w http.ResponseWriter, r *http.Request) {
116 token, _ := csrf.GenerateToken()
117
118 html := fmt.Sprintf(`
119 <form method="POST" action="/submit">
120 <input type="hidden" name="csrf_token" value="%s">
121 <input type="text" name="data" placeholder="Enter data">
122 <button type="submit">Submit</button>
123 </form>
124 `, token)
125
126 w.Header().Set("Content-Type", "text/html")
127 w.Write([]byte(html))
128 })
129
130 // POST endpoint
131 mux.HandleFunc("/submit", func(w http.ResponseWriter, r *http.Request) {
132 w.Write([]byte("Form submitted successfully"))
133 })
134
135 handler := CSRFMiddleware(csrf)(mux)
136
137 fmt.Println("Server running on :8080")
138 http.ListenAndServe(":8080", handler)
139}
Exercise 3: Content Security Policy Builder
Learning Objective: Create a flexible Content Security Policy builder that prevents XSS attacks and other client-side security vulnerabilities.
Context: CSP is a critical defense layer against XSS attacks, which affect over 70% of web applications. The 2017 British Airways breach, which compromised 380,000 customer records, could have been prevented with proper CSP implementation. Modern browsers enforce CSP as a primary security mechanism.
Difficulty: Advanced | Time: 30-35 minutes
Build a production-ready CSP builder that provides fine-grained control over resource loading and prevents various client-side attacks:
- Design fluent API for constructing complex CSP policies
- Support all major CSP directives with proper validation
- Implement both enforcement and report-only modes
- Include nonce and hash-based CSP generation
- Handle browser compatibility and fallback strategies
- Provide CSP violation monitoring and logging capabilities
Requirements:
- Fluent API for constructing CSP headers
- Support all major CSP directives
- Validate directive values against CSP specification
- Generate CSP header string with proper formatting
- Support both Content-Security-Policy and Report-Only modes
- Include nonce generation for dynamic content
- Provide CSP violation reporting endpoint
Real-world application: CSP implementation is mandatory for financial applications, healthcare systems, and any web application handling sensitive user data or requiring PCI DSS compliance.
Solution with Explanation
1// run
2package main
3
4import (
5 "fmt"
6 "net/http"
7 "strings"
8)
9
10// CSPBuilder builds Content Security Policy headers
11type CSPBuilder struct {
12 directives map[string][]string
13 reportOnly bool
14}
15
16// NewCSPBuilder creates a new CSP builder
17func NewCSPBuilder() *CSPBuilder {
18 return &CSPBuilder{
19 directives: make(map[string][]string),
20 reportOnly: false,
21 }
22}
23
24// ReportOnly sets the policy to report-only mode
25func (csb *CSPBuilder) ReportOnly() *CSPBuilder {
26 csb.reportOnly = true
27 return csb
28}
29
30// DefaultSrc sets the default-src directive
31func (csb *CSPBuilder) DefaultSrc(sources ...string) *CSPBuilder {
32 csb.directives["default-src"] = sources
33 return csb
34}
35
36// ScriptSrc sets the script-src directive
37func (csb *CSPBuilder) ScriptSrc(sources ...string) *CSPBuilder {
38 csb.directives["script-src"] = sources
39 return csb
40}
41
42// StyleSrc sets the style-src directive
43func (csb *CSPBuilder) StyleSrc(sources ...string) *CSPBuilder {
44 csb.directives["style-src"] = sources
45 return csb
46}
47
48// ImgSrc sets the img-src directive
49func (csb *CSPBuilder) ImgSrc(sources ...string) *CSPBuilder {
50 csb.directives["img-src"] = sources
51 return csb
52}
53
54// ConnectSrc sets the connect-src directive
55func (csb *CSPBuilder) ConnectSrc(sources ...string) *CSPBuilder {
56 csb.directives["connect-src"] = sources
57 return csb
58}
59
60// FrameAncestors sets the frame-ancestors directive
61func (csb *CSPBuilder) FrameAncestors(sources ...string) *CSPBuilder {
62 csb.directives["frame-ancestors"] = sources
63 return csb
64}
65
66// ObjectSrc sets the object-src directive
67func (csb *CSPBuilder) ObjectSrc(sources ...string) *CSPBuilder {
68 csb.directives["object-src"] = sources
69 return csb
70}
71
72// UpgradeInsecureRequests adds the upgrade-insecure-requests directive
73func (csb *CSPBuilder) UpgradeInsecureRequests() *CSPBuilder {
74 csb.directives["upgrade-insecure-requests"] = []string{}
75 return csb
76}
77
78// ReportURI sets the report-uri directive
79func (csb *CSPBuilder) ReportURI(uri string) *CSPBuilder {
80 csb.directives["report-uri"] = []string{uri}
81 return csb
82}
83
84// Build generates the CSP header string
85func (csb *CSPBuilder) Build() string {
86 var parts []string
87
88 // Ensure consistent ordering
89 order := []string{
90 "default-src", "script-src", "style-src", "img-src",
91 "connect-src", "object-src", "frame-ancestors",
92 "upgrade-insecure-requests", "report-uri",
93 }
94
95 for _, directive := range order {
96 if sources, ok := csb.directives[directive]; ok {
97 if len(sources) == 0 {
98 // Boolean directive
99 parts = append(parts, directive)
100 } else {
101 // Directive with sources
102 parts = append(parts, directive+" "+strings.Join(sources, " "))
103 }
104 }
105 }
106
107 return strings.Join(parts, "; ")
108}
109
110// HeaderName returns the appropriate CSP header name
111func (csb *CSPBuilder) HeaderName() string {
112 if csb.reportOnly {
113 return "Content-Security-Policy-Report-Only"
114 }
115 return "Content-Security-Policy"
116}
117
118// Apply applies the CSP to an HTTP response
119func (csb *CSPBuilder) Apply(w http.ResponseWriter) {
120 w.Header().Set(csb.HeaderName(), csb.Build())
121}
122
123// Common CSP source values
124const (
125 Self = "'self'"
126 None = "'none'"
127 UnsafeInline = "'unsafe-inline'"
128 UnsafeEval = "'unsafe-eval'"
129 Data = "data:"
130 HTTPS = "https:"
131)
132
133func main() {
134 // Example 1: Strict CSP for maximum security
135 strictCSP := NewCSPBuilder().
136 DefaultSrc(Self).
137 ScriptSrc(Self).
138 StyleSrc(Self).
139 ImgSrc(Self, Data, HTTPS).
140 ConnectSrc(Self).
141 ObjectSrc(None).
142 FrameAncestors(None).
143 UpgradeInsecureRequests()
144
145 fmt.Println("=== Strict CSP ===")
146 fmt.Printf("Header: %s\n", strictCSP.HeaderName())
147 fmt.Printf("Policy: %s\n\n", strictCSP.Build())
148
149 // Example 2: Development CSP
150 devCSP := NewCSPBuilder().
151 DefaultSrc(Self).
152 ScriptSrc(Self, UnsafeInline, UnsafeEval).
153 StyleSrc(Self, UnsafeInline).
154 ImgSrc("*", Data).
155 ConnectSrc(Self, "ws://localhost:*").
156 ReportURI("/csp-violations")
157
158 fmt.Println("=== Development CSP ===")
159 fmt.Printf("Policy: %s\n\n", devCSP.Build())
160
161 // Example 3: Production CSP with CDN
162 prodCSP := NewCSPBuilder().
163 DefaultSrc(Self).
164 ScriptSrc(Self, "https://cdn.example.com").
165 StyleSrc(Self, "https://cdn.example.com", "https://fonts.googleapis.com").
166 ImgSrc(Self, "https://cdn.example.com", Data).
167 ConnectSrc(Self, "https://api.example.com").
168 ObjectSrc(None).
169 UpgradeInsecureRequests().
170 ReportURI("/csp-violations")
171
172 fmt.Println("=== Production CSP ===")
173 fmt.Printf("Policy: %s\n\n", prodCSP.Build())
174
175 // Example 4: Report-Only mode
176 reportOnlyCSP := NewCSPBuilder().
177 ReportOnly().
178 DefaultSrc(Self).
179 ScriptSrc(Self).
180 ReportURI("/csp-violations")
181
182 fmt.Println("=== Report-Only CSP ===")
183 fmt.Printf("Header: %s\n", reportOnlyCSP.HeaderName())
184 fmt.Printf("Policy: %s\n\n", reportOnlyCSP.Build())
185
186 // Example 5: API-only CSP
187 apiCSP := NewCSPBuilder().
188 DefaultSrc(None).
189 ConnectSrc(Self).
190 FrameAncestors(None)
191
192 fmt.Println("=== API-Only CSP ===")
193 fmt.Printf("Policy: %s\n", apiCSP.Build())
194}
Explanation:
Content Security Policy prevents XSS attacks by controlling which resources can load and execute.
Key Directives:
- default-src: Fallback for other directives
- script-src: JavaScript sources
- style-src: CSS sources
- img-src: Image sources
- connect-src: AJAX, WebSocket, EventSource
- object-src:
<object>,<embed>,<applet> - frame-ancestors: Clickjacking protection
Common Source Values:
- 'self': Same origin only
- 'none': Block all sources
- 'unsafe-inline': Allow inline scripts/styles
- 'unsafe-eval': Allow eval()
- data:: Data URIs
- https:: Any HTTPS source
- Specific domains: e.g.,
https://cdn.example.com
Security Benefits:
- XSS Prevention: Blocks injected scripts
- Clickjacking Protection:
frame-ancestors 'none' - Data Exfiltration:
connect-srclimits AJAX targets - Mixed Content:
upgrade-insecure-requests
Best Practices:
- Start strict, relax as needed
- Avoid
'unsafe-inline'and'unsafe-eval' - Use
'none'for unused resources - Test with Report-Only mode first
- Monitor violation reports
Real-World CSP Examples:
GitHub:
default-src 'none';
script-src github.githubassets.com;
style-src github.githubassets.com;
img-src *;
Google:
default-src 'self';
script-src 'self' https://www.google.com;
report-uri /csp-report
Attack Prevention:
1// Attacker tries XSS:
2<script>alert(document.cookie)</script>
3
4// CSP blocks with:
5script-src 'self' // Inline scripts blocked!
Violation Reporting:
CSP violations sent to report-uri:
1{
2 "csp-report": {
3 "document-uri": "https://example.com",
4 "violated-directive": "script-src 'self'",
5 "blocked-uri": "https://evil.com/bad.js"
6 }
7}
Exercise 4: Secure Password Hashing System
Learning Objective: Implement production-grade password hashing with proper security practices, rate limiting, and user experience considerations.
Context: Weak password hashing has been responsible for some of the largest data breaches in history. The 2012 LinkedIn breach exposed 167 million passwords because they used unsalted SHA1 hashes. Modern applications must use industry-standard hashing algorithms with proper configuration.
Difficulty: Intermediate | Time: 25-30 minutes
Build a comprehensive password hashing system that protects user credentials against modern attack vectors:
- Implement secure password hashing using bcrypt with proper cost configuration
- Add rate limiting for login attempts to prevent brute-force attacks
- Include password strength validation and breach checking
- Support password reset functionality with secure token generation
- Implement secure password storage and validation practices
- Handle edge cases like migration from legacy hashing systems
Real-world application: This system protects user accounts in authentication systems, preventing credential stuffing attacks and minimizing damage if password databases are compromised.
Solution
1// run
2package main
3
4import (
5 "crypto/rand"
6 "encoding/base64"
7 "fmt"
8 "strings"
9 "time"
10
11 "golang.org/x/crypto/bcrypt"
12)
13
14// PasswordHasher handles secure password hashing and validation
15type PasswordHasher struct {
16 minCost int
17 maxCost int
18 minLength int
19}
20
21func NewPasswordHasher() *PasswordHasher {
22 return &PasswordHasher{
23 minCost: 12, // bcrypt cost for production
24 maxCost: 14, // maximum acceptable cost
25 minLength: 8, // minimum password length
26 }
27}
28
29// HashPassword securely hashes a password with bcrypt
30func (ph *PasswordHasher) HashPassword(password string) (string, error) {
31 // Validate password strength
32 if err := ph.ValidatePasswordStrength(password); err != nil {
33 return "", err
34 }
35
36 // Generate hash with adaptive cost based on current system load
37 cost := ph.calculateOptimalCost()
38
39 hash, err := bcrypt.GenerateFromPassword([]byte(password), cost)
40 if err != nil {
41 return "", fmt.Errorf("failed to hash password: %w", err)
42 }
43
44 return string(hash), nil
45}
46
47// ValidatePassword verifies a password against its hash
48func (ph *PasswordHasher) ValidatePassword(password, hash string) bool {
49 err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
50 return err == nil
51}
52
53// ValidatePasswordStrength checks if password meets security requirements
54func (ph *PasswordHasher) ValidatePasswordStrength(password string) error {
55 if len(password) < ph.minLength {
56 return fmt.Errorf("password must be at least %d characters", ph.minLength)
57 }
58
59 // Check for common weak passwords
60 weakPasswords := []string{
61 "password", "12345678", "qwerty", "abc123", "password123",
62 "admin", "letmein", "welcome", "monkey", "dragon",
63 }
64
65 for _, weak := range weakPasswords {
66 if strings.EqualFold(password, weak) {
67 return fmt.Errorf("password is too common and weak")
68 }
69 }
70
71 // Basic complexity checks
72 var hasUpper, hasLower, hasDigit, hasSpecial bool
73
74 for _, char := range password {
75 switch {
76 case char >= 'A' && char <= 'Z':
77 hasUpper = true
78 case char >= 'a' && char <= 'z':
79 hasLower = true
80 case char >= '0' && char <= '9':
81 hasDigit = true
82 case strings.ContainsRune("!@#$%^&*()_+-=[]{}|;:,.<>?", char):
83 hasSpecial = true
84 }
85 }
86
87 complexityScore := 0
88 if hasUpper {
89 complexityScore++
90 }
91 if hasLower {
92 complexityScore++
93 }
94 if hasDigit {
95 complexityScore++
96 }
97 if hasSpecial {
98 complexityScore++
99 }
100
101 if complexityScore < 3 {
102 return fmt.Errorf("password must contain at least 3 of: uppercase, lowercase, digits, special characters")
103 }
104
105 return nil
106}
107
108// calculateOptimalCost determines bcrypt cost based on system performance
109func (ph *PasswordHasher) calculateOptimalCost() int {
110 // In production, benchmark system performance
111 // For demo, return fixed cost
112 return ph.minCost
113}
114
115// GeneratePasswordResetToken creates a secure token for password resets
116func (ph *PasswordHasher) GeneratePasswordResetToken() (string, error) {
117 bytes := make([]byte, 32) // 256-bit token
118 if _, err := rand.Read(bytes); err != nil {
119 return "", fmt.Errorf("failed to generate reset token: %w", err)
120 }
121
122 return base64.URLEncoding.EncodeToString(bytes), nil
123}
124
125func main() {
126 fmt.Println("=== Secure Password Hashing System ===\n")
127
128 hasher := NewPasswordHasher()
129
130 // Test password hashing
131 fmt.Println("--- Password Hashing ---")
132 password := "SecurePassword123!"
133 hash, err := hasher.HashPassword(password)
134 if err != nil {
135 fmt.Printf("Hashing failed: %v\n", err)
136 } else {
137 fmt.Printf("Original: %s\n", password)
138 fmt.Printf("Hash: %s\n", hash[:20]+"...")
139
140 // Test validation
141 valid := hasher.ValidatePassword(password, hash)
142 fmt.Printf("Password validation: %v\n", valid)
143
144 // Test invalid password
145 invalid := hasher.ValidatePassword("wrongpassword", hash)
146 fmt.Printf("Invalid password validation: %v\n", invalid)
147 }
148
149 // Test weak password rejection
150 fmt.Println("\n--- Password Strength Validation ---")
151 weakPasswords := []string{"weak", "password", "12345678"}
152 for _, weak := range weakPasswords {
153 err := hasher.ValidatePasswordStrength(weak)
154 if err != nil {
155 fmt.Printf("'%s': %v\n", weak, err)
156 }
157 }
158
159 // Test strong password acceptance
160 strongPassword := "Str0ng!P@ssw0rd#2023"
161 err = hasher.ValidatePasswordStrength(strongPassword)
162 if err == nil {
163 fmt.Printf("'%s': Strong password accepted\n", strongPassword)
164 }
165
166 // Test password reset token generation
167 fmt.Println("\n--- Password Reset Tokens ---")
168 token, err := hasher.GeneratePasswordResetToken()
169 if err != nil {
170 fmt.Printf("Token generation failed: %v\n", err)
171 } else {
172 fmt.Printf("Generated token: %s\n", token[:20]+"...")
173 fmt.Printf("Token length: %d characters\n", len(token))
174 }
175}
Run the code:
1go run .
Security Features Demonstrated:
- Secure Password Hashing: Uses bcrypt with configurable cost
- Password Strength Validation: Enforces complexity requirements and blocks common passwords
- Secure Token Generation: Cryptographically secure random tokens for password resets
- Constant-Time Comparison: Uses bcrypt's built-in timing-safe comparison
- Memory-Hard Function: bcrypt is designed to be slow and memory-intensive
Production Considerations:
- Use environment-specific bcrypt costs
- Implement rate limiting for password reset requests
- Add password breach checking
- Implement secure password reset workflow
- Add account lockout after failed attempts
- Use HTTPS for all authentication endpoints
Exercise 5: SQL Injection Prevention System
Learning Objective: Build a comprehensive SQL injection prevention system that demonstrates proper database security practices.
Context: SQL injection remains one of the most critical web vulnerabilities, responsible for major breaches like the 2014 TalkTalk hack that exposed 157,000 customer records. The FBI estimates that SQL injection costs businesses over $1 billion annually in damages and remediation costs.
Difficulty: Advanced | Time: 30-35 minutes
Implement a multi-layered SQL injection prevention system that protects database interactions from various attack vectors:
- Create parameterized query builder that prevents SQL injection
- Implement input validation and sanitization layers
- Build query logging and monitoring for suspicious patterns
- Include dynamic query construction with safe validation
- Add database connection pooling with security controls
- Implement query result sanitization for output encoding
Real-world application: This system protects web applications from SQL injection attacks, which are responsible for data breaches, financial fraud, and complete system compromises in production environments.
Solution
1// run
2package main
3
4import (
5 "database/sql"
6 "fmt"
7 "regexp"
8 "strconv"
9 "strings"
10 "time"
11 "unicode"
12
13 _ "github.com/mattn/go-sqlite3"
14)
15
16// SQLInjectionPrevention prevents SQL injection through multiple layers
17type SQLInjectionPrevention struct {
18 db *sql.DB
19 queryLogger *QueryLogger
20 validator *InputValidator
21}
22
23func NewSQLInjectionPrevention(dbPath string) (*SQLInjectionPrevention, error) {
24 db, err := sql.Open("sqlite3", dbPath)
25 if err != nil {
26 return nil, fmt.Errorf("failed to open database: %w", err)
27 }
28
29 return &SQLInjectionPrevention{
30 db: db,
31 queryLogger: NewQueryLogger(),
32 validator: NewInputValidator(),
33 }, nil
34}
35
36// SafeQueryBuilder builds parameterized queries safely
37type SafeQueryBuilder struct {
38 query strings.Builder
39 args []interface{}
40}
41
42func NewSafeQueryBuilder() *SafeQueryBuilder {
43 return &SafeQueryBuilder{
44 query: strings.Builder{},
45 args: make([]interface{}, 0),
46 }
47}
48
49func (sqb *SafeQueryBuilder) Select(columns ...string) *SafeQueryBuilder {
50 sqb.query.WriteString("SELECT ")
51 sqb.query.WriteString(strings.Join(columns, ", "))
52 return sqb
53}
54
55func (sqb *SafeQueryBuilder) From(table string) *SafeQueryBuilder {
56 // Validate table name to prevent injection
57 if !isValidIdentifier(table) {
58 panic(fmt.Sprintf("invalid table name: %s", table))
59 }
60 sqb.query.WriteString(" FROM ")
61 sqb.query.WriteString(table)
62 return sqb
63}
64
65func (sqb *SafeQueryBuilder) Where(condition string, args ...interface{}) *SafeQueryBuilder {
66 sqb.query.WriteString(" WHERE ")
67 sqb.query.WriteString(condition)
68 sqb.args = append(sqb.args, args...)
69 return sqb
70}
71
72func (sqb *SafeQueryBuilder) Build() (string, []interface{}) {
73 return sqb.query.String(), sqb.args
74}
75
76// isValidIdentifier validates SQL identifiers
77func isValidIdentifier(name string) bool {
78 if len(name) == 0 || len(name) > 64 {
79 return false
80 }
81
82 // Must start with letter or underscore
83 if !unicode.IsLetter(rune(name[0])) && name[0] != '_' {
84 return false
85 }
86
87 // Can contain letters, numbers, and underscores
88 for _, char := range name {
89 if !unicode.IsLetter(char) && !unicode.IsDigit(char) && char != '_' {
90 return false
91 }
92 }
93
94 // Check for SQL keywords
95 sqlKeywords := []string{
96 "SELECT", "FROM", "WHERE", "INSERT", "UPDATE", "DELETE",
97 "DROP", "CREATE", "ALTER", "EXEC", "UNION", "SCRIPT",
98 }
99
100 upperName := strings.ToUpper(name)
101 for _, keyword := range sqlKeywords {
102 if upperName == keyword {
103 return false
104 }
105 }
106
107 return true
108}
109
110// InputValidator validates and sanitizes user input
111type InputValidator struct {
112 patterns map[string]*regexp.Regexp
113}
114
115func NewInputValidator() *InputValidator {
116 return &InputValidator{
117 patterns: map[string]*regexp.Regexp{
118 "email": regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`),
119 "username": regexp.MustCompile(`^[a-zA-Z0-9_-]{3,20}$`),
120 "id": regexp.MustCompile(`^[0-9]{1,10}$`),
121 "name": regexp.MustCompile(`^[a-zA-Z\s'-]{1,50}$`),
122 },
123 }
124}
125
126func (iv *InputValidator) ValidateEmail(email string) error {
127 email = strings.TrimSpace(email)
128 if !iv.patterns["email"].MatchString(email) {
129 return fmt.Errorf("invalid email format: %s", email)
130 }
131 return nil
132}
133
134func (iv *InputValidator) SanitizeString(input string) string {
135 // Remove potential SQL injection characters
136 dangerous := []string{"'", "\"", ";", "--", "/*", "*/", "xp_", "sp_"}
137
138 result := input
139 for _, char := range dangerous {
140 result = strings.ReplaceAll(result, char, "")
141 }
142
143 return strings.TrimSpace(result)
144}
145
146// QueryLogger logs and monitors database queries for security
147type QueryLogger struct {
148 queries []QueryLog
149}
150
151type QueryLog struct {
152 Query string
153 Args []interface{}
154 Timestamp time.Time
155 Duration time.Duration
156 Success bool
157}
158
159func NewQueryLogger() *QueryLogger {
160 return &QueryLogger{
161 queries: make([]QueryLog, 0),
162 }
163}
164
165func (ql *QueryLogger) LogQuery(query string, args []interface{}, duration time.Duration, success bool) {
166 log := QueryLog{
167 Query: query,
168 Args: args,
169 Timestamp: time.Now(),
170 Duration: duration,
171 Success: success,
172 }
173
174 ql.queries = append(ql.queries, log)
175
176 // In production, send to logging system
177 if !success {
178 fmt.Printf("FAILED QUERY: %s with args %v\n", query, args)
179 }
180}
181
182// User represents a user in the database
183type User struct {
184 ID int `json:"id"`
185 Username string `json:"username"`
186 Email string `json:"email"`
187 Name string `json:"name"`
188 CreatedAt time.Time `json:"created_at"`
189}
190
191// UserRepository provides safe database operations for users
192type UserRepository struct {
193 sip *SQLInjectionPrevention
194}
195
196func NewUserRepository(sip *SQLInjectionPrevention) *UserRepository {
197 return &UserRepository{sip: sip}
198}
199
200// CreateUser safely creates a new user with validation
201func (ur *UserRepository) CreateUser(username, email, name string) (*User, error) {
202 // Validate all inputs
203 if err := ur.sip.validator.ValidateEmail(email); err != nil {
204 return nil, fmt.Errorf("invalid email: %w", err)
205 }
206
207 name = ur.sip.validator.SanitizeString(name)
208
209 // Build safe query
210 query := `
211 INSERT INTO users (username, email, name, created_at)
212 VALUES (?, ?, ?, ?)
213 `
214
215 start := time.Now()
216 result, err := ur.sip.db.Exec(query, username, email, name, time.Now())
217 duration := time.Since(start)
218
219 ur.sip.queryLogger.LogQuery(query, []interface{}{username, email, name}, duration, err == nil)
220
221 if err != nil {
222 return nil, fmt.Errorf("failed to create user: %w", err)
223 }
224
225 id, err := result.LastInsertId()
226 if err != nil {
227 return nil, fmt.Errorf("failed to get user ID: %w", err)
228 }
229
230 return &User{
231 ID: int(id),
232 Username: username,
233 Email: email,
234 Name: name,
235 CreatedAt: time.Now(),
236 }, nil
237}
238
239// InitializeDatabase creates the database schema
240func (sip *SQLInjectionPrevention) InitializeDatabase() error {
241 schema := `
242 CREATE TABLE IF NOT EXISTS users (
243 id INTEGER PRIMARY KEY AUTOINCREMENT,
244 username TEXT UNIQUE NOT NULL,
245 email TEXT UNIQUE NOT NULL,
246 name TEXT NOT NULL,
247 created_at DATETIME DEFAULT CURRENT_TIMESTAMP
248 );
249 `
250
251 _, err := sip.db.Exec(schema)
252 return err
253}
254
255func main() {
256 fmt.Println("=== SQL Injection Prevention System ===\n")
257
258 // Initialize system
259 sip, err := NewSQLInjectionPrevention(":memory:")
260 if err != nil {
261 panic(err)
262 }
263 defer sip.db.Close()
264
265 // Create database schema
266 if err := sip.InitializeDatabase(); err != nil {
267 panic(err)
268 }
269
270 userRepo := NewUserRepository(sip)
271
272 // Test safe user creation
273 fmt.Println("--- Safe User Creation ---")
274 user1, err := userRepo.CreateUser("alice123", "alice@example.com", "Alice Johnson")
275 if err != nil {
276 fmt.Printf("Failed to create user: %v\n", err)
277 } else {
278 fmt.Printf("Created user: %+v\n", user1)
279 }
280
281 // Test SQL injection attempt
282 fmt.Println("\n--- SQL Injection Prevention ---")
283
284 // Attempt SQL injection through username
285 maliciousUsername := "alice'; DROP TABLE users; --"
286 _, err = userRepo.CreateUser(maliciousUsername, "evil@example.com", "Hacker")
287 if err != nil {
288 fmt.Printf("SQL injection blocked: %v\n", err)
289 }
290
291 // Test input validation
292 fmt.Println("\n--- Input Validation ---")
293 testEmails := []string{
294 "valid@example.com",
295 "invalid-email",
296 "user'; DROP TABLE users; --@example.com",
297 }
298
299 for _, email := range testEmails {
300 err := sip.validator.ValidateEmail(email)
301 if err != nil {
302 fmt.Printf("Rejected '%s': %v\n", email, err)
303 } else {
304 fmt.Printf("Accepted '%s'\n", email)
305 }
306 }
307
308 fmt.Println("\nSystem successfully prevented SQL injection attacks!")
309}
Run the code:
1go run .
SQL Injection Prevention Techniques:
- Parameterized Queries: All database queries use parameter binding
- Input Validation: Strict validation for all user inputs
- Identifier Whitelisting: Table and column names are validated
- Query Builder: Safe query construction with validation
- Input Sanitization: Remove dangerous characters
- Query Logging: Monitor for suspicious query patterns
Attack Vectors Prevented:
- Classic SQL injection:
' OR '1'='1 - Union-based attacks:
' UNION SELECT * FROM users - Blind SQL injection:
' AND (SELECT COUNT(*) FROM users) > 0 - Time-based attacks:
'; WAITFOR DELAY '00:00:05'-- - Stacked queries:
'; DROP TABLE users--