Skip to content

Case-insensitive usmarshalling for json/v1 and json-schema / openapi validation #1111

@aywan

Description

@aywan

Hi!

I found a tricky case that should probably be known about, but I couldn't find the information.

TLDR> validation is breaking due to problems in json/v1 with case insensitivity, which allows you to bypass validation.

It's easier to explain with an example:

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"

	"github.com/getkin/kin-openapi/openapi3"
	"github.com/getkin/kin-openapi/openapi3filter"
	"github.com/getkin/kin-openapi/routers/gorillamux"
)

var schema = `openapi: 3.0.0
info:
  title: Test API
  version: 1.0.0
paths:
  /test:
    post:
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                value:
                  type: string
                  enum:
                    - "aniki"
      responses:
        '204':
          description: OK
`

var examples = []struct {
	name string
	body string
}{
	{"good", `{"value": "aniki"}`},
	{"bad", `{"value": "beniki"}`},
	{"evil", `{"value": "aniki", "Value": "beniki"}`},
	{"chaotic evil", `{"Value": "beniki", "vaLue": "chaos"}`},
}

type ValueObj struct {
	Value string `json:"value"`
}

func main() {
	ctx := context.Background()
	loader := &openapi3.Loader{Context: ctx, IsExternalRefsAllowed: true}

	doc, err := loader.LoadFromData([]byte(schema))
	if err != nil {
		log.Fatal("load schema: ", err)
	}

	err = doc.Validate(ctx)
	if err != nil {
		log.Fatal("validate schema: ", err)
	}

	router, err := gorillamux.NewRouter(doc)
	if err != nil {
		log.Fatal("create router: ", err)
	}

	for _, ex := range examples {
		httpReq, _ := http.NewRequest(http.MethodPost, "/test", strings.NewReader(ex.body))
		httpReq.Header.Set("Content-Type", "application/json")

		route, pathParams, _ := router.FindRoute(httpReq)
		requestValidationInput := &openapi3filter.RequestValidationInput{
			Request:    httpReq,
			PathParams: pathParams,
			Route:      route,
		}

		err := openapi3filter.ValidateRequest(ctx, requestValidationInput)
		if err != nil {
			fmt.Println(ex.name, "failed:", err)
		} else {
			fmt.Println(ex.name, "passed")
		}

		var val ValueObj
		err = json.Unmarshal([]byte(ex.body), &val)
		if err != nil {
			log.Fatal("unmarshal: ", err)
		}
		fmt.Printf("%s value: %+v\n", ex.name, val)
		fmt.Println("-----")
	}
}

output:

good passed
good value: {Value:aniki}
-----
bad failed: request body has an error: doesn't match schema: Error at "/value": value is not one of the allowed values ["aniki"]
Schema:
  {
    "enum": [
      "aniki"
    ],
    "type": "string"
  }

Value:
  "beniki"

bad value: {Value:beniki}
-----
evil passed
evil value: {Value:beniki}
-----
chaotic evil passed
chaotic evil value: {Value:chaos}
-----

As you can see, you can slip in any value and bypass the validation. This means that in many cases it is not worth relying on validation.

https://github.com/getkin/kin-openapi/blob/master/openapi3/schema.go#L1999
It seems we need to fix the code here a bit to solve this problem. You need to add an option to reduce all names to the same case.

go.mod

go 1.25.5

require github.com/getkin/kin-openapi v0.133.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions