Changeset 60 in code


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'))

Location:
trunk
Files:
2 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
  • trunk/morty_test.go

    r55 r60  
    1313}
    1414
     15type SanitizeURITestCase struct {
     16        Input          []byte
     17        ExpectedOutput []byte
     18        ExpectedScheme string
     19}
     20
    1521type StringTestCase struct {
    1622        Input          string
     
    3844                []byte("console.log(document.cookies)"),
    3945                nil,
     46        },
     47}
     48
     49var sanitizeUriTestData []*SanitizeURITestCase = []*SanitizeURITestCase{
     50        &SanitizeURITestCase{
     51                []byte("http://example.com/"),
     52                []byte("http://example.com/"),
     53                "http:",
     54        },
     55        &SanitizeURITestCase{
     56                []byte("HtTPs://example.com/     \t"),
     57                []byte("https://example.com/"),
     58                "https:",
     59        },
     60        &SanitizeURITestCase{
     61                []byte("      Ht  TPs://example.com/     \t"),
     62                []byte("https://example.com/"),
     63                "https:",
     64        },
     65        &SanitizeURITestCase{
     66                []byte("javascript:void(0)"),
     67                []byte("javascript:void(0)"),
     68                "javascript:",
     69        },
     70        &SanitizeURITestCase{
     71                []byte("      /path/to/a/file/without/protocol     "),
     72                []byte("/path/to/a/file/without/protocol"),
     73                "",
     74        },
     75        &SanitizeURITestCase{
     76                []byte("      #fragment     "),
     77                []byte("#fragment"),
     78                "",
     79        },
     80        &SanitizeURITestCase{
     81                []byte("      qwertyuiop     "),
     82                []byte("qwertyuiop"),
     83                "",
     84        },
     85        &SanitizeURITestCase{
     86                []byte(""),
     87                []byte(""),
     88                "",
     89        },
     90        &SanitizeURITestCase{
     91                []byte(":"),
     92                []byte(":"),
     93                ":",
     94        },
     95        &SanitizeURITestCase{
     96                []byte("   :"),
     97                []byte(":"),
     98                ":",
     99        },
     100        &SanitizeURITestCase{
     101                []byte("schéma:"),
     102                []byte("schéma:"),
     103                "schéma:",
    40104        },
    41105}
     
    75139}
    76140
     141func TestSanitizeURI(t *testing.T) {
     142        for _, testCase := range sanitizeUriTestData {
     143                newUrl, scheme := sanitizeURI(testCase.Input)
     144                if !bytes.Equal(newUrl, testCase.ExpectedOutput) {
     145                        t.Errorf(
     146                                `URL proxifier error. Expected: "%s", Got: "%s"`,
     147                                testCase.ExpectedOutput,
     148                                newUrl,
     149                        )
     150                }
     151                if scheme != testCase.ExpectedScheme {
     152                        t.Errorf(
     153                                `URL proxifier error. Expected: "%s", Got: "%s"`,
     154                                testCase.ExpectedScheme,
     155                                scheme,
     156                        )
     157                }
     158        }
     159}
     160
    77161func TestURLProxifier(t *testing.T) {
    78162        u, _ := url.Parse("http://127.0.0.1/")
    79163        rc := &RequestConfig{BaseURL: u}
    80164        for _, testCase := range urlTestData {
    81                 newUrl, err := rc.ProxifyURI(testCase.Input)
     165                newUrl, err := rc.ProxifyURI([]byte(testCase.Input))
    82166                if err != nil {
    83167                        t.Errorf("Failed to parse URL: %s", testCase.Input)
Note: See TracChangeset for help on using the changeset viewer.