├── README.md └── tomcat-memshell-scanner.jsp /README.md: -------------------------------------------------------------------------------- 1 | # java-memshell-scan 2 | ### 简单的修改了一下c0ny1师傅的代码,添加了对websocket内存马查杀的支持, 3 | ### 源项目地址:https://github.com/c0ny1/java-memshell-scanner 4 | -------------------------------------------------------------------------------- /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.tomcat.websocket.server.WsServerContainer" %> 9 | <%@ page import="javax.websocket.server.ServerContainer" %> 10 | <%@ page import="java.util.*" %> 11 | <%@ page import="javax.websocket.server.ServerEndpointConfig" %> 12 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> 13 | 14 | 15 | tomcat-memshell-killer 16 | 17 | 18 |
19 |
20 | <%! 21 | public Object getStandardContext(HttpServletRequest request) throws NoSuchFieldException, IllegalAccessException { 22 | Object context = request.getSession().getServletContext(); 23 | Field _context = context.getClass().getDeclaredField("context"); 24 | _context.setAccessible(true); 25 | Object appContext = _context.get(context); 26 | Field __context = appContext.getClass().getDeclaredField("context"); 27 | __context.setAccessible(true); 28 | Object standardContext = __context.get(appContext); 29 | return standardContext; 30 | } 31 | 32 | public HashMap getFilterConfig(HttpServletRequest request) throws Exception { 33 | Object standardContext = getStandardContext(request); 34 | Field _filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs"); 35 | _filterConfigs.setAccessible(true); 36 | HashMap filterConfigs = (HashMap) _filterConfigs.get(standardContext); 37 | return filterConfigs; 38 | } 39 | 40 | // FilterMap[] 41 | public Object[] getFilterMaps(HttpServletRequest request) throws Exception { 42 | Object standardContext = getStandardContext(request); 43 | Field _filterMaps = standardContext.getClass().getDeclaredField("filterMaps"); 44 | _filterMaps.setAccessible(true); 45 | Object filterMaps = _filterMaps.get(standardContext); 46 | 47 | Object[] filterArray = null; 48 | try { // tomcat 789 49 | Field _array = filterMaps.getClass().getDeclaredField("array"); 50 | _array.setAccessible(true); 51 | filterArray = (Object[]) _array.get(filterMaps); 52 | } catch (Exception e) { // tomcat 6 53 | filterArray = (Object[]) filterMaps; 54 | } 55 | 56 | return filterArray; 57 | } 58 | 59 | /** 60 | * 遗留问题,getFilterConfig()依然存在2个 61 | * @param request 62 | * @param filterName 63 | * @throws Exception 64 | */ 65 | public synchronized void deleteFilter(HttpServletRequest request, String filterName) throws Exception { 66 | Object standardContext = getStandardContext(request); 67 | // org.apache.catalina.core.StandardContext#removeFilterDef 68 | HashMap filterConfig = getFilterConfig(request); 69 | Object appFilterConfig = filterConfig.get(filterName); 70 | Field _filterDef = appFilterConfig.getClass().getDeclaredField("filterDef"); 71 | _filterDef.setAccessible(true); 72 | Object filterDef = _filterDef.get(appFilterConfig); 73 | Class clsFilterDef = null; 74 | try { 75 | // Tomcat 8 76 | clsFilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef"); 77 | } catch (Exception e) { 78 | // Tomcat 7 79 | clsFilterDef = Class.forName("org.apache.catalina.deploy.FilterDef"); 80 | } 81 | Method removeFilterDef = standardContext.getClass().getDeclaredMethod("removeFilterDef", new Class[]{clsFilterDef}); 82 | removeFilterDef.setAccessible(true); 83 | removeFilterDef.invoke(standardContext, filterDef); 84 | 85 | // org.apache.catalina.core.StandardContext#removeFilterMap 86 | Class clsFilterMap = null; 87 | try { 88 | // Tomcat 8 89 | clsFilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap"); 90 | } catch (Exception e) { 91 | // Tomcat 7 92 | clsFilterMap = Class.forName("org.apache.catalina.deploy.FilterMap"); 93 | } 94 | Object[] filterMaps = getFilterMaps(request); 95 | for (Object filterMap : filterMaps) { 96 | Field _filterName = filterMap.getClass().getDeclaredField("filterName"); 97 | _filterName.setAccessible(true); 98 | String filterName0 = (String) _filterName.get(filterMap); 99 | if (filterName0.equals(filterName)) { 100 | Method removeFilterMap = standardContext.getClass().getDeclaredMethod("removeFilterMap", new Class[]{clsFilterMap}); 101 | removeFilterDef.setAccessible(true); 102 | removeFilterMap.invoke(standardContext, filterMap); 103 | } 104 | } 105 | } 106 | 107 | public synchronized void deleteServlet(HttpServletRequest request, String servletName) throws Exception { 108 | HashMap childs = getChildren(request); 109 | Object objChild = childs.get(servletName); 110 | String urlPattern = null; 111 | HashMap servletMaps = getServletMaps(request); 112 | for (Map.Entry servletMap : servletMaps.entrySet()) { 113 | if (servletMap.getValue().equals(servletName)) { 114 | urlPattern = servletMap.getKey(); 115 | break; 116 | } 117 | } 118 | 119 | if (urlPattern != null) { 120 | // 反射调用 org.apache.catalina.core.StandardContext#removeServletMapping 121 | Object standardContext = getStandardContext(request); 122 | Method removeServletMapping = standardContext.getClass().getDeclaredMethod("removeServletMapping", new Class[]{String.class}); 123 | removeServletMapping.setAccessible(true); 124 | removeServletMapping.invoke(standardContext, urlPattern); 125 | // Tomcat 6必须removeChild 789可以不用 126 | // 反射调用 org.apache.catalina.core.StandardContext#removeChild 127 | Method removeChild = standardContext.getClass().getDeclaredMethod("removeChild", new Class[]{org.apache.catalina.Container.class}); 128 | removeChild.setAccessible(true); 129 | removeChild.invoke(standardContext, objChild); 130 | } 131 | } 132 | 133 | public synchronized void deleteWebsocket(HttpServletRequest request, String websocketName)throws Exception{ 134 | WsServerContainer wsServerContainer = (WsServerContainer) request.getServletContext().getAttribute(ServerContainer.class.getName()); 135 | 136 | // 利用反射获取 WsServerContainer 类中的私有变量 configExactMatchMap 137 | Class obj = Class.forName("org.apache.tomcat.websocket.server.WsServerContainer"); 138 | Field field = obj.getDeclaredField("configExactMatchMap"); 139 | field.setAccessible(true); 140 | Map configExactMatchMap = (Map) field.get(wsServerContainer); 141 | 142 | configExactMatchMap.remove(request.getParameter("websocketName")); 143 | } 144 | 145 | public synchronized HashMap getChildren(HttpServletRequest request) throws Exception { 146 | Object standardContext = getStandardContext(request); 147 | Field _children = standardContext.getClass().getSuperclass().getDeclaredField("children"); 148 | _children.setAccessible(true); 149 | HashMap children = (HashMap) _children.get(standardContext); 150 | return children; 151 | } 152 | 153 | 154 | public synchronized HashMap getServletMaps(HttpServletRequest request) throws Exception { 155 | Object standardContext = getStandardContext(request); 156 | Field _servletMappings = standardContext.getClass().getDeclaredField("servletMappings"); 157 | _servletMappings.setAccessible(true); 158 | HashMap servletMappings = (HashMap) _servletMappings.get(standardContext); 159 | return servletMappings; 160 | } 161 | 162 | public synchronized List getListenerList(HttpServletRequest request) throws Exception { 163 | Object standardContext = getStandardContext(request); 164 | Field _listenersList = standardContext.getClass().getDeclaredField("applicationEventListenersList"); 165 | _listenersList.setAccessible(true); 166 | List listenerList = (CopyOnWriteArrayList) _listenersList.get(standardContext); 167 | return listenerList; 168 | } 169 | 170 | public synchronized Map getWebsocketList(javax.servlet.http.HttpServletRequest request) throws Exception{ 171 | 172 | 173 | List websockets=new ArrayList<>(); 174 | String path=""; 175 | Map map=new HashMap(); 176 | WsServerContainer wsServerContainer = (WsServerContainer) request.getServletContext().getAttribute(ServerContainer.class.getName()); 177 | 178 | // 利用反射获取 WsServerContainer 类中的私有变量 configExactMatchMap 179 | Class obj = Class.forName("org.apache.tomcat.websocket.server.WsServerContainer"); 180 | Field field = obj.getDeclaredField("configExactMatchMap"); 181 | field.setAccessible(true); 182 | Map configExactMatchMap = (Map) field.get(wsServerContainer); 183 | 184 | // 遍历configExactMatchMap, 打印所有注册的 websocket 服务 185 | Set keyset = configExactMatchMap.keySet(); 186 | Iterator iterator = keyset.iterator(); 187 | while (iterator.hasNext()){ 188 | String key = iterator.next(); 189 | 190 | Object object = wsServerContainer.findMapping(key); 191 | 192 | 193 | Class wsMappingResultObj = Class.forName("org.apache.tomcat.websocket.server.WsMappingResult"); 194 | Field configField = wsMappingResultObj.getDeclaredField("config"); 195 | configField.setAccessible(true); 196 | ServerEndpointConfig config1 = (ServerEndpointConfig)configField.get(object); 197 | Class clazz = config1.getEndpointClass(); 198 | 199 | 200 | Object ob= clazz.newInstance(); 201 | map.put(key,ob); 202 | } 203 | return map; 204 | // 如果参数带name, 删除该服务,名字为name参数值 205 | 206 | } 207 | 208 | public String getFilterName(Object filterMap) throws Exception { 209 | Method getFilterName = filterMap.getClass().getDeclaredMethod("getFilterName"); 210 | getFilterName.setAccessible(true); 211 | return (String) getFilterName.invoke(filterMap, null); 212 | } 213 | 214 | public String[] getURLPatterns(Object filterMap) throws Exception { 215 | Method getFilterName = filterMap.getClass().getDeclaredMethod("getURLPatterns"); 216 | getFilterName.setAccessible(true); 217 | return (String[]) getFilterName.invoke(filterMap, null); 218 | } 219 | 220 | 221 | String classFileIsExists(Class clazz) { 222 | if (clazz == null) { 223 | return "class is null"; 224 | } 225 | 226 | String className = clazz.getName(); 227 | String classNamePath = className.replace(".", "/") + ".class"; 228 | URL is = clazz.getClassLoader().getResource(classNamePath); 229 | if (is == null) { 230 | return "在磁盘上没有对应class文件,可能是内存马"; 231 | } else { 232 | return is.getPath(); 233 | } 234 | } 235 | 236 | String arrayToString(String[] str) { 237 | String res = "["; 238 | for (String s : str) { 239 | res += String.format("%s,", s); 240 | } 241 | res = res.substring(0, res.length() - 1); 242 | res += "]"; 243 | return res; 244 | } 245 | %> 246 | 247 | <% 248 | out.write("

Tomcat memshell scanner 0.1.0

"); 249 | String action = request.getParameter("action"); 250 | String filterName = request.getParameter("filterName"); 251 | String servletName = request.getParameter("servletName"); 252 | String className = request.getParameter("className"); 253 | String websocketName = request.getParameter("websocketName"); 254 | if (action != null && action.equals("kill") && filterName != null) { 255 | deleteFilter(request, filterName); 256 | } else if (action != null && action.equals("kill") && servletName != null) { 257 | deleteServlet(request, servletName); 258 | }else if (action != null && action.equals("kill") && websocketName != null){ 259 | deleteWebsocket(request,websocketName); 260 | } else if (action != null && action.equals("dump") && className != null) { 261 | out.println(className); 262 | byte[] classBytes = Repository.lookupClass(Class.forName(className)).getBytes(); 263 | response.addHeader("content-Type", "application/octet-stream"); 264 | String filename = className + ".class"; 265 | 266 | String agent = request.getHeader("User-Agent"); 267 | if (agent.toLowerCase().indexOf("chrome") > 0) { 268 | response.addHeader("content-Disposition", "attachment;filename=" + new String(filename.getBytes("UTF-8"), "ISO8859-1")); 269 | } else { 270 | response.addHeader("content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8")); 271 | } 272 | ServletOutputStream outDumper = response.getOutputStream(); 273 | outDumper.write(classBytes, 0, classBytes.length); 274 | outDumper.close(); 275 | } else { 276 | // Scan filter 277 | out.write("

Filter scan result

"); 278 | out.write("\n" + 279 | " \n" + 280 | " \n" + 281 | " \n" + 282 | " \n" + 283 | " \n" + 284 | " \n" + 285 | " \n" + 286 | " \n" + 287 | " \n" + 288 | " \n" + 289 | " "); 290 | 291 | HashMap filterConfigs = getFilterConfig(request); 292 | Object[] filterMaps1 = getFilterMaps(request); 293 | for (int i = 0; i < filterMaps1.length; i++) { 294 | out.write(""); 295 | Object fm = filterMaps1[i]; 296 | Object appFilterConfig = filterConfigs.get(getFilterName(fm)); 297 | if (appFilterConfig == null) { 298 | continue; 299 | } 300 | Field _filter = appFilterConfig.getClass().getDeclaredField("filter"); 301 | _filter.setAccessible(true); 302 | Object filter = _filter.get(appFilterConfig); 303 | String filterClassName = filter.getClass().getName(); 304 | String filterClassLoaderName = filter.getClass().getClassLoader().getClass().getName(); 305 | // ID Filtername 匹配路径 className classLoader 是否存在file dump kill 306 | out.write(String.format("" 307 | , i + 1 308 | , getFilterName(fm) 309 | , arrayToString(getURLPatterns(fm)) 310 | , filterClassName 311 | , filterClassLoaderName 312 | , classFileIsExists(filter.getClass()) 313 | , filterClassName 314 | , getFilterName(fm))); 315 | out.write(""); 316 | } 317 | out.write("
IDFilter namePaternFilter classFilter classLoaderFilter class file pathdump classkill
%d%s%s%s%s%sdumpkill
"); 318 | 319 | // Scan servlet 320 | out.write("

Servlet scan result

"); 321 | out.write("\n" + 322 | " \n" + 323 | " \n" + 324 | " \n" + 325 | " \n" + 326 | " \n" + 327 | " \n" + 328 | " \n" + 329 | " \n" + 330 | " \n" + 331 | " \n" + 332 | " "); 333 | 334 | HashMap children = getChildren(request); 335 | Map servletMappings = getServletMaps(request); 336 | 337 | int servletId = 0; 338 | for (Map.Entry map : servletMappings.entrySet()) { 339 | String servletMapPath = map.getKey(); 340 | String servletName1 = map.getValue(); 341 | StandardWrapper wrapper = (StandardWrapper) children.get(servletName1); 342 | 343 | Class servletClass = null; 344 | try { 345 | servletClass = Class.forName(wrapper.getServletClass()); 346 | } catch (Exception e) { 347 | Object servlet = wrapper.getServlet(); 348 | if (servlet != null) { 349 | servletClass = servlet.getClass(); 350 | } 351 | } 352 | if (servletClass != null) { 353 | out.write(""); 354 | String servletClassName = servletClass.getName(); 355 | String servletClassLoaderName = null; 356 | try { 357 | servletClassLoaderName = servletClass.getClassLoader().getClass().getName(); 358 | } catch (Exception e) { 359 | } 360 | out.write(String.format("" 361 | , servletId + 1 362 | , servletName1 363 | , servletMapPath 364 | , servletClassName 365 | , servletClassLoaderName 366 | , classFileIsExists(servletClass) 367 | , servletClassName 368 | , servletName1)); 369 | out.write(""); 370 | } 371 | servletId++; 372 | } 373 | out.write("
IDServlet namePaternServlet classServlet classLoaderServlet class file pathdump classkill
%d%s%s%s%s%sdumpkill
"); 374 | 375 | List listeners = getListenerList(request); 376 | // if (listeners == null || listeners.size() == 0) { 377 | // return; 378 | // } 379 | out.write(""); 380 | List newListeners = new ArrayList<>(); 381 | for (Object o : listeners) { 382 | if (o instanceof ServletRequestListener) { 383 | newListeners.add((ServletRequestListener) o); 384 | } 385 | } 386 | 387 | // Scan listener 388 | out.write("

Listener scan result

"); 389 | out.write("\n" + 390 | " \n" + 391 | " \n" + 392 | " \n" + 393 | " \n" + 394 | " \n" + 395 | " \n" + 396 | " \n" + 397 | " \n" + 398 | " "); 399 | 400 | int index = 0; 401 | for (ServletRequestListener listener : newListeners) { 402 | out.write(""); 403 | out.write(String.format("" 404 | , index + 1 405 | , listener.getClass().getName() 406 | , listener.getClass().getClassLoader() 407 | , classFileIsExists(listener.getClass()) 408 | , listener.getClass().getName() 409 | , listener.getClass().getName())); 410 | out.write(""); 411 | index++; 412 | } 413 | out.write("
IDListener classListener classLoaderListener class file pathdump classkill
%d%s%s%sdumpkill
"); 414 | 415 | 416 | Map websockets=getWebsocketList(request); 417 | out.println(websockets); 418 | if (websockets == null || websockets.size() == 0) { 419 | return; 420 | } 421 | out.write(""); 422 | // Scan Websocket 423 | out.write("

Websocket scan result

"); 424 | out.write("\n" + 425 | " \n" + 426 | " \n" + 427 | " \n" + 428 | " \n" + 429 | " \n" + 430 | " \n" + 431 | " \n" + 432 | " \n" + 433 | " "); 434 | 435 | int index1 = 0; 436 | 437 | 438 | for (Object websocket : websockets.keySet()) { 439 | out.write(""); 440 | out.write(String.format("" 441 | , index1 + 1 442 | , websockets.get(websocket).getClass().getName() 443 | , websockets.get(websocket).getClass().getClassLoader() 444 | , websocket 445 | , websockets.get(websocket).getClass().getName()+"$1" 446 | , websocket)); 447 | out.write(""); 448 | index1++; 449 | } 450 | } 451 | 452 | %> 453 | 454 |
455 | code by c0ny1 456 | 457 | 458 | 459 | --------------------------------------------------------------------------------
IDWebsocket classWebsocket classLoaderWebsocket Url pathdump classkill
%d%s%s%sdumpkill