目录
一、nginx解析php的流程
1.原理
2.CGI、FastCGI、PHP-FPM、PHP-CG、WrapperI的定义
二、Fastcgi协议
1.Fastecgi Record
2.Fastcgi Type
3.PHP-FPM(FastCGI进程管理器)
4.总结FastCGI解析的流程
三、nginx配置错误导致的漏洞
1.CRLF注入漏洞($uri解码漏洞,换行符导致的注入漏洞)
(1)原理
(2)利用CRLF修改头部信息
(3)CRLF+Bottle HTTP造成的反射性xss漏洞
(4)防御
2.目录穿越漏洞
(1)原理
(2)漏洞复现
(3)防御
3.Http add_header被覆盖的问题
(1)原理
(2)CSP(Content-Security-Policy)
(3)漏洞的复现
(4)防御
四、nginx文件名逻辑漏洞(Nginx 0.8.41 ~ 1.4.3 /1.5.0 ~ 1.5.7 )
1.原理
2.文件名逻辑漏洞的复现
(1)首先创建一个文本,里面输入php代码
(2)修改文件后缀名为1.gif
(3)打开浏览器输入
(4)选择我们1.gif文件
(5)打开抓包工具,之后上传文件抓包
(6)找到文件名给他加成1.gifaa.php
(7)进入到hex也就是编码数字的部分,修改文件名,将aa改成20和00,主要是让他生成 \0(也就是一个空格和阶段符号)。
(8)将修改的数据包放行提交到端
(9)查看我们上传图片的目录
(10)打开抓包工具抓取该网页,出现以下问题,是因为我们禁止了抓包工具抓取图片,我们点击开启就可以了。
(11)抓包
(13)修改我们的文件名为1.gifaa.php
(14)进入到hex下,修改我们的aa编码为20和00,就是空格和截断符,这里主要是为了和我们上传的文件名一样
(15)将我们修改过的数据包发送就会看到我们注意的php页面
3.防御
五、nginx的解析漏洞
1.原理
2.nginx解析漏洞复现
(1)先看我们php-fpm配置文件,发现我们的后缀名安全策略为空的
(2)访问http://192.168.191.129/uplocalfiles/nginx.png
(3)在上面路径后面随意添加一个以.php结尾的文件
(4)触发我们的php恶意代码
(5)还有一种方法和上面nginx逻辑名漏洞一样上传php恶意文件,修改名称,第二次直接访问就会触发
首先浏览器对nginx进行传输数据,nginx接收到浏览器传过来的数据之后,通过fastcgi协议将数据格式化成fastcgi规定的形式,格式完之后通过php-fpm进程管理工具,格式成n多个的phpcgi,从而处理成fastcgi规范的数据,处理完的数据交给我们的php,然后PHP将数据传递给我们的nginx。
CGI:CGI是一种协议,它定义了Nginx或者其他Web Server传递过来的数据格式,全称是(Common Gateway Interface,CGI),CGI是一个独立的程序,独立与WebServer之外,任何语言都可以写CGI程序,例如C、Perl、Python等。
FastCGI:FastCGI是一种协议,它的前身是CGI,可以简单的理解为是优化版的CGI,拥有更够的稳定性和性能。
PHP-CGI:只是一个PHP的解释器,本身只能解析请求,返回结果,不会做进程管理。
PHP-FPM:全称FastCGI Process Manager,看名称就可以知道,PHP-FPM是FastCGI进程的管理器,但前面讲到FastCGI是协议并不是程序,所以它管理的是PHP-CGI,形成了一个类似PHP-CGI进程池的概念。
Wrapper:字母意思是包装的意思,包装的是谁呢?包装的是FastCGI,通过FastCGI接口,Wrapper接收到请求后,会生成一个新的线程调用PHP解释器来处理数据。
Fastcgi其实是一个通信协议,fastcgi协议则是服务器中间件和某个语言后端进行数据交换的协议。Fastcgi协议由多个record组成,record也有header和body一说,服务器中间件将这二者按照fastcgi的规则封装好发送给语言后端,语言后端解码以后拿到具体数据,进行指定操作,并将结果再按照该协议封装好后返回给服务器中间件。其结构如下
typedef struct {
/* Header */
unsigned char version; // 版本
unsigned char type; // 本次record的类型
unsigned char requestIdB1; // 本次record对应的请求id
unsigned char requestIdB0;
unsigned char contentLengthB1; // body体的大小
unsigned char contentLengthB0;
unsigned char paddingLength; // 额外块大小
unsigned char reserved;/* Body */
unsigned char contentData[contentLength];
unsigned char paddingData[paddingLength];
} FCGI_Record;
头由8个uchar类型的变量组成,每个变量1字节。其中,requestId
占两个字节,一个唯一的标志id,以避免多个请求之间的影响;contentLength
占两个字节,表示body的大小。
语言端解析了fastcgi头以后,拿到contentLength
,然后再在TCP流里读取大小等于contentLength
的数据,这就是body体。
我们需要注意的是typre类型
type是用来编辑fastcgi的主要作用,最主要的是1、4、6
值得注意的是,当我们的type是4的时候,就会按照对应的结构解析成我们的key-value,结构如下
typedef struct {
unsigned char nameLengthB0; /* nameLengthB0 >> 7 == 0 */
unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */
unsigned char nameData[nameLength];
unsigned char valueData[valueLength];
} FCGI_NameValuePair11;typedef struct {
unsigned char nameLengthB0; /* nameLengthB0 >> 7 == 0 */
unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */
unsigned char valueLengthB2;
unsigned char valueLengthB1;
unsigned char valueLengthB0;
unsigned char nameData[nameLength];
unsigned char valueData[valueLength
((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
} FCGI_NameValuePair14;typedef struct {
unsigned char nameLengthB3; /* nameLengthB3 >> 7 == 1 */
unsigned char nameLengthB2;
unsigned char nameLengthB1;
unsigned char nameLengthB0;
unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */
unsigned char nameData[nameLength
((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
unsigned char valueData[valueLength];
} FCGI_NameValuePair41;typedef struct {
unsigned char nameLengthB3; /* nameLengthB3 >> 7 == 1 */
unsigned char nameLengthB2;
unsigned char nameLengthB1;
unsigned char nameLengthB0;
unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */
unsigned char valueLengthB2;
unsigned char valueLengthB1;
unsigned char valueLengthB0;
unsigned char nameData[nameLength
((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
unsigned char valueData[valueLength
((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
} FCGI_NameValuePair44;
我们需要注意的是上述我标红的部分,当为44的部分时候,会将我们的key-value,传递到我们的php-fpm解析成我们真正的数据。
FPM其实是一个fastcgi协议解析器,Nginx等服务器中间件将用户请求按照fastcgi的规则打包好通过TCP传给FPM,FPM按照fastcgi的协议将key-value解析成真正的数据。结构如下
{
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'GET',
'SCRIPT_FILENAME': '/var/www/html/index.php',
'SCRIPT_NAME': '/index.php',
'QUERY_STRING': '?a=1&b=2',
'REQUEST_URI': '/index.php?a=1&b=2',
'DOCUMENT_ROOT': '/var/www/html',
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '12345',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1'
}
最后执行SCRIPT_FILENAM的指向的PHP文件,将这个数据返回给我们的浏览器。
首先nginx接收到数据后,会被fastcgi处理,会按照type为4,构造出我们的key-value,然后传递给我们的php-fpm,php-fpm会将key-value对解析成我们真正的数据,这个数据中包含我们的版本,传参方式,请求文件根目录路径,请求的文件,请求参数等。然后根据数据请求我们的php,完成我们的nginx+php的请求。
注意:1.先下载好文件(漏洞文件下载地址)
2.进入到vulhub/nginx/insecure-configuration/这个目录下
3.启用docker加载环境(docker-compose up -d)
在我们访问http://example.com/abc时,我们的网页会自动的跳转到http://www.example.com/abc,但是在跳转的过程之中需要保持我们的uri(也就是网址后面的部分,比如abc)不变,在nginx配置中有三种配置uri的方法$uri、$document_uri、$request_uri,在这里面$uri和$document_uri是解码后的请求路径,而$request_uri并没进行解码,正是因为uri解码,我们可以利用%0d%0a(换行符),在我们的uri后面写入我们恶意代码,从而插入到我们的referer的头部,从而修改我们的头部信息。
a.nginx运维的错误配置
b.注入一个set-cookie
curl -I http://127.0.0.1:8080%0d%0aset-cookie:%20jiege666
a.bottle搭建起来的环境,利用python运行下面代码
import bottle from bottle import route, run, template, request, response @route('/') def index(): server = request.query.get('server') response.add_header('Server',server) return response if __name__ == '__main__': bottle.debug(True) run(host='localhost', port=8081)
b.在浏览器输入以下的请求
http://localhost:8081/server=jiege%0d%0aX-XSS-Protection:0%0d%0a%0d%0a
c.结果
d.原理
我们分析代码,发现server接收我们的内容,我们随意赋值,加入一个%0d%0a(换行符)先给他换行,然后在他的referer头部插入X-XSS-Protection:0,这个是浏览用来防止我们进行xss注入的,所以我们拆入头部将他关闭,然后%0d%0a%0d%0a之所以是两个换行符,是为了让后面的内容插入到我们的body也就是我们的html里面的,后面跟上我们,就可以造成我们的xss反射性漏洞弹窗,从而获取我们的路径,也可以是其他恶意代码。以上就完成我们的CRLF+Bottle HTTP的xss漏洞
e.防御CRLF+Bottle HTTP
第一种就是使用CSP禁止iframe跳转,第二种是将跳转的url端口设置<80
我们可以在配置nginx的uri的跳转的时候,配置成$request_uri这样就可以防止浏览器自己解码了,从而防御住了我们的CRLF(换行符漏洞)
nginx在配置别名(alias)的时候忘记加了/,让原本访问某个目录下的文件,攻击者多加../就会返回到根目录,从而造成了目录穿越漏洞。
a.出现错误的配置文件
b.在浏览器输入
http://192.168.191.129:8081/files../
c.结果
d.漏洞利用
我们可以进入到/usr/local/nginx/html/下,下载我们的config.php也就是我们的数据库连接文件,获得数据库的用户名和密码,登陆数据库阻止其他人的连接,或者获取数据。
给location /files 和alias /home的后面同时加/,或者都不加
nginx在配置子模块(server location if)的add_header的时候,将会覆盖父模块的add_header头部,从而造成的隐患。
主要是为了防止XSS的存在,阻止入侵者插入存在第三方跳转的链接
a.后端出现配置错误的文件
我们不难发现在我们的子模块中出现了add_header,而我们的父模块是只允许我们内部引用js,而我们的字模块却可以引用外部js,又因为我们的子模块会覆盖父模块,这就导致了漏洞的存在。
b.我们正常访问页面test1,发现页面存在csp只允许加载本地js
c.我们在访问test2,发现csp被覆盖了可以访问外部js,我们利用这一点进行攻击
d.在浏览器插入以下的请求
http://192.168.191.129:8082/test2#
e.我们查看页面弹出弹窗就可以进行xss漏洞的注入
一般情况下会出现一个弹窗的,可能由于我们的浏览器版本可能太高了,可以降低浏览器版本就可以实现弹窗了
在配置子模块的时候,不要重复去添加父模块所含有的add_header头部
因为我们上传了一个文件名为1.gif \0.php文件,首先我们的nginx解析到他是php结尾的文件,之后将他发送给我们的fastcgi,我们的fastcgi将这个数据解析完成后,又由于1.gif \0.php,\0也就是c语言的结束符的意思,之后我们的通过我们的\0将这个文件名截断成我们的fastcgi认为他到1.gif就结束了,之后对上传的文件进行检查,发现他没有以php结尾,就将这个文件上传上去,然后我们的php-fpm.conf的安全后缀策略(security.limit_extensions = )是空的,之后他就会执行我们上传的1.gif \0.php文件。
http://192.168.191.129:8080
http://192.168.191.129:8080/uploadfiles/1.gif
我们在配置php-fpm.conf的安全后缀策略(security.limit_extensions = jpg gif)尽量不要设置为空,这样就可以防御我们的nginx文件名逻辑漏洞。
我们在配置php.ini的时候,开启了我们的自动修复路径(cgi.fix_pathinfo=1),也就是我们访问http://127.0.0.1/test.jpg/test.php,由于没有test.php这个文件我们服务器会自动到上一个路径,发现有test.jpg于是就自己执行了这个文件,但是由于我们在配置php-fpm.conf的安全后缀策略(security.limit_extensions = jpg php )为空或者就只有jpg和php,我们的服务就会执行jpg和php,我们的test.jpg里面就是我们的恶意的php代码,之后通过解析为php文件就导致了我们执行该文件,从而产生了我们的漏洞。
http://192.168.191.129/uplocalfiles/nginx.png
http://192.168.191.129/uplocalfiles/nginx.png/nginx.php