-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Closed
Description
Issue Description
Commit b129098 removed content length and request method check from c.bind() function. This causes many unwanted side effects
- security vulnerability - now it is possible to inject field values from query param to POST method body with bind. This could allow attacker to inject to fields that are not meant to be filled from bind. I know that you should not pass binded values directly to services etc but before this change this was not possible with query params. It is way easier to manipulate query params than request body.
see example:
func TestSecurityRisk(t *testing.T) {
type User struct {
ID int `json:"id"`
IsAdmin bool // not 'exposed' value should not be bind DEFINITELY from URL/ROUTE parameters
}
e := echo.New()
req := httptest.NewRequest(http.MethodPost, "/api/endpoint?IsAdmin=true", strings.NewReader(`{"id": 1}`))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
err := func(c echo.Context) error {
var payload User
if err := c.Bind(&payload); err != nil {
return c.JSON(http.StatusBadRequest, echo.Map{"error": err})
}
// passing bind value directly to service is bad practice and could lead to security risks but c.bind()
// doing it implicitly is also very bad (even worse)
// err := service.addUser(c.Request().Context(), user)
if payload.IsAdmin {
panic("passing bind value directly to service is bad practice and could lead to security risks")
}
return c.JSON(http.StatusOK, payload)
}(c)
if err != nil {
t.Fatal(err)
}
}
- if request contains query param + body and we are binding to slice request fails with
binding element must be a struct
Example:
func TestBindingToSliceWithQueryParam(t *testing.T) {
type User struct {
ID int `json:"id"`
}
e := echo.New()
req := httptest.NewRequest(http.MethodPost, "/api/endpoint?lang=et", strings.NewReader(`[{"id": 1}]`))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
err := func(c echo.Context) error {
var payload []User
if err := c.Bind(&payload); err != nil {
return c.JSON(http.StatusBadRequest, echo.Map{"error": err})
}
return c.JSON(http.StatusOK, payload)
}(c)
if err != nil {
t.Fatal(err)
}
response := string(rec.Body.Bytes())
if strings.Contains(response, "binding element must be a struct"){
t.Fatal(response)
}
}
Expected behaviour
I think expected behavior is as it was before b129098
Version/commit
v4.1.17
Metadata
Metadata
Assignees
Labels
No labels