深入解析sprintf格式化字符串漏洞

您所在的位置:网站首页 格式化字符串溢出怎么解决 深入解析sprintf格式化字符串漏洞

深入解析sprintf格式化字符串漏洞

2024-07-17 14:53| 来源: 网络整理| 查看: 265

深入解析sprintf格式化字符串漏洞

0x00 前言

从相遇到相识

从相识到相知

.........

不过你真的懂ta吗

这次故事的主角是PHP中的格式化函数sprintf

0x01 sprintf()讲解

首先我们先了解sprintf()函数

sprintf() 函数把格式化的字符串写入变量中。

sprintf(format,arg1,arg2,arg++)

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

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

详细看一下sprintf的用法

语法

sprintf(format,arg1,arg2,arg++)

参数

描述

format

必需。规定字符串以及如何格式化其中的变量。

可能的格式值:

%% - 返回一个百分号 %

%b - 二进制数

%c - ASCII 值对应的字符

%d - 包含正负号的十进制数(负数、0、正数)

%e - 使用小写的科学计数法(例如 1.2e+2)

%E - 使用大写的科学计数法(例如 1.2E+2)

%u - 不包含正负号的十进制数(大于等于 0)

%f - 浮点数(本地设置)

%F - 浮点数(非本地设置)

%g - 较短的 %e 和 %f

%G - 较短的 %E 和 %f

%o - 八进制数

%s - 字符串

%x - 十六进制数(小写字母)

%X - 十六进制数(大写字母)

附加的格式值。必需放置在 % 和字母之间(例如 %.2f):

+ (在数字前面加上 + 或 - 来定义数字的正负性。默认情况下,只有负数才做标记,正数不做标记)

' (规定使用什么作为填充,默认是空格。它必须与宽度指定器一起使用。例如:%'x20s(使用 "x" 作为填充))

- (左调整变量值)

[0-9] (规定变量值的最小宽度)

.[0-9] (规定小数位数或最大字符串长度)

注释:如果使用多个上述的格式值,它们必须按照以上顺序使用。

arg1

必需。规定插到 format 字符串中第一个 % 符号处的参数。

arg2

可选。规定插到 format 字符串中第二个 % 符号处的参数。

arg++

可选。规定插到 format 字符串中第三、四等 % 符号处的参数。

返回值:

返回已格式化的字符串。

PHP 版本:

4+

通过几个例子回顾一下sprintf

例子1:

输出结果:

带有两位小数:123.00 

不带小数:123

例子2:

输出结果:

%b = 111010110111100110100010101

%c = 2 //注意var_dump('2')为string

%s = 123456789

%x = 75bcd15

%X = 75BCD15

0x02 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

不使用占位符号



【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3