User-Profile-Image
hankin
  • 5
请到[后台->外观->菜单]中设置菜单。
  • 分类
    • 靶机渗透
    • 计算机小技巧
    • 未分类
    • 数据结构
    • 内网渗透
    • 代码审计
    • XSS
    • WEB安全漏洞学习
    • Web
    • python
    • PHP
    • NodeJS
    • MYSQL
    • Misc
    • JavaScript
    • Docker
    • CTF相关知识点
    • CTFWP
    • Crypto
    • Cobalt Strike
  • 页面
  • 友链
    • 三哥的博客
    • Root师傅的博客
    • EDS师傅的博客
    • 天正哥的博客
    • 天尘翼师傅的博客
    • 熵增师傅的github
    • 信仰的博客
    • Jadore的博客
Help?

Please contact us on our email for need any support

Support
    首页   ›   CTF相关知识点   ›   正文
CTF相关知识点

sprintf格式化字符串

2020-05-08 18:28:32
76  0 0

Contents

  • 1 0x01 定义和用法:
  • 2 0x02 sprintf注入原理
  • 3 0x03 CTF应用
  • 4 0x04 总结

0x01 定义和用法:

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

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

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

语法:sprintf(format,arg1,arg2,arg++)

详情参看:https://www.w3school.com.cn/php/func_string_sprintf.asp

这里我将我觉得更为重要的部分列出

<?php
    $num=123;
    echo sprintf("the number is %1\$d",%num);
?>

​这个Demo就使用了占位符,输出结果: the number is 123 可以理解为1\$会被置为空,所以就以%d的形式输出。

<?php
    $num=123;
    echo sprintf("the number is %1$\d",%num);
?>

再看这个Demo,输出结果:the number is d 可以理解为%1$\会被置为空

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匹配整数,如果不在定义的也会匹配,匹配空,比如%\,这样我们的目的只有一个,使得单引号逃逸,也就是能够起到闭合的作用。

举两个例子:

不使用占位符

<?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#’;

使用占位符

<?php
$input = addslashes ("%1$' and 1=1#" );//%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#’

通过以上两个例子可以看到是通过吃掉’\’来使得单引号逃逸出来

0x03 CTF应用

以[DASCTF2020]babytricks为例

首先是一个登录框,在hint中找到select * from user where user='$user' and passwd='%s'

其中原题部分关键代码如下,同时因为有print_r的存在,所以出现了非预期解

<?php
$user = $_GET['user'];
$passwd = $_GET['passwd'];
$sql = "select * from user where user='$user' and passwd='%s' limit 0,1";
$sql = sprintf( $sql , $passwd);
echo $sql;
$query = mysqli_query($conn , $sql);
$row = mysqli_fetch_array($query);
print_r($row);
传入user=%1$&passwd=^0# 

输出结果: select * from user where user=’nd passwd=’^0#’ limit 0,1 可以看到没加\后没有构成占位符置空,反而向后吞噬三位吃掉了’ a

异或规则:相同为0,不同为1,而’nd passwd’会被PHP当作0来与后面的数值进行异或,从而实现了类似万能密码的构造,打印输出了内容

0x04 总结

sprintf的格式化字符串漏洞,首先通过传入%,看是否有sprintf函数的报错来进行判断。再根据格式化的位置利用占位符进行注入。本篇文章中要注意区分%1$\和%1\$的不同。

文章参考https://blog.csdn.net/weixin_41185953/article/details/80485075

评论 (0)

点击这里取消回复。

欢迎您 游客  

近期文章
  • SUCTF 2019 Guess_game
  • Python pickle反序列化
  • [安洵杯 2019]easy_serialize_php
  • Session反序列化
  • 原生类序列化
近期评论
    文章归档
    • 2021年1月
    • 2020年12月
    • 2020年11月
    • 2020年9月
    • 2020年7月
    • 2020年6月
    • 2020年5月
    • 2020年4月
    • 2020年3月
    • 2020年2月
    • 2020年1月
    分类目录
    • Cobalt Strike
    • Crypto
    • CTFWP
    • CTF相关知识点
    • Docker
    • JavaScript
    • Misc
    • MYSQL
    • NodeJS
    • PHP
    • python
    • Web
    • WEB安全漏洞学习
    • XSS
    • 代码审计
    • 内网渗透
    • 数据结构
    • 未分类
    • 计算机小技巧
    • 靶机渗透
    功能
    • 登录
    • 项目feed
    • 评论feed
    • WordPress.org
    分类目录
    Copyright © 2021 网站备案号: 蒙ICP备20000552号-1
    smarty_hankin 主题. Designed by hankin
    主页
    页面
    博主
    purplet 管理员
    努力并有所方向
    157 文章 0 评论 20371 浏览
    测试
    测试