SQL injection from event data in SQL string in AWS Lambda

Critical Risk SQL Injection
goaws-lambdasql-injectiondatabaseserverless

What it is

SQL injection vulnerability in Go AWS Lambda functions where event fields are concatenated or formatted into SQL strings instead of using parameterized queries, allowing attacker-controlled tokens to alter SQL structure.

// VULNERABLE: Go Lambda with SQL string concatenation
package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    "log"
    "os"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    _ "github.com/go-sql-driver/mysql"
)

var db *sql.DB

func init() {
    var err error
    dsn := os.Getenv("DATABASE_URL")
    db, err = sql.Open("mysql", dsn)
    if err != nil {
        log.Fatal(err)
    }
}

// Attack examples:
// userId = "1 OR 1=1"
// accountType = "savings'; DROP TABLE accounts; --"
// sortBy = "transaction_date; DELETE FROM users; --"
// SECURE: Parameterized queries prevent SQL injection
package main

import (
    "database/sql"
    "fmt"
    "log"
    "os"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    _ "github.com/go-sql-driver/mysql"
)

var db *sql.DB

func init() {
    var err error
    dsn := os.Getenv("DATABASE_URL")
    db, err = sql.Open("mysql", dsn)
    if err != nil {
        log.Fatal(err)
    }
}

// SECURE: Using parameterized queries with ? placeholders
func getTransactions(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    userID := request.PathParameters["userId"]
    accountType := request.QueryStringParameters["accountType"]

    // Use ? placeholders instead of concatenation
    query := "SELECT * FROM transactions WHERE user_id = ? AND account_type = ?"

    rows, err := db.Query(query, userID, accountType)  // Pass as parameters
    if err != nil {
        return events.APIGatewayProxyResponse{
            StatusCode: 500,
            Body:       "Query error",
        }, nil
    }
    defer rows.Close()

    return events.APIGatewayProxyResponse{
        StatusCode: 200,
        Body:       "Success",
    }, nil
}

func main() {
    lambda.Start(getTransactions)
}

💡 Why This Fix Works

The vulnerable code directly concatenates Lambda event data into SQL strings using fmt.Sprintf, allowing injection attacks. The fixed version uses parameterized queries with placeholders, comprehensive input validation against whitelists, database transactions for consistency, and proper error handling.

Why it happens

Event fields from AWS Lambda requests are concatenated or formatted into SQL strings without parameterization, enabling injection through crafted event payloads.

Root causes

Lambda Event Data in SQL String Concatenation

Event fields from AWS Lambda requests are concatenated or formatted into SQL strings without parameterization, enabling injection through crafted event payloads.

Preview example – GO
// VULNERABLE: Event data in SQL string concatenation
package main

import (
    "database/sql"
    "fmt"
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    _ "github.com/go-sql-driver/mysql"
)

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    userID := request.PathParameters["userId"]
    status := request.QueryStringParameters["status"]
    sortBy := request.QueryStringParameters["sortBy"]

    // Direct event data concatenation vulnerability
    query := fmt.Sprintf("SELECT * FROM orders WHERE user_id = %s AND status = '%s' ORDER BY %s",
        userID, status, sortBy)

    db, _ := sql.Open("mysql", dsn)
    defer db.Close()

    rows, err := db.Query(query)
    if err != nil {
        return events.APIGatewayProxyResponse{StatusCode: 500}, err
    }
    defer rows.Close()

    return events.APIGatewayProxyResponse{StatusCode: 200}, nil
}

Request Body JSON Fields in SQL

Parsing JSON request bodies from Lambda events and directly embedding field values into SQL strings using string formatting.

Preview example – GO
// VULNERABLE: Request body fields in SQL strings
import (
    "encoding/json"
    "fmt"
)

type CreateUserRequest struct {
    Name       string `json:"name"`
    Email      string `json:"email"`
    Department string `json:"department"`
}

