# Gin Security Audit Guide

> Gin Framework Security Audit Module
> Applicable to: Gin, Gin-GORM, Gin-JWT

---

## Identification Patterns

```go
// Gin project identification
import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.Run()
}

// Common directory structure
├── cmd/
│   └── main.go
├── internal/
│   ├── handler/     # Controllers
│   ├── service/     # Business logic
│   ├── model/       # Data models
│   └── middleware/  # Middleware
├── pkg/             # Common packages
├── config/          # Configuration
└── go.mod
```

---

## Critical Vulnerabilities

### 1. SQL Injection

```go
import (
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
)

// Dangerous: Raw concatenation
func GetUser(c *gin.Context) {
    id := c.Param("id")
    var user User
    db.Raw("SELECT * FROM users WHERE id = " + id).Scan(&user)
    c.JSON(200, user)
}

// Dangerous: Where string concatenation
func SearchUsers(c *gin.Context) {
    name := c.Query("name")
    var users []User
    db.Where("name LIKE '%" + name + "%'").Find(&users)
    c.JSON(200, users)
}

// Dangerous: Order injection
func ListUsers(c *gin.Context) {
    order := c.Query("order")  // User-controlled sort
    var users []User
    db.Order(order).Find(&users)  // ORDER BY injection
    c.JSON(200, users)
}
// payload: ?order=id; DROP TABLE users;--

// Safe: Parameterized queries
func GetUser(c *gin.Context) {
    id := c.Param("id")
    var user User
    db.Raw("SELECT * FROM users WHERE id = ?", id).Scan(&user)
    c.JSON(200, user)
}

// Safe: ORM methods
func SearchUsers(c *gin.Context) {
    name := c.Query("name")
    var users []User
    db.Where("name LIKE ?", "%"+name+"%").Find(&users)
    c.JSON(200, users)
}

// Safe: Order whitelist
var allowedOrders = map[string]bool{
    "id":         true,
    "created_at": true,
    "name":       true,
}

func ListUsers(c *gin.Context) {
    order := c.DefaultQuery("order", "id")
    if !allowedOrders[order] {
        order = "id"
    }
    var users []User
    db.Order(order).Find(&users)
    c.JSON(200, users)
}
```

### 2. Command Injection

```go
import "os/exec"

// Dangerous: Shell execution
func RunCommand(c *gin.Context) {
    cmd := c.Query("cmd")
    out, _ := exec.Command("sh", "-c", cmd).Output()
    c.String(200, string(out))
}

// Dangerous: Argument concatenation
func Ping(c *gin.Context) {
    host := c.Query("host")
    cmd := exec.Command("sh", "-c", "ping -c 4 "+host)
    out, _ := cmd.Output()
    c.String(200, string(out))
}
// payload: ?host=127.0.0.1;id

// Safe: Direct execution, no shell
func Ping(c *gin.Context) {
    host := c.Query("host")
    // Validate host format
    if !isValidHost(host) {
        c.String(400, "Invalid host")
        return
    }
    cmd := exec.Command("ping", "-c", "4", host)
    out, _ := cmd.Output()
    c.String(200, string(out))
}
```

### 3. Path Traversal

```go
// Dangerous: Direct path concatenation
func DownloadFile(c *gin.Context) {
    filename := c.Query("file")
    c.File("/uploads/" + filename)
}
// payload: ?file=../../../etc/passwd

// Safe: Path validation
func DownloadFile(c *gin.Context) {
    filename := c.Query("file")

    // Clean path
    cleanName := filepath.Clean(filename)

    // Prevent directory traversal
    if strings.Contains(cleanName, "..") {
        c.String(400, "Invalid filename")
        return
    }

    fullPath := filepath.Join("/uploads", cleanName)

    // Validate within allowed directory
    if !strings.HasPrefix(fullPath, "/uploads/") {
        c.String(403, "Access denied")
        return
    }

    c.File(fullPath)
}
```

---

## High Vulnerabilities

### 4. SSRF

