去年发现的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