func createUser(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    var userReq CreateUserRequest
    json.Unmarshal([]byte(request.Body), &userReq)

    // String formatting vulnerability
    insertSQL := fmt.Sprintf(
        "INSERT INTO users (name, email, department) VALUES ('%s', '%s', '%s')",
        userReq.Name, userReq.Email, userReq.Department)

    db, _ := sql.Open("mysql", dsn)
    defer db.Close()

    _, err := db.Exec(insertSQL)
    if err != nil {
        return events.APIGatewayProxyResponse{StatusCode: 500}, err
    }

    return events.APIGatewayProxyResponse{StatusCode: 201}, nil
}

Fixes

1

Use Parameterized Queries with Placeholders

Replace string concatenation with database/sql prepared statements. Use placeholders (? or $1) and pass values as parameters.

View implementation – GO
// SECURE: Parameterized queries with placeholders
func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    userID := request.PathParameters["userId"]
    status := request.QueryStringParameters["status"]
    sortBy := request.QueryStringParameters["sortBy"]

    // Validate inputs
    if !isValidUserID(userID) || !isValidStatus(status) || !isValidSortField(sortBy) {
        return events.APIGatewayProxyResponse{StatusCode: 400}, nil
    }

    db, err := sql.Open("mysql", dsn)
    if err != nil {
        return events.APIGatewayProxyResponse{StatusCode: 500}, err
    }
    defer db.Close()

    // Use parameterized query with placeholders
    query := fmt.Sprintf("SELECT * FROM orders WHERE user_id = ? AND status = ? ORDER BY %s", sortBy)

    rows, err := db.Query(query, userID, status)
    if err != nil {
        return events.APIGatewayProxyResponse{StatusCode: 500}, err
    }
    defer rows.Close()

    return events.APIGatewayProxyResponse{StatusCode: 200}, nil
}
2

Implement Input Validation and Sanitization

Validate all Lambda event data before database operations. Use whitelists for dynamic SQL components and sanitize user input.

View implementation – GO
// SECURE: Input validation with parameterized queries
import (
    "encoding/json"
    "regexp"
    "strconv"
)

var (
    validSortFields = map[string]bool{
        "order_date": true,
        "amount":     true,
        "status":     true,
        "user_id":    true,
    }

    validStatuses = map[string]bool{
        "pending":   true,
        "completed": true,
        "cancelled": true,
        "shipped":   true,
    }

    userIDRegex = regexp.MustCompile(`^[0-9]+$`)
)

func isValidUserID(userID string) bool {
    if !userIDRegex.MatchString(userID) {
        return false
    }
    id, err := strconv.Atoi(userID)
    return err == nil && id > 0
}

func isValidStatus(status string) bool {
    return validStatuses[status]
}

func isValidSortField(sortBy string) bool {
    return validSortFields[sortBy]
}

func createUser(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    var userReq CreateUserRequest
    if err := json.Unmarshal([]byte(request.Body), &userReq); err != nil {
        return events.APIGatewayProxyResponse{StatusCode: 400}, err
    }

    // Validate input fields
    if !isValidName(userReq.Name) || !isValidEmail(userReq.Email) || !isValidDepartment(userReq.Department) {
        return events.APIGatewayProxyResponse{StatusCode: 400}, nil
    }

    db, err := sql.Open("mysql", dsn)
    if err != nil {
        return events.APIGatewayProxyResponse{StatusCode: 500}, err
    }
    defer db.Close()

    // Use parameterized query
    insertSQL := "INSERT INTO users (name, email, department) VALUES (?, ?, ?)"

    _, err = db.Exec(insertSQL, userReq.Name, userReq.Email, userReq.Department)
    if err != nil {
        return events.APIGatewayProxyResponse{StatusCode: 500}, err
    }

    return events.APIGatewayProxyResponse{StatusCode: 201}, nil
}

Detect This Vulnerability in Your Code

Sourcery automatically identifies sql injection from event data in sql string in aws lambda and many other security issues in your codebase.