Nginx的merge_slashes解析 - 边窗

/ 0评 / 1

Nginx有一个配置项是merge_slashes,官方的说明如下:

Syntax: merge_slashes on | off;

Default: merge_slashes on;

Context: http, server

Enables or disables compression of two or more adjacent slashes in a URI into a single slash.

顾名思义,就是会在处理URI时将相邻的单斜杠合并。这个配置项在Nginx中对应的源码是在https://github.com/nginx/nginx/blob/master/src/http/ngx_http_parse.c#L1248,该函数的声明是:

ngx_int_t ngx_http_parse_complex_uri(ngx_http_request_t *r, ngx_uint_t merge_slashes)

第一个参数是URI的请求结构,第二个int类型参数merge_slashes就是配置项的开(on)或关(off)。

函数中,首先有一个枚举类型的变量state用于函数执行中标识每个字符的状态,或者是字符的分类,初始情况下是sw_usual正常状态。

enum {
        sw_usual = 0,
        sw_slash,
        sw_dot,
        sw_dot_dot,
        sw_quoted,
        sw_quoted_second
} state, quoted_state;

字符型指针*u在函数执行过程中用于记录处理的中间URI字符串,字符型指针*p是URI的字符串,字符ch用于记录当前在处理中的URI的字符。

接着是一个大的while循环,用于逐个处理URI中的字符:

ch = *p++;
while (p <= r->uri_end) {
	switch (state) {
	case sw_usual:
		...
		switch (ch) {
		...
		case '/':
		        ...
			state = sw_slash;
			*u++ = ch;
			break;
			...
		}
		ch = *p++;
		break;
		
	case sw_slash:
		...
		switch (ch) {
		case '/':
			if (!merge_slashes) {
				*u++ = ch;
			}
			break;
			...
		}
		ch = *p++;
		break;
		...
	case sw_dot_dot:
		...
		switch (ch) {
		case '/':
		        u -= 4;
		        for ( ;; ) {
		                if (u < r->uri.data) {
		                        return NGX_HTTP_PARSE_INVALID_REQUEST;
		                }
		                if (*u == '/') {
		                        u++;
		                        break;
		                }
		                u--;
		        }
		        state = sw_slash;
		        break;
                }
        }
}

函数根据每个处理的字符类型标识当前字符的状态或类型,该状态决定在之后的条件判断语句中该如何处理后续字符。通过以上函数的逻辑可知,如果merge_slashes为1,相邻出现的多个单斜杠会在sw_slash状态的条件处理中被忽略,*u不记录单斜杠,如果为0,则会记录单斜杠字符。

因此,多个斜杠相邻的URI会被处理为单个斜杠,如果需要在URI中传输BASE64字符,则需要关闭merge_slashes,避免其中的斜杠被处理。

另外,如果URI中存在路径穿越的利用,比如“/../../../”,会条件处理到sw_dot_dot中,并返回NGX_HTTP_PARSE_INVALID_REQUEST,即无效的请求。所以Nginx默认情况下可以防范路径穿越攻击,除非将merge_slashes参数设置为关闭。

知识共享许可协议  本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。

发表回复

您的电子邮箱地址不会被公开。