From 6dee48ce174393b8934dfa02f7378643dc8708a8 Mon Sep 17 00:00:00 2001 From: Jimmy Zelinskie Date: Sun, 8 Oct 2017 17:16:02 -0400 Subject: [PATCH] frontend/http: add request sanitization --- frontend/http/frontend.go | 14 ++++--- frontend/http/parser.go | 77 +++++++++++++++++++++++++-------------- 2 files changed, 58 insertions(+), 33 deletions(-) diff --git a/frontend/http/frontend.go b/frontend/http/frontend.go index c347d10..dc10dab 100644 --- a/frontend/http/frontend.go +++ b/frontend/http/frontend.go @@ -65,11 +65,10 @@ type Config struct { Addr string `yaml:"addr"` ReadTimeout time.Duration `yaml:"read_timeout"` WriteTimeout time.Duration `yaml:"write_timeout"` - AllowIPSpoofing bool `yaml:"allow_ip_spoofing"` - RealIPHeader string `yaml:"real_ip_header"` TLSCertPath string `yaml:"tls_cert_path"` TLSKeyPath string `yaml:"tls_key_path"` EnableRequestTiming bool `yaml:"enable_request_timing"` + ParseOptions `yaml:",inline"` } // LogFields renders the current config as a set of Logrus fields. @@ -78,11 +77,14 @@ func (cfg Config) LogFields() log.Fields { "addr": cfg.Addr, "readTimeout": cfg.ReadTimeout, "writeTimeout": cfg.WriteTimeout, - "allowIPSpoofing": cfg.AllowIPSpoofing, - "realIPHeader": cfg.RealIPHeader, "tlsCertPath": cfg.TLSCertPath, "tlsKeyPath": cfg.TLSKeyPath, "enableRequestTiming": cfg.EnableRequestTiming, + "allowIPSpoofing": cfg.AllowIPSpoofing, + "realIPHeader": cfg.RealIPHeader, + "maxNumWant": cfg.MaxNumWant, + "defaultNumWant": cfg.DefaultNumWant, + "maxScrapeInfohashes": cfg.MaxScrapeInfoHashes, } } @@ -219,7 +221,7 @@ func (f *Frontend) announceRoute(w http.ResponseWriter, r *http.Request, _ httpr } }() - req, err := ParseAnnounce(r, f.RealIPHeader, f.AllowIPSpoofing) + req, err := ParseAnnounce(r, f.ParseOptions) if err != nil { WriteError(w, err) return @@ -258,7 +260,7 @@ func (f *Frontend) scrapeRoute(w http.ResponseWriter, r *http.Request, _ httprou } }() - req, err := ParseScrape(r) + req, err := ParseScrape(r, f.ParseOptions) if err != nil { WriteError(w, err) return diff --git a/frontend/http/parser.go b/frontend/http/parser.go index 7f4e2ca..29178bd 100644 --- a/frontend/http/parser.go +++ b/frontend/http/parser.go @@ -7,12 +7,19 @@ import ( "github.com/chihaya/chihaya/bittorrent" ) -// ParseAnnounce parses an bittorrent.AnnounceRequest from an http.Request. +// ParseOptions is the configuration used to parse an Announce Request. // -// If allowIPSpoofing is true, IPs provided via params will be used. -// If realIPHeader is not empty string, the first value of the HTTP Header with +// If AllowIPSpoofing is true, IPs provided via BitTorrent params will be used. +// If RealIPHeader is not empty string, the value of the first HTTP Header with // that name will be used. -func ParseAnnounce(r *http.Request, realIPHeader string, allowIPSpoofing bool) (*bittorrent.AnnounceRequest, error) { +type ParseOptions struct { + AllowIPSpoofing bool `yaml:"allowIPSpoofing"` + RealIPHeader string `yaml:"realIPHeader"` + bittorrent.RequestSanitizer `yaml:",inline"` +} + +// ParseAnnounce parses an bittorrent.AnnounceRequest from an http.Request. +func ParseAnnounce(r *http.Request, opts ParseOptions) (*bittorrent.AnnounceRequest, error) { qp, err := bittorrent.ParseURLData(r.RequestURI) if err != nil { return nil, err @@ -20,15 +27,23 @@ func ParseAnnounce(r *http.Request, realIPHeader string, allowIPSpoofing bool) ( request := &bittorrent.AnnounceRequest{Params: qp} - eventStr, _ := qp.String("event") - request.Event, err = bittorrent.NewEvent(eventStr) - if err != nil { - return nil, bittorrent.ClientError("failed to provide valid client event") + // Attempt to parse the event from the request. + var eventStr string + eventStr, request.EventProvided = qp.String("event") + if request.EventProvided { + request.Event, err = bittorrent.NewEvent(eventStr) + if err != nil { + return nil, bittorrent.ClientError("failed to provide valid client event") + } + } else { + request.Event = bittorrent.None } + // Determine if the client expects a compact response. compactStr, _ := qp.String("compact") request.Compact = compactStr != "" && compactStr != "0" + // Parse the infohash from the request. infoHashes := qp.InfoHashes() if len(infoHashes) < 1 { return nil, bittorrent.ClientError("no info_hash parameter supplied") @@ -38,6 +53,7 @@ func ParseAnnounce(r *http.Request, realIPHeader string, allowIPSpoofing bool) ( } request.InfoHash = infoHashes[0] + // Parse the PeerID from the request. peerID, ok := qp.String("peer_id") if !ok { return nil, bittorrent.ClientError("failed to parse parameter: peer_id") @@ -47,43 +63,54 @@ func ParseAnnounce(r *http.Request, realIPHeader string, allowIPSpoofing bool) ( } request.Peer.ID = bittorrent.PeerIDFromString(peerID) + // Determine the number of remaining pieces for the client. request.Left, err = qp.Uint64("left") if err != nil { return nil, bittorrent.ClientError("failed to parse parameter: left") } + // Determine the number of pieces downloaded by the client. request.Downloaded, err = qp.Uint64("downloaded") if err != nil { return nil, bittorrent.ClientError("failed to parse parameter: downloaded") } + // Determine the number of pieces shared by the client. request.Uploaded, err = qp.Uint64("uploaded") if err != nil { return nil, bittorrent.ClientError("failed to parse parameter: uploaded") } + // Determine the number of peers the client wants in the response. numwant, err := qp.Uint64("numwant") if err != nil && err != bittorrent.ErrKeyNotFound { return nil, bittorrent.ClientError("failed to parse parameter: numwant") } request.NumWant = uint32(numwant) + request.NumWantProvided = err == nil + // Parse the port where the client is listening. port, err := qp.Uint64("port") if err != nil { return nil, bittorrent.ClientError("failed to parse parameter: port") } request.Peer.Port = uint16(port) - request.Peer.IP.IP = requestedIP(r, qp, realIPHeader, allowIPSpoofing) + // Parse the IP address where the client is listening. + request.Peer.IP.IP, request.IPProvided = requestedIP(r, qp, opts) if request.Peer.IP.IP == nil { return nil, bittorrent.ClientError("failed to parse peer IP address") } + if err := opts.SanitizeAnnounce(request); err != nil { + return nil, err + } + return request, nil } // ParseScrape parses an bittorrent.ScrapeRequest from an http.Request. -func ParseScrape(r *http.Request) (*bittorrent.ScrapeRequest, error) { +func ParseScrape(r *http.Request, opts ParseOptions) (*bittorrent.ScrapeRequest, error) { qp, err := bittorrent.ParseURLData(r.RequestURI) if err != nil { return nil, err @@ -99,39 +126,35 @@ func ParseScrape(r *http.Request) (*bittorrent.ScrapeRequest, error) { Params: qp, } + if err := opts.SanitizeScrape(request); err != nil { + return nil, err + } + return request, nil } // requestedIP determines the IP address for a BitTorrent client request. -// -// If allowIPSpoofing is true, IPs provided via params will be used. -// If realIPHeader is not empty string, the first value of the HTTP Header with -// that name will be used. -func requestedIP(r *http.Request, p bittorrent.Params, realIPHeader string, allowIPSpoofing bool) net.IP { - if allowIPSpoofing { +func requestedIP(r *http.Request, p bittorrent.Params, opts ParseOptions) (ip net.IP, provided bool) { + if opts.AllowIPSpoofing { if ipstr, ok := p.String("ip"); ok { - ip := net.ParseIP(ipstr) - return ip + return net.ParseIP(ipstr), true } if ipstr, ok := p.String("ipv4"); ok { - ip := net.ParseIP(ipstr) - return ip + return net.ParseIP(ipstr), true } if ipstr, ok := p.String("ipv6"); ok { - ip := net.ParseIP(ipstr) - return ip + return net.ParseIP(ipstr), true } } - if realIPHeader != "" { - if ips, ok := r.Header[realIPHeader]; ok && len(ips) > 0 { - ip := net.ParseIP(ips[0]) - return ip + if opts.RealIPHeader != "" { + if ips, ok := r.Header[opts.RealIPHeader]; ok && len(ips) > 0 { + return net.ParseIP(ips[0]), false } } host, _, _ := net.SplitHostPort(r.RemoteAddr) - return net.ParseIP(host) + return net.ParseIP(host), false }