go-restful: [security] Path parser inconsistency could lead to bypass several security checks in emicklei/go-restful

Reported via huntr.dev here.

Created: May 28th 2022

Description

There is a inconsistency between how golang(and other packages) and go-restful parses url path. This incosistency could lead several security check bypass in a complex system.

Steps to reproduce

Copy and run the code below

package main

import (
    "fmt"
    "html"
    "log"
    "net/http"
)

func main() {

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
    })

// If a user request matches with this path then it will be checked and if it didn't match then the request will be directly forwarded to the go-restful server.
    http.HandleFunc("/users/id_will_be_here", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "I'm checking if the user is authorized to see this user info. If he is authorized then I will forward this request to the go-restful server which will return the user info")
    })

    log.Fatal(http.ListenAndServe(":8081", nil))

}

Now If you send a request to the http://localhost:8081/users/id_will_be_here then you will get a hit to the expected endpoint and it will check the authorization part Now if you send a request to the http://localhost:8081/users/id_will_be_here/ (notice the extra / in last) then this server won’t process this request as the path name doesn’t match but the problem is that go-restful treat this path and the previous as same. So this inconsistency could lead some issues in this scenerio as the first sever wont check the security checks but the second server(go-restful) will return user data.

Impact

Security check bypass

Occurrences

web_service.go L80

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 3
  • Comments: 16 (7 by maintainers)

Commits related to this issue

Most upvoted comments

Thank you for reporting this. I will investigate this further.

I’m not sure this is a bypass. Yes, go-restful handles trailing slashes differently than net/http, but how could a bypass realistically occur? From above go-restful example,


// called from ws.Route(ws.GET("/users/id_will_be_here").To(check))
func check(req *restful.Request, resp *restful.Response) {
	fmt.Fprintf(resp, "I'm checking if the user is authorized to see this user info. If he is authorized then I will forward this request to the go-restful server which will return the user info")
}

The presence or absence of the trailing / won’t cause the authorization check to be skipped, or the wrong route to be selected. I’m trying to think of a realistic example where it would, and I can’t.

i was able to reproduce this issue.

your example output

~/tmp/issue497
$ curl http://localhost:8081/users/id_will_be_here
I'm checking if the user is authorized to see this user info. If he is authorized then I will forward this request to the go-restful server which will return the user info

emicklei@Ernests-iMac-10 on Sat Jun 04 at 10:35 AM
~/tmp/issue497
$ curl http://localhost:8081/users/id_will_be_here/
Hello, "/users/id_will_be_here/"

go-restful example:

package main

import (
	"fmt"
	"html"
	"log"
	"net/http"

	restful "github.com/emicklei/go-restful/v3"
)

func main() {
	ws := new(restful.WebService)
	ws.Route(ws.GET("/").To(hello))
	ws.Route(ws.GET("/users/id_will_be_here").To(check))
	restful.Add(ws)
	log.Fatal(http.ListenAndServe(":8081", nil))
}

func hello(req *restful.Request, resp *restful.Response) {
	fmt.Fprintf(resp, "Hello, %q", html.EscapeString(req.Request.URL.Path))
}

func check(req *restful.Request, resp *restful.Response) {
	fmt.Fprintf(resp, "I'm checking if the user is authorized to see this user info. If he is authorized then I will forward this request to the go-restful server which will return the user info")
}

go-restful output:

~/tmp/issue497
$ curl http://localhost:8081/users/id_will_be_here/
I'm checking if the user is authorized to see this user info. If he is authorized then I will forward this request to the go-restful server which will return the user info

emicklei@Ernests-iMac-10 on Sat Jun 04 at 10:39 AM
~/tmp/issue497
$ curl http://localhost:8081/users/id_will_be_here
I'm checking if the user is authorized to see this user info. If he is authorized then I will forward this request to the go-restful server which will return the user info