Tembok Api Aplikasi Web (WAF) telah lama menjadi penyelesaian keselamatan standard untuk melindungi aplikasi web. WAF berasaskan awan seperti AWS WAF dan Cloudflare WAF amat popular kerana kemudahan pelaksanaannya. Walau bagaimanapun, ia datang dengan beberapa cabaran:
Untuk menangani cabaran ini, pendekatan baharu yang dipanggil WAF Dalam Apl atau RASP (Perlindungan Kendiri Aplikasi Waktu Jalan) telah mendapat perhatian.
Dalam siaran ini, saya akan memperkenalkan Waffle, sebuah perpustakaan untuk menyepadukan keupayaan WAF Dalam apl ke dalam aplikasi web Go.
WAF/RASP dalam apl bukan bertujuan untuk menggantikan WAF awan sedia ada tetapi untuk melengkapkannya dengan membenamkan fungsi WAF terus ke dalam aplikasi anda untuk perlindungan yang dipertingkatkan.
Ia boleh mengendalikan serangan aplikasi web biasa seperti suntikan SQL dan XSS, serta serangan logik perniagaan aplikasi seperti pemadat bukti kelayakan dan percubaan kekerasan.
Kelebihan utama ialah pengesanan dan pencegahan yang tepat melalui kesedaran konteks permintaan yang lengkap.
Pertimbangkan permintaan HTTP ini untuk membuat catatan blog:
POST /blog/post HTTP/1.1 ... { "title": "What is SQL ?" "body": "SQL example code: `SELECT * FROM users` ..." }
Jika aplikasi anda menggunakan ruang letak untuk membina pernyataan SQL dengan selamat, suntikan SQL tidak boleh dilakukan. Walau bagaimanapun, WAF berasaskan awan yang bergantung pada padanan corak akan menyekat permintaan ini kerana ia mengandungi corak seperti SQL yang mencurigakan (rentetan SELECT * FROM menimbulkan kebimbangan suntikan SQL).
Pembangun sering mendapati diri mereka membosankan melaraskan parameter, titik akhir atau peraturan WAF untuk mengurangkan positif palsu ini. Tugas yang menyusahkan!
Sebaliknya, WAF / RASP dalam apl memahami konteks permintaan. Ia mengiktiraf apabila pemegang tempat tidak digunakan dan hanya menyekat serangan apabila "suntikan SQL sebenarnya boleh dilakukan." Pendekatan sedar konteks ini menghasilkan lebih sedikit positif palsu malah boleh membantu mengurangkan kerentanan sifar hari.
Waffle ialah perpustakaan yang mendayakan fungsi WAF / RASP Dalam Apl dalam aplikasi web Go.
Mari lihat cara menyepadukan Waffle ke dalam aplikasi anda dan cara ia menghalang serangan.
Walaupun contoh ini menggunakan jaring/http perpustakaan standard, Waffle turut menyokong perpustakaan lain seperti Gin dan GORM.
Untuk butiran lanjut, lihat dokumentasi Perpustakaan yang Disokong.
Aplikasi berikut mempunyai kelemahan suntikan SQL dalam titik akhir /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 }
Mari kita integrasikan Waffle untuk mengelakkan suntikan 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
Ubah suai main.go seperti berikut:
$ go get github.com/sitebatch/waffle-go
Perubahan adalah minimum:
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 }
Kini apabila kami mencuba serangan suntikan SQL, Waffle menyekatnya:
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) }
HTML ini ialah mesej ralat yang dikembalikan secara lalai oleh wafel dan kelihatan seperti ini:
Apabila menggunakan ruang letak, Waffle menyedari bahawa suntikan SQL tidak boleh dilakukan dan tidak akan menyekat permintaan:
$ 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 }
Perhatikan bahawa walaupun dalam kes ini, Waffle masih boleh mengesan percubaan suntikan SQL seperti WAF berasaskan awan (walaupun ia tidak akan menyekatnya):
# 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
Walaupun kami telah menunjukkan pencegahan suntikan SQL, Waffle boleh mengesan dan menghalang pelbagai serangan:
Untuk butiran lanjut, lihat dokumentasi Senarai Peraturan.
Peraturan sentiasa dikemas kini dan sumbangan dialu-alukan.
Dengan menyepadukan Waffle ke dalam aplikasi anda, anda boleh mengesan dan mencegah serangan dengan tepat.
Untuk panduan pelaksanaan khusus rangka kerja dan arahan penggunaan terperinci, rujuk bahagian Panduan dalam dokumentasi.
Wafel sedang dalam pembangunan aktif. Kami mengalu-alukan maklum balas dan sumbangan.
Atas ialah kandungan terperinci Pengenalan kepada Wafel: Aplikasi WAF untuk Go dalam apl. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!