Les pare-feu d'applications Web (WAF) sont depuis longtemps une solution de sécurité standard pour protéger les applications Web. Les WAF basés sur le cloud comme AWS WAF et Cloudflare WAF sont particulièrement populaires en raison de leur facilité de mise en œuvre. Cependant, ils comportent plusieurs défis :
Pour relever ces défis, une nouvelle approche appelée In-app WAF ou RASP (Runtime Application Self-Protection) a retenu l'attention.
Dans cet article, je présenterai Waffle, une bibliothèque permettant d'intégrer les fonctionnalités WAF intégrées à l'application dans les applications Web Go.
WAF/RASP intégré à l'application n'est pas destiné à remplacer les WAF cloud existants, mais plutôt à les compléter en intégrant la fonctionnalité WAF directement dans votre application pour une protection améliorée.
Il peut gérer les attaques courantes d'applications Web telles que l'injection SQL et XSS, ainsi que les attaques de logique métier d'application telles que le credential stuffing et les tentatives de force brute.
Le principal avantage est une détection et une prévention précises grâce à une connaissance complète du contexte de la demande.
Considérez cette requête HTTP pour créer un article de blog :
POST /blog/post HTTP/1.1 ... { "title": "What is SQL ?" "body": "SQL example code: `SELECT * FROM users` ..." }
Si votre application utilise des espaces réservés pour construire des instructions SQL en toute sécurité, l'injection SQL n'est pas possible. Cependant, les WAF basés sur le cloud qui s'appuient sur la correspondance de modèles bloqueraient cette requête car elle contient des modèles suspects de type SQL (la chaîne SELECT * FROM soulève des problèmes d'injection SQL).
Les développeurs se retrouvent souvent fastidieux à ajuster les paramètres, les points de terminaison ou les règles WAF pour réduire ces faux positifs. Quelle lourde tâche !
En revanche, In-app WAF / RASP comprend le contexte de la requête. Il reconnaît quand les espaces réservés ne sont pas utilisés et bloque les attaques uniquement lorsque « l'injection SQL est réellement possible ». Cette approche contextuelle entraîne moins de faux positifs et peut même contribuer à atténuer les vulnérabilités du jour zéro.
Waffle est une bibliothèque qui active la fonctionnalité In-App WAF/RASP dans les applications Web Go.
Voyons comment intégrer Waffle dans votre application et comment il prévient les attaques.
Bien que cet exemple utilise le net/http de la bibliothèque standard, Waffle prend également en charge d'autres bibliothèques comme Gin et GORM.
Pour plus de détails, consultez la documentation des bibliothèques prises en charge.
L'application suivante présente une vulnérabilité d'injection SQL dans le point de terminaison /login :
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 }
Intégrons Waffle pour empêcher l'injection SQL :
$ 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
Modifiez main.go comme suit :
$ go get github.com/sitebatch/waffle-go
Les changements sont minimes :
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 }
Maintenant, lorsque nous essayons une attaque par injection SQL, Waffle la bloque :
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) }
Ce code HTML est le message d'erreur renvoyé par défaut par Waffle et ressemble à ceci :
Lors de l'utilisation d'espaces réservés, Waffle reconnaît que l'injection SQL n'est pas possible et ne bloquera pas la requête :
$ 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 }
Notez que même dans ce cas, Waffle peut toujours détecter les tentatives d'injection SQL comme un WAF basé sur le cloud (bien qu'il ne la bloque pas) :
# 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
Bien que nous ayons démontré la prévention des injections SQL, Waffle peut détecter et prévenir diverses attaques :
Pour plus de détails, consultez la documentation sur la liste des règles.
Les règles sont continuellement mises à jour et les contributions sont les bienvenues.
En intégrant Waffle dans votre application, vous pouvez détecter et prévenir avec précision les attaques.
Pour les guides de mise en œuvre spécifiques au framework et les instructions d'utilisation détaillées, reportez-vous à la section Guides de la documentation.
Waffle est en cours de développement actif. Nous apprécions les commentaires et les contributions.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!