Insiden serangan siber tingkat lanjut (seperti kasus nation-state targeting core auth endpoints) sering kali mengeksploitasi diskrepansi parsing antara reverse proxy dan aplikasi, atau melompat tahapan (state-jumping) pada alur otentikasi multi-step. Mengandalkan Web Application Firewall (WAF) saja tidak menjamin mitigasi payload yang dimanipulasi secara spesifik. Solusi defensif yang berkelanjutan adalah memvalidasi batas otentikasi langsung di lapisan aplikasi melalui automated regression test suites di pipeline CI.

Vulnerability Vector: Header Smuggling & State Jumping

Penyerang sering menggunakan dua vektor utama untuk membobol alur otentikasi tanpa memicu signature WAF standar:

  • Header Parsing Discrepancies: Injeksi raw bytes (null-byte \x00, multiline header values, atau double Authorization headers) yang diproses secara permisif oleh runtime aplikasi backend namun diabaikan oleh upstream proxy.
  • State Machine Desynchronization: Melewati fase intermediat (misalnya mengirim request Step 3: SMS/TOTP validation tanpa pernah mengeksekusi Step 1: Password authentication atau Step 2: Challenge issuance), mengandalkan session store yang implicitly menginisialisasi intermediate states sebagai truthy.

Uji regresi CI harus mengeksekusi request langsung ke HTTP entry point aplikasi secara deterministik, memverifikasi bahwa parser internal menolak format anomali secara konsisten sebelum pipeline deployment disetujui.

Arsitektur Test Harness Deterministik

Uji regresi keamanan tingkat CI harus cepat, terisolasi, dan tidak bergantung pada dependensi jaringan eksternal. Gunakan in-memory HTTP server dan in-memory state store (seperti Redis test mock atau slice internal) untuk mengisolasi boundary logic.

Harness ini memanfaatkan modul httptest dan standard library net/http di Go untuk menyuntikkan raw bytes dan manipulasi state secara presisi.

package auth_test

import (
	"bytes"
	"context"
	"crypto/subtle"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"sync"
	"testing"
	"time"
)

type SessionState int

const (
	StateInit SessionState = iota
	StatePasswordVerified
	StateMFAIssued
	StateFullyAuthenticated
)

type Session struct {
	ID        string
	State     SessionState
	ExpiresAt time.Time
}

type AuthServer struct {
	mu       sync.Mutex
	sessions map[string]*Session
}

func NewAuthServer() *AuthServer {
	return &AuthServer{
		sessions: make(map[string]*Session),
	}
}

func (s *AuthServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 1. Strict Header Validation: Tolak multiple Authorization headers
	authHeaders := r.Header.Values("Authorization")
	if len(authHeaders) > 1 {
		http.Error(w, "Ambiguous Authorization Headers", http.StatusBadRequest)
		return
	}

	// 2. Strict Header Validation: Tolak control characters dalam Header
	for name, values := range r.Header {
		for _, v := range values {
			for _, b := range []byte(v) {
				if b < 0x20 || b == 0x7f {
					http.Error(w, fmt.Sprintf("Malformed character in %s", name), http.StatusBadRequest)
					return
				}
			}
		}
	}

	switch r.URL.Path {
	case "/auth/step1":
		s.handleStep1(w, r)
	case "/auth/step2-verify-mfa":
		s.handleStep2(w, r)
	default:
		http.NotFound(w, r)
	}
}

func (s *AuthServer) handleStep1(w http.ResponseWriter, r *http.Request) {
	sessionID := "sess_" + fmt.Sprintf("%d", time.Now().UnixNano())
	s.mu.Lock()
	s.sessions[sessionID] = &Session{
		ID:        sessionID,
		State:     StatePasswordVerified,
		ExpiresAt: time.Now().Add(5 * time.Minute),
	}
	s.mu.Unlock()

	w.Header().Set("Set-Cookie", "SID=" + sessionID + "; HttpOnly")
	w.WriteHeader(http.StatusOK)
}

func (s *AuthServer) handleStep2(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie("SID")
	if err != nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	s.mu.Lock()
	sess, exists := s.sessions[cookie.Value]
	s.mu.Unlock()

	// Enforce strict sequential state transition
	if !exists || sess.State != StatePasswordVerified {
		http.Error(w, "Invalid State Sequence", http.StatusForbidden)
		return
	}

	var payload struct {
		Code string `json:"code"`
	}
	if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
		http.Error(w, "Bad Payload", http.StatusBadRequest)
		return
	}

	// Constant-time check untuk mitigasi timing attack
	expectedCode := "849201"
	if subtle.ConstantTimeCompare([]byte(payload.Code), []byte(expectedCode)) != 1 {
		http.Error(w, "Invalid Challenge", http.StatusUnauthorized)
		return
	}

	sess.State = StateFullyAuthenticated
	w.WriteHeader(http.StatusOK)
}

Uji Validasi Evasion Header Parsing

Penyerang sering mengirim raw socket data yang memuat null bytes atau duplikasi header untuk memintas proxy parser. Menggunakan high-level HTTP client library sering kali gagal mereproduksi masalah ini karena client secara otomatis menormalkan data. Gunakan low-level net.Dial pada HTTP mock server untuk mengirim evasion payload secara mentah.

func TestRawHeaderEvasionRejection(t *testing.T) {
	srv := httptest.NewServer(NewAuthServer())
	defer srv.Close()

	conn, err := net.Dial("tcp", srv.Listener.Addr().String())
	if err != nil {
		t.Fatalf("Gagal inisialisasi raw socket: %v", err)
	}
	defer conn.Close()

	// Evasion: Injeksi null-byte dan duplicate Authorization headers
	rawPayload := "POST /auth/step1 HTTP/1.1\r\n" +
		"Host: localhost\r\n" +
		"Authorization: Bearer valid_token\r\n" +
		"Authorization: Bearer injected\x00_token\r\n" +
		"Content-Length: 0\r\n\r\n"

	if _, err := conn.Write([]byte(rawPayload)); err != nil {
		t.Fatalf("Gagal write raw payload: %v", err)
	}

	respBuf := make([]byte, 1024)
	n, _ := conn.Read(respBuf)
	response := string(respBuf[:n])

	if !bytes.Contains(respBuf[:n], []byte("400 Bad Request")) {
		t.Errorf("Evasion berhasil lolos, server tidak mengembalikan HTTP 400:\n%s", response)
	}
}

Uji Replay Timing Attack & State Jumping

Pola bypass state machine terjadi saat penyerang mengeksekusi tahapan validasi akhir menggunakan identitas dummy tanpa inisialisasi state awal. Assertion harus memverifikasi bahwa state transitif ditolak dengan HTTP 403 Forbidden atau HTTP 401 Unauthorized, bukan HTTP 500 Internal Server Error (yang mengindikasikan nil-pointer exception atau unhandled condition).

func TestStrictStateMachineBypass(t *testing.T) {
	auth := NewAuthServer()
	ts := httptest.NewServer(auth)
	defer ts.Close()

	client := &http.Client{Timeout: 2 * time.Second}

	// 1. Eksekusi MFA Verify langsung dengan session ID acak (State Jumping Attack)
	bypassBody := bytes.NewBufferString(`{"code":"849201"}`)
	req, _ := http.NewRequest(http.MethodPost, ts.URL+"/auth/step2-verify-mfa", bypassBody)
	req.AddCookie(&http.Cookie{Name: "SID", Value: "sess_fake_bypass_123"})

	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("Request error: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusForbidden {
		t.Fatalf("State machine bypass lolos. Expected 403, got: %d", resp.StatusCode)
	}
}

Integrasi pada Pipeline CI

Agar regresi ini berjalan efektif tanpa menambah overhead build time, terapkan prinsip-prinsip berikut:

  1. Parallel Execution: Jalankan security regression suite terpisah dari end-to-end (E2E) browser test. Karena berbasis in-memory mock, runtime suite harus di bawah 5 detik.
  2. Exit Code Enforcement: Konfigurasikan runner (seperti GitHub Actions atau GitLab CI) agar membatalkan build apabila terdapat failure pada suite *_security_test.go.
  3. Deterministic Assertions: Hindari time.Sleep pada verifikasi replay timing. Gunakan clock mocking atau crypto/subtle verification counter untuk menjaga determinisme uji tanpa flakiness.
Verifikasi auth di level unit/integration test menjamin keamanan software supply chain sebelum binary terdistribusi ke stage deployment, menutup cela di runtime bahkan saat layer perimeter firewall mengalami bypass.