IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> 『PHP内核』PHP 7 escapeshellcmd底层探究 -> 正文阅读

[PHP知识库]『PHP内核』PHP 7 escapeshellcmd底层探究

escapeshellcmd描述

escapeshellcmd归于程序执行类函数,对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。

escapeshellcmd(string $command): string

非Windows使用反斜杠\转义以下字符:&#;|*?~<>^()[]{}$\\x0A\xFF和反引号`,其中单双引号只在不成对、独立时转义

Windows使用脱字符^也转义上述字符再加上%!对于单双引号均转义

勘误

PHP Manual中文版本将Windows处理的脱字符翻译为空格
在这里插入图片描述

静态调试

PHP_FUNCTION(escapeshellcmd)

PHP函数定义为PHP_FUNCTION(foo),在vscode搜索PHP_FUNCTION(escapeshellcmd)
在这里插入图片描述
定位到了标准函数库的exec.c,这也反映了其属于程序执行类函数。来到478行:
在这里插入图片描述
首先是接收传参:通过宏ZEND_PARSE_PARAMETERS_START(1, 1)接收一个参数command,并通过Z_PARAM_STRING(command, command_len)存储接收的字符串和其长度

	char *command;
	size_t command_len;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_STRING(command, command_len)
	ZEND_PARSE_PARAMETERS_END();

接着就是if判断传入字符串的有效性:非空判断、非法输入NULL,然后就进入php_escape_shell_cmd(command)实现功能,传入字符串

	if (command_len) {
		if (command_len != strlen(command)) {
			php_error_docref(NULL, E_ERROR, "Input string contains NULL bytes");
			return;
		}
		RETVAL_STR(php_escape_shell_cmd(command));
	} else {
		RETVAL_EMPTY_STRING();
	}

最后通过RETVAL返回值

PHPAPI zend_string *php_escape_shell_cmd(char *str)

有效性判断后就进入此PHPAPI进一步实现功能,参数command被传入,来到286行:
在这里插入图片描述
注释里提供了此函数的功能介绍:

转义所有可能被用于逃逸shell的字符
这个函数对一个字符串进行了emalloc,并返回指针
用完后记得将其释放
对二进制字符串不安全

首先是一些变量的声明,函数对不同平台有不同的处理方式:此处非Windows多定义了一个指针,下文会提到它的用处。其中estimate是假定全部字符都需要转义再加一个结束符的长度

