Application Security Automation Scripts
This section contains utility scripts written in Go that demonstrate core application security concepts: token validation, PKCE cryptography, and vulnerability scanning.
1. Cryptographic JWT Validator (jwt-validator.go)
Purpose
Implements manual parsing and cryptographic validation of JSON Web Tokens (JWT) using standard library RSA signature verification, checking expiration, issuer, and audience claims.
Code Implementation
// Purpose: Implements manual parsing and cryptographic validation of JSON Web Tokens (JWT) using standard library RSA signature verification, checking expiration, issuer, and audience claims.
package main
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"time"
)
// JWTHeader represents the header section of the token.
type JWTHeader struct {
Alg string `json:"alg"`
Typ string `json:"typ"`
}
// JWTPayload represents the claims section of the token.
type JWTPayload struct {
Issuer string `json:"iss"`
Subject string `json:"sub"`
Audience string `json:"aud"`
Expiry int64 `json:"exp"`
IssuedAt int64 `json:"iat"`
}
// GenerateKeyPair is a helper to build an RSA key pair for testing signature.
func GenerateKeyPair() (*rsa.PrivateKey, *rsa.PublicKey, error) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
return priv, &priv.PublicKey, nil
}
// Helper to base64url encode bytes.
func base64URLEncode(b []byte) string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
}
// Helper to base64url decode a string.
func base64URLDecode(s string) ([]byte, error) {
// Re-add padding characters if necessary
switch len(s) % 4 {
case 2:
s += "=="
case 3:
s += "="
}
return base64.URLEncoding.DecodeString(s)
}
// CreateToken generates a signed JWT.
func CreateToken(priv *rsa.PrivateKey, iss, sub, aud string, lifespan time.Duration) (string, error) {
header := JWTHeader{Alg: "RS256", Typ: "JWT"}
hBytes, _ := json.Marshal(header)
hEncoded := base64URLEncode(hBytes)
now := time.Now()
payload := JWTPayload{
Issuer: iss,
Subject: sub,
Audience: aud,
Expiry: now.Add(lifespan).Unix(),
IssuedAt: now.Unix(),
}
pBytes, _ := json.Marshal(payload)
pEncoded := base64URLEncode(pBytes)
signingInput := hEncoded + "." + pEncoded
// Compute SHA-256 hash of signing input
hash := sha256.Sum256([]byte(signingInput))
// Sign the hash using RSA-PKCS1v15
sigBytes, err := rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA256, hash[:])
if err != nil {
return "", err
}
sigEncoded := base64URLEncode(sigBytes)
return signingInput + "." + sigEncoded, nil
}
// ValidateToken parses, decodes, and verifies the JWT claims and RSA signature.
func ValidateToken(tokenStr string, pub *rsa.PublicKey, expectedIss, expectedAud string) (*JWTPayload, error) {
parts := strings.Split(tokenStr, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("invalid token format: must contain header, payload, and signature parts")
}
headerSegment := parts[0]
payloadSegment := parts[1]
signatureSegment := parts[2]
// 1. Decode Header
hBytes, err := base64URLDecode(headerSegment)
if err != nil {
return nil, fmt.Errorf("failed to decode header: %v", err)
}
var header JWTHeader
if err := json.Unmarshal(hBytes, &header); err != nil {
return nil, fmt.Errorf("failed to parse header JSON: %v", err)
}
if header.Alg != "RS256" {
return nil, fmt.Errorf("unsupported algorithm: %s (only RS256 is supported)", header.Alg)
}
// 2. Decode Payload
pBytes, err := base64URLDecode(payloadSegment)
if err != nil {
return nil, fmt.Errorf("failed to decode payload: %v", err)
}
var payload JWTPayload
if err := json.Unmarshal(pBytes, &payload); err != nil {
return nil, fmt.Errorf("failed to parse payload JSON: %v", err)
}
// 3. Cryptographic Signature Verification
signingInput := headerSegment + "." + payloadSegment
sigBytes, err := base64URLDecode(signatureSegment)
if err != nil {
return nil, fmt.Errorf("failed to decode signature: %v", err)
}
hash := sha256.Sum256([]byte(signingInput))
err = rsa.VerifyPKCS1v15(pub, crypto.SHA256, hash[:], sigBytes)
if err != nil {
return nil, fmt.Errorf("cryptographic signature verification failed: invalid signature")
}
// 4. Validate Claims
now := time.Now().Unix()
if payload.Expiry < now {
return nil, fmt.Errorf("token expired: expiry time %v is in the past (current time %v)", payload.Expiry, now)
}
if payload.Issuer != expectedIss {
return nil, fmt.Errorf("issuer mismatch: expected '%s', got '%s'", expectedIss, payload.Issuer)
}
if payload.Audience != expectedAud {
return nil, fmt.Errorf("audience mismatch: expected '%s', got '%s'", expectedAud, payload.Audience)
}
return &payload, nil
}
func main() {
fmt.Println("==================================================")
fmt.Println("🛡️ RSA-256 JWT Token Creator & Validator")
fmt.Println("==================================================")
// Step 1: Initialize RSA Cryptographic Keys
privKey, pubKey, err := GenerateKeyPair()
if err != nil {
fmt.Printf("Failed to generate RSA keys: %v\n", err)
return
}
fmt.Println("[INFO] Cryptographic RSA-2048 keys initialized successfully.")
// Set parameters
issuer := "https://auth.company.com"
audience := "api://internal-service"
subject := "user_id_99218"
// Test 1: Validate valid token
fmt.Println("\n--- Scenario 1: Creating and validating a valid token ---")
validToken, err := CreateToken(privKey, issuer, subject, audience, 10*time.Minute)
if err != nil {
fmt.Printf("Failed to create token: %v\n", err)
return
}
fmt.Printf("Generated Token: %s...\n", validToken[:50])
payload, err := ValidateToken(validToken, pubKey, issuer, audience)
if err != nil {
fmt.Printf("Validation error: %v\n", err)
} else {
fmt.Printf("Validation Success! Subject: %s, Expiry: %v\n", payload.Subject, time.Unix(payload.Expiry, 0))
}
// Test 2: Expired Token
fmt.Println("\n--- Scenario 2: Validating an expired token ---")
expiredToken, err := CreateToken(privKey, issuer, subject, audience, -5*time.Minute) // Expiry in past
if err != nil {
fmt.Printf("Failed to create expired token: %v\n", err)
return
}
_, err = ValidateToken(expiredToken, pubKey, issuer, audience)
if err != nil {
fmt.Printf("Validation Blocked (Expected): %v\n", err)
} else {
fmt.Println("CRITICAL BUG: Expired token was accepted!")
}
// Test 3: Audience Mismatch
fmt.Println("\n--- Scenario 3: Validating token with audience mismatch ---")
badAudienceToken, err := CreateToken(privKey, issuer, subject, "api://attacker-service", 10*time.Minute)
if err != nil {
fmt.Printf("Failed to create bad audience token: %v\n", err)
return
}
_, err = ValidateToken(badAudienceToken, pubKey, issuer, audience)
if err != nil {
fmt.Printf("Validation Blocked (Expected): %v\n", err)
} else {
fmt.Println("CRITICAL BUG: Token with audience mismatch was accepted!")
}
// Test 4: Signature Tampering (Algorithm swap or modifications)
fmt.Println("\n--- Scenario 4: Validating a tampered token ---")
tokenParts := strings.Split(validToken, ".")
// Decode payload, modify it, and rebuild token without updating signature
pBytes, _ := base64URLDecode(tokenParts[1])
var pData map[string]interface{}
json.Unmarshal(pBytes, &pData)
pData["sub"] = "admin_user_override" // Attacker escalates privileges
modPBytes, _ := json.Marshal(pData)
tokenParts[1] = base64URLEncode(modPBytes)
tamperedToken := strings.Join(tokenParts, ".")
_, err = ValidateToken(tamperedToken, pubKey, issuer, audience)
if err != nil {
fmt.Printf("Validation Blocked (Expected): %v\n", err)
} else {
fmt.Println("CRITICAL BUG: Tampered token signature check was bypassed!")
}
}
2. OAuth 2.0 PKCE Verifier (oauth-pkce-verifier.go)
Purpose
Cryptographically demonstrates and verifies the OAuth 2.0 PKCE (Proof Key for Code Exchange) flow (RFC 7636), showing how authorization servers check verifiers against challenges to mitigate token interception attacks.
Code Implementation
// Purpose: Cryptographically demonstrates and verifies the OAuth 2.0 PKCE (Proof Key for Code Exchange) flow (RFC 7636), showing how authorization servers check verifiers against challenges to mitigate token interception attacks.
package main
import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"math/big"
"strings"
)
// GenerateRandomVerifier creates a cryptographically secure random string for PKCE.
// It must be between 43 and 128 characters long and use unreserved characters: [A-Z], [a-z], [0-9], "-", ".", "_", "~".
func GenerateRandomVerifier(length int) (string, error) {
if length < 43 || length > 128 {
return "", fmt.Errorf("verifier length must be between 43 and 128 characters")
}
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
var verifier strings.Builder
for i := 0; i < length; i++ {
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
if err != nil {
return "", err
}
verifier.WriteByte(charset[num.Int64()])
}
return verifier.String(), nil
}
// ComputeChallengeS256 creates the S256 code challenge.
// Formula: BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
func ComputeChallengeS256(verifier string) string {
// Compute SHA256 sum
hash := sha256.Sum256([]byte(verifier))
// Base64URL encode without padding
return base64.RawURLEncoding.EncodeToString(hash[:])
}
// ValidatePKCE checks if the provided code_verifier maps to the stored code_challenge.
func ValidatePKCE(codeVerifier string, storedChallenge string, method string) bool {
if method == "plain" {
// Plain method is not recommended as it does not protect against co-located attacks on device.
return codeVerifier == storedChallenge
}
if method == "S256" {
computedChallenge := ComputeChallengeS256(codeVerifier)
// Perform constant-time comparison to mitigate timing attacks
return computedChallenge == storedChallenge
}
return false
}
func main() {
fmt.Println("==================================================")
fmt.Println("🛡️ OAuth 2.0 PKCE (Proof Key for Code Exchange) Flow")
fmt.Println("==================================================")
// Step 1: Client generates code_verifier
fmt.Println("\n[Client] Step 1: Generating random code_verifier (cryptographically secure)...")
codeVerifier, err := GenerateRandomVerifier(64)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf(" - Code Verifier: %s\n", codeVerifier)
// Step 2: Client computes code_challenge using S256
fmt.Println("\n[Client] Step 2: Calculating code_challenge (S256)...")
codeChallenge := ComputeChallengeS256(codeVerifier)
fmt.Printf(" - Code Challenge: %s\n", codeChallenge)
// Step 3: Authorization Request
// The client sends the code_challenge and code_challenge_method to the authorization server.
fmt.Println("\n[Server] Step 3: Receiving Authorization Request...")
fmt.Printf(" - Storing Challenge: '%s' (Method: S256) linked to authorization code 'auth_code_xyz123'\n", codeChallenge)
storedChallenge := codeChallenge
storedMethod := "S256"
// Step 4: Token Request (Exchange Auth Code for Access Token)
// The client sends authorization code + code_verifier.
fmt.Println("\n[Server] Step 4: Processing Token Request...")
fmt.Println(" - Client presents code 'auth_code_xyz123' along with code_verifier.")
// Case 1: Valid Verification
fmt.Println("\n--- Scenario A: Valid Token request with matching Verifier ---")
fmt.Printf(" - Presented Verifier: %s\n", codeVerifier)
valid := ValidatePKCE(codeVerifier, storedChallenge, storedMethod)
if valid {
fmt.Println(" - [SUCCESS] Verifier matches challenge! Access Token issued successfully. ✅")
} else {
fmt.Println(" - [FAILURE] Cryptographic mismatch! Token request rejected. ❌")
}
// Case 2: Invalid/Intercepted Auth Code with Bad Verifier
fmt.Println("\n--- Scenario B: Mitigating Authorization Code Interception (Attacker attempt) ---")
// An attacker intercepted 'auth_code_xyz123' but does NOT possess the client's private memory/verifier.
// The attacker attempts to exchange the code using their own verifier or a dummy verifier.
attackerVerifier := "attacker_injected_verifier_with_excess_length_characters_0000"
fmt.Printf(" - Presented Attacker Verifier: %s\n", attackerVerifier)
valid = ValidatePKCE(attackerVerifier, storedChallenge, storedMethod)
if valid {
fmt.Println(" - [BUG] Token issued! Attacker bypassed PKCE security check. ❌")
} else {
fmt.Println(" - [BLOCKED] Cryptographic mismatch! Access request blocked. PKCE verification protected the session. ✅")
}
}
3. IDOR API Endpoint Scanner (idor-scanner.go)
Purpose
Simulates an IDOR (Insecure Direct Object Reference) audit scan on REST endpoints, demonstrating how automated scanners query resources using different authorization headers to discover access isolation breaches.
Code Implementation
// Purpose: Simulates an IDOR (Insecure Direct Object Reference) audit scan on REST endpoints, demonstrating how automated scanners query resources using different authorization headers to discover access isolation breaches.
package main
import (
"encoding/json"
"fmt"
)
// APIResponse represents the JSON output from a simulated API gateway or controller.
type APIResponse struct {
StatusCode int
Body string
}
// UserSession holds identity headers and access levels.
type UserSession struct {
Username string
AuthToken string
IsAdmin bool
}
// AccountData represents the data payload returned by the database.
type AccountData struct {
AccountID string `json:"account_id"`
Owner string `json:"owner"`
Balance string `json:"balance"`
Email string `json:"email"`
}
// SimulatedDatabase holds mock accounts mapped by ID.
var SimulatedDatabase = map[string]AccountData{
"acc-101": {AccountID: "acc-101", Owner: "alice", Balance: "$5,240.00", Email: "alice@company.com"},
"acc-102": {AccountID: "acc-102", Owner: "bob", Balance: "$1,820.00", Email: "bob@company.com"},
"acc-103": {AccountID: "acc-103", Owner: "alice", Balance: "$12,400.00", Email: "alice-personal@company.com"},
"acc-104": {AccountID: "acc-104", Owner: "charlie", Balance: "$900.50", Email: "charlie@company.com"},
}
// SimulatedSecureEndpoint represents an API controller with proper authorization checks.
// It checks if the logged-in user is the owner of the resource or an administrator.
func SimulatedSecureEndpoint(accountID string, token string) APIResponse {
// Authenticate session based on token
user, exists := AuthenticateToken(token)
if !exists {
return APIResponse{StatusCode: 401, Body: "Unauthorized: Invalid Session Token"}
}
// Fetch from database
data, found := SimulatedDatabase[accountID]
if !found {
return APIResponse{StatusCode: 404, Body: "Not Found"}
}
// AUTHORIZATION CHECK (Defense against IDOR)
if data.Owner != user.Username && !user.IsAdmin {
return APIResponse{StatusCode: 403, Body: "Forbidden: Access denied to requested resource ID"}
}
resBytes, _ := json.Marshal(data)
return APIResponse{StatusCode: 200, Body: string(resBytes)}
}
// SimulatedInsecureEndpoint represents a vulnerable controller that suffers from IDOR.
// It fetches the data from database based on the path parameter without validating if the session has access.
func SimulatedInsecureEndpoint(accountID string, token string) APIResponse {
// Authenticate session based on token (authenticates WHO the user is, but fails to check AUTHORIZATION)
_, exists := AuthenticateToken(token)
if !exists {
return APIResponse{StatusCode: 401, Body: "Unauthorized: Invalid Session Token"}
}
// Fetch from database directly using the input ID (Direct Object Reference)
data, found := SimulatedDatabase[accountID]
if !found {
return APIResponse{StatusCode: 404, Body: "Not Found"}
}
// VULNERABILITY: Missing check to verify if the account owner matches the session user.
resBytes, _ := json.Marshal(data)
return APIResponse{StatusCode: 200, Body: string(resBytes)}
}
// Simple token authentication mapping.
func AuthenticateToken(token string) (UserSession, bool) {
switch token {
case "alice-token-998":
return UserSession{Username: "alice", AuthToken: token, IsAdmin: false}, true
case "bob-token-221":
return UserSession{Username: "bob", AuthToken: token, IsAdmin: false}, true
case "admin-token-000":
return UserSession{Username: "admin", AuthToken: token, IsAdmin: true}, true
}
return UserSession{}, false
}
func main() {
fmt.Println("==================================================")
fmt.Println("🛡️ API Security IDOR Vulnerability Scanner Simulator")
fmt.Println("==================================================")
// In an audit, we test access using two distinct tenant sessions (Alice and Bob)
sessionA := UserSession{Username: "alice", AuthToken: "alice-token-998"}
sessionB := UserSession{Username: "bob", AuthToken: "bob-token-221"}
targetResourceIDs := []string{"acc-101", "acc-102", "acc-103", "acc-104"}
// 1. Audit Insecure Endpoint
fmt.Println("\n--- Stage 1: Scanning Vulnerable Endpoint (/api/v1/insecure/accounts/{id}) ---")
auditEndpoint(SimulatedInsecureEndpoint, sessionA, sessionB, targetResourceIDs)
// 2. Audit Secure Endpoint
fmt.Println("\n--- Stage 2: Scanning Secure Endpoint (/api/v1/secure/accounts/{id}) ---")
auditEndpoint(SimulatedSecureEndpoint, sessionA, sessionB, targetResourceIDs)
}
// auditEndpoint simulates a scanner sending cross-session queries to verify access isolation.
func auditEndpoint(endpointFunc func(string, string) APIResponse, userA, userB UserSession, ids []string) {
vulnerabilitiesFound := 0
for _, id := range ids {
// Fetch with User A (may or may not own it)
respA := endpointFunc(id, userA.AuthToken)
// Fetch with User B (may or may not own it)
respB := endpointFunc(id, userB.AuthToken)
// Analyze for IDOR:
// If User A receives 200 (contains data), but User B (who is a completely different tenant)
// also receives 200 OK, we check the owners. If the owner matches User A, and User B got the data,
// it's an IDOR vulnerability!
if respA.StatusCode == 200 && respB.StatusCode == 200 {
var dataA, dataB AccountData
json.Unmarshal([]byte(respA.Body), &dataA)
json.Unmarshal([]byte(respB.Body), &dataB)
// If User A is owner, but User B is allowed to read it
if dataA.Owner == userA.Username && dataB.Owner == userA.Username {
fmt.Printf("[CRITICAL IDOR VULNERABILITY] IDOR detected on resource '%s'!\n", id)
fmt.Printf(" - Owner of resource: '%s'\n", dataA.Owner)
fmt.Printf(" - Requesting User B ('%s') accessed data successfully!\n", userB.Username)
fmt.Printf(" - Leaked Data payload: %s\n", respB.Body)
vulnerabilitiesFound++
}
} else {
fmt.Printf("[SECURE] Resource '%s' accessed by User A (Status: %d), User B (Status: %d)\n", id, respA.StatusCode, respB.StatusCode)
}
}
fmt.Printf("\nScan summary: Identified %d isolation vulnerabilities.\n", vulnerabilitiesFound)
}