```go
import "net/http"

// Dangerous: Unvalidated URL
func FetchURL(c *gin.Context) {
    url := c.Query("url")
    resp, _ := http.Get(url)
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)
    c.String(200, string(body))
}
// payload: ?url=http://169.254.169.254/latest/meta-data/

// Safe: URL whitelist
var allowedHosts = map[string]bool{
    "api.example.com": true,
}

func FetchURL(c *gin.Context) {
    rawURL := c.Query("url")

    parsedURL, err := url.Parse(rawURL)
    if err != nil {
        c.String(400, "Invalid URL")
        return
    }

    if !allowedHosts[parsedURL.Host] {
        c.String(403, "Host not allowed")
        return
    }

    // Check for internal IPs
    if isPrivateIP(parsedURL.Host) {
        c.String(403, "Private IP not allowed")
        return
    }

    resp, _ := http.Get(rawURL)
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)
    c.String(200, string(body))
}
```

### 5. Authentication Bypass

```go
// Dangerous: Hardcoded JWT secret
var jwtSecret = []byte("secret")

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenString := c.GetHeader("Authorization")
        token, _ := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
            return jwtSecret, nil
        })
        // ...
    }
}

// Dangerous: Signing algorithm not verified
func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token, _ := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
            // Algorithm not checked!
            return jwtSecret, nil
        })
        // ...
    }
}
// Attack: Change alg to none

// Safe: Verify algorithm + environment variable secret
var jwtSecret = []byte(os.Getenv("JWT_SECRET"))

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenString := c.GetHeader("Authorization")
        token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
            // Verify signing algorithm
            if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, fmt.Errorf("unexpected signing method")
            }
            return jwtSecret, nil
        })

        if err != nil || !token.Valid {
            c.AbortWithStatus(401)
            return
        }

        c.Next()
    }
}
```

### 6. Broken Access Control (IDOR)

```go
// Dangerous: Resource ownership not verified
func GetOrder(c *gin.Context) {
    orderID := c.Param("id")
    var order Order
    db.First(&order, orderID)
    c.JSON(200, order)  // Anyone can access any order
}

// Safe: Verify ownership
func GetOrder(c *gin.Context) {
    orderID := c.Param("id")
    userID := c.GetInt("user_id")  // From JWT

    var order Order
    result := db.Where("id = ? AND user_id = ?", orderID, userID).First(&order)
    if result.Error != nil {
        c.JSON(404, gin.H{"error": "Order not found"})
        return
    }
    c.JSON(200, order)
}
```

---

## Medium Vulnerabilities

### 7. XSS

```go
// Dangerous: Direct HTML output
func Hello(c *gin.Context) {
    name := c.Query("name")
    c.Writer.Write([]byte("<h1>Hello " + name + "</h1>"))
}

// Dangerous: template.HTML not escaped
func Hello(c *gin.Context) {
    name := c.Query("name")
    c.HTML(200, "hello.tmpl", gin.H{
        "name": template.HTML(name),  // Not escaped!
    })
}

// Safe: Use template auto-escaping
func Hello(c *gin.Context) {
    name := c.Query("name")
    c.HTML(200, "hello.tmpl", gin.H{
        "name": name,  // Auto-escaped
    })
}
```

### 8. CORS Misconfiguration

```go
import "github.com/gin-contrib/cors"

// Dangerous: Allow all origins
r.Use(cors.Default())

// More dangerous: Allow credentials + all origins
r.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"*"},
    AllowCredentials: true,  // Dangerous combination!
}))

// Safe: Specify allowed origins
r.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"https://example.com"},
    AllowMethods:     []string{"GET", "POST"},
    AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
    AllowCredentials: true,
    MaxAge:           12 * time.Hour,
}))
```

### 9. Trusted Proxy Headers

```go
// Dangerous: Trust all proxy headers by default
r := gin.Default()
// Reads X-Forwarded-For by default

// May lead to IP spoofing
func GetClientIP(c *gin.Context) {
    ip := c.ClientIP()  // Can be spoofed
    // ...
}

// Safe: Configure trusted proxies
r.SetTrustedProxies([]string{"192.168.1.0/24", "10.0.0.0/8"})

// Or disable proxy headers
r.SetTrustedProxies(nil)
```

