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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM