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;
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);
添加前面的引号
然后就是第一步给返回值的第一个字符赋值为引号: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
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) {
cmd = zend_string_truncate(cmd, y, 0);
}
ZSTR_LEN(cmd) = y;
return cmd;
}
登记cmd的长度,返回转义结果
动态调试
测试代码:
<?php
echo escapeshellarg('"\\');
断点:
先把前面的双引号添加了
然后遍历字符串:第一个字符是双引号,需要转义,直接被空格替换,存入cmd,然后break跳出当前switch,读入下一个字符
第二个字符是反斜杠,无需转义,存入cmd
转义结束,之后检测末尾是否是反斜杠,是就再将其转义一次
添加后面的引号,加上结束符截断
检查结果有效,完成操作,返回
完
欢迎在评论区留言,欢迎关注我的CSDN @Ho1aAs
|