labstack / echo

High performance, minimalist Go web framework

Home Page:https://echo.labstack.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

CSRF middleware form lookup consumes all the request body

alessandro-p opened this issue · comments

Issue Description

Echo's CORS middleware when TokenLookup is set to form:<your-input-name> consumes all the request body making impossible for downstream operations to use it.

Checklist

  • Dependencies installed
  • No typos
  • Searched existing issues and docs

Expected behaviour

When using TokenLookup to inspect formData to find csrf token it should be possible to reuse the request body.
For example, forward the request to a downstream service that will be able to use it.

Actual behaviour

When using TokenLookup to inspect formData, body is completely consumed. This might introduce issues when proxying a request.

Steps to reproduce

See working code below for a full example to reproduce the error

Working code to debug

package main

import (
	"io"
	"net/http"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

func main() {
	e := echo.New()

	e.Use(NewCSRFMiddleware())

	e.POST("/csrf-example", PostExample)

	e.Logger.Fatal(e.Start(":1323"))
}

func NewCSRFMiddleware() echo.MiddlewareFunc {
	return middleware.CSRFWithConfig(middleware.CSRFConfig{ //nolint:exhaustruct
		TokenLookup:    "form:csrf",
		ContextKey:     "csrf",
		CookieName:     "__csrf",
		CookiePath:     "/",
		CookieHTTPOnly: true,
		CookieSecure:   true,
	})
}

func PostExample(c echo.Context) error {
	body, err := io.ReadAll(c.Request().Body)
	if err != nil {
		return c.String(http.StatusInternalServerError, "Error reading request body")
	}
	bodyStr := string(body)

	return c.JSON(http.StatusOK, struct {
		ContentLength int    `json:"content_length"`
		Body          string `json:"body"`
	}{
		ContentLength: int(c.Request().ContentLength),
		Body:          bodyStr,
	})
}

After running the server, simply invoke the route with curl:

curl --location 'http://localhost:1323/csrf-example?=' \
--header 'Cookie: __csrf=test-token' \
--form 'csrf="test-token"'

Result is:

{"content_length":149,"body":""}

Version/commit

echo version: v4.11.4

Additional Debug already done

It seems the issue is in the github.com/labstack/echo/v4@v4.11.4/middleware/extractor.go in the function valuesFromForm:

// valuesFromForm returns a function that extracts values from the form field.
func valuesFromForm(name string) ValuesExtractor {
	return func(c echo.Context) ([]string, error) {
		if c.Request().Form == nil {
			_ = c.Request().ParseMultipartForm(32 << 20) // same what `c.Request().FormValue(name)` does
		}
        ....

It seems in fact that the line: c.Request().ParseMultipartForm(32 << 20) is consuming all the body.
One workaround that seems to fix the issue is copying the body and restoring after it has been consumed.