sprintf() 函数把格式化的字符串写入变量中。
arg1、arg2、++ 参数将被插入到主字符串中的百分号(%)符号处。该函数是逐步执行的。在第一个 % 符号处,插入 arg1,在第二个 % 符号处,插入 arg2,依此类推。
注释:如果 % 符号多于 arg 参数,则您必须使用占位符,如果不多于也可使用。占位符位于 % 符号之后,由数字和 “\$” 组成
语法:sprintf(format,arg1,arg2,arg++)
详情参看:
https://www.w3school.com.cn/php/func_string_sprintf.asp
这里我将我觉得更为重要的部分列出
这个Demo就使用了占位符,输出结果:the number is 123 可以理解为1\$会被置为空,所以就以%d的形式输出。
再看这个Demo,输出结果:the number is d 可以理解为%1$\会被置为空
可以看到, php源码中只对15种类型做了匹配, 其他字符类型都直接break了,php未做任何处理,直接跳过,所以导致了这个问题:
没做字符类型检测的最大危害就是它可以吃掉一个转义符\, 如果%后面出现一个\,那么php会把\当作一个格式化字符的类型而吃掉\, 最后%\(或%1$\)被替换为空.
因此sprintf注入,或者说php格式化字符串注入的原理为:
要明白%后的一个字符(除了%,%上面表格已经给出了)都会被当作字符型类型而被吃掉,也就是被当作一个类型进行匹配后面的变量,比如%c匹配asciii码,%d匹配整数,如果不在定义的也会匹配,匹配空,比如%\,这样我们的目的只有一个,使得单引号逃逸,也就是能够起到闭合的作用。
举两个例子:
不使用占位符
所以输出结果是:select * from user where username = '' and 1=1#';
使用占位符
所以输出结果是:SELECT * FROM t WHERE a='admin' AND b='' and 1=1#'
通过以上两个例子可以看到是通过吃掉'\'来使得单引号逃逸出来
以[DASCTF2020]babytricks为例首先是一个登录框,在hint中找到select * from user where user='$user' and passwd='%s'
其中原题部分关键代码如下,同时因为有print_r的存在,所以出现了非预期解
输出结果:select * from user where user='nd passwd='^0#' limit 0,1 可以看到没加\后没有构成占位符置空,反而向后吞噬三位吃掉了' a
异或规则:相同为0,不同为1,而'nd passwd’会被PHP当作0来与后面的数值进行异或,从而实现了类似万能密码的构造,打印输出了内容
sprintf的格式化字符串漏洞,首先通过传入%,看是否有sprintf函数的报错来进行判断。再根据格式化的位置利用占位符进行注入。本篇文章中要注意区分%1$\和%1\$的不同。