### 10. Parameter Binding Vulnerabilities

```go
// Dangerous: Auto-bind all fields
type User struct {
    ID       uint   `json:"id"`
    Name     string `json:"name"`
    IsAdmin  bool   `json:"is_admin"`
}

func CreateUser(c *gin.Context) {
    var user User
    c.BindJSON(&user)  // User can set is_admin=true
    db.Create(&user)
}

// Safe: Use DTO
type CreateUserDTO struct {
    Name string `json:"name" binding:"required"`
}

func CreateUser(c *gin.Context) {
    var dto CreateUserDTO
    if err := c.ShouldBindJSON(&dto); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    user := User{Name: dto.Name, IsAdmin: false}
    db.Create(&user)
}
```

---

## Gin Middleware Security

### Rate Limiting

```go
import "github.com/ulule/limiter/v3/drivers/middleware/gin"

// Dangerous: No rate limiting
r.POST("/login", loginHandler)

// Safe: Add rate limiting
store := memory.NewStore()
rate := limiter.Rate{
    Period: 1 * time.Minute,
    Limit:  10,
}
middleware := mgin.NewMiddleware(limiter.New(store, rate))
r.POST("/login", middleware, loginHandler)
```

### Logging Security

```go
// Dangerous: Logging sensitive information
func Login(c *gin.Context) {
    password := c.PostForm("password")
    log.Printf("Login attempt with password: %s", password)  // Do not do this!
}

// Safe: Desensitized logging
func Login(c *gin.Context) {
    username := c.PostForm("username")
    log.Printf("Login attempt for user: %s", username)
}
```

### Error Handling

```go
// Dangerous: Exposing internal errors
func GetUser(c *gin.Context) {
    var user User
    if err := db.First(&user).Error; err != nil {
        c.JSON(500, gin.H{"error": err.Error()})  // Exposes database information
        return
    }
    c.JSON(200, user)
}

// Safe: Generic error message
func GetUser(c *gin.Context) {
    var user User
    if err := db.First(&user).Error; err != nil {
        log.Printf("Database error: %v", err)  // Internal logging
        c.JSON(500, gin.H{"error": "Internal server error"})
        return
    }
    c.JSON(200, user)
}
```

---

## Detection Commands

```bash
# SQL Injection
grep -rn "\.Raw\|\.Where\|\.Order" --include="*.go" | grep -E '"\s*\+|\+\s*"'

# Command Execution
grep -rn "exec\.Command" --include="*.go"

# Path Operations
grep -rn "c\.File\|filepath\.Join" --include="*.go"

# JWT Secret
grep -rn "jwt\|secret\|Secret" --include="*.go"

# CORS Configuration
grep -rn "cors\." --include="*.go"

# Binding Operations
grep -rn "c\.Bind\|c\.ShouldBind" --include="*.go"

# Static Analysis
gosec ./...
staticcheck ./...
```

---

## Audit Checklist

```
[ ] Check if SQL queries use parameterization
[ ] Check if command execution has injection risk
[ ] Check if file operations have path traversal
[ ] Check if HTTP requests have SSRF
[ ] Check JWT configuration (secret, algorithm)
[ ] Check for IDOR vulnerabilities
[ ] Check XSS protection
[ ] Check CORS configuration
[ ] Check proxy header trust configuration
[ ] Check if parameter binding is secure
[ ] Check rate limiting
[ ] Check error handling
[ ] Run gosec static analysis
```

---

## Minimum PoC Examples
```bash
# SQL Injection
curl "http://localhost:8080/users?id=1' OR '1'='1"

# SSRF
curl "http://localhost:8080/fetch?url=http://169.254.169.254/latest/meta-data/"

# Path Traversal
curl "http://localhost:8080/download?file=../../etc/passwd"
```

---
**Last Updated**: 2026-01-23
**Version**: 1.0.0
