看到一篇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>'; $sql1 = "select * from user where username = '%1$\' and 1=1#' and password='%s';"; $sql2 = "select * from user where username = '\' and 1=1#' and password='%s';"; $args = "admin"; echo sprintf( $sql1, $args).'<br>' ; echo sprintf( $sql2, $args) ; ?>
结果:
总结
除此之外还有宽字节吞掉单引号,substr吞掉单引号,在cms不断成熟,db类使用逐渐规范的今天,了解一些吞噬单引号的技巧对于审计工作来说非常重要。