转载至:

https://www.anquanke.com/post/id/170850

PHP中的格式化字符串函数

在PHP中存在多个字符串格式化函数,分别是printf()sprintf()vsprintf()。他们的功能都大同小异。

  • printf, int printf ( string $format [, mixed $args [, mixed $... ]] ),直接将格式化的结果输出,返回值是int。
  • sprintf, string sprintf ( string $format [, mixed $args [, mixed $... ]] ),返回格式化字符串的结果
  • vsprintf, string vsprintf ( string $format , array $args ),与sprintf()相似,不同之处在于参数是以数组的方式传入。

三者的功能类似,以下仅以sprintf()来说明常规的格式化字符串的方法。

单个参数格式化的方法

var_dump(sprintf('1%s9','monkey'));         # 格式化字符串。结果是1monkey9
var_dump(sprintf('1%d9','456')); # 格式化数字。结果是14569
var_dump(sprintf("1%10s9",'moneky')); # 设置格式化字符串的长度为10,如果长度不足10,则以空格代替。结果是1 moneky9(length=12)
var_dump(sprintf("1%10s9",'many monkeys')); # 设置格式化字符串的长度为10,如果长度超过10,则保持不变。结果是1many monkeys9(length=14)
var_dump(sprintf("1%'^10s9",'monkey')); # 设置格式化字符串的长度为10,如果长度不足10,则以^代替。结果是1^^^^monkey9(length=12)
var_dump(sprintf("1%'^10s9",'monkey')); # 设置格式化字符串的长度为10,如果长度超过10,则保持不变。结果是1many monkeys9(length=14)

多个参数格式化的方法

$num = 5;
$location = 'tree';
echo sprintf('There are %d monkeys in the %s', $num, $location); # 位置对应,
echo sprintf('The %s contains %d monkeys', $location, $num); # 位置对应
echo sprintf('The %2$s contains %1$d monkeys', $num, $location); # 通过%2、%1来申明需要格式化的是第多少个参数,比如%2$s表示的是使用第二个格式化参数即$location进行格式化,同时该参数的类型是字符串类型(s表明了类型)

在格式化中申明的格式化参数类型有几个就说明是存在几个格式化参数,在上面的例子都是两个参数。如果是下方这种:

echo sprintf('The %s contains %d monkeys', 'tree');                     # 返回结果为False

则会出现Too few arguments,因为存在两个格式化参数%s%d但仅仅只是传入了一个变量tree导致格式化出错返回结果为False,无法进行格式化。

格式化字符串的特性

除了上面的一般用法之外,格式化中的一些怪异的用法常常被人忽略,则这些恰好是漏洞的来源。

字符串padding

常规的padding默认采用的是空格方式进行填充,如果需要使用其他的字符进行填充,则需要以%'[需要填充的字符]10s格式来表示,如%'#10s表示以#填充,%'$10s表示以$填充

var_dump(sprintf("1%10s9",'monkey'));           # 使用空格进行填充
var_dump(sprintf("1%'#10s9",'monkey')); # 使用#填充,结果是 1####monkey9
var_dump(sprintf("1%'$10s9",'monkey')); # 使用$填充,结果是 1$$$$monkey9

从上面的例子看到,在某些情况下单引号在格式化时会被吞掉,而这就有可能会埋下漏洞的隐患。

字符串按位置格式化

按位置格式化字符串的常规用法

$num = 5;
$location = 'tree';
var_dump(sprintf('The %2$s contains %1$d monkeys', $num, $location));

这种制定参数位置的格式化方法会使用到%2$s这种格式化的方式表示。其中%2表示格式化第二个参数,$s表示需要格式化的参数类型是字符串。如下:

var_dump(sprintf('%1$s-%s', 'monkey'));         # 结果是monkey-monkey

因为%1$s表示格式化第一个字符串,而后面的%s默认情况下同样格式化的是第一个字符串,所以最终的结果是monkey-monkey。如果是:

var_dump(sprintf('%2$s-%s', 'monkey1','monkey2'));      # 结果是monkey2-monkey1

因为%2$s格式化第二个字符串,%s格式化第一个字符串。

下面看一些比较奇怪的写法。首先我们需要知道在sprintf用法中已经说明了可以格式化的类型

如果遇到无法识别的格式化类型呢?如:

var_dump(sprintf('%1$as', 'monkey'));               # 结果是s

由于在格式化类型中不存在a类型,导致格式化失败。此时%1$a在格式化字符串时无用就直接舍弃,最后得到的就是s。但是如果我们写成:

