『PHP内核』PHP 7 escapeshellarg底层探究

2021/10/23 7:12:58

本文主要是介绍『PHP内核』PHP 7 escapeshellarg底层探究,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

文章目录

  • escapeshellarg描述
    • 歧义
  • 静态调试
    • PHP_FUNCTION(escapeshellarg)
    • PHPAPI zend_string *php_escape_shell_arg(char *str)
      • 添加前面的引号
      • 跳过多字节字符
        • PHP历史漏洞:GBK宽字节注入
      • 转义
      • 添加后面的引号
      • 检查结果有效性返回
  • 动态调试

escapeshellarg描述

escapeshellarg把字符串转义为安全的shell参数

escapeshellarg(string $arg): string
  • Linux:对传入的字符串用一对单引号包围,将内容的'先用反斜杠转义,再添加一对单引号包围,即单引号会被转义为'\''
  • Windows:对传入的字符串用一对双引号包围,将内容的"%!以空格替换

歧义

PHP Manual中文版翻译为:给字符串增加一个单引号

在这里插入图片描述

静态调试

PHP_FUNCTION(escapeshellarg)

函数大概和之前分析过的它的兄弟escapeshellcmd类似

『PHP内核』PHP 7 escapeshellcmd底层探究 CSDN@Ho1aAs

在这里插入图片描述

第510行判断传入参数的有效性不同于escapeshellcmd:后者是以传入字符串的长度验证的,而这里是直接以字符串本身来判断,因此传入任何变量都能够进入这个if,即使是NULL;另外:escapeshellcmd在这里还有个else返回空字符串的操作

在这里插入图片描述

第二步检验是否传入了空字符,没有则进入功能实现函数,最后通过RETVAL返回转义后的字符串

PHPAPI zend_string *php_escape_shell_arg(char *str)

进入到PHPAPI类的函数实现

在这里插入图片描述

estimate是预估转义后的最大字符串长度,最大长度是Linux下传入单引号的情况:一个单引号被转义成'\'',然后整个字符串用一堆单引号包裹,再加上最后的结束符,因此是4l+3;然后就是判断传入字符串是否超过了最大单行命令的长度,条件见注释

	uint64_t estimate = (4 * (uint64_t)l) + 3;

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

给返回值cmd分配内存,大小为4l+2

	cmd = zend_string_safe_alloc(4, l, 2, 0); /* worst case */

添加前面的引号

然后就是第一步给返回值的第一个字符赋值为引号:Windows是双引号、Linux是单引号

#ifdef PHP_WIN32
	ZSTR_VAL(cmd)[y++] = '"';
#else
	ZSTR_VAL(cmd)[y++] = '\'';
#endif

跳过多字节字符

处理方式同escapeshellcmd

在这里插入图片描述

PHP历史漏洞:GBK宽字节注入

同理这一部分也是在PHP 5.2.6 之后加入以修复漏洞

转义

遍历字符转义,分平台

  • Windows将"%!直接以空格替换
  • Linux将'首先以反斜杠转义再套上一对单引号,实际操作是自左向右的,借用了default

default是无需转义,直接向cmd赋值

		switch (str[x]) {
#ifdef PHP_WIN32
		case '"':
		case '%':
		case '!':
			ZSTR_VAL(cmd)[y++] = ' ';
			break;
#else
		case '\'':
			ZSTR_VAL(cmd)[y++] = '\'';
			ZSTR_VAL(cmd)[y++] = '\\';
			ZSTR_VAL(cmd)[y++] = '\'';
#endif
			/* fall-through */
		default:
			ZSTR_VAL(cmd)[y++] = str[x];
		}
	}

添加后面的引号

接下来是添加后面的引号,这里对于Windows在这之前多处理了一步:如果字符串末尾是个反斜杠需要再用反斜杠转义,为了防止逃出双引号执行命令

#ifdef PHP_WIN32
	if (y > 0 && '\\' == ZSTR_VAL(cmd)[y - 1]) {
		int k = 0, n = y - 1;
		for (; n >= 0 && '\\' == ZSTR_VAL(cmd)[n]; n--, k++);
		if (k % 2) {
			ZSTR_VAL(cmd)[y++] = '\\';
		}
	}

	ZSTR_VAL(cmd)[y++] = '"';
#else
	ZSTR_VAL(cmd)[y++] = '\'';
#endif
	ZSTR_VAL(cmd)[y] = '\0';

然后就是添加结束符

检查结果有效性返回

判断转义结果的长度是否有效、判断分配空间是否溢出

	if (y > cmd_max_len + 1) {
		php_error_docref(NULL, E_ERROR, "Escaped argument 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的长度,返回转义结果

动态调试

测试代码:

// test.php
<?php
  	echo escapeshellarg('"\\');

断点:

在这里插入图片描述

先把前面的双引号添加了

在这里插入图片描述

然后遍历字符串:第一个字符是双引号,需要转义,直接被空格替换,存入cmd,然后break跳出当前switch,读入下一个字符

在这里插入图片描述

第二个字符是反斜杠,无需转义,存入cmd

在这里插入图片描述

转义结束,之后检测末尾是否是反斜杠,是就再将其转义一次

在这里插入图片描述

添加后面的引号,加上结束符截断

在这里插入图片描述

检查结果有效,完成操作,返回

在这里插入图片描述

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



这篇关于『PHP内核』PHP 7 escapeshellarg底层探究的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程