PHP-fpm 远程代码执行漏洞(CVE-2019-11043)源码分析

一、漏洞复现

1、搭建docker环境(yum install docker-re)

2、拉取镜像
配置docker-compose.yml文件,并拉取镜像

docker-compose up -d

version: ‘2‘
services:
 nginx:
   image: nginx:1
   volumes:
    - ./www:/usr/share/nginx/html
    - ./default.conf:/etc/nginx/conf.d/default.conf
   depends_on:
    - php
   ports:
    - "8080:80"
 php:
   image: php:7.1.32-fpm
   volumes:
    - ./www:/var/www/html

default.conf

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /usr/share/nginx/html;

    index index.html index.php;

    server_name _;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ [^/]\.php(/|$) {
        fastcgi_split_path_info ^(.+?\.php)(/.*)$;
        include fastcgi_params;

        fastcgi_param PATH_INFO       $fastcgi_path_info;
        fastcgi_index index.php;
        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;
    }

}

二、源码分析

static void init_request_info(void)
{
	fcgi_request *request = (fcgi_request*) SG(server_context);
//文件绝对路径
	char *env_script_filename = FCGI_GETENV(request, "SCRIPT_FILENAME");
//env_path_translated值和env_script_filename值一样
	char *env_path_translated = FCGI_GETENV(request, "PATH_TRANSLATED");
	char *script_path_translated = env_script_filename;
	char *ini;
	int apache_was_here = 0;

	/* some broken servers do not have script_filename or argv0
	 * an example, IIS configured in some ways.  then they do more
	 * broken stuff and set path_translated to the cgi script location */
	if (!script_path_translated && env_path_translated) {
		script_path_translated = env_path_translated;
	}

	/* initialize the defaults */
	SG(request_info).path_translated = NULL;
	SG(request_info).request_method = NULL;
	SG(request_info).proto_num = 1000;
	SG(request_info).query_string = NULL;
	SG(request_info).request_uri = NULL;
	SG(request_info).content_type = NULL;
	SG(request_info).content_length = 0;
	SG(sapi_headers).http_response_code = 200;
	if (script_path_translated) {
		const char *auth;
//获取request请求中的参数
		char *content_length = FCGI_GETENV(request, "CONTENT_LENGTH");
		char *content_type = FCGI_GETENV(request, "CONTENT_TYPE");
		char *env_path_info = FCGI_GETENV(request, "PATH_INFO");
		char *env_script_name = FCGI_GETENV(request, "SCRIPT_NAME");
...

		if (CGIG(fix_pathinfo)) {
			struct stat st;
			char *real_path = NULL;
			char *env_redirect_url = FCGI_GETENV(request, "REDIRECT_URL");
			char *env_document_root = FCGI_GETENV(request, "DOCUMENT_ROOT");
			char *orig_path_translated = env_path_translated;
			char *orig_path_info = env_path_info;
			char *orig_script_name = env_script_name;
			char *orig_script_filename = env_script_filename;
			int script_path_translated_len;
...
			if (script_path_translated &&
//script_path_translated_len是请求uri_path中第一个斜杠前的内容:如http://127.0.0.1/index.php/test,则变量的值为/var/www/html/index.php的长度
				(script_path_translated_len = strlen(script_path_translated)) > 0 &&
				(script_path_translated[script_path_translated_len-1] == ‘/‘ ||
#ifdef PHP_WIN32
				script_path_translated[script_path_translated_len-1] == ‘\\‘ ||
#endif
				(real_path = tsrm_realpath(script_path_translated, NULL)) == NULL)
			) {
//字符串复制
				char *pt = estrndup(script_path_translated, script_path_translated_len);
//url的长度取决于nginx的配置当请求url,http://127.0.0.1/index.php/123%0atest.php。script_path_translated来自于nginx的配置,为/var/www/html/index.php/123\ntest.php
				int len = script_path_translated_len;
...
							int ptlen = strlen(pt);
							int slen = len - ptlen;
//request中path_info的长度,此参数值可控
							int pilen = env_path_info ? strlen(env_path_info) : 0;
							int tflag = 0;
							char *path_info;
if (apache_was_here) {
								/* recall that PATH_INFO won‘t exist */
								path_info = script_path_translated + ptlen;
								tflag = (slen != 0 && (!orig_path_info || strcmp(orig_path_info, path_info) != 0));
							} else {
//在c语言中,char *变量,加一个int数字,是一个使指针指向的地址偏移
								path_info = env_path_info ? env_path_info + pilen - slen : NULL;
								tflag = (orig_path_info != path_info);
							}

 下面的代码进行举例分析:

path_info = env_path_info ? env_path_info + pilen - slen : NULL;替换成类似代码:由此可以看出,下面的代码在c语言中可以起到偏移char *首地址的#include <stdio.h>
int main() {
    char *a = "aaaaaaaa";
    char *b = a-2;
    printf("%s",b);//path_info[0]此地址对应的是path_info的首地址。根据fpm代码,则是可操作堆上任意数据置为0,那我们就可以把_fcgi_data_seg结构体的char* pos置零
									FCGI_PUTENV(request, "ORIG_PATH_INFO", orig_path_info);
									old = path_info[0];
									path_info[0] = 0;
//orig_script_name变量不为空
									if (!orig_script_name ||
										strcmp(orig_script_name, env_path_info) != 0) {
										if (orig_script_name) {
											FCGI_PUTENV(request, "ORIG_SCRIPT_NAME", orig_script_name);//进入此函数
}

php源码中FCGI_PUTENV函数

#define FCGI_PUTENV(request, name, value) 	fcgi_quick_putenv(request, name, sizeof(name)-1, FCGI_HASH_FUNC(name, sizeof(name)-1), value)

  查看fcgi_quick_putenv函数

char* fcgi_quick_putenv(fcgi_request *req, char* var, int var_len, unsigned int hash_value, char* val)
{
	if (val == NULL) {
		fcgi_hash_del(&req->env, hash_value, var, var_len);
		return NULL;
	} else {
		return fcgi_hash_set(&req->env, hash_value, var, var_len, val, (unsigned int)strlen(val));
	}
}

  查看fcgi_hash_set函数,其中fcgi_request结构体为

//此为fcgi_request的机构体
struct _fcgi_request {
	int            listen_socket;
	int            tcp;
	int            fd;
	int            id;
	int            keep;
#ifdef TCP_NODELAY
	int            nodelay;
#endif
	int            ended;
	int            in_len;
	int            in_pad;

	fcgi_header   *out_hdr;

	unsigned char *out_pos;
	unsigned char  out_buf[1024*8];
	unsigned char  reserved[sizeof(fcgi_end_request_rec)];

	fcgi_req_hook  hook;

	int            has_env;
	fcgi_hash      env;//此处是request->env存放的位置,存储的是nginx配置的ENV全局变量,在fcgi_hash_set中的变量名为h
};

 现在请联系之前,path_info[0]=0这段代码,这段代码表示我们可以随意在栈上的任意位置置为0。现在也就是说,传入fcgi_hash_set函数中的fcgi_hash *h我们可以修改这个变量中的任意位置为0, 

static char* fcgi_hash_set(fcgi_hash *h, unsigned int hash_value, char *var, unsigned int var_len, char *val, unsigned int val_len)
{
	unsigned int      idx = hash_value & FCGI_HASH_TABLE_MASK;
	fcgi_hash_bucket *p = h->hash_table[idx];

	while (UNEXPECTED(p != NULL)) {//php全局变量名称hash_value相等,p中的var值以传入的固定的全局变量名称开头就可以,以及全局变量名称长度相同,则可覆盖到php其他全局变量的值,hash函数如下/****

memcmp是比较内存区域buf1和buf2的前count个字节。该函数是按字节进行比较的

memcmp(p->var, var, var_len)这段函数是比较p->var中的值是否是以var的值开头

#define FCGI_HASH_FUNC(var, var_len) \
(UNEXPECTED(var_len < 3) ? (unsigned int)var_len : \
(((unsigned int)var[3]) << 2) + \
(((unsigned int)var[var_len-2]) << 4) + \
(((unsigned int)var[var_len-1]) << 2) + \
var_len)

*/
		if (UNEXPECTED(p->hash_value == hash_value) &&
		    p->var_len == var_len &&
		    memcmp(p->var, var, var_len) == 0) {

			p->val_len = val_len;
			p->val = fcgi_hash_strndup(h, val, val_len);
			return p->val;
		}
		p = p->next;
	}

	if (UNEXPECTED(h->buckets->idx >= FCGI_HASH_TABLE_SIZE)) {
		fcgi_hash_buckets *b = (fcgi_hash_buckets*)malloc(sizeof(fcgi_hash_buckets));
		b->idx = 0;
		b->next = h->buckets;
		h->buckets = b;
	}
	p = h->buckets->data + h->buckets->idx;
	h->buckets->idx++;
	p->next = h->hash_table[idx];
	h->hash_table[idx] = p;
	p->list_next = h->list;
	h->list = p;
	p->hash_value = hash_value;
	p->var_len = var_len;//进入fcgi_hash_strndup此函数
	p->var = fcgi_hash_strndup(h, var, var_len);
	p->val_len = val_len;
	p->val = fcgi_hash_strndup(h, val, val_len);
	return p->val;
}

  进入fcgi_hash_strndup此函数

static inline char* fcgi_hash_strndup(fcgi_hash *h, char *str, unsigned int str_len)
{
	char *ret;

	if (UNEXPECTED(h->data->pos + str_len + 1 >= h->data->end)) {
		unsigned int seg_size = (str_len + 1 > FCGI_HASH_SEG_SIZE) ? str_len + 1 : FCGI_HASH_SEG_SIZE;
		fcgi_data_seg *p = (fcgi_data_seg*)malloc(sizeof(fcgi_data_seg) - 1 + seg_size);

		p->pos = p->data;
		p->end = p->pos + seg_size;
		p->next = h->data;
		h->data = p;
	}//写入数据
	ret = h->data->pos;
	memcpy(ret, str, str_len);
	ret[str_len] = 0;
	h->data->pos += str_len + 1;
	return ret;
}

  到现在,全局变量的值已经可控的了。

如何使用修改全局变量导致远程代码执行,可参考poc

 

原文地址:https://www.cnblogs.com/ermei/p/11781369.html

时间: 2024-10-28 10:38:10

PHP-fpm 远程代码执行漏洞(CVE-2019-11043)源码分析的相关文章

关于发布的CVE-2013-2251漏洞,strust远程代码执行漏洞

(*该漏洞影响版本:Struts 2.0.0 – Struts 2.3.15) (*该博客仅仅只是记录我工作学习时遇到的问题,仅供参考!) (*如果,描述中可能存在错误,请多指教!) 在昨天在对我目前负责的那个项目进行日常维护的时候,系统被别人攻克,上传了一个.txt文件,他人可以直接访问这个项目下txt文件,就可以获取到txt文件内的内容. 首先,介绍下我目前维护的项目,使用的是strust2.1+hibernate3.0架构模式,也就是javaweb+SSH框架,不过为了简化,并没有添加sp

Struts2再爆远程代码执行漏洞(S2-016)

Struts又爆远程代码执行漏洞了!在这次的漏洞中,攻击者可以通过操纵参数远程执行恶意代码.Struts 2.3.15.1之前的版本,参数action的值redirect以及redirectAction没有正确过滤,导致ognl代码执行.  描述 影响版本 Struts 2.0.0 - Struts 2.3.15 报告者 Takeshi Terada of Mitsui Bussan Secure Directions, Inc. CVE编号 CVE-2013-2251 漏洞证明 参数会以OGN

[EXP]CVE-2019-0604微软SharePoint远程代码执行漏洞利用

研表究明,汉字的序顺并不定一能影阅响读,比如当你看完这句话后,才发这现里的字全是都乱的. 剑桥大学的研究结果,当单词的字母顺序颠倒时,你仍旧可以明白整个单词的意思.其中重要的是:只要单词的第一个字母和最后一个子字母位置正确即可.其他的可以是完全的乱码,你仍旧可以清楚的完全没有问题的阅读.原因是因为人脑在认知单词的过程中不是依靠辨识字母的顺序,而是从整体来看.同理,汉字的阅读也会受到大脑先入为主的分析.如果你所看到的句子在大脑中事先有过印象,那么你就能顺利的将它读出.如果句子是大脑之前没有处理过的

漏洞复现:MS14-064 OLE远程代码执行漏洞

MS14-064OLE远程代码执行漏洞 攻击机:Kali Linux 2019 靶机:Windows 7 x64.x32 攻击步骤: 1.打开攻击机Kali Linux 2019系统和靶机Windows 7系统 2.确定IP地址后,Kali Linux开启msf模块,准备攻击测试 3.在这里我们使用MS14-064OLE远程代码执行漏洞,开始搜索MS14-064 (search MS14-064) 4.我们利用use exploit/windows/browser/ms14_064_ole_co

Office CVE-2017-8570远程代码执行漏洞复现

实验环境 操作机:Kali Linux IP:172.16.11.2 目标机:windows7 x64 IP:172.16.12.2 实验目的 掌握漏洞的利用方法 实验工具 Metaspliot:它是一款开源的安全漏洞检测工具,可以帮助安全和IT专业人士识别安全性问题,验证漏洞的缓解措施,并管理专家驱动的安全性进行评估,提供真正的安全风险情报.这些功能包括智能开发,代码审计,Web应用程序扫描,社会工程,团队合作. 实验内容 Office CVE-2017-8570 CVE-2017-8570漏

Android WebView远程代码执行漏洞简析

0x00 本文参考Android WebView 远程代码执行漏洞简析.代码地址为,https://github.com/jltxgcy/AppVulnerability/tree/master/WebViewFileDemo.下面我们分析代码. 0x01 首先列出项目工程目录: MainActivity.java的代码如下: public class MainActivity extends Activity { private WebView webView; private Uri mUr

Apache ActiveMQ Fileserver远程代码执行漏洞

扫端口的时候遇到8161端口,输入admin/admin,成功登陆,之前就看到过相关文章,PUT了一句话上去,但是没有什么效果,于是本地搭建了一个环境,记录一下测试过程. 环境搭建: ActiveMQ 5.1.0 下载地址:http://activemq.apache.org/activemq-510-release.html 解压后,双击运行abtivemq.bat运行.(进入bin目录,根据自己的操作系统选择win32或win64,5.1.0只有win32目录) 访问8161端口: 漏洞利用

ElasticSearch Groovy脚本远程代码执行漏洞

什么是ElasticSearch? 它是一种分布式的.实时性的.由JAVA开发的搜索和分析引擎. 2014年,曾经被曝出过一个远程代码执行漏洞(CVE-2014-3120),漏洞出现在脚本查询模块,由于搜索引擎支持使用脚本代码(MVEL),作为表达式进行数据操作,攻击者可以通过MVEL构造执行任意java代码,后来脚本语言引擎换成了Groovy,并且加入了沙盒进行控制,危险的代码会被拦截,结果这次由于沙盒限制的不严格,导致远程代码执行任意命令..."任意"你懂的,比如:利用nc反弹sh

Portable OpenSSH GSSAPI远程代码执行漏洞(CVE-2006-5051)漏洞解决方案

漏洞的名称为Portable OpenSSH GSSAPI远程代码执行漏洞(CVE-2006-5051)及OpenSSH J-PAKE授权问题漏洞(CVE-2010-4478),厂家给出的解决方案很笼统.经过各方查找资料,大致的解决方案是升级到高版本的openssh,目前最新版本是openssh 6.7p1. 下载地址:http://mirror.internode.on.net/pub/OpenBSD/OpenSSH/portable/ 可以根据自己的需要选择下载升级,其实没别要升级到最新版本

小米手机MIUI远程代码执行漏洞分析

7月我在研究webview漏洞时专门挑小米手机的MIUI测试了下,发现了非常明显的安全漏洞.通过该漏洞可以远程获取本地APP的权限,突破本地漏洞和远程漏洞的界限,使本地app的漏洞远程也能被利用,达到隔山打牛的效果.在漏洞发现的第一时间,我已经将漏洞细节报告给了小米安全响应中心,目前漏洞已经修复. 测试环境:手机型号:MI 3 Android版本:4.2.1 JOP40D MIUI版本:MIUI-JXCCNBE21 内核版本:3.4.35-ga656ab9 一.   小米MIUI原生浏览器存在意