Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net/http: document security considerations for serving internet traffic #22085

Open
mmcloughlin opened this issue Sep 28, 2017 · 17 comments
Open
Labels
Documentation NeedsFix The path to resolution is known, but the work has not been done.
Milestone

Comments

@mmcloughlin
Copy link
Contributor

mmcloughlin commented Sep 28, 2017

The net/http/pprof package implicitly registers HTTP handlers through its init() function. I argue this implicit behavior is too subtle and may contribute to people inadvertently leaving such endpoints open. Some IPv4 scans reveal a non-trivial number of pprof endpoints exposed (http://mmcloughlin.com/posts/your-pprof-is-showing). Since Go 1.9 an exposed pprof endpoint leaks source code.

I would like to spark a discussion on the possibility of moving to an explicit handler registration model. It is not clear to me that this would qualify for the Security exemption of the Go 1 compatibility guarantee, but I thought it was worthy of mention. Perhaps a warning in documentation would be enough.

@mvdan mvdan added the NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. label Sep 28, 2017
@mvdan mvdan added this to the Go1.10 milestone Sep 28, 2017
@dlsniper
Copy link
Contributor

dlsniper commented Sep 28, 2017

It only registers those handlers implicitly if you use the default http server instance. If you use a custom one, that doesn't happen. The problem is that people are using http.ListenAndServe without understanding its implications, not net/http/pprof imho. Furthermore, the default server usually needs tweaking when exposed to the Internet.

@mmcloughlin
Copy link
Contributor Author

This is ultimately user error as you say. People are indeed using http.ListenAndServe and net/http/pprof without understanding its implications. I'm suggesting that this is an easy and understandable mistake, and we should strive to make the consequences as benign as possible.

If we were to address this by making backwards incompatible changes, I would suggest that replacing the init() function of net/http/pprof with a RegisterHandlers() (or similar) would be the most effective.

Arguably this is not serious enough to break compatibility. I think at least a warning in documentation is warranted.

@docmerlin
Copy link

I concur about a warning in documentation!

@mappu
Copy link

mappu commented Sep 29, 2017

I understand the function names and file names are embedded into all binaries for panics and for reflection. I don't even particularly mind the pprof behaviour of attaching to the default mux.

But leaking source code is beyond the pale.

How does pprof get this information? Is it compiled into 1.9 binaries or loaded externally? If the former, is it only compiled into 1.9 binaries when pprof is used?

@dlsniper
Copy link
Contributor

@mmcloughlin shouldn't you update your blog post if you think that the better solution is to not use the default ListenAndServe / http server and instead initialize your own?

As for Go documentations, PRs are welcomed by the team, so improving the usage example of net/http/pprof would be nice as well.

@4ad
Copy link
Member

4ad commented Sep 29, 2017

The source code is not embedded in the binary! go tool pprof has to have access to the source code in order to print the source code. The binaries only contain source code coordinates for symbols.

You ran all your tests on localhost, where pprof had access to the source of the server.

Of course, this does not mean the current functionality is not a footgun. I wish net/http/pprof would not work like this and I wish it didn't have a default mux, but it's too late to change any of that.

@mmcloughlin
Copy link
Contributor Author

@4ad You're completely right, apologies for my mistake. Classic case of getting excited at a result and not doing the due diligence. Thanks for pointing this out. I'll update the post accordingly.

As you say there are still risks here people should be aware of.

@dlsniper I believe the Prevention section of the post covers this.

@rsc
Copy link
Contributor

rsc commented Oct 23, 2017

There are a number of packages that automatically register handlers under /debug/.... We chose that prefix specifically so that servers that serve direct internet traffic can put a handler in front of the http.DefaultServeMux to filter those out for untrusted clients.

@FiloSottile also has some tips about what to do as far as configuring other HTTP settings to avoid problems when serving direct internet traffic.

Probably a doc fix is correct, and probably http.ListenAndServe would be a good place, since (probably?) more people read the ListenAndServe docs than the pprof docs. Or maybe a link to a wiki page? Unclear.

@rsc rsc changed the title net/http/pprof: handler registration in init as potential security risk net/http: document security considerations for serving internet traffic Oct 23, 2017
@rsc
Copy link
Contributor

rsc commented Oct 23, 2017

It seems like perhaps the right next step is for someone to write a wiki page, and then the ListenAndServe docs can have a short link to the page. Putting lots of detail into the doc comment doesn't make as much sense as a wiki page we can update out-of-band.

@rsc rsc added the NeedsFix The path to resolution is known, but the work has not been done. label Oct 23, 2017
@gopherbot gopherbot removed the NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. label Oct 23, 2017
@rsc rsc modified the milestones: Go1.10, Go1.11 Nov 22, 2017
@FiloSottile FiloSottile self-assigned this Mar 30, 2018
@bradfitz
Copy link
Contributor

Also to document somewhere: use of MaxBytesReader to limit the bytes read by Request.ParseForm, Request.FormValue, etc.

@tsuna
Copy link
Contributor

tsuna commented Jul 13, 2018

Russ wrote:

@FiloSottile also has some tips about what to do as far as configuring other HTTP settings to avoid problems when serving direct internet traffic.

Just in case it's of any help to someone else wondering what those tips might be, a bunch of Googling revealed that Russ was most probably referring to this blog post: So you want to expose Go on the Internet, although that doesn't have anything to say about the things @bradfitz mentioned.

In any case, thanks @mmcloughlin for raising this issue, I was just about to push a change that would've exposed our debug endpoints to the Internets, the mistake is definitely very easy to make.

@bradfitz
Copy link
Contributor

@FiloSottile, are you going to do this for Go 1.11?

@rsc
Copy link
Contributor

rsc commented Aug 17, 2018

Too late.

@rsc rsc modified the milestones: Go1.11, Go1.12 Aug 17, 2018
@andybons andybons modified the milestones: Go1.12, Go1.13 Feb 12, 2019
@andybons andybons modified the milestones: Go1.13, Go1.14 Jul 8, 2019
@rsc rsc removed this from the Go1.14 milestone Oct 9, 2019
@rsc rsc added this to the Backlog milestone Oct 9, 2019
@mvdan
Copy link
Member

mvdan commented Jun 25, 2020

As another doc suggestion, I think the godoc for net/http/pprof could explicitly say that the endpoints should, in general, not be made public.

@jdemeyer
Copy link

What I particularly like about http://mmcloughlin.com/posts/your-pprof-is-showing is that it gives examples how to do it right. If you decide to document the "don't do this" part, be sure to also document the "do that instead" part.

@liuzengh
Copy link
Contributor

how about add go tag // +build !disable_init in net/http/pprof?

// +build !disable_init
func init() {
	http.HandleFunc("/debug/pprof/", Index)
	http.HandleFunc("/debug/pprof/cmdline", Cmdline)
	http.HandleFunc("/debug/pprof/profile", Profile)
	http.HandleFunc("/debug/pprof/symbol", Symbol)
	http.HandleFunc("/debug/pprof/trace", Trace)
}

@liuzengh
Copy link
Contributor

we don't want net/http/pprof package implicitly registers HTTP handlers through its init() function because of security problem. Before go1.22, we use reflection to unregister http handlers that init at net/http/pprof. But the following code will failed at go1.22 because go 1.222 modify the implemention of http.ServeMux. Any better suggestion?

// unregisterHandlers delete router from http.DefaultServeMux.
// admin import net/http/pprof,Will automatically register pprof related routes on http.DefaultServeMux,
// avoid causing security problems.
// Can refer to:https://github.com/golang/go/issues/22085
func unregisterHandlers(patterns []string) error {
	// http.ServeMux struct:
	// type ServeMux struct {
	// 	mu    sync.RWMutex
	// 	m     map[string]muxEntry
	// 	es    []muxEntry
	// 	hosts bool
	// }

	// Need to import muxEntry in net/http pkg.
	type muxEntry struct {
		h       http.Handler
		pattern string
	}

	v := reflect.ValueOf(http.DefaultServeMux)

	// lock
	muField := v.Elem().FieldByName("mu")
	if !muField.IsValid() {
		return errors.New("http.DefaultServeMux does not have a field called `mu`")
	}
	muPointer := unsafe.Pointer(muField.UnsafeAddr())
	mu := (*sync.RWMutex)(muPointer)
	(*mu).Lock()
	defer (*mu).Unlock()

	// delete value of map.
	mField := v.Elem().FieldByName("m")
	if !mField.IsValid() {
		return errors.New("http.DefaultServeMux does not have a field called `m`")
	}
	mPointer := unsafe.Pointer(mField.UnsafeAddr())
	m := (*map[string]muxEntry)(mPointer)
	for _, pattern := range patterns {
		delete(*m, pattern)
	}

	// delete value of muxEntry slice.
	esField := v.Elem().FieldByName("es")
	if !esField.IsValid() {
		return errors.New("http.DefaultServeMux does not have a field called `es`")
	}
	esPointer := unsafe.Pointer(esField.UnsafeAddr())
	es := (*[]muxEntry)(esPointer)
	for _, pattern := range patterns {
		// removes muxEntry of same pattern.
		var j int
		for _, muxEntry := range *es {
			if muxEntry.pattern != pattern {
				(*es)[j] = muxEntry
				j++
			}
		}
		*es = (*es)[:j]
	}

	// modify hosts.
	hostsField := v.Elem().FieldByName("hosts")
	if !hostsField.IsValid() {
		return errors.New("http.DefaultServeMux does not have a field called `hosts`")
	}
	hostsPointer := unsafe.Pointer(hostsField.UnsafeAddr())
	hosts := (*bool)(hostsPointer)
	*hosts = false
	for _, v := range *m {
		if v.pattern[0] != '/' {
			*hosts = true
		}
	}

	return nil
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Documentation NeedsFix The path to resolution is known, but the work has not been done.
Projects
None yet
Development

No branches or pull requests