Tổng quan ba thành phần trong java web
Listener
Theo dõi các sự kiện khởi tạo hay hủy bỏ của những đối tượng như application, session hay request để thực thi các đoạn mã tương ứng với các sự kiện.Filter
Khi request đến servlet và trả về response có thể đi qua một hoặc nhiều filter(còn gọi là filter chain). Filter có thể thực hiện kiểm tra, sửa đổi các thuộc tính của request hay response.Servlet
Là module chính đóng vai trò như lớp trung gian giữa client và application phía server. Servlet tiếp nhận client request, xử lý rồi sau đó gửi trả response. Ngoài ra, để hiểu rõ hơn cách tomcat xử lí một file jsp bạn có thể tham khao qua link sau.Thực hiện tạo jsp memory webshell
Listener type
Ví dụ thêm listener trong tomcat

ServletRequestListener có chức năng lắng nghe sự khởi tạo hay hủy bỏ của ServletRequest object và invoke hai method tương ứng là requestInitialized() và requestDestroyed() mỗi khi request được thực hiện. Lợi dụng class này ta có thể override lại hai method trên để gọi lệnh thực thi OS command, ở đây mình sẽ override method requestInitialized().
Đầu tiên, để lấy được các tham số truyền vào từ request thì method requestInitialized() có nhận parameter là một ServletRequestEvent nhờ đó có thể lấy được ServletRequest thông qua ServletRequestEvent#getServletRequest().
Tiếp đến, để có thể trả về output của command được thực thi cần có HttpServletResponse để gửi trả response. Tuy nhiên, mặc định thì không thể lấy được object này từ class ServletRequestListener, do đó ta cần tạo một custom listener có constructor nhận argument là một HttpServletResponse object.
Vấn đề tiếp theo là làm sao để thêm custom listener trên vào tomcat trong runtime.
Thử debug thì thấy khi request đi qua StandardContext#fireRequestInitEvent() thì method getApplicationEventListeners() sẽ lấy tất cả listeners được định nghĩa ở file web.xml rồi kiểm tra từng object có phải instanceof class ServletRequestListener hay không, nếu đúng thì gọi method requestInitialized() như dòng 3752
Thêm vào đó StandardContext có method addApplicationEventListener(T) mà ta có thể dùng để thêm custom listener:
Cuối cùng, làm sao để lấy được StandardContext từ request.
-
Đầu tiên có thể lấy
ServletContexttừ request bằng cách dùngrequest.getServletContext(), thật ra method này không trực tiếp trả vềServletContextmà trả vềApplicationContextFacade, cái implementsServletContext -
Hơn nữa, class
ApplicationContextFacadecó thuộc tính(ApplicationContext)contextvà classApplicationContextlại có thuộc tính(StandardContext)context. Tuy nhiên có thể thấy các thuộc tính này đều là private nên ta cần dùng cơ chế java reflection để có thể lấy được.
Cuối cùng, ta được listener-type memory webshell sau:
Upload webshell lên server, inject listener:
Sau khi inject có thể xóa file jsp này, webshell lúc này đã được load vào memory của tomcat và vẫn có thể thực thi command một cách bình thường:
Từ đây ListenerShell#requestInitialized() sẽ được gọi ở mọi request, nếu request có đúng param name như đã tự định nghĩa - ở đây là cmd_secret thì command sẽ được thực thi và response trả về ouput, nếu không request sẽ tiếp tục một cách bình thường.
Filter type
Ví dụ thêm filter trong tomcat:

doFilter() để thực hiện filter tasks. Đặt breakpoint tại FilterTest#doFilter ta được đoạn stack frames đáng chú ý sau:
StandardWrapperValve#invoke()
-> StandardWrapperValve#filterChain.doFilter()
-> ApplicationFilterChain#this.internalDoFilter()
-> ApplicationFilterChain#filter.doFilter()
-> FilterTest#doFilter()
Có thể thấy tại StandardWrapperValve#invoke() thì filterChain.doFilter() sẽ được gọi, biến filterChain thì được khởi tạo ở factory.createFilterChain():
Đi vào method này:
Ở đây method sẽ lấy list filterMaps từ context.findFilterMaps() dựa vào cấu hình ở file web.xml, các filterMap chứa toàn bộ thông tin về filter-mapping bao gồm cả filterName và urlPatterns:
List filterMaps này sau đó sẽ đi vào vòng lặp for để so sánh urlPattern của từng filterMap có match với requestPath của request hiện tại, nếu đúng sẽ lấy filterConfig từ filterMap này và cuối cùng đi vào filterChain.addFilter(filterConfig).
Cấu trúc của (ApplicationFilterConfig)filterConfig như sau, chú ý member filterDef là object chứa các thông tin về filter như filterName hay filterClass:
Trở lại với StandardWrapperValve#invoke(), filterChain sẽ bắt đầu thực thi method doFilter() của từng filter:
Do vậy, để thêm một custom filter:
- Tạo
FilterDefobject chứa thông tin về custom filter muốn thêm. - Thêm
FilterDefvàoStandardContextvà regenerate lạifilterConfigbằngStandardContext#filterStart()
3. Thêm tiếp FilterMap chứa thông tin về mapping vào StandardContext
Cuối cùng, ta được filter-type memory webshell như sau:
Upload webshell lên tomcat, inject filter:
Tương tự, sau khi inject có thể xóa file jsp đi:
Servlet type
Ví dụ thêm servlet trong tomcat:

StandardContext sau khi load các servlet:
Trong StandardContext có member children là một HashMap với các item là StandardWrapper wrap các servlet.
Một member khác là servletMappings cũng là một HashMap với key - value là mapping giữa các urlPattern và servlet:
Tóm lại, để thêm một custom servlet ta cần:
- Tạo
StandardWrapperobject wrap custom servlet cần thêm - Thêm
StandardWrapperobject vào memberchildrencủaStandardContext - Thêm mapping giữa urlPattern và custom servlet vào member
servletMappingcủaStandardContext
Ta được servlet-type memory webshell sau:
Upload webshell, inject servlet:
Xóa file jsp, khi requestPath match với urlPattern tự định nghĩa ở trên thì sẽ gọi đến ServletShell thực thi command: