Heim >Backend-Entwicklung >Golang >Einführung in Waffle: In-App-WAF für Go-Anwendungen
Web Application Firewalls (WAF) sind seit langem eine Standard-Sicherheitslösung zum Schutz von Webanwendungen. Cloudbasierte WAFs wie AWS WAF und Cloudflare WAF erfreuen sich aufgrund ihrer einfachen Implementierung besonders großer Beliebtheit. Sie bringen jedoch mehrere Herausforderungen mit sich:
Um diese Herausforderungen anzugehen, hat ein neuer Ansatz namens In-App WAF oder RASP (Runtime Application Self-Protection) an Aufmerksamkeit gewonnen.
In diesem Beitrag stelle ich Waffle vor, eine Bibliothek zur Integration von In-App-WAF-Funktionen in Go-Webanwendungen.
In-App-WAF/RASP ist nicht dazu gedacht, bestehende Cloud-WAFs zu ersetzen, sondern sie vielmehr zu ergänzen, indem die WAF-Funktionalität direkt in Ihre Anwendung eingebettet wird, um den Schutz zu verbessern.
Es kann gängige Webanwendungsangriffe wie SQL-Injection und XSS sowie Angriffe auf die Geschäftslogik von Anwendungen wie Credential Stuffing und Brute-Force-Versuche bewältigen.
Der Hauptvorteil ist die genaue Erkennung und Prävention durch vollständige Kenntnis des Anforderungskontexts.
Bedenken Sie diese HTTP-Anfrage zum Erstellen eines Blogbeitrags:
POST /blog/post HTTP/1.1 ... { "title": "What is SQL ?" "body": "SQL example code: `SELECT * FROM users` ..." }
Wenn Ihre Anwendung Platzhalter verwendet, um SQL-Anweisungen sicher zu erstellen, ist eine SQL-Injection nicht möglich. Cloudbasierte WAFs, die auf Mustervergleich basieren, würden diese Anfrage jedoch blockieren, da sie verdächtige SQL-ähnliche Muster enthält (die Zeichenfolge SELECT * FROM wirft Bedenken hinsichtlich der SQL-Injection auf).
Entwickler müssen oft mühsam Parameter, Endpunkte oder WAF-Regeln anpassen, um diese Fehlalarme zu reduzieren. Was für eine umständliche Aufgabe!
Im Gegensatz dazu versteht In-App WAF/RASP den Anforderungskontext. Es erkennt, wenn keine Platzhalter verwendet werden und blockiert Angriffe nur dann, wenn „SQL-Injection tatsächlich möglich ist“. Dieser kontextbewusste Ansatz führt zu weniger Fehlalarmen und kann sogar dazu beitragen, Zero-Day-Schwachstellen zu mindern.
Waffle ist eine Bibliothek, die In-App WAF/RASP-Funktionalität in Go-Webanwendungen ermöglicht.
Sehen wir uns an, wie Sie Waffle in Ihre Anwendung integrieren und wie es Angriffe verhindert.
Während dieses Beispiel net/http der Standardbibliothek verwendet, unterstützt Waffle auch andere Bibliotheken wie Gin und GORM.
Weitere Informationen finden Sie in der Dokumentation zu den unterstützten Bibliotheken.
Die folgende Anwendung weist eine SQL-Injection-Schwachstelle im /login-Endpunkt auf:
POST /blog/post HTTP/1.1 ... { "title": "What is SQL ?" "body": "SQL example code: `SELECT * FROM users` ..." }
package main import ( "context" "database/sql" "fmt" "net/http" _ "github.com/mattn/go-sqlite3" ) var database *sql.DB func init() { setupDB() } func newHTTPHandler() http.Handler { mux := http.NewServeMux() mux.Handle("/login", http.HandlerFunc(loginController)) return mux } func main() { srv := &http.Server{ Addr: ":8000", Handler: newHTTPHandler(), } srv.ListenAndServe() } func loginController(w http.ResponseWriter, r *http.Request) { email := r.FormValue("email") password := r.FormValue("password") if err := login(r.Context(), email, password); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Write([]byte("Login success")) } func login(ctx context.Context, email, password string) error { // ⚠️ SQL INJECTION VULNERABILITY rows, err := database.QueryContext(ctx, fmt.Sprintf("SELECT * FROM users WHERE email = '%s' AND password = '%s'", email, password)) if err != nil { return err } defer rows.Close() if !rows.Next() { return fmt.Errorf("invalid email or password") } // do something return nil } func setupDB() { db, err := sql.Open("sqlite3", "file::memory:?cache=shared") if err != nil { panic(err) } if _, err := db.Exec("CREATE TABLE users(id int, email text, password text);"); err != nil { panic(err) } if _, err := db.Exec("INSERT INTO users(id, email, password) VALUES(1, 'user@example.com', 'password');"); err != nil { panic(err) } database = db }
Lassen Sie uns Waffle integrieren, um SQL-Injection zu verhindern:
$ go run . # SQL injection attack $ curl -i -X POST 'http://localhost:8000/login' \ --data "email=user@example.com' OR 1=1--&password=" HTTP/1.1 200 OK Date: Sun, 05 Jan 2025 10:32:50 GMT Content-Length: 13 Content-Type: text/plain; charset=utf-8 Login success
Ändern Sie main.go wie folgt:
$ go get github.com/sitebatch/waffle-go
Die Änderungen sind minimal:
package main import ( "context" "database/sql" "errors" "fmt" "net/http" "github.com/sitebatch/waffle-go" "github.com/sitebatch/waffle-go/action" waffleSQL "github.com/sitebatch/waffle-go/contrib/database/sql" waffleHTTP "github.com/sitebatch/waffle-go/contrib/net/http" _ "github.com/mattn/go-sqlite3" ) var database *sql.DB func init() { setupDB() } func newHTTPHandler() http.Handler { mux := http.NewServeMux() mux.Handle("/login", http.HandlerFunc(loginController)) handler := waffleHTTP.WafMiddleware(mux) return handler } func main() { srv := &http.Server{ Addr: ":8000", Handler: newHTTPHandler(), } // Start waffle with debug mode waffle.Start(waffle.WithDebug()) srv.ListenAndServe() } func loginController(w http.ResponseWriter, r *http.Request) { email := r.FormValue("email") password := r.FormValue("password") if err := login(r.Context(), email, password); err != nil { var actionErr *action.BlockError if errors.As(err, &actionErr) { return } http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Write([]byte("Login success")) } func login(ctx context.Context, email, password string) error { // ⚠️ SQL INJECTION VULNERABILITY rows, err := database.QueryContext(ctx, fmt.Sprintf("SELECT * FROM users WHERE email = '%s' AND password = '%s'", email, password)) if err != nil { return err } defer rows.Close() if !rows.Next() { return fmt.Errorf("invalid email or password") } // do something return nil } func setupDB() { db, err := waffleSQL.Open("sqlite3", "file::memory:?cache=shared") if err != nil { panic(err) } if _, err := db.Exec("CREATE TABLE users(id int, email text, password text);"); err != nil { panic(err) } if _, err := db.Exec("INSERT INTO users(id, email, password) VALUES(1, 'user@example.com', 'password');"); err != nil { panic(err) } database = db }
Wenn wir jetzt einen SQL-Injection-Angriff versuchen, blockiert Waffle ihn:
diff --git a/main.go b/main.go index 90b8197..9fefb06 100644 --- a/main.go +++ b/main.go @@ -3,9 +3,15 @@ package main import ( "context" "database/sql" + "errors" "fmt" "net/http" + "github.com/sitebatch/waffle-go" + "github.com/sitebatch/waffle-go/action" + waffleSQL "github.com/sitebatch/waffle-go/contrib/database/sql" + waffleHTTP "github.com/sitebatch/waffle-go/contrib/net/http" + _ "github.com/mattn/go-sqlite3" ) @@ -19,7 +25,9 @@ func newHTTPHandler() http.Handler { mux := http.NewServeMux() mux.Handle("/login", http.HandlerFunc(loginController)) - return mux + handler := waffleHTTP.WafMiddleware(mux) + + return handler } func main() { @@ -28,6 +36,9 @@ func main() { Handler: newHTTPHandler(), } + // Start waffle with debug mode + waffle.Start(waffle.WithDebug()) + srv.ListenAndServe() } @@ -36,6 +47,11 @@ func loginController(w http.ResponseWriter, r *http.Request) { password := r.FormValue("password") if err := login(r.Context(), email, password); err != nil { + var actionErr *action.BlockError + if errors.As(err, &actionErr) { + return + } + http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -60,7 +76,7 @@ func login(ctx context.Context, email, password string) error { } func setupDB() { - db, err := sql.Open("sqlite3", "file::memory:?cache=shared") + db, err := waffleSQL.Open("sqlite3", "file::memory:?cache=shared") if err != nil { panic(err) }
Dieser HTML-Code ist die standardmäßig von Waffle zurückgegebene Fehlermeldung und sieht folgendermaßen aus:
Bei der Verwendung von Platzhaltern erkennt Waffle, dass eine SQL-Injection nicht möglich ist und blockiert die Anfrage nicht:
$ curl -i -X POST 'http://localhost:8000/login' \ --data "email=user@example.com' OR 1=1--&password=" -i HTTP/1.1 403 Forbidden Date: Sun, 05 Jan 2025 10:38:22 GMT Content-Length: 1574 Content-Type: text/html; charset=utf-8 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Access Denied</title>
# Fix SQL injection vulnerability diff --git a/main.go b/main.go index 9fefb06..5b482f2 100644 --- a/main.go +++ b/main.go @@ -60,7 +60,7 @@ func loginController(w http.ResponseWriter, r *http.Request) { } func login(ctx context.Context, email, password string) error { - rows, err := database.QueryContext(ctx, fmt.Sprintf("SELECT * FROM users WHERE email = '%s' AND password = '%s'", email, password)) + rows, err := database.QueryContext(ctx, "SELECT * FROM users WHERE email = ? AND password = ?", email, password) if err != nil { return err }
Beachten Sie, dass Waffle selbst in diesem Fall immer noch versuchte SQL-Injection wie eine cloudbasierte WAF erkennen kann (obwohl es diese nicht blockiert):
# Waffle won't block the request since SQL injection isn't possible $ curl -i -X POST 'http://localhost:8000/login' \ --data "email=user@example.com' OR 1=1--&password=" HTTP/1.1 500 Internal Server Error Content-Type: text/plain; charset=utf-8 X-Content-Type-Options: nosniff Date: Sun, 05 Jan 2025 10:49:05 GMT Content-Length: 26 invalid email or password
Während wir die Verhinderung von SQL-Injection demonstriert haben, kann Waffle verschiedene Angriffe erkennen und verhindern:
Weitere Einzelheiten finden Sie in der Dokumentation zur Regelliste.
Die Regeln werden kontinuierlich aktualisiert und Beiträge sind willkommen.
Durch die Integration von Waffle in Ihre Anwendung können Sie Angriffe genau erkennen und verhindern.
Framework-spezifische Implementierungsleitfäden und detaillierte Nutzungsanweisungen finden Sie im Abschnitt „Leitfäden“ in der Dokumentation.
Waffle befindet sich in aktiver Entwicklung. Wir freuen uns über Feedback und Beiträge.
Das obige ist der detaillierte Inhalt vonEinführung in Waffle: In-App-WAF für Go-Anwendungen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!