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

 

posted @ 2013-03-28 10:37  有安科技  阅读(46476)  评论(0编辑  收藏  举报