PHP部分

1).vsprintfsprintf导致的单引号逃逸问题

两个函数都是用来格式化字符串的。

函数区别

区别在于vsprintf接收的参数是数组,sprintf接收的参数是字符串。

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$name = "Tom";
$sex = "boy";
$result = sprintf("%s is a %s.", $name, $sex); //传递参数为字符串
echo $result;

echo "\r\n";

$name = "Tom";
$sex = "boy";
$result = vsprintf("%s is a %s.", [$name, $sex]); //传递参数为数组
echo $result;

返回结果都是一样的:

1
2
Tom is a boy.
Tom is a boy.

基本用法

1
2
3
4
5
6
7
8
<?php
$name = "Tom";
$sex = "boy";
echo sprintf("|%5s| is a %s.\r\n", $name, $sex); //表示用空格填充到5个字符
echo sprintf("|%-5s| is a %s.\r\n", $name, $sex); //表示用空格填充到5个字符
echo sprintf("|%05s| is a %s.\r\n", $name, $sex); //表示用0填充到5个字符
echo sprintf("|%'#5s| is a %s.\r\n", $name, $sex); //表示用\#填充到5个字符
echo sprintf("|%1$'#5s| is a %2$'#5s.\r\n", $name, $sex);

注:最后面的s代表的是参数类型(s表示是字符串,还可以是其他类型,比如d,c等)

执行结果:

1
2
3
4
5
|  Tom| is a boy.
|Tom | is a boy.
|00Tom| is a boy.
|##Tom| is a boy.
|##Tom| is a ##boy.

前3个输出比较好理解,是基本的填充功能。

比较有意思的是后两个:

1
2
echo sprintf("|%'#5s| is a %s.\r\n", $name, $sex);  //表示用\#填充到5个字符
echo sprintf("|%1$'#5s| is a %2$'#5s.\r\n", $name, $sex);

%'#5s表示用#把字符串填充到5个字符。

PHP规定如果用空格和0以外的字符进行填充的话,必须用单引号在前面”转义”。

%1$'#5s表示用后面的第1个参数($name)进行替换,并且用#填充到5个字符。

%2$'#5s表示用后面的第2个参数($sex)进行替换,并且用#填充到5个字符。

以此类推。

单引号逃逸方式一

1
2
3
4
5
6
<?php
$name = addslashes($_GET['name']);
$sex = addslashes($_GET['sex']);
$sub_sql = " AND name = '{$name}'";
$sql = sprintf("SELECT * FROM ADMIN WHERE sex = '%s'".$sub_sql, $sex);
echo $sql;

我们提交参数name=Tom%1$c-- sdf&sex=39,$sub_sql就变成了:

$sub_sql = " AND name = 'Tom%1$c-- sdf'";

格式化之前的$sql就成了:

1
SELECT * FROM ADMIN WHERE sex = '39' AND name = 'Tom%1$c-- sdf'"

sprintf格式化之后, %c为格式化为字符格式,39是单引号的ASCII值,直接被格式化为单引号,触发逃逸。

单引号逃逸方式二

这里是另外一个技巧。
前面测试如果利用非空格或者0进行填充,需要在填充字符前添加单引号,比如:

1
2
3
4
5
<?php
$name = "Tom";
$sex = "boy";
echo sprintf("|%'#5s| is a %s.\r\n", $name, $sex); //表示用\#填充到5个字符
echo sprintf("|%1$'#5s| is a %2$'#5s.\r\n", $name, $sex);

如果不添加这个单引号,测试一下:

1
2
3
4
5
<?php
$name = "Tom";
$sex = "boy";
echo sprintf("|%#5s| is a %s.\r\n", $name, $sex); //表示用\#填充到5个字符
echo sprintf("|%1$#5s| is a %2$'#5s.\r\n", $name, $sex);

得到结果:

1
|5s| is a boy. |5s| is a ##boy.

对比发现,%#5s%1$#5s都消失了,5s被当做字符串输出。也就是说,%#5s%1$#5s都被“吃掉”了。

也就是说,如果不加单引号进行填充,任何填充字符都会被前面的%吃掉,包括\

如下漏洞代码:

1
2
3
4
5
6
<?php
$name = addslashes($_GET['name']);
$sex = addslashes($_GET['sex']);
$sub_sql = " AND name = '{$name}'";
$sql = sprintf("SELECT * FROM ADMIN WHERE sex = '%s'".$sub_sql, $sex);
echo $sql;

这段代码如果我们直接提交name=Tom'&sex=Boy,因为addslashes()的缘故,SQL会变成:

1
SELECT * FROM ADMIN WHERE sex = 'Boy' AND name = 'Tom\''

单引号无法逃逸。

但是我们提交name=Tom%'&sex=Boy,这样经过addslashes()之后Tom%\',然后%吃掉\,导致单引号逃逸成功。

因为参数个数的问题,如果上面这个会提示:

1
Warning: sprintf(): Too few arguments

没关系,我们可以用%1$这种方式指定所有占位符都读取第一个参数。

我们提交 name=Tom%1$'-- sdf&sex=Boy,触发逃逸,单引号成功闭合。

PYTHON部分

参考:

https://superxiaoxiong.github.io/2017/11/02/wordpress-%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%B3%A8%E5%85%A5/

https://paper.seebug.org/386/
https://github.com/80vul/pasc2at

https://www.leavesongs.com/PENETRATION/python-string-format-vulnerability.html