ホームページ >バックエンド開発 >Golang >Waffle の紹介: Go アプリケーション用のアプリ内 WAF

Waffle の紹介: Go アプリケーション用のアプリ内 WAF

Patricia Arquette
Patricia Arquetteオリジナル
2025-01-05 22:42:45529ブラウズ

導入

Web アプリケーション ファイアウォール (WAF) は、Web アプリケーションを保護するための標準的なセキュリティ ソリューションとして長い間使用されてきました。 AWS WAF や Cloudflare WAF などのクラウドベースの WAF は、実装が簡単なため特に人気があります。ただし、いくつかの課題があります:

  • アプリケーションのコンテキストについての理解が限定的
  • 誤検知率が高い
  • 制限されたカスタム ロジックの実装

これらの課題に対処するために、アプリ内 WAF または RASP (Runtime Application Self-Protection) と呼ばれる新しいアプローチが注目を集めています。

この投稿では、アプリ内 WAF 機能を Go Web アプリケーションに統合するためのライブラリである Waffle を紹介します。

  • https://sitebatch.github.io/waffle-website
  • https://github.com/sitebatch/waffle-go

Introduction to Waffle: In-app WAF for Go Applications

アプリ内WAF/RASPとは何ですか?

アプリ内 WAF/RASP は、既存のクラウド WAF を置き換えることを目的としたものではなく、WAF 機能をアプリケーションに直接埋め込んで保護を強化することでクラウド WAF を補完することを目的としています。
SQL インジェクションや XSS などの一般的な Web アプリケーション攻撃だけでなく、資格情報スタッフィングやブルート フォース攻撃などのアプリケーション ビジネス ロジック攻撃も処理できます。

主な利点は、完全なリクエスト コンテキスト認識による正確な検出と防止です。

ブログ投稿を作成するための次の HTTP リクエストについて考えてみましょう:

POST /blog/post HTTP/1.1
...

{
  "title": "What is SQL ?"
  "body": "SQL example code: `SELECT * FROM users` ..."
}

アプリケーションがプレースホルダーを使用して SQL ステートメントを安全に構築する場合、SQL インジェクションは不可能です。ただし、パターン マッチングに依存するクラウドベースの WAF は、このリクエストに疑わしい SQL のようなパターンが含まれているため、このリクエストをブロックします (文字列 SELECT * FROM は SQL インジェクションの懸念を引き起こします)。

開発者は、こうした誤検知を減らすためにパラメータ、エンドポイント、または WAF ルールを調整するという面倒な作業を行うことがよくあります。なんて面倒な作業でしょう!

対照的に、アプリ内 WAF / RASP はリクエストのコンテキストを理解します。プレースホルダーが使用されていないことを認識し、「SQL インジェクションが実際に可能である」場合にのみ攻撃をブロックします。このコンテキスト認識型のアプローチにより、誤検知が減少し、ゼロデイ脆弱性の軽減にも役立ちます。

Go アプリケーションでの Waffle を使用したアプリ内 WAF / RASP の実装

Waffle は、Go Web アプリケーションでアプリ内 WAF / RASP 機能を有効にするライブラリです。

Waffle をアプリケーションに統合する方法と、それがどのように攻撃を防ぐかを見てみましょう。

応用例

この例では標準ライブラリの net/http を使用していますが、Waffle は、Gin や GORM などの他のライブラリもサポートしています。
詳細については、サポートされているライブラリのドキュメントを確認してください。

次のアプリケーションには、/login エンドポイントに SQL インジェクションの脆弱性があります:

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
}

Waffle を統合して SQL インジェクションを防止する

SQL インジェクションを防ぐために Waffle を統合しましょう:

$ 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

main.go を次のように変更します。

$ go get github.com/sitebatch/waffle-go

変更は最小限です:

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
}

SQL インジェクション攻撃を試みると、Waffle がそれをブロックします。

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 は、waffle によってデフォルトで返されるエラー メッセージで、次のようになります。

Introduction to Waffle: In-app WAF for Go Applications

プレースホルダーを使用する場合:

プレースホルダーを使用すると、Waffle は SQL インジェクションが不可能であることを認識し、リクエストをブロックしません。

$ 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
    }

この場合でも、Waffle はクラウドベースの WAF と同様に、試行された SQL インジェクションを検出できることに注意してください (ブロックはしませんが)。

# 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

Waffle によって検出および防止される攻撃

SQL インジェクションの防止を実証しましたが、Waffle はさまざまな攻撃を検出して防止できます。

  • 既知のセキュリティスキャナーによる偵察
  • ディレクトリトラバーサル
  • XSS
  • SQL インジェクション
  • 機密ファイルへのアクセス
  • SSRF
  • アカウント乗っ取り

詳細については、ルール リストのドキュメントを参照してください。

ルールは継続的に更新され、貢献は歓迎されます。

結論

Waffle をアプリケーションに統合することで、攻撃を正確に検出して防止できます。

フレームワーク固有の実装ガイドと詳細な使用手順については、ドキュメントの「ガイド」セクションを参照してください。

ワッフルは鋭意開発中です。フィードバックや貢献を歓迎します。

  • https://github.com/sitebatch/waffle-go

以上がWaffle の紹介: Go アプリケーション用のアプリ内 WAFの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。