├── .gitignore ├── .idea ├── $PROJECT_FILE$ ├── .gitignore ├── dictionaries ├── misc.xml ├── modules.xml └── qaplug_profiles.xml ├── README.md ├── doc ├── listener.png ├── othershell.png └── tomcat-memshell-scanner.png └── tomcat-memshell-scanner.jsp /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Java template 3 | # Compiled class file 4 | *.class 5 | 6 | # Log file 7 | *.log 8 | 9 | # BlueJ files 10 | *.ctxt 11 | 12 | # Mobile Tools for Java (J2ME) 13 | .mtj.tmp/ 14 | 15 | # Package Files # 16 | *.jar 17 | *.war 18 | *.nar 19 | *.ear 20 | *.zip 21 | *.tar.gz 22 | *.rar 23 | 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | *.iml 27 | .DS_Store 28 | .idea 29 | 30 | -------------------------------------------------------------------------------- /.idea/$PROJECT_FILE$: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/dictionaries: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/qaplug_profiles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 465 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 0x01 简介 2 | 通过jsp脚本扫描并查杀各类中间件内存马,比Java agent要温和一些。 3 | 4 | ## 0x02 截图 5 | ![Tomcat内存马扫描结果展示](doc/tomcat-memshell-scanner.png) 6 | 7 | 增加Listener型内存马检测,如果不存在Listener则不显示该项 8 | 9 | ![Tomcat内存马扫描结果展示](doc/listener.png) 10 | 11 | 增加Tomcat-Value、Timer、Websocket 、Upgrade 、Executor等内存马检测。 12 | 13 | ![Tomcat内存马扫描结果展示](doc/othershell.png) 14 | 15 | ## 0x03 更多 16 | [Filter/Servlet型内存马的扫描抓捕与查杀](https://gv7.me/articles/2020/filter-servlet-type-memshell-scan-capture-and-kill/) 17 | -------------------------------------------------------------------------------- /doc/listener.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruyueattention/java-memshell-scanner/1d5240006b864b76d85444f572cf0155caf78e52/doc/listener.png -------------------------------------------------------------------------------- /doc/othershell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruyueattention/java-memshell-scanner/1d5240006b864b76d85444f572cf0155caf78e52/doc/othershell.png -------------------------------------------------------------------------------- /doc/tomcat-memshell-scanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruyueattention/java-memshell-scanner/1d5240006b864b76d85444f572cf0155caf78e52/doc/tomcat-memshell-scanner.png -------------------------------------------------------------------------------- /tomcat-memshell-scanner.jsp: -------------------------------------------------------------------------------- 1 | <%@ page import="java.net.URL" %> 2 | <%@ page import="java.lang.reflect.Field" %> 3 | <%@ page import="com.sun.org.apache.bcel.internal.Repository" %> 4 | <%@ page import="java.net.URLEncoder" %> 5 | <%@ page import="org.apache.catalina.core.StandardWrapper" %> 6 | <%@ page import="java.lang.reflect.Method" %> 7 | <%@ page import="java.util.concurrent.CopyOnWriteArrayList" %> 8 | <%@ page import="org.apache.catalina.Pipeline" %> 9 | <%@ page import="org.apache.catalina.Valve" %> 10 | <%@ page import="org.apache.catalina.core.StandardContext" %> 11 | <%@ page import="org.apache.catalina.connector.Request" %> 12 | <%@ page import="java.util.*" %> 13 | <%@ page import="org.apache.tomcat.websocket.server.WsServerContainer" %> 14 | <%@ page import="javax.websocket.server.ServerEndpointConfig" %> 15 | <%@ page import="javax.websocket.server.ServerContainer" %> 16 | <%@ page import="org.apache.coyote.UpgradeProtocol" %> 17 | <%@ page import="org.apache.coyote.http11.AbstractHttp11Protocol" %> 18 | <%@ page import="org.apache.catalina.connector.Connector" %> 19 | <%@ page import="org.apache.tomcat.util.net.NioEndpoint" %> 20 | <%@ page import="org.apache.tomcat.util.threads.ThreadPoolExecutor" %> 21 | <%@ page import="java.util.concurrent.TimeUnit" %> 22 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> 23 | 24 | 25 | tomcat-memshell-killer 26 | 27 | 28 |
29 |
30 | <%! 31 | public Object getStandardContext(HttpServletRequest request) throws NoSuchFieldException, IllegalAccessException { 32 | Object context = request.getSession().getServletContext(); 33 | Field _context = context.getClass().getDeclaredField("context"); 34 | _context.setAccessible(true); 35 | Object appContext = _context.get(context); 36 | Field __context = appContext.getClass().getDeclaredField("context"); 37 | __context.setAccessible(true); 38 | Object standardContext = __context.get(appContext); 39 | return standardContext; 40 | } 41 | private static Object getField(Object object, String fieldName) throws Exception { 42 | Field field = null; 43 | Class clazz = object.getClass(); 44 | 45 | while (clazz != Object.class) { 46 | try { 47 | field = clazz.getDeclaredField(fieldName); 48 | break; 49 | } catch (NoSuchFieldException var5) { 50 | clazz = clazz.getSuperclass(); 51 | } 52 | } 53 | 54 | if (field == null) { 55 | throw new NoSuchFieldException(fieldName); 56 | } else { 57 | field.setAccessible(true); 58 | return field.get(object); 59 | } 60 | } 61 | public HashMap getFilterConfig(HttpServletRequest request) throws Exception { 62 | Object standardContext = getStandardContext(request); 63 | Field _filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs"); 64 | _filterConfigs.setAccessible(true); 65 | HashMap filterConfigs = (HashMap) _filterConfigs.get(standardContext); 66 | return filterConfigs; 67 | } 68 | 69 | // FilterMap[] 70 | public Object[] getFilterMaps(HttpServletRequest request) throws Exception { 71 | Object standardContext = getStandardContext(request); 72 | Field _filterMaps = standardContext.getClass().getDeclaredField("filterMaps"); 73 | _filterMaps.setAccessible(true); 74 | Object filterMaps = _filterMaps.get(standardContext); 75 | 76 | Object[] filterArray = null; 77 | try { // tomcat 789 78 | Field _array = filterMaps.getClass().getDeclaredField("array"); 79 | _array.setAccessible(true); 80 | filterArray = (Object[]) _array.get(filterMaps); 81 | } catch (Exception e) { // tomcat 6 82 | filterArray = (Object[]) filterMaps; 83 | } 84 | 85 | return filterArray; 86 | } 87 | 88 | /** 89 | * 遗留问题,getFilterConfig()依然存在2个 90 | * @param request 91 | * @param filterName 92 | * @throws Exception 93 | */ 94 | public synchronized void deleteFilter(HttpServletRequest request, String filterName) throws Exception { 95 | Object standardContext = getStandardContext(request); 96 | // org.apache.catalina.core.StandardContext#removeFilterDef 97 | HashMap filterConfig = getFilterConfig(request); 98 | Object appFilterConfig = filterConfig.get(filterName); 99 | Field _filterDef = appFilterConfig.getClass().getDeclaredField("filterDef"); 100 | _filterDef.setAccessible(true); 101 | Object filterDef = _filterDef.get(appFilterConfig); 102 | Class clsFilterDef = null; 103 | try { 104 | // Tomcat 8 105 | clsFilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef"); 106 | } catch (Exception e) { 107 | // Tomcat 7 108 | clsFilterDef = Class.forName("org.apache.catalina.deploy.FilterDef"); 109 | } 110 | Method removeFilterDef = standardContext.getClass().getDeclaredMethod("removeFilterDef", new Class[]{clsFilterDef}); 111 | removeFilterDef.setAccessible(true); 112 | removeFilterDef.invoke(standardContext, filterDef); 113 | 114 | // org.apache.catalina.core.StandardContext#removeFilterMap 115 | Class clsFilterMap = null; 116 | try { 117 | // Tomcat 8 118 | clsFilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap"); 119 | } catch (Exception e) { 120 | // Tomcat 7 121 | clsFilterMap = Class.forName("org.apache.catalina.deploy.FilterMap"); 122 | } 123 | Object[] filterMaps = getFilterMaps(request); 124 | for (Object filterMap : filterMaps) { 125 | Field _filterName = filterMap.getClass().getDeclaredField("filterName"); 126 | _filterName.setAccessible(true); 127 | String filterName0 = (String) _filterName.get(filterMap); 128 | if (filterName0.equals(filterName)) { 129 | Method removeFilterMap = standardContext.getClass().getDeclaredMethod("removeFilterMap", new Class[]{clsFilterMap}); 130 | removeFilterDef.setAccessible(true); 131 | removeFilterMap.invoke(standardContext, filterMap); 132 | } 133 | } 134 | } 135 | 136 | public synchronized void deleteServlet(HttpServletRequest request, String servletName) throws Exception { 137 | HashMap childs = getChildren(request); 138 | Object objChild = childs.get(servletName); 139 | String urlPattern = null; 140 | HashMap servletMaps = getServletMaps(request); 141 | for (Map.Entry servletMap : servletMaps.entrySet()) { 142 | if (servletMap.getValue().equals(servletName)) { 143 | urlPattern = servletMap.getKey(); 144 | break; 145 | } 146 | } 147 | 148 | if (urlPattern != null) { 149 | // 反射调用 org.apache.catalina.core.StandardContext#removeServletMapping 150 | Object standardContext = getStandardContext(request); 151 | Method removeServletMapping = standardContext.getClass().getDeclaredMethod("removeServletMapping", new Class[]{String.class}); 152 | removeServletMapping.setAccessible(true); 153 | removeServletMapping.invoke(standardContext, urlPattern); 154 | // Tomcat 6必须removeChild 789可以不用 155 | // 反射调用 org.apache.catalina.core.StandardContext#removeChild 156 | Method removeChild = standardContext.getClass().getDeclaredMethod("removeChild", new Class[]{org.apache.catalina.Container.class}); 157 | removeChild.setAccessible(true); 158 | removeChild.invoke(standardContext, objChild); 159 | } 160 | } 161 | 162 | public synchronized HashMap getChildren(HttpServletRequest request) throws Exception { 163 | Object standardContext = getStandardContext(request); 164 | Field _children = standardContext.getClass().getSuperclass().getDeclaredField("children"); 165 | _children.setAccessible(true); 166 | HashMap children = (HashMap) _children.get(standardContext); 167 | return children; 168 | } 169 | 170 | 171 | public synchronized HashMap getServletMaps(HttpServletRequest request) throws Exception { 172 | Object standardContext = getStandardContext(request); 173 | Field _servletMappings = standardContext.getClass().getDeclaredField("servletMappings"); 174 | _servletMappings.setAccessible(true); 175 | HashMap servletMappings = (HashMap) _servletMappings.get(standardContext); 176 | return servletMappings; 177 | } 178 | 179 | public synchronized List getListenerList(HttpServletRequest request) throws Exception { 180 | Object standardContext = getStandardContext(request); 181 | Field _listenersList = standardContext.getClass().getDeclaredField("applicationEventListenersList"); 182 | _listenersList.setAccessible(true); 183 | List listenerList = (CopyOnWriteArrayList) _listenersList.get(standardContext); 184 | return listenerList; 185 | } 186 | 187 | public String getFilterName(Object filterMap) throws Exception { 188 | Method getFilterName = filterMap.getClass().getDeclaredMethod("getFilterName"); 189 | getFilterName.setAccessible(true); 190 | return (String) getFilterName.invoke(filterMap, null); 191 | } 192 | 193 | public String[] getURLPatterns(Object filterMap) throws Exception { 194 | Method getFilterName = filterMap.getClass().getDeclaredMethod("getURLPatterns"); 195 | getFilterName.setAccessible(true); 196 | return (String[]) getFilterName.invoke(filterMap, null); 197 | } 198 | 199 | 200 | String classFileIsExists(Class clazz) { 201 | if (clazz == null) { 202 | return "class is null"; 203 | } 204 | 205 | String className = clazz.getName(); 206 | String classNamePath = className.replace(".", "/") + ".class"; 207 | URL is = clazz.getClassLoader().getResource(classNamePath); 208 | if (is == null) { 209 | return "在磁盘上没有对应class文件,可能是内存马"; 210 | } else { 211 | return is.getPath(); 212 | } 213 | } 214 | 215 | String arrayToString(String[] str) { 216 | String res = "["; 217 | for (String s : str) { 218 | res += String.format("%s,", s); 219 | } 220 | res = res.substring(0, res.length() - 1); 221 | res += "]"; 222 | return res; 223 | } 224 | public Object getNioEndpoint() throws Exception { 225 | Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads"); 226 | Field f = ThreadGroup.class.getDeclaredField("threads"); 227 | f.setAccessible(true); 228 | for (Thread thread: threads) { 229 | if (thread.getName().contains("http") && thread.getName().contains("Poller")) { 230 | f = Thread.class.getDeclaredField("target"); 231 | f.setAccessible(true); 232 | Object pollor = f.get(thread); 233 | f = pollor.getClass().getDeclaredField("this$0"); 234 | f.setAccessible(true); 235 | Object nioEndpoint = (NioEndpoint)f.get(pollor); 236 | return nioEndpoint; 237 | } 238 | } 239 | return new Object(); 240 | } 241 | %> 242 | 243 | <% 244 | out.write("

Tomcat memshell scanner

"); 245 | String action = request.getParameter("action"); 246 | String filterName = request.getParameter("filterName"); 247 | String servletName = request.getParameter("servletName"); 248 | String className = request.getParameter("className"); 249 | String tomcatValue = request.getParameter("tomcatValue"); 250 | String threadName = request.getParameter("threadName"); 251 | String webSocket = request.getParameter("webSocket"); 252 | String upgrade = request.getParameter("upgrade"); 253 | String executors = request.getParameter("executor"); 254 | 255 | //获取ServletContext对象(得到的其实是ApplicationContextFacade对象) 256 | ServletContext servletContext = request.getServletContext(); 257 | StandardContext standardContext = null; 258 | //从 request 的 ServletContext 对象中循环判断获取 Tomcat StandardContext对象 259 | while (standardContext == null) { 260 | //因为是StandardContext对象是私有属性,所以需要用反射去获取 261 | Field f = servletContext.getClass().getDeclaredField("context"); 262 | f.setAccessible(true); 263 | Object object = f.get(servletContext); 264 | 265 | if (object instanceof ServletContext) { 266 | servletContext = (ServletContext) object; 267 | } else if (object instanceof StandardContext) { 268 | standardContext = (StandardContext) object; 269 | } 270 | } 271 | Pipeline pipeline = standardContext.getPipeline(); 272 | Valve[] valves = pipeline.getValves(); 273 | 274 | if (action != null && action.equals("kill") && filterName != null) { 275 | deleteFilter(request, filterName); 276 | out.write(""); 277 | 278 | } else if (action != null && action.equals("kill") && servletName != null) { 279 | deleteServlet(request, servletName); 280 | out.write(""); 281 | 282 | }else if (action != null && action.equals("kill") && tomcatValue != null){ 283 | int id = Integer.valueOf(tomcatValue).intValue(); 284 | if(id!=0 & id!=valves.length-1 ){ 285 | pipeline.removeValve(valves[id]); 286 | out.write(""); 287 | 288 | } 289 | }else if(action!=null && action.equals("kill") && threadName!= null){ 290 | Thread[] threads = (Thread[]) ((Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads")); 291 | for (Thread thread : threads) { 292 | if(threadName.equals(thread.getName())){ 293 | Class clzTimerThread = thread.getClass(); 294 | Field queueField = clzTimerThread.getDeclaredField("queue"); 295 | queueField.setAccessible(true); 296 | //Timer里面的TaskQueue()对象 297 | Object queue = queueField.get(thread); 298 | Class clzTaskQueue = queue.getClass(); 299 | Method getTimeTask = clzTaskQueue.getDeclaredMethod("get",int.class); 300 | getTimeTask.setAccessible(true); 301 | //从TaskQueue对象中获取TimerTask,然后取消这个TimerTask以清除任务。 302 | TimerTask timerTask = (TimerTask) getTimeTask.invoke(queue,1); 303 | if(timerTask!=null){ 304 | timerTask.cancel(); 305 | out.write(""); 306 | break; 307 | 308 | } 309 | } 310 | } 311 | } else if(action!=null && action.equals("kill") && webSocket!= null){ 312 | WsServerContainer wsServerContainer = (WsServerContainer) servletContext.getAttribute(ServerContainer.class.getName()); 313 | 314 | // 利用反射获取 WsServerContainer 类中的私有变量 configExactMatchMap 315 | Class obj = Class.forName("org.apache.tomcat.websocket.server.WsServerContainer"); 316 | Field field = obj.getDeclaredField("configExactMatchMap"); 317 | field.setAccessible(true); 318 | Map configExactMatchMap = (Map) field.get(wsServerContainer); 319 | 320 | // 遍历configExactMatchMap, 打印所有注册的 websocket 服务 321 | Set keyset = configExactMatchMap.keySet(); 322 | 323 | configExactMatchMap.remove(webSocket); 324 | out.write(""); 325 | 326 | 327 | 328 | }else if(action!=null && action.equals("kill") && upgrade!= null){ 329 | Request request1 =(Request) getField(request, "request");; 330 | Connector realConnector = (Connector) getField(request1, "connector"); 331 | AbstractHttp11Protocol handler = (AbstractHttp11Protocol) getField(realConnector, "protocolHandler"); 332 | HashMap upgradeProtocols = (HashMap) getField(handler,"httpUpgradeProtocols"); 333 | upgradeProtocols.remove(upgrade); 334 | out.write(""); 335 | 336 | 337 | 338 | }else if(action!=null && action.equals("kill") && executors!= null){ 339 | NioEndpoint nioEndpoint = null; 340 | try { 341 | nioEndpoint = (NioEndpoint) getNioEndpoint(); 342 | } catch (Exception e) { 343 | e.printStackTrace(); 344 | } 345 | ThreadPoolExecutor executor = (ThreadPoolExecutor)nioEndpoint.getExecutor(); 346 | 347 | nioEndpoint.setExecutor(new org.apache.tomcat.util.threads.ThreadPoolExecutor(executor.getCorePoolSize(), executor.getMaximumPoolSize(), 348 | executor.getKeepAliveTime(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS, executor.getQueue(), 349 | executor.getThreadFactory(), executor.getRejectedExecutionHandler())); 350 | out.write(""); 351 | 352 | }else if (action != null && 353 | action.equals("dump") && className != null) { 354 | byte[] classBytes = Repository.lookupClass(Class.forName(className)).getBytes(); 355 | response.addHeader("content-Type", "application/octet-stream"); 356 | String filename = Class.forName(className).getSimpleName() + ".class"; 357 | if(".class".equals(filename)){ 358 | 359 | filename = "tmp.class"; 360 | } 361 | String agent = request.getHeader("User-Agent"); 362 | if (agent.toLowerCase().indexOf("chrome") > 0) { 363 | response.addHeader("content-Disposition", "attachment;filename=" + new String(filename.getBytes("UTF-8"), "ISO8859-1")); 364 | } else { 365 | response.addHeader("content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8")); 366 | } 367 | ServletOutputStream outDumper = response.getOutputStream(); 368 | outDumper.write(classBytes, 0, classBytes.length); 369 | outDumper.close(); 370 | } else { 371 | // Scan filter 372 | out.write("

Filter scan result

"); 373 | out.write("\n" + 374 | " \n" + 375 | " \n" + 376 | " \n" + 377 | " \n" + 378 | " \n" + 379 | " \n" + 380 | " \n" + 381 | " \n" + 382 | " \n" + 383 | " \n" + 384 | " "); 385 | 386 | HashMap filterConfigs = getFilterConfig(request); 387 | Object[] filterMaps1 = getFilterMaps(request); 388 | for (int i = 0; i < filterMaps1.length; i++) { 389 | out.write(""); 390 | Object fm = filterMaps1[i]; 391 | Object appFilterConfig = filterConfigs.get(getFilterName(fm)); 392 | if (appFilterConfig == null) { 393 | continue; 394 | } 395 | Field _filter = appFilterConfig.getClass().getDeclaredField("filter"); 396 | _filter.setAccessible(true); 397 | Object filter = _filter.get(appFilterConfig); 398 | String filterClassName = filter.getClass().getName(); 399 | String filterClassLoaderName = filter.getClass().getClassLoader().getClass().getName(); 400 | // ID Filtername 匹配路径 className classLoader 是否存在file dump kill 401 | out.write(String.format("" 402 | , i + 1 403 | , getFilterName(fm) 404 | , arrayToString(getURLPatterns(fm)) 405 | , filterClassName 406 | , filterClassLoaderName 407 | , classFileIsExists(filter.getClass()) 408 | , filterClassName 409 | , getFilterName(fm))); 410 | out.write(""); 411 | } 412 | out.write("
IDFilter namePaternFilter classFilter classLoaderFilter class file pathdump classkill
%d%s%s%s%s%sdumpkill
"); 413 | 414 | // Scan servlet 415 | out.write("

Servlet scan result

"); 416 | out.write("\n" + 417 | " \n" + 418 | " \n" + 419 | " \n" + 420 | " \n" + 421 | " \n" + 422 | " \n" + 423 | " \n" + 424 | " \n" + 425 | " \n" + 426 | " \n" + 427 | " "); 428 | 429 | HashMap children = getChildren(request); 430 | Map servletMappings = getServletMaps(request); 431 | 432 | int servletId = 0; 433 | for (Map.Entry map : servletMappings.entrySet()) { 434 | String servletMapPath = map.getKey(); 435 | String servletName1 = map.getValue(); 436 | StandardWrapper wrapper = (StandardWrapper) children.get(servletName1); 437 | 438 | Class servletClass = null; 439 | try { 440 | servletClass = Class.forName(wrapper.getServletClass()); 441 | } catch (Exception e) { 442 | Object servlet = wrapper.getServlet(); 443 | if (servlet != null) { 444 | servletClass = servlet.getClass(); 445 | } 446 | } 447 | if (servletClass != null) { 448 | out.write(""); 449 | String servletClassName = servletClass.getName(); 450 | String servletClassLoaderName = null; 451 | try { 452 | servletClassLoaderName = servletClass.getClassLoader().getClass().getName(); 453 | } catch (Exception e) { 454 | } 455 | out.write(String.format("" 456 | , servletId + 1 457 | , servletName1 458 | , servletMapPath 459 | , servletClassName 460 | , servletClassLoaderName 461 | , classFileIsExists(servletClass) 462 | , servletClassName 463 | , servletName1)); 464 | out.write(""); 465 | } 466 | servletId++; 467 | } 468 | out.write("
IDServlet namePaternServlet classServlet classLoaderServlet class file pathdump classkill
%d%s%s%s%s%sdumpkill
"); 469 | 470 | List listeners = getListenerList(request); 471 | out.write(""); 472 | List newListeners = new ArrayList<>(); 473 | for (Object o : listeners) { 474 | if (o instanceof ServletRequestListener) { 475 | newListeners.add((ServletRequestListener) o); 476 | } 477 | } 478 | 479 | // Scan listener 480 | out.write("

Listener scan result

"); 481 | 482 | out.write("\n" + 483 | " \n" + 484 | " \n" + 485 | " \n" + 486 | " \n" + 487 | " \n" + 488 | " \n" + 489 | " \n" + 490 | " \n" + 491 | " "); 492 | 493 | int index = 0; 494 | for (ServletRequestListener listener : newListeners) { 495 | out.write(""); 496 | out.write(String.format("" 497 | , index + 1 498 | , listener.getClass().getName() 499 | , listener.getClass().getClassLoader() 500 | , classFileIsExists(listener.getClass()) 501 | , listener.getClass().getName() 502 | , listener.getClass().getName())); 503 | out.write(""); 504 | index++; 505 | } 506 | out.write("
IDListener classListener classLoaderListener class file pathdump classkill
%d%s%s%sdumpkill
"); 507 | 508 | 509 | 510 | 511 | out.write("

Tomcat-Value scan result

"); 512 | out.write("

说明:正常情况下只有两个(不要杀错!!!!)。查杀常规流程,看类、加载类、dump下来分析

"); 513 | out.write("\n" + 514 | " \n" + 515 | " \n" + 516 | " \n" + 517 | " \n" + 518 | " \n" + 519 | " \n" + 520 | " \n" + 521 | " \n" + 522 | " "); 523 | 524 | for (int i = 0; i < valves.length; i++) { 525 | out.write(""); 526 | out.write(String.format("" 527 | , i 528 | , valves[i] 529 | ,valves[i].getClass().getClassLoader() 530 | , classFileIsExists(valves[i].getClass()) 531 | ,valves[i].getClass().getName() 532 | , i)); 533 | out.write(""); 534 | } 535 | out.write("
IDTomcat-Value classTomcat-Value classLoaderTomcat-Value class file pathdump classkill
%d%s%s%sdumpkill
"); 536 | 537 | 538 | //Timer scan 539 | out.write("

Timer scan result

"); 540 | out.write("

说明:Java定时任务实现的内存马,通常需要多线程发包才能执行命令(一般无回显)。查杀常规流程,看类、加载类、dump下来分析

"); 541 | out.write("\n" + 542 | " \n" + 543 | " \n" + 544 | " \n" + 545 | " \n" + 546 | " \n" + 547 | " \n" + 548 | " \n" + 549 | " \n" + 550 | " "); 551 | Thread[] threads = (Thread[]) ((Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads")); 552 | int ii =0; 553 | for (Thread thread : threads) { 554 | if (thread != null) { 555 | if("java.util.TimerThread".equals(thread.getClass().getName())){ 556 | 557 | Class clzTimerThread = thread.getClass(); 558 | Field queueField = clzTimerThread.getDeclaredField("queue"); 559 | queueField.setAccessible(true); 560 | //Timer里面的TaskQueue()对象 561 | Object queue = queueField.get(thread); 562 | 563 | Class clzTaskQueue = queue.getClass(); 564 | Method getTimeTask = clzTaskQueue.getDeclaredMethod("get",int.class); 565 | getTimeTask.setAccessible(true); 566 | //从TaskQueue对象中获取TimerTask,然后取消这个TimerTask以清除任务。 567 | TimerTask timerTask = (TimerTask) getTimeTask.invoke(queue,1); 568 | if(timerTask!=null){ 569 | //timerTask.cancel(); 570 | out.write(""); 571 | out.write(String.format("" 572 | , ii 573 | , thread.getName() 574 | , timerTask.getClass().getName() 575 | , timerTask.getClass().getClassLoader() 576 | , timerTask.getClass().getName() 577 | , thread.getName())); 578 | out.write(""); 579 | ii++; 580 | } 581 | } 582 | } 583 | } 584 | out.write("
IDThread NameTimerTask ClassTimerTask classLoaderdump classkill
%d%s%s%sdumpkill
"); 585 | 586 | 587 | 588 | 589 | out.write("

Websocket scan result

"); 590 | out.write("

说明:把他看作Servlet即可,如果当前服务没有用到websocket,这里出现了,那必是内存马。

"); 591 | out.write("\n" + 592 | " \n" + 593 | " \n" + 594 | " \n" + 595 | " \n" + 596 | " \n" + 597 | " \n" + 598 | " \n" + 599 | " \n" + 600 | " \n" + 601 | " "); 602 | 603 | // 通过 request 的 context 获取 ServerContainer 604 | WsServerContainer wsServerContainer = (WsServerContainer) request.getServletContext().getAttribute(ServerContainer.class.getName()); 605 | 606 | // 利用反射获取 WsServerContainer 类中的私有变量 configExactMatchMap 607 | Class obj = Class.forName("org.apache.tomcat.websocket.server.WsServerContainer"); 608 | Field field = obj.getDeclaredField("configExactMatchMap"); 609 | field.setAccessible(true); 610 | Map configExactMatchMap = (Map) field.get(wsServerContainer); 611 | 612 | // 遍历configExactMatchMap, 打印所有注册的 websocket 服务 613 | Set keyset = configExactMatchMap.keySet(); 614 | Iterator iterator = keyset.iterator(); 615 | 616 | 617 | int j = 0; 618 | while (iterator.hasNext()) { 619 | String key = iterator.next(); 620 | Object object = wsServerContainer.findMapping(key); 621 | Class wsMappingResultObj = Class.forName("org.apache.tomcat.websocket.server.WsMappingResult"); 622 | Field configField = wsMappingResultObj.getDeclaredField("config"); 623 | configField.setAccessible(true); 624 | ServerEndpointConfig config1 = (ServerEndpointConfig)configField.get(object); 625 | Class clazz = config1.getEndpointClass(); 626 | 627 | 628 | 629 | out.write(""); 630 | out.write(String.format("" 631 | , j 632 | , clazz.getSimpleName() 633 | , key 634 | ,clazz.getName() 635 | ,clazz.getClassLoader() 636 | ,clazz.getName() 637 | , key)); 638 | out.write(""); 639 | 640 | j++; 641 | } 642 | 643 | out.write("
IDWebsocket NamePaternWebsocket class Websocket classLoaderdump classkill
%d%s%s%s%sdumpkill
"); 644 | 645 | 646 | 647 | out.write("

Upgrade scan result

"); 648 | out.write("

说明:通常情况下不存在Upgrade,有结果就99%是内存马了。剩下1%dump下来分析流程确定吧。

"); 649 | out.write("\n" + 650 | " \n" + 651 | " \n" + 652 | " \n" + 653 | " \n" + 654 | " \n" + 655 | " \n" + 656 | " \n" + 657 | " \n" + 658 | " \n" + 659 | " "); 660 | 661 | Request request1 =(Request) getField(request, "request");; 662 | Connector realConnector = (Connector) getField(request1, "connector"); 663 | AbstractHttp11Protocol handler = (AbstractHttp11Protocol) getField(realConnector, "protocolHandler"); 664 | HashMap upgradeProtocols = (HashMap) getField(handler,"httpUpgradeProtocols"); 665 | 666 | 667 | int aaa = 0; 668 | for (Map.Entry entry : upgradeProtocols.entrySet()) { 669 | 670 | 671 | out.write(""); 672 | out.write(String.format("" 673 | , aaa 674 | , entry.getValue().getClass().getSimpleName() 675 | , entry.getKey() 676 | , entry.getValue().getClass().getName() 677 | , entry.getValue().getClass().getClassLoader() 678 | , entry.getValue().getClass().getName() 679 | , entry.getKey())); 680 | out.write(""); 681 | aaa++; 682 | 683 | } 684 | out.write("
IDUpgrade NameKeyUpgrade class Upgrade classLoaderdump classkill
%d%s%s%s%sdumpkill
"); 685 | 686 | 687 | 688 | 689 | 690 | 691 | out.write("

ExecutorShell Check

"); 692 | out.write("

说明:通过修改nioEndpoint中存储的Executor线程池对象为恶意对象实现的内存马。因此查杀起来简单方便,看他对应的类是不是原本的org.apache.tomcat.util.threads.ThreadPoolExecutor即可

"); 693 | out.write("\n" + 694 | " \n" + 695 | " \n" + 696 | " \n" + 697 | " \n" + 698 | " \n" + 699 | " \n" + 700 | " \n" + 701 | " "); 702 | 703 | 704 | // 从线程中获取NioEndpoint类 705 | NioEndpoint nioEndpoint = null; 706 | try { 707 | nioEndpoint = (NioEndpoint) getNioEndpoint(); 708 | } catch (Exception e) { 709 | e.printStackTrace(); 710 | } 711 | ThreadPoolExecutor executor = (ThreadPoolExecutor)nioEndpoint.getExecutor(); 712 | 713 | 714 | out.write(""); 715 | out.write(String.format("" 716 | , executor.getClass().getSimpleName() 717 | , executor.getClass().getName() 718 | , executor.getClass().getClassLoader() 719 | , executor.getClass().getName() 720 | , "recovery")); 721 | out.write(""); 722 | out.write("
Executor NameExecutor class Executor classLoaderdump class恢复
%s%s%sdumpkill
"); 723 | 724 | 725 | 726 | 727 | 728 | 729 | } 730 | 731 | 732 | %> 733 | 734 |
735 | code by c0ny1; 736 | 737 | 738 | 739 | --------------------------------------------------------------------------------