深入解析sprintf格式化字符串漏洞 |
您所在的位置:网站首页 › 格式化字符串溢出怎么解决 › 深入解析sprintf格式化字符串漏洞 |
深入解析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 |