Trước tiên ta cần hiểu cơ bản về kiến trúc của Tomcat. Mình đa số đều ăn cắp từ bài blog này nên các bạn muốn hiểu rõ có thể đọc bài gốc.
Về tổng quan thì Tomcat Server bao gồm nhiều Service, và mỗi Service đều có Connector và Container
Các Connector có nhiệm vụ kết nối giữa Service và Container, nhận request từ Service đưa qua Container xử lý và trả về response cho Service.
Còn Container sẽ là nơi xử lý chính, một Container sẽ chứa nhiều Servlet. Servlet chính là các object mà ta gọi khi code để xử lý và return dữ liệu (HTTPServlet là một ví dụ). Trong Container còn các thành phần râu ria khác mà các bạn có thể đọc bài gốc để hiểu hơn. Ở đây mình chỉ note về Context và Wrappers.
Context là một thành phần trong Container, Context đại diện cho mỗi Web application dưới ROOT path (có thể hiểu đơn giản context chính là đại diện cho cái file war đang chạy web). Context sẽ lưu những thông tin về web application đó trong quá trình running, những thông tin này có thể được gọi là context informaion, ví dụ như: session, mime type,...Trong Context sẽ có nhiều Wrappers
Wrappers có thể hiểu là đại diện của Servlet, ví dụ ta define một Servlet xử lý dữ liệu tại path /demo thì sẽ có một Wrappers làm đại diện để load, khởi tạo, thực thi và recovery cho Servlet này
2. Một số thành phần trong Tomcat cần quan tâm
A. Context
Để hiểu rõ về Memshell trong tomcat ta cần phải biết 3 loại context được dùng để khai thác.
ServletContext
ServletContext là context được định nghĩa bởi Servlet API, nó chứa context infor của các Servlets trong ứng dụng web, thông qua ServletContext ta có thể tương tác với các thông tin của Servlets, ví dụ như MIME type, dispatch request,....
ApplicationContext
ApplicationContext là một implementation của ServletContext, cũng có công dụng để là context infor của application. ApplicationContext được triển khai theo kiểu facade pattern
StandardContext
Cuối cùng là StandardContext. StandardContext sẽ được ApplicationContext gọi đến. Context này không chỉ dừng lại ở mức lưu context infor, mà nó còn cung cấp API cho phép ta chỉnh sửa trực tiếp các context infor mà các context khác không làm được (docs) -> ta sẽ lợi dụng tính năng này để triển khai memshell
Tóm lại
Nếu muốn chỉnh sửa các context infor trong tomcat servlet ta cần gọi được StandardContext, thông qua StandardContext ta có thể setup được memshell mong muốn
B. Servlet
Như mình đề cập ở trên thì Servlet chính là các object mà ta gọi khi code để xử lý và return dữ liệu. Có 3 Servlet interface mà implement từ trên xuống như sau
Ta đều có thể gọi và sử dụng 3 interface Servlet này, tuy nhiên thông thường ta chỉ sử dụng HttpServlet, nếu muốn dùng 2 cái trên ta phải tự tay define nhiều thứ
C. Filter
Filter đúng như tên gọi thì là thành phần được dùng để intercept request hoặc response để thực hiện một số chỉnh sửa nào đó. Ví dụ dễ thấy nhất là ta sử dụng filter trong file web.xml chính là ta đang sử dụng thành phần Filter trong Tomcat
Mỗi khi Filter được trigger thì hàm doFilter sẽ được gọi để thực thi Filter đó. Đối với filter có một Object gọi là FiltersConfig, lưu thông tin về Filter đó như FilterName, ServletContext, thông tin về Parameter,...
Tập hợp các Filter lại với nhau ta sẽ có FilterChain
D. Listener
Listener là một interface đặc biệt trong Tomcat, cho phép ta monitor một đối tượng nào đó, mỗi khi có một event thì Listener được trigger. Event có thể đơn giản là việc gọi hàm , hoặc attribute của đối tượng bị thay đổi. Trong Servlet có nhiều loại Listener cho phép ta có thể listen nhiều kiểu event khác nhau.
D. Loading Order
Với 3 thành phần đã nêu ở trên, thứ tự Tomcat xử lý request để đưa vào các thành phần ở trên là
Listener->Filter->Servlet
3. Tomcat memshell (triển khai với JSP)
Bây giờ thì mình sẽ đi vào phần chính của bài này. Ta sẽ khai thác vào 3 thành phần cơ bản đã nêu ở trên của Tomcat để triển khai mememshell
Listener Memshell
Filter Memshell
Servlet Memshell
Valve Memshell (bonus)
Ý tưởng của ta khi triển khai phần này là debug xem cách thành phần đó được load vào trong Runtime. Sau đó tìm cách viết file jsp để load được các thành phần độc hại (Listener độc hại, Filter độc hại, Servlet độc hại) vào quá trình Runtime của Tomcat.
Các phương pháp trên chỉ exploit được ở Servlet 3.0 vì từ phiên bản này Servlet mới hỗ trợ dynamic reg components, mà chỉ có các bản Tomcat 7.x trở lên mới hỗ trợ Servlet 3.0. Do đó để triển khai memshell thì điều kiện cần là target phải sử dụng Tomcat bản 7.x trở lên
A. Listener Memshell
Đầu tiên ta implement một Listener độc hại để debug xem quá trình define Listener diễn ra như thế nào.
Khi chạy server thì mỗi lần có request thì event ServletRequestEvent sẽ trigger Listener, nếu request có parameter cmd thì Listener sẽ thực thi command.
Dòng Object[] instances = this.getApplicationEventListeners() sẽ lấy ra list các Listeners và các Listeners này được trigger với ServletRequestEvent tại listener.requestInitialized(event)
Ta thấy StandardContext có một method cho phép ta trực tiếp thêm Listeners vào Runtime đó là method addApplicationEventListener (docs)
Vậy thì ta chỉ cần tìm được cách gọi được StandardContext trong JSP file là có thể add được Listeners
Để gọi được StandardContext ta sẽ có 2 cách
Cách 1: Dùng org.apache.catalina.connector.Request để gọi đến StandardContext vì Object này cung cấp hàm getContext cho ta lấy ra được StandardContext (docs). Ta sẽ dùng reflection để lấy ra Request Object. Ví dụ
Cách 2: Dựa vào cấu trúc Tomcat đã đề cập ở trên, ta cũng có thể lấy ra StandardContext bằng cách đi từ ServletContext->ApplicationContext->StandardContext.
Khi đã lấy được StandardContext, ta chỉ cần đơn giản gọi addApplicationEventListener để thêm Listener độc hại vào Runtime. Ta có file JSP sau để triển khai
Nó tiến hành check synchronized filterDefs trước rồi mới lấy ra filterConfigs. Mục đích của việc synchronized là để tránh việc bị Race condition thay đổi filterDefs. Việc triển khai memshell của ta không liên quan nên ta không cần quan tâm lắm. Thứ ta cần quan tâm là 3 giá trị filterDefs , filterMap và filterConfigs
Khi debug ta sẽ thấy StandardContext đều lưu trữ 3 giá trị này
Đầu tiên là filterDefs là một Hashmap lưu các filterDef, mỗi filterDef lưu defination của Filter hay nói cách khác là filterDef bọc lấy Filter
Giá trị filterName và filterClass tương ứng với 2 giá trị sau khi ta khai báo Filter trong file web.xml
Và cuối cùng là filterConfigs là một Hashmap, lưu các filterConfig, Mỗi filterConfig sẽ lưu trữ chính Filter đó và các thông tin râu ria của Filter đó như Context, filterDef, log
Tóm lại dựa vào flow code trên ta sẽ có flow sau để inject malicious Filter vào Runtime
Tạo filterDef để bọc lấy malicious Filter
Tạo filterMapđể map 1 đường dẫn bất kỳ với Filter Malicious
Tạo filterConfig (instance cụ thể là là ApplicationFilterConfig) từ filterDef và StandardContext
Thêm filterConfig vào HashMap filterConfigs
Đầu tiên tạo filterDef
Shell_Filter filter = new Shell_Filter();
String name = "CommonFilter";
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
Map tên filter với path
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
Tại hàm internalDoFilter ta sẽ thấy method service của Servlet sẽ được gọi -> nhờ đó ta có thể thực thi cmd qua method service
Tuy nhiên muốn trace về cách Servlet được khai báo vào Runtime thì ta phải xem vào quá trình, loading khi khởi tạo Tomcat.
Ta sẽ nhìn vào hàm startInternal của StandardContext. Hàm này sẽ được gọi mỗi State START của StandardContext để triển khai các component của StardContext (trong đó có Servlet) (docs)
Trong hàm này ta cũng sẽ thấy thứ tự các coponent được triển khai theo thứ tự là Listener->Filter->Servlet
Debug ngược về 1 chút ta biết được method setup các coponent trong là startInternalfireLifecycleEvent
Tại hàm này sẽ gọi hàm lifecycleEvent theo event truyền vào.
Trong trường hợp này event truyền vào là configure_start và LifecycleEvent được khởi tạo là ContextConfig, nên ta tiếp tục đi đến method configureStart của ContextConfig
configureStart gọi đến webConfig để load config từ file web.xml
Hàm webConfig sẽ gọi đến configureContext với tham số là webXml
webXml sẽ chứa defination của những components mà ta quan tâm
Trong hàm configureContext sẽ có code để triển khai các coponents trong Context bao gồm cả Servlet, Filter và Listener
Ta có thể thấy, từ đây ta cũng có thể xem được cách triển khai Listener và Filter mà không cần phải debug như các cách ở trên
Nhưng thứ ta muốn tìm là cách triển khai Servlet nằm ở đoạn gần cuối hàm
Đoạn code trên sẽ có 1 vài thứ quan trọng ta cần quan tâm:
Cần phải có 1 wrapper để bọc lấy servet, sau đó setup Servlet Name và ServletClas vào wrapper.
Ta phải setup getLoadOnStartup cho wrapper để Servlet có thể được gọi.
Khi setup đầy đủ thì wrapper sẽ được add vào context như 1 child của context, và gán 1 path nào đó để context biết đường gọi đến wrapper này mà thực thi Servlet
Nói về nguyên nhân thì ta phải setup getLoadOnStartup cho wrapper thì ta phải quay về đoạn load Servlet trong StanderContext.startInternal
Khi lấy ra list các child (các wrapper) của context. Nếu như wrapper có loadOnStartup < 0 (giá trị mặc định khi không khai báo loadOnStarup sẽ = -1) thì sẽ không được load, cón nếu >= 0 thì wrapper tiếp tục được xử lý và được load
Do đó ta phải khai báo loadOnStartup làm sao >= 0
Vậy tóm lại để khai báo Servlet độc hại bằng jsp ta cần:
Khai báo wrapper đẻ bọc Servlet
Set loadOnStartup >= 0
Set Servlet name và Servlet class
Add wrapper vào làm child của context
Map wrapper với một path nào đó để trigger. Lưu ý: ta cũng có thẻ set giá trị path là /* để match all path, tuy nhiên nếu làm vậy sẽ overide mọi Servlet hiện tại của web -> trang web mất hành vi ban đầu. Do đó ta phải set 1 path cụ thể để trigger, đây cũng là điểm hạn chế lớn nhất của memshell Servlet