Changeset 60 in code for trunk/morty.go


Ignore:
Timestamp:
Dec 1, 2016, 12:45:38 PM (9 years ago)
Author:
alex
Message:

[enh] ignore all special characters in the URI protocol (example : jav	ascript:alert('XSS'))

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/morty.go

    r59 r60  
    1515        "strings"
    1616        "time"
     17        "unicode/utf8"
    1718
    1819        "github.com/valyala/fasthttp"
     
    208209
    209210        req.SetRequestURI(requestURIStr)
    210         req.Header.SetUserAgentBytes([]byte("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0"))
     211        req.Header.SetUserAgentBytes([]byte("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36"))
    211212
    212213        resp := fasthttp.AcquireResponse()
     
    237238                        if loc != nil {
    238239                                rc := &RequestConfig{Key: p.Key, BaseURL: parsedURI}
    239                                 url, err := rc.ProxifyURI(string(loc))
     240                                url, err := rc.ProxifyURI(loc)
    240241                                if err == nil {
    241242                                        ctx.SetStatusCode(resp.StatusCode())
     
    346347                urlEnd := s[5]
    347348
    348                 if uri, err := rc.ProxifyURI(string(css[urlStart:urlEnd])); err == nil {
     349                if uri, err := rc.ProxifyURI(css[urlStart:urlEnd]); err == nil {
    349350                        out.Write(css[startIndex:urlStart])
    350351                        out.Write([]byte(uri))
     
    500501                                }
    501502
    502                         case html.CommentToken:
    503                                 // ignore comment. TODO : parse IE conditional comment
    504 
    505                         case html.DoctypeToken:
     503                        case html.DoctypeToken, html.CommentToken:
    506504                                out.Write(decoder.Raw())
    507505                        }
     
    586584                }
    587585                // output proxify result
    588                 if uri, err := rc.ProxifyURI(string(contentUrl)); err == nil {
     586                if uri, err := rc.ProxifyURI(contentUrl); err == nil {
    589587                        fmt.Fprintf(out, ` http-equiv="refresh" content="%surl=%s"`, content[:urlIndex], uri)
    590588                }
     
    611609        switch string(attrName) {
    612610        case "src", "href", "action":
    613                 if uri, err := rc.ProxifyURI(string(attrValue)); err == nil {
     611                if uri, err := rc.ProxifyURI(attrValue); err == nil {
    614612                        fmt.Fprintf(out, " %s=\"%s\"", attrName, uri)
    615613                } else {
     
    627625}
    628626
    629 func (rc *RequestConfig) ProxifyURI(uri string) (string, error) {
     627// Sanitized URI : removes all runes bellow 32 (included) as the begining and end of URI, and lower case the scheme.
     628// avoid memory allocation (except for the scheme)
     629func sanitizeURI(uri []byte) ([]byte, string) {
     630        first_rune_index := 0
     631        first_rune_seen := false
     632        scheme_last_index := -1
     633        buffer := bytes.NewBuffer(make([]byte, 0, 10))
     634
     635        // remove trailing space and special characters
     636        uri = bytes.TrimRight(uri, "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x20")
     637
     638        // loop over byte by byte
     639        for i, c := range uri {
     640                // ignore special characters and space (c <= 32)
     641                if c > 32 {
     642                        // append to the lower case of the rune to buffer
     643                        if c < utf8.RuneSelf && 'A' <= c && c <= 'Z' {
     644                                c = c + 'a' - 'A'
     645                        }
     646
     647                        buffer.WriteByte(c)
     648
     649                        // update the first rune index that is not a special rune
     650                        if !first_rune_seen {
     651                                first_rune_index = i
     652                                first_rune_seen = true
     653                        }
     654
     655                        if c == ':' {
     656                                // colon rune found, we have found the scheme
     657                                scheme_last_index = i
     658                                break
     659                        } else if c == '/' || c == '?' || c == '\\' || c == '#' {
     660                                // special case : most probably a relative URI
     661                                break
     662                        }
     663                }
     664        }
     665
     666        if scheme_last_index != -1 {
     667                // scheme found
     668                // copy the "lower case without special runes scheme" before the ":" rune
     669                scheme_start_index := scheme_last_index - buffer.Len() + 1
     670                copy(uri[scheme_start_index:], buffer.Bytes())
     671                // and return the result
     672                return uri[scheme_start_index:], buffer.String()
     673        } else {
     674                // scheme NOT found
     675                return uri[first_rune_index:], ""
     676        }
     677}
     678
     679func (rc *RequestConfig) ProxifyURI(uri []byte) (string, error) {
     680        // sanitize URI
     681        uri, scheme := sanitizeURI(uri)
     682
    630683        // remove javascript protocol
    631         if strings.HasPrefix(uri, "javascript:") {
     684        if scheme == "javascript:" {
    632685                return "", nil
    633686        }
    634687
    635688        // TODO check malicious data: - e.g. data:script
    636         if strings.HasPrefix(uri, "data:") {
    637                 return uri, nil
     689        if scheme == "data:" {
     690                return string(uri), nil
    638691        }
    639692
    640693        // parse the uri
    641         u, err := url.Parse(uri)
     694        u, err := url.Parse(string(uri))
    642695        if err != nil {
    643696                return "", err
     
    668721
    669722        // return full URI and fragment (if not empty)
    670         uri = u.String()
     723        morty_uri := u.String()
    671724
    672725        if rc.Key == nil {
    673                 return fmt.Sprintf("./?mortyurl=%s%s", url.QueryEscape(uri), fragment), nil
    674         }
    675         return fmt.Sprintf("./?mortyhash=%s&mortyurl=%s%s", hash(uri, rc.Key), url.QueryEscape(uri), fragment), nil
     726                return fmt.Sprintf("./?mortyurl=%s%s", url.QueryEscape(morty_uri), fragment), nil
     727        }
     728        return fmt.Sprintf("./?mortyhash=%s&mortyurl=%s%s", hash(morty_uri, rc.Key), url.QueryEscape(morty_uri), fragment), nil
    676729}
    677730
Note: See TracChangeset for help on using the changeset viewer.