PHPAPI zend_string *php_escape_shell_cmd(char *str)
{
	register size_t x, y;
	size_t l = strlen(str);
	uint64_t estimate = (2 * (uint64_t)l) + 1;
	zend_string *cmd;
#ifndef PHP_WIN32
	char *p = NULL;
#endif

然后是判断传入command长度是否超出最大范围,最大长度减去字符串自身一对单引号和结束符;如果长度非法抛错,并返回一个空zend_string(的指针)

	/* max command line length - two single quotes - \0 byte length */
	if (l > cmd_max_len - 2 - 1) {
		php_error_docref(NULL, E_ERROR, "Command exceeds the allowed length of %zu bytes", cmd_max_len);
		return ZSTR_EMPTY_ALLOC();
	}

为cmd分配内存,类型为zend_string ;它是返回值,用于存储处理好的字符串

	cmd = zend_string_safe_alloc(2, l, 0, 0);

处理完了就进入遍历、转义部分:首先跳过传入cmd中的无效多字节字符,也就是说escapeshellcmd是忽略多字节字符处理(如:中文),然后再进行逐字符转义

跳过多字节字符

这里选用两个变量x和y,其中:x是索引当前字符在传入字符串str的下标、y是cmd字符串的末尾下标。当遇到多字节字符时mb_len>1,然后函数就会将该字符通过memcpy的方式全部复制到cmd中,然后x和y向后移动,读取下一个字符,x、y增加该多字节字符的长度。如果不是多字节字符mb_len<0,进入下面的转义判断处理

细节见动态调试分析

	for (x = 0, y = 0; x < l; x++) {
		int mb_len = php_mblen(str + x, (l - x));

		/* skip non-valid multibyte characters */
		if (mb_len < 0) {
			continue;
		} else if (mb_len > 1) {
			memcpy(ZSTR_VAL(cmd) + y, str + x, mb_len);
			y += mb_len;
			x += mb_len - 1;
			continue;
		}

PHP历史漏洞:宽字节注入

PHP5.2.6之前未对多字节字符的处理,会导致GBK宽字节注入,因此添加了此部分以修复漏洞

对有效字符转义

当字符不是多字节字符时进入switch判断转义,这里有分系统的处理。

  1. 第一步:

#ifndef是对非Windows平台(可认为就是Linux)有效的函数。这一步是处理Linux平台的单双引号成对性判断的部分:仅转义不成对的引号,之前的p指针就是为此而声明的。

memchr原型是void *memchr(const void *str, int c, size_t n),在字符串str的前n个字节中搜索第一次出现字符 c(一个无符号字符)的位置,该函数返回一个指向匹配字节的指针,如果在给定的内存区域未出现字符,则返回 NULL。

第一次匹配进入noop,第二次若匹配到了进入NULL也就是判定为成对、p置空;若未匹配到进入ZSTR_VAL(cmd)[y++] = '\\'也就是判定为不成对,此时先向cmd写入转义符再写入引号。判断是否成对是从后往前判定。

		switch (str[x]) {
#ifndef PHP_WIN32
			case '"':
			case '\'':
				if (!p && (p = memchr(str + x + 1, str[x], l - x - 1))) {
					/* noop */
				} else if (p && *p == str[x]) {
					p = NULL;
				} else {
					ZSTR_VAL(cmd)[y++] = '\\';
				}
				ZSTR_VAL(cmd)[y++] = str[x];
				break;

对Windows平台特别转义%!,注意看注释

%是环境变量的标识符,windows会解析^%PATH%,而不解析^%PATH^%

#else
			/* % is Windows specific for environmental variables, ^%PATH% will
				output PATH while ^%PATH^% will not. escapeshellcmd->val will escape all % and !.
			*/
			case '%':
			case '!':
			case '"':
			case '\'':
#endif
  1. 第二步:

声明了Windows和Linux共用的转义符号表

			case '#': /* This is character-set independent */
			case '&':
			case ';':
			case '`':
			case '|':
			case '*':
			case '?':
			case '~':
			case '<':
			case '>':
			case '^':
			case '(':
			case ')':
			case '[':
			case ']':
			case '{':
			case '}':
			case '$':
			case '\\':
			case '\x0A': /* excluding these two */
			case '\xFF':
  1. 第三步:

分系统转义:如果需要转义,首先先写入转义符号,Windows使用脱字符,Linux使用反斜杠;然后再进入default写入字符。

简而言之就是:如果字符在转义表中:分平台向cmd写入转义符再写入字符,否则只写入字符

最后在cmd末尾写入结束符

#ifdef PHP_WIN32
				ZSTR_VAL(cmd)[y++] = '^';
#else
				ZSTR_VAL(cmd)[y++] = '\\';
#endif
				/* fall-through */
			default:
				ZSTR_VAL(cmd)[y++] = str[x];

		}
	}
	ZSTR_VAL(cmd)[y] = '\0';

检查结果有效性并返回

最后检查转义结果cmd是否有效:判断长度是否有效和分配空间是否溢出,然后登记cmd的长度。

	if (y > cmd_max_len + 1) {
		php_error_docref(NULL, E_ERROR, "Escaped command exceeds the allowed length of %zu bytes", cmd_max_len);
		zend_string_release(cmd);
		return ZSTR_EMPTY_ALLOC();
	}

	if ((estimate - y) > 4096) {
		/* realloc if the estimate was way overill
		 * Arbitrary cutoff point of 4096 */
		cmd = zend_string_truncate(cmd, y, 0);
	}

	ZSTR_LEN(cmd) = y;

	return cmd;
}

返回cmd,完成escapeshellcmd的调用

动态调试

动态调试基于Windows

常规输入

测试代码:

// test.php
<?php
    echo escapeshellcmd('1"');
?>

断点:
在这里插入图片描述
初始化:假设所有都转义,再加上结束符estimate就是5,读入1"两字节

读取字符1,判断属于单字符,进入switch判断是否需要转义
在这里插入图片描述
无需转义
在这里插入图片描述
x和y都+1,进入第二个循环
在这里插入图片描述
需要转义,添加转义符
在这里插入图片描述
最后写入结束符截断字符串
在这里插入图片描述
完成转义,返回
在这里插入图片描述

输入多字节字符

测试代码:

// test.php
<?php
    echo escapeshellcmd('"我1');
?>

断点:
在这里插入图片描述
初始变量,由于还暂未对y赋值,是奇奇怪怪的值;UTF8中:中文占三字节
在这里插入图片描述
双引号是有效的单字符,进入switch
在这里插入图片描述
switch判定为需要转义:因此向cmd先写入转义符再写入双引号,此时y==2
在这里插入图片描述
第一个循环结束前x+1,x==1,之后进入下一个循环读取下一个字符
在这里插入图片描述
由于UTF8是三字节,因此这里只读入了前两个字节
在这里插入图片描述
y由于此时写入了四个字符:转义的双引号和两个UTF8的字节,所以是4;然后因为循环每次都x++,这里加多了1,要减去,相当于只是多写了1个字节
在这里插入图片描述
然后下一个循环开始前,x++,现在x指向UTF8的第三个字节
在这里插入图片描述
这一步读取UTF8的第三个字节和后面的一个字符1,将其视为一个多字节字符了,读取过程同上,然后循环结束,写入一个结束符\0
在这里插入图片描述
最后转义完毕,返回cmd
在这里插入图片描述
cmd的内容是:转义的双引号(2) + UTF中文前两个字节(2) + UTF8中文第三个字节加字符1(2) + 结束符(0),因此y==6。

欢迎在评论区留言,欢迎关注我的CSDN @Ho1aAs

  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-10-20 12:17:36  更:2021-10-20 12:17:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 13:30:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计