escapeshellarg把字符串转义为安全的shell参数
escapeshellarg(string $arg): string
'
先用反斜杠转义,再添加一对单引号包围,即单引号会被转义为'\''
"%!
以空格替换PHP Manual中文版翻译为:给字符串增加一个单引号
函数大概和之前分析过的它的兄弟escapeshellcmd类似
『PHP内核』PHP 7 escapeshellcmd底层探究 CSDN@Ho1aAs
第510行判断传入参数的有效性不同于escapeshellcmd:后者是以传入字符串的长度验证的,而这里是直接以字符串本身来判断,因此传入任何变量都能够进入这个if,即使是NULL;另外:escapeshellcmd在这里还有个else返回空字符串的操作
第二步检验是否传入了空字符,没有则进入功能实现函数,最后通过RETVAL
返回转义后的字符串
进入到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 5.2.6 之后加入以修复漏洞
遍历字符转义,分平台
"%!
直接以空格替换'
首先以反斜杠转义再套上一对单引号,实际操作是自左向右的,借用了defaultdefault是无需转义,直接向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