웹 애플리케이션 방화벽(WAF)은 오랫동안 웹 애플리케이션 보호를 위한 표준 보안 솔루션이었습니다. AWS WAF 및 Cloudflare WAF와 같은 클라우드 기반 WAF는 구현이 간편하기 때문에 특히 인기가 높습니다. 그러나 여기에는 몇 가지 과제가 있습니다.
이러한 문제를 해결하기 위해 In-app WAF 또는 RASP(런타임 애플리케이션 자체 보호)라는 새로운 접근 방식이 주목을 받고 있습니다.
이번 게시물에서는 In-app WAF 기능을 Go 웹 애플리케이션에 통합하기 위한 라이브러리인 Waffle을 소개하겠습니다.
인앱 WAF/RASP는 기존 클라우드 WAF를 대체하기 위한 것이 아니라 향상된 보호를 위해 WAF 기능을 애플리케이션에 직접 내장하여 이를 보완하기 위한 것입니다.
SQL 주입, XSS 등 일반적인 웹 애플리케이션 공격은 물론 크리덴셜 스터핑, 무차별 대입 시도 등 애플리케이션 비즈니스 로직 공격도 처리할 수 있습니다.
가장 큰 장점은 완전한 요청 상황 인식을 통한 정확한 탐지 및 예방입니다.
블로그 게시물을 작성하려면 다음 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 주입이 실제로 가능"할 때만 공격을 차단합니다. 이러한 상황 인식 접근 방식을 통해 오탐률이 줄어들고 제로데이 취약점을 완화하는 데도 도움이 될 수 있습니다.
Waffle은 Go 웹 애플리케이션에서 In-App 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 }
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은 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
SQL 주입 방지를 시연했지만 Waffle은 다양한 공격을 감지하고 예방할 수 있습니다.
자세한 내용은 규칙 목록 문서를 확인하세요.
규칙은 지속적으로 업데이트되며 기여를 환영합니다.
Waffle을 애플리케이션에 통합하면 공격을 정확하게 탐지하고 예방할 수 있습니다.
프레임워크별 구현 가이드 및 자세한 사용 지침은 설명서의 가이드 섹션을 참조하세요.
와플은 활발하게 개발 중입니다. 피드백과 기여를 환영합니다.
위 내용은 Waffle 소개: Go 애플리케이션용 인앱 WAF의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!