PHP代码审计-sprintf函数中的安全问题

新媒体管家

看到一篇WorldPress注入漏洞分析,其中sprintf单引号逃逸的思路很巧妙,在此对这类函数做一些简单的测试和总结。

sprintf & vsprintf

sprintf是以一种规定的格式对不同的数据进行拼接,并将拼接结果返回,它并不像C语言里的printf一样直接输出,而是需要另外的输出函数,如echo将返回的结果输出出来。sprintf的用法可以在w3school的介绍中查看。至于vsprintf除了传参的时候使用了数组,其余的与sprintf一样。

自动类型转换

首先要注意的就是,sprintf的自动类型转换功能。当按照某一格式输出时,遇到第一个非本格式的字符就会自动截断后面的字符。测试代码:

<?php
  $str = '788 1and 1=1';
  echo sprintf('output is %d',$str);
  ?>

输出结果为:

可以看到,当检测到第一个不属于%d类型的空格时,就会自动地去进行截断。所以从程序员的角度来讲,很容易忘记对%d输入的数据进行强制的类型转换,因为即使不手动转换,程序也能正常运行。所以这一承载着危险payload的变量就很可能被保留了下来,进入了下一步操作。就像WP的SQLi漏洞一样。

吞噬单引号

sprintf的第一个参数format的语法为(PS:使用了[]对每个元素进行分隔) 必须,百分号%可选,美元符号$和单引号'可选,长度百分号为识别符,被认为是特定匹配模式的开始;后面的数字是从模式参数后面的第n个参数输入数据;美元符号和后面的单引号是开启padding模式(字符填充)的标识,紧跟在$'后面的是用来填充的字符;长度则为规定的输入数据长度,如果数据足够的话无效,如果数据不够的话就使用$'后面的填充字符进行填充;最后的为数据类型 s表示字符串,d表示整数测试代码:

<?php
$str = '788 1and 1=1';
echo sprintf('output is %d hello',$str).'<br>';
echo sprintf('output is %s hello',$str).'<br>';
echo sprintf('output is %20s hello',$str).'<br>';
echo sprintf('output is %1\'#20s\' hello',$str).'<br>';
echo sprintf('output is %1$\'#20s\' hello',$str).'<br>';
echo sprintf('output is %1$\' aand 1=1',$str).'<br>';
?>

其中\'的作用与'是一样的,这里因为是单引号包裹的字符串,所以需要对字符串中的单引号进行转义

不加$的话只会吞掉单引号,但却无法正常带入参数$'两个都存在的话,被理解为开启padding模式(补齐模式),所以这个单引号就被吞掉了,导致了单引号的逃逸。最后一种模式会吞掉单引号后面的两个字符,同样导致单引号溢出

未知类型吞噬斜杠

%d为整数,%s为字符串,但%y是没有规定的格式。但如果在sprintf中使用%y的话,并不会报错而是输出空,所以可以利用这个特性吞掉反转义符

<?php
$str = '788 1and 1=1';
echo sprintf('output is %y hello',$str).'<br>';