frontend/http: add request sanitization

This commit is contained in:
Jimmy Zelinskie 2017-10-08 17:16:02 -04:00
parent b7e6719129
commit 6dee48ce17
2 changed files with 58 additions and 33 deletions

View file

@ -65,11 +65,10 @@ type Config struct {
Addr string `yaml:"addr"` Addr string `yaml:"addr"`
ReadTimeout time.Duration `yaml:"read_timeout"` ReadTimeout time.Duration `yaml:"read_timeout"`
WriteTimeout time.Duration `yaml:"write_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"` TLSCertPath string `yaml:"tls_cert_path"`
TLSKeyPath string `yaml:"tls_key_path"` TLSKeyPath string `yaml:"tls_key_path"`
EnableRequestTiming bool `yaml:"enable_request_timing"` EnableRequestTiming bool `yaml:"enable_request_timing"`
ParseOptions `yaml:",inline"`
} }
// LogFields renders the current config as a set of Logrus fields. // LogFields renders the current config as a set of Logrus fields.
@ -78,11 +77,14 @@ func (cfg Config) LogFields() log.Fields {
"addr": cfg.Addr, "addr": cfg.Addr,
"readTimeout": cfg.ReadTimeout, "readTimeout": cfg.ReadTimeout,
"writeTimeout": cfg.WriteTimeout, "writeTimeout": cfg.WriteTimeout,
"allowIPSpoofing": cfg.AllowIPSpoofing,
"realIPHeader": cfg.RealIPHeader,
"tlsCertPath": cfg.TLSCertPath, "tlsCertPath": cfg.TLSCertPath,
"tlsKeyPath": cfg.TLSKeyPath, "tlsKeyPath": cfg.TLSKeyPath,
"enableRequestTiming": cfg.EnableRequestTiming, "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 { if err != nil {
WriteError(w, err) WriteError(w, err)
return 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 { if err != nil {
WriteError(w, err) WriteError(w, err)
return return

View file

@ -7,12 +7,19 @@ import (
"github.com/chihaya/chihaya/bittorrent" "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 AllowIPSpoofing is true, IPs provided via BitTorrent params will be used.
// If realIPHeader is not empty string, the first value of the HTTP Header with // If RealIPHeader is not empty string, the value of the first HTTP Header with
// that name will be used. // 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) qp, err := bittorrent.ParseURLData(r.RequestURI)
if err != nil { if err != nil {
return nil, err return nil, err
@ -20,15 +27,23 @@ func ParseAnnounce(r *http.Request, realIPHeader string, allowIPSpoofing bool) (
request := &bittorrent.AnnounceRequest{Params: qp} request := &bittorrent.AnnounceRequest{Params: qp}
eventStr, _ := qp.String("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) request.Event, err = bittorrent.NewEvent(eventStr)
if err != nil { if err != nil {
return nil, bittorrent.ClientError("failed to provide valid client event") 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") compactStr, _ := qp.String("compact")
request.Compact = compactStr != "" && compactStr != "0" request.Compact = compactStr != "" && compactStr != "0"
// Parse the infohash from the request.
infoHashes := qp.InfoHashes() infoHashes := qp.InfoHashes()
if len(infoHashes) < 1 { if len(infoHashes) < 1 {
return nil, bittorrent.ClientError("no info_hash parameter supplied") 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] request.InfoHash = infoHashes[0]
// Parse the PeerID from the request.
peerID, ok := qp.String("peer_id") peerID, ok := qp.String("peer_id")
if !ok { if !ok {
return nil, bittorrent.ClientError("failed to parse parameter: peer_id") 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) request.Peer.ID = bittorrent.PeerIDFromString(peerID)
// Determine the number of remaining pieces for the client.
request.Left, err = qp.Uint64("left") request.Left, err = qp.Uint64("left")
if err != nil { if err != nil {
return nil, bittorrent.ClientError("failed to parse parameter: left") return nil, bittorrent.ClientError("failed to parse parameter: left")
} }
// Determine the number of pieces downloaded by the client.
request.Downloaded, err = qp.Uint64("downloaded") request.Downloaded, err = qp.Uint64("downloaded")
if err != nil { if err != nil {
return nil, bittorrent.ClientError("failed to parse parameter: downloaded") return nil, bittorrent.ClientError("failed to parse parameter: downloaded")
} }
// Determine the number of pieces shared by the client.
request.Uploaded, err = qp.Uint64("uploaded") request.Uploaded, err = qp.Uint64("uploaded")
if err != nil { if err != nil {
return nil, bittorrent.ClientError("failed to parse parameter: uploaded") 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") numwant, err := qp.Uint64("numwant")
if err != nil && err != bittorrent.ErrKeyNotFound { if err != nil && err != bittorrent.ErrKeyNotFound {
return nil, bittorrent.ClientError("failed to parse parameter: numwant") return nil, bittorrent.ClientError("failed to parse parameter: numwant")
} }
request.NumWant = uint32(numwant) request.NumWant = uint32(numwant)
request.NumWantProvided = err == nil
// Parse the port where the client is listening.
port, err := qp.Uint64("port") port, err := qp.Uint64("port")
if err != nil { if err != nil {
return nil, bittorrent.ClientError("failed to parse parameter: port") return nil, bittorrent.ClientError("failed to parse parameter: port")
} }
request.Peer.Port = uint16(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 { if request.Peer.IP.IP == nil {
return nil, bittorrent.ClientError("failed to parse peer IP address") return nil, bittorrent.ClientError("failed to parse peer IP address")
} }
if err := opts.SanitizeAnnounce(request); err != nil {
return nil, err
}
return request, nil return request, nil
} }
// ParseScrape parses an bittorrent.ScrapeRequest from an http.Request. // 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) qp, err := bittorrent.ParseURLData(r.RequestURI)
if err != nil { if err != nil {
return nil, err return nil, err
@ -99,39 +126,35 @@ func ParseScrape(r *http.Request) (*bittorrent.ScrapeRequest, error) {
Params: qp, Params: qp,
} }
if err := opts.SanitizeScrape(request); err != nil {
return nil, err
}
return request, nil return request, nil
} }
// requestedIP determines the IP address for a BitTorrent client request. // requestedIP determines the IP address for a BitTorrent client request.
// func requestedIP(r *http.Request, p bittorrent.Params, opts ParseOptions) (ip net.IP, provided bool) {
// If allowIPSpoofing is true, IPs provided via params will be used. if opts.AllowIPSpoofing {
// 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 {
if ipstr, ok := p.String("ip"); ok { if ipstr, ok := p.String("ip"); ok {
ip := net.ParseIP(ipstr) return net.ParseIP(ipstr), true
return ip
} }
if ipstr, ok := p.String("ipv4"); ok { if ipstr, ok := p.String("ipv4"); ok {
ip := net.ParseIP(ipstr) return net.ParseIP(ipstr), true
return ip
} }
if ipstr, ok := p.String("ipv6"); ok { if ipstr, ok := p.String("ipv6"); ok {
ip := net.ParseIP(ipstr) return net.ParseIP(ipstr), true
return ip
} }
} }
if realIPHeader != "" { if opts.RealIPHeader != "" {
if ips, ok := r.Header[realIPHeader]; ok && len(ips) > 0 { if ips, ok := r.Header[opts.RealIPHeader]; ok && len(ips) > 0 {
ip := net.ParseIP(ips[0]) return net.ParseIP(ips[0]), false
return ip
} }
} }
host, _, _ := net.SplitHostPort(r.RemoteAddr) host, _, _ := net.SplitHostPort(r.RemoteAddr)
return net.ParseIP(host) return net.ParseIP(host), false
} }