• nginx url解码引发的waf漏洞


    去年发现的ngx一个bug,直到最近有空才写了这篇。
    Nginx ngx_unescape_uri函数在处理url decode时没有遵照标准的url decode,从而引起一系列使用该函数解码的waf
    都存在绕过漏洞
    出现该问题的函数位于src\core\ngx_string.c代码中ngx_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type)
      1 void
      2 ngx_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type)
      3 {
      4     u_char  *d, *s, ch, c, decoded;
      5     enum {
      6         sw_usual = 0,
      7         sw_quoted,
      8         sw_quoted_second
      9     } state;
     10 
     11     d = *dst;
     12     s = *src;
     13 
     14     state = 0;
     15     decoded = 0;
     16 
     17     while (size--) {
     18 
     19         ch = *s++;
     20 
     21         switch (state) {
     22         case sw_usual:
     23             if (ch == '?'
     24                 && (type & (NGX_UNESCAPE_URI|NGX_UNESCAPE_REDIRECT)))
     25             {
     26                 *d++ = ch;
     27                 goto done;
     28             }
     29 
     30             if (ch == '%') {
     31                 state = sw_quoted;
     32                 break;
     33             }
     34 
     35             *d++ = ch;
     36             break;
     37 
     38         case sw_quoted:
     39 
     40             if (ch >= '0' && ch <= '9') {
     41                 decoded = (u_char) (ch - '0');
     42                 state = sw_quoted_second;
     43                 break;
     44             }
     45 
     46             c = (u_char) (ch | 0x20);
     47             if (c >= 'a' && c <= 'f') {
     48                 decoded = (u_char) (c - 'a' + 10);
     49                 state = sw_quoted_second;
     50                 break;
     51             }
     52 
     53             /* the invalid quoted character */
     54 
     55             state = sw_usual;
     56 
     57             *d++ = ch;
     58 
     59             break;
     60 
     61         case sw_quoted_second:
     62 
     63             state = sw_usual;
     64 
     65             if (ch >= '0' && ch <= '9') {
     66                 ch = (u_char) ((decoded << 4) + ch - '0');
     67 
     68                 if (type & NGX_UNESCAPE_REDIRECT) {
     69                     if (ch > '%' && ch < 0x7f) {
     70                         *d++ = ch;
     71                         break;
     72                     }
     73 
     74                     *d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1);
     75 
     76                     break;
     77                 }
     78 
     79                 *d++ = ch;
     80 
     81                 break;
     82             }
     83 
     84             c = (u_char) (ch | 0x20);
     85             if (c >= 'a' && c <= 'f') {
     86                 ch = (u_char) ((decoded << 4) + c - 'a' + 10);
     87 
     88                 if (type & NGX_UNESCAPE_URI) {
     89                     if (ch == '?') {
     90                         *d++ = ch;
     91                         goto done;
     92                     }
     93 
     94                     *d++ = ch;
     95                     break;
     96                 }
     97 
     98                 if (type & NGX_UNESCAPE_REDIRECT) {
     99                     if (ch == '?') {
    100                         *d++ = ch;
    101                         goto done;
    102                     }
    103 
    104                     if (ch > '%' && ch < 0x7f) {
    105                         *d++ = ch;
    106                         break;
    107                     }
    108 
    109                     *d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1);
    110                     break;
    111                 }
    112 
    113                 *d++ = ch;
    114 
    115                 break;
    116             }
    117 
    118             /* the invalid quoted character */
    119 
    120             break;
    121         }
    122     }
    123 
    124 done:
    125 
    126     *dst = d;
    127     *src = s;

    128 }

    该函数在处理%号编码时,如果%后面第一个字符非16进制范围则会丢弃%,否则第二个字符非16进制范围则会丢弃%和第一个字符,具体表现为Sql注入关键字select如果写成s%elect经过ngx编码处理后则会变成slect从而绕过waf过滤规则,例如IIS asp 对s%elect的编码处理结果为select,还有%and经过ngx解码函数后会变为nd等等。
    下面我们来看看国际知名web安全组织OWASP的开源WAF NAXSI,出现问题的代码位于naxsi_src\naxsi_utils.c中
      1 /*
      2 ** Patched ngx_unescape_uri : 
      3 ** The original one does not care if the character following % is in valid range.
      4 ** For example, with the original one :
      5 ** '%uff' -> 'uff'
      6 */
      7 void
      8 naxsi_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type)
      9 {
     10     u_char  *d, *s, ch, c, decoded;
     11     enum {
     12         sw_usual = 0,
     13         sw_quoted,
     14         sw_quoted_second
     15     } state;
     16 
     17     d = *dst;
     18     s = *src;
     19 
     20     state = 0;
     21     decoded = 0;
     22 
     23     while (size--) {
     24 
     25         ch = *s++;
     26 
     27         switch (state) {
     28         case sw_usual:
     29             if (ch == '?'
     30                 && (type & (NGX_UNESCAPE_URI|NGX_UNESCAPE_REDIRECT)))
     31             {
     32                 *d++ = ch;
     33                 goto done;
     34             }
     35 
     36             if (ch == '%') {
     37                 state = sw_quoted;
     38                 break;
     39             }
     40 
     41             *d++ = ch;
     42             break;
     43 
     44         case sw_quoted:
     45 
     46             if (ch >= '0' && ch <= '9') {
     47                 decoded = (u_char) (ch - '0');
     48                 state = sw_quoted_second;
     49                 break;
     50             }
     51 
     52             c = (u_char) (ch | 0x20);
     53             if (c >= 'a' && c <= 'f') {
     54                 decoded = (u_char) (c - 'a' + 10);
     55                 state = sw_quoted_second;
     56                 break;
     57             }
     58 
     59             /* the invalid quoted character */
     60 
     61             state = sw_usual;
     62          *d++ = '%';
     63             *d++ = ch;
     64 
     65             break;
     66 
     67         case sw_quoted_second:
     68 
     69             state = sw_usual;
     70 
     71             if (ch >= '0' && ch <= '9') {
     72                 ch = (u_char) ((decoded << 4) + ch - '0');
     73 
     74                 if (type & NGX_UNESCAPE_REDIRECT) {
     75                     if (ch > '%' && ch < 0x7f) {
     76                         *d++ = ch;
     77                         break;
     78                     }
     79 
     80                     *d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1);
     81 
     82                     break;
     83                 }
     84 
     85                 *d++ = ch;
     86 
     87                 break;
     88             }
     89 
     90             c = (u_char) (ch | 0x20);
     91             if (c >= 'a' && c <= 'f') {
     92                 ch = (u_char) ((decoded << 4) + c - 'a' + 10);
     93 
     94                 if (type & NGX_UNESCAPE_URI) {
     95                     if (ch == '?') {
     96                         *d++ = ch;
     97                         goto done;
     98                     }
     99 
    100                     *d++ = ch;
    101                     break;
    102                 }
    103 
    104                 if (type & NGX_UNESCAPE_REDIRECT) {
    105                     if (ch == '?') {
    106                         *d++ = ch;
    107                         goto done;
    108                     }
    109 
    110                     if (ch > '%' && ch < 0x7f) {
    111                         *d++ = ch;
    112                         break;
    113                     }
    114 
    115                     *d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1);
    116                     break;
    117                 }
    118 
    119                 *d++ = ch;
    120 
    121                 break;
    122             }
    123          
    124             /* the invalid quoted character */
    125          *d++ = ch;
    126 
    127             break;
    128         }
    129     }
    130 
    131 done:
    132 
    133     *dst = d;
    134     *src = s;

    135 }

    从上面代码我没看到作者已经发现ngx解码函数的问题并且做了处理,但是NAXSI作者只处理了第一种情况,也就是如果%后面的第一个字符不是16进制编码则会保留%,但是如果第一个字符是,而第二个字符不是。例如上面提到的s%elect编码处理后会变成slect,所以该代码依然存在编码处理畸形Waf规则被绕过的问题,除了select还有其他的很多关键字都可以绕过。
    这里不仅是NAXSI,很多使用nginx解码代码的模块都存在该问题,例如nginx lua模块中的ngx.unescape_uri和ngx.req.get_uri_args等

    下面我给出修正后的标准urldecode代码。 

    void
    ngx_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type)
    {
        u_char  *d, *s, ch, c, decoded;
        enum {
            sw_usual = 0,
            sw_quoted,
            sw_quoted_second
        } state;

        d = *dst;
        s = *src;

        state = 0;
        decoded = 0;

        while (size--) {

            ch = *s++;

            switch (state) {
            case sw_usual:
                if (ch == '?'
                    && (type & (NGX_UNESCAPE_URI|NGX_UNESCAPE_REDIRECT)))
                {
                    *d++ = ch;
                    goto done;
                }

                if (ch == '%'&&size>1) {
                    ch=*s;
                    c = (u_char) (ch | 0x20);
                    if ((ch >= '0' && ch <= '9')||(c >= 'a' && c <= 'f')) {
                    ch=*(s+1);
                    c = (u_char) (ch | 0x20);
                    if ((ch >= '0' && ch <= '9')||(c >= 'a' && c <= 'f')) {
                    state = sw_quoted;
                    break;
                    }
                    }
                    *d++ = '%';
                    break;
                }

                if (ch == '+') {
                    *d++ = ' ';
                    break;
                }

                *d++ = ch;
                break;

            case sw_quoted:

                if (ch >= '0' && ch <= '9') {
                    decoded = (u_char) (ch - '0');
                    state = sw_quoted_second;
                    break;
                }

                c = (u_char) (ch | 0x20);
                if (c >= 'a' && c <= 'f') {
                    decoded = (u_char) (c - 'a' + 10);
                    state = sw_quoted_second;
                    break;
                }

                /* the invalid quoted character */

                state = sw_usual;

                *d++ = ch;

                break;

            case sw_quoted_second:

                state = sw_usual;

                if (ch >= '0' && ch <= '9') {
                    ch = (u_char) ((decoded << 4) + ch - '0');

                    if (type & NGX_UNESCAPE_REDIRECT) {
                        if (ch > '%' && ch < 0x7f) {
                            *d++ = ch;
                            break;
                        }

                        *d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1);

                        break;
                    }

                    *d++ = ch;

                    break;
                }

                c = (u_char) (ch | 0x20);
                if (c >= 'a' && c <= 'f') {
                    ch = (u_char) ((decoded << 4) + c - 'a' + 10);

                    if (type & NGX_UNESCAPE_URI) {
                        if (ch == '?') {
                            *d++ = ch;
                            goto done;
                        }

                        *d++ = ch;
                        break;
                    }

                    if (type & NGX_UNESCAPE_REDIRECT) {
                        if (ch == '?') {
                            *d++ = ch;
                            goto done;
                        }

                        if (ch > '%' && ch < 0x7f) {
                            *d++ = ch;
                            break;
                        }

                        *d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1);
                        break;
                    }

                    *d++ = ch;

                    break;
                }

                /* the invalid quoted character */

                break;
            }
        }

    done:

        *dst = d;
        *src = s;

    } 

    相关参考请见

    https://code.google.com/p/naxsi/wiki/SecAdvisories
    http://packetstormsecurity.com/files/120960/OWASP-WAF-Naxsi-Bypass.html
    http://seclists.org/bugtraq/2013/Mar/133
  • 相关阅读:
    刷题柱 -- 暂封
    模板重搭建計劃
    思路与好题记录与小技巧
    错误记录
    随便记点东西……
    图床
    杂碎的小技巧
    hnsdfz -- 6.21 -- day7
    hsdf -- 6.21 -- day6
    hnsdfz -- 6.20 -- day5
  • 原文地址:https://www.cnblogs.com/Safe3/p/2986203.html
Copyright © 2020-2023  润新知