var_dump(sprintf('%1$a%s', 'monkey'));             # 结果是monkey

因为%1$a%sa为无法识别的类型,则直接舍弃。剩下的%s可以继续进行格式化得到monkey

那么结论就是%1$[格式化类型],如果所声明的格式化类型不存在,则%1$[格式化类型]会被全部舍弃,留下剩下的字符。

如果在$接上数字呢?如%1$10s呢?

var_dump(sprintf('%1$10s', 'monkey'));             # 结果是'    monkey' (length=10)

此时表示的是格式化字符串的长度,默认使用的是空格进行填充。如果需要使用其他的字符串填充呢?此时格式是%1$'[需要填充的字符]10s

var_dump(sprintf("%1$'#10s", 'monkey'));           # 结果是 '####monkey' (length=10)

除此之外,还存在一些其他的奇怪的用法,如下:

var_dump(sprintf("%1$'%s", 'monkey'));            # 得到的结果就是 monkey
`

按照之前的说法,由于'是无法识别的类型,所以%1$'会被舍弃,剩余的%s进行格式化得到的就是monkey。可以发现在这种情况下'已经消失了。假设程序经过过滤得到的字符串是%1$'%s',那么就会导致中间的'被吞掉,如下:

var_dump(sprintf("%1$'%s'", 'monkey'));        # 得到的结果是 monkey'

吞掉引号

对上面进行一个简单的总结,除了一些不常见的字符串的格式化用法之外,还存在一些吞掉引号的用法。都是处在字符串padding的情况下。

var_dump(sprintf("1%'#10s9",'monkey'));         # 使用#填充,结果是 1####monkey9
var_dump(sprintf("%1$'#10s", 'monkey')); # 结果是 '####monkey' (length=10)

这两种'被吞掉的情况都有可能会引起漏洞。

漏洞示例

通过一段存在漏洞的代码来说明这种情况

$value1 = $_GET['value1'];
$value2 = $_GET['value2'];
$a = prepare("AND meta_value=%s",$value1);
$b = prepare("SELECT * FROM table WHERE key=%s $a",$value2);
function prepare($query,$args) {
$query = str_replace("'%s'",'%s',$query);
$query = str_replace('"%s"','$s',$query);
$query = preg_replace('|(?<!%)%f|','%F',$query);
$query = preg_replace('|(?<!%)%s|', "'%s'", $query);
return @vsprintf($query,$args);
}

$value1$value2是用户可控,函数prepare()会去掉格式化字符串%s的单引号和双引号,同时在最后加上单引号。虽然最后加上了一个',但是我们还是有办法能够逃脱这个单引号。利用方式就是通过之前申明字符串填充padding的方式吞掉单引号。

利用%1$’%s

之前已经说过sprintf("%1$'%s", 'monkey')就可以吞掉其中的'。那么在本例中,我们可以设置:

$value1 = '1 %1$%s (here sqli payload) --';
$value2 = '_dump';

此时,经过$a = prepare("AND meta_value=%s",$value1);,得到$aAND meta_value='1 %1$%s (here sqli payload) --'。之后执行$b = prepare("SELECT * FROM table WHERE key=%s $a",$value2);,其中$value2_dump。下面仔细分析:

经过$query = preg_replace('|(?<!%)%s|', "'%s'", $query)会将所有的%s全部变为'%s',所以此时得到的$querySELECT * FROM table WHERE key='%s' AND meta_value='1 %1$'%s' (here sqli payload) --'

此时其中刚好存在有1 %1$'%s这种形式的格式化字符串,导致其中的%1$'会被去除,剩下1 %s',此时就类似于SELECT * FROM table WHERE key='%s' AND meta_value='1 %s' (here sqli payload) --',格式化vsprintf("SELECT * FROM table WHERE key='%s' AND meta_value='1 %s' (here sqli payload) --'",_dump)刚好闭合了前面的单引号形成SQL注入。得到的结果如下:

方式二

上面利用的是%1$'%s,即在位置声明时出错导致吞掉单引号的方式,本方式是通过自身引入'与加入的单引号重合的方式。如:

$query = '1 %s 2';
$query = preg_replace('|(?<!%)%s|', "'%s'", $query); # 得到 1 '%s' 2'
$query = preg_replace('|(?<!%)%s|', "'%s'", $query); # 得到 1 ''%s'' 2

可以发现经过两次相同的过滤,最终导致%s逃逸出来。而在本题中的$value1同样是经过了两个的过滤。

所以,我们如果设置

$value1 = ' %s ';       # 注意%s 前后的空格
$value2 = array('_dump', '(here sqli payload) --');

经过$a = prepare("AND meta_value=%s",$value1);得到$aAND meta_value=' %s '。其中$valuearray('_dump', '(here sqli payload) --'),分析代码$b = prepare("SELECT * FROM table WHERE key=%s $a",$value2);

分析执行$query = preg_replace('|(?<!%)%s|', "'%s'", $query);之前和之后的代码:

执行之前,$query为“

执行之后,$query为SELECT * FROM table WHERE key='%s' AND meta_value=' '%s' '

可以发现所有的%s全部被左右全被加上了单引号,刚好与之前的单引号进行匹配,导致AND meta_value=' '%s' '中的%s逃逸出来。最后的几个就是SELECT * FROM table WHERE key='_dump' AND meta_value=' '(here sqli payload) --' '

其他

虽然本篇文章主要讨论的是PHP中的字符串漏洞,但是对于其他语言如(Java/Python)也在这里进行一个简单的讨论。(以下的例子借用的是xiaoxiong文章wordpress 格式化字符串注入中的例子)

Java格式化

StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb, Locale.US);
formatter.format("%s %s %1$s", "a", "monkey");
System.out.println(formatter);

最后输出的结果是a monkey a,因为前面两个%s是按照顺序取,得到的是amonkey,而后面的%1$s按照位置取,得到的是a,所以最后的结果是a monkey a

如果写为:

StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb, Locale.US);
formatter.format("%s %s '%2$c %1$s", "a", 39, "c", "d");
System.out.println(formatter);

最后得到的结果是a 39 '' a,前面两个%s按照顺序去得到a39,而%1$s取第一个参数,得到a%2$c取第二个参数,并且将其值作为数字得到其对应的ASCII字符,因为39对应的ASCII字符是',所以'%2$c得到的就是''

那么,我们能否借鉴PHP中的思路,吞掉'呢?

StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb, Locale.US);
formatter.format("%2$'s", "a", "monkey");
System.out.println(formatter);

程序会出现java.util.UnknownFormatConversionException,无法进行类型转换的错误,所以利用Java中进行格式化的转换,目前还需要进一步的研究。

Python

def view(request, *args, **kwargs):
template = 'Hello {user}, This is your email: ' + request.GET.get('email')
return HttpResponse(template.format(user=request.user)) poc:
http://localhost:8000/?email={user.groups.model._meta.app_config.module.admin.settings.SECRET_KEY}
http://localhost:8000/?email={user.user_permissions.model._meta.app_config.module.admin.settings.SECRET_KEY}

这个代码是基于Django的环境下的存在漏洞的代码。通过第一次格式化改变了语句结构,第二次格式化进行赋值。由于平时对Django接触得比较少,所以这个代码理解得还不是很透,需要进一步的实践才能够知道。

京亟:补充两篇关于python的字符串格式化漏洞的文章

https://www.anquanke.com/post/id/170620

https://www.leavesongs.com/PENETRATION/python-string-format-vulnerability.html

总结

看似一些正常功能的函数在某些特殊情况下恰好能够为埋下漏洞的隐患,而字符串格式化刚好就是一个这样的例子,也从侧面说明了安全需要猥琐呀。

参考

https://superxiaoxiong.github.io/2017/11/02/wordpress-%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%B3%A8%E5%85%A5/

PHP字符串格式化特点和漏洞利用点的更多相关文章

  1. 格式化字符串漏洞利用实战之 njctf-decoder

    前言 格式化字符串漏洞也是一种比较常见的漏洞利用技术.ctf 中也经常出现. 本文以 njctf 线下赛的一道题为例进行实战. 题目链接:https://gitee.com/hac425/blog_d ...

  2. Linux环境下常见漏洞利用技术(培训ppt+实例+exp)

    记得以前在drops写过一篇文章叫 linux常见漏洞利用技术实践 ,现在还可以找得到(https://woo.49.gs/static/drops/binary-6521.html), 不过当时开始 ...

  3. Nmap备忘单:从探索到漏洞利用(Part 5)

    这是备忘单的最后一部分,在这里主要讲述漏洞评估和渗透测试. 数据库审计 列出数据库名称 nmap -sV --script=mysql-databases 192.168.195.130 上图并没有显 ...

  4. CTF中做Linux下漏洞利用的一些心得

    其实不是很爱搞Linux,但是因为CTF必须要接触一些,漏洞利用方面也是因为CTF基本都是linux的pwn题目. 基本的题目分类,我认为就下面这三种,这也是常见的类型. 下面就分类来说说 0x0.栈 ...

  5. 安全学习概览——恶意软件分析、web渗透、漏洞利用和挖掘、内网渗透、IoT安全分析、区块链、黑灰产对抗

    1 基础知识1.1 网络熟悉常见网络协议:https://www.ietf.org/standards/rfcs/1.2 操作系统1.3 编程2 恶意软件分析2.1 分类2.1.1 木马2.1.2 B ...

  6. python(七)字符串格式化、生成器与迭代器

    字符串格式化 Python的字符串格式化有两种方式:百分号方式.format方式 1.百分号的方式 %[(name)][flags][width].[precision]typecode (name) ...

  7. ewebeditor编辑器ASP/ASPX/PHP/JSP版本漏洞利用总结及解决方法

    这个编辑器按脚本分主要有4个版本,ASP/ASPX/PHP/JSP 每个版本都有可以利用的漏洞.判断网站是否使用了eWebEditor查看程序源代码,看看源码中是否存在类似”ewebeditor.as ...

  8. Nmap备忘单:从探索到漏洞利用 Part1

    在侦查过程中,信息收集的初始阶段是扫描. 侦查是什么? 侦查是尽可能多的收集目标网络的信息.从黑客的角度来看,信息收集对攻击非常有帮助,一般来说可以收集到以下信息: 电子邮件.端口号.操作系统.运行的 ...

  9. Python_Day_5装饰器、字符串格式化、序列化、内置模块、生成器、迭代器之篇

    一.装饰器 为什么要用装饰器??? 在实际的开发环境中应遵循开发封闭原则,虽然在这个原则是用的面向对象开发,但也适用于函数式编程,简单地说,它规定已经实现的功能代码不是允许修改的,但是可以被扩展: 封 ...

随机推荐

  1. 有标号的DAG计数I~IV

    题解: https://www.cnblogs.com/zhoushuyu/p/10077241.html 看到这么一篇,发现挺不错的..

  2. Kafka生产者发送消息的三种方式

    Kafka是一种分布式的基于发布/订阅的消息系统,它的高吞吐量.灵活的offset是其它消息系统所没有的. Kafka发送消息主要有三种方式: 1.发送并忘记 2.同步发送 3.异步发送+回调函数 下 ...

  3. DDoS攻击与防御(3)

    3.攻击应用资源网络应用和服务在处理数据时,通常需要消耗一定的网络连接.计算和存储资源,这些资源是由应用程序向系统进行申请并自行管理和维护的.消耗应用资源的DDoS攻击就是通过向应用提交大量消耗资源的 ...

  4. 在 Mac OS X 下,如何向 sudoers 文件添加新用户

    注: 1.使用待添加的用户名替换"username". 2.以下涉及输入的标点符号均为半角字符,即英文输入法下的标点符号. 以管理员账号密码登录 Mac OS X,打开 Termi ...

  5. python3 request模块初使用

    import requests class Interface_Request: def __init__(self,url,mobilephone,pwd): '''login参数初始化''' se ...

  6. XII Open Cup named after E.V. Pankratiev. GP of Eastern Europe (AMPPZ-2012)

    A. Automat $m$超过$1600$是没用的. 从后往前考虑,设$f[i][j][k]$表示考虑$[i,n]$这些物品,一共花费$j$元钱,买了$k$个物品的最大收益. 时间复杂度$O(n^5 ...

  7. 层居中绝对定位的div的居中方法,下面的写法兼容IE系列浏览器和火狐浏览器

    详细解说,直接看样式:#dingwei{padding:10px;background-color:#003300;color:#FFFFFF; width:600px;height:300px; d ...

  8. css 网格布局简单应用

    将属性 display 值设为 grid 或 inline-grid 就创建了一个网格容器,所有容器直接子结点自动成为网格项目. grid :网格项目按行排列,网格项目占用整个容器的宽度. inlin ...

  9. 根据两点经纬度计算距离和角度——java实现

    原理:见上一篇博客   http://blog.csdn.net/xiaobai091220106/article/details/50879365 百度地图拾取经纬度坐标:http://api.ma ...

  10. ie下js不执行的几种可能

    1.首先考虑的就是代码是否有报错2.其次搜索代码中是否有console.log等测试的代码3.检查所用的方法是否兼容ie 以上是鄙人在开发过程中遇到的几种情况,抛砖来引玉~~