去年發現的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;
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;
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;
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
