Phân tích CVE-2017-3066 - AMF Deserialization trong Adobe ColdFusion
1. Giới thiệu
Adobe ColdFusion là một nền tảng phát triển ứng dụng web nhanh chóng, ngôn ngữ lập trình được sử dụng với nền tảng Adobe ColdFusion cũng thường được gọi là ColdFusion, mặc dù được biết chính xác hơn là CFML. ColdFusion ban đầu được thiết kế để giúp dễ dàng kết nối các trang HTML đơn giản với cơ sở dữ liệu. Đến phiên bản 2 (1996), nó đã trở thành một nền tảng đầy đủ bao gồm cả IDE
2. Về lỗ hổng
Đây là một lỗ hổng liên quan đến AMF Deserialization, tuy là một CVE từ lâu nhưng do có nhiều kiến thức mới với mình, vì vậy mình sẽ phân tích lại nó.
2.1. Vị trí lỗi
Vị trí gây ra lỗi là /flex2gateway/amf
cấu hình trong cfusion/wwwroot/WEB-INF/flex/services-config.xml
Trong file web.xml, nơi handle chức năng này là
MessageBrokerServlet
được handle bởi coldfusion.bootstrap.BootstrapServlet
với param khi init service là coldfusion.flex.ColdFusionMessageBrokerServlet
2.2. Vòng đời của một Servlet
Một Servlet cơ bản sẽ có 3 phần, init()
, service()
và destroy()
.
- Init method được gọi bởi Servlet container để cho biết rằng một Servlet được khởi tạo thành công và sắp đưa vào sử dụng
- Service method được gọi để thông báo cho Servlet container về các request của client. Method này sử dụng
ServletRequest
object để collect data request của client và dùngServletResponse
object để tạo output. - Destroy method chạy duy nhất 1 lần để báo hiệu sự kết thúc của Servlet instance.
2.3. Nơi diễn ra Deserialization
Đầu tiên, ta xét đến method init()
của coldfusion.bootstrap.BootstrapServlet
:
Hàm này lấy đầu vào là servlet.class
, chính là coldfusion.flex.ColdFusionMessageBrokerServlet
, sau đó đưa vào hàm ClassloaderHelper.initServletClass
.
Trong hàm này, nó tiếp tục gọi đến loadClass
để class này được load.
Tiếp theo, xét đến method service()
của coldfusion.bootstrap.BootstrapServlet
:
Nó sẽ gọi đến coldfusion.flex.ColdFusionMessageBrokerServlet#services()
Trong hàm này, nó gọi đến hàm services()
của MessageBrokerServlet
vì ColdFusionMessageBrokerServlet
đã implement nó.
public void service(HttpServletRequest req, HttpServletResponse res) {
...
encoded = ((HttpServletRequest)req).getContextPath();
String pathInfo = ((HttpServletRequest)req).getPathInfo();
String endpointPath = ((HttpServletRequest)req).getServletPath();
if (pathInfo != null) {
endpointPath = endpointPath + pathInfo;
}
Endpoint endpoint;
try {
endpoint = this.broker.getEndpoint(endpointPath, encoded);
} catch (MessageException var29) {
...
try {
...
endpoint.service((HttpServletRequest)req, res);
Như ta đã xem xét ở trên, với endpoint là /flex2gateway/amf
thì Endpoint class của nó sẽ là flex.messaging.endpoints.AMFEndpoint
.
Vì thế việc gọi endpoint.service((HttpServletRequest)req, res);
sẽ gọi tới flex.messaging.endpoints.AMFEndpoint#services()
Điều đặc biệt ở đây là trong AMFEndpoint
không có phương thức services
, nhưng như ta thấy như sau:
flex.messaging.endpoints.AMFEndpoint
kế thừa flex.messaging.endpoints.BasePollingHTTPEndpoint
, rồi nó kế thừa từ flex.messaging.endpoints.BaseHTTPEndpoint
, và trong flex.messaging.endpoints.BaseHTTPEndpoint
có phương thức services()
. Theo tính chất trong OOP, phương thức lớp cha không được lớp con override thì khi gọi phương thức đó từ lớp con sẽ gọi đến phương thức đó trong lớp cha. Do vậy AMFEndpoint.service() --> BasePollingHTTPEndpoint.service() --> BaseHTTPEndpoint.service()
.
Trong hàm này, nó gọi tới filterChain#invoke()
với
Hàm createFilterChain() trả về một object là serializationFilter
AMFFilter
là một abstract class và được 4 Filter Class implement, trong đó có flex.messaging.endpoints.amf.SerializationFilter
.
Do vậy, this.filterChain#invoke()
sẽ gọi tới flex.messaging.endpoints.amf.SerializationFilter.invoke()
.
public void invoke(ActionContext context) throws IOException {
SerializationContext sc = SerializationContext.getSerializationContext();
try {
MessageDeserializer deserializer = sc.newMessageDeserializer();
InputStream in = FlexContext.getHttpRequest().getInputStream();
deserializer.initialize(sc, in, debugTrace);
int reqLen = FlexContext.getHttpRequest().getContentLength();
context.setDeserializedBytes(reqLen);
...
ActionMessage m = new ActionMessage();
context.setRequestMessage(m);
deserializer.readMessage(m, context);
success = true;
Phương thức readMessage
là liên quan đến AMF Deserialization, thứ ta sẽ tìm hiểu tiếp ở phần sau.
Kết quả: Gửi request tới /flex2gateway/amf
với input là 1 Object để thực hiện deserialize.
2.4. Tìm hiểu về AMF Deserialization
AMF (Action Message Format) là định dạng binary serialization thường được sử dụng bởi các ứng dụng Flash. Có một số thư viện sử dụng AMF thường dùng như:
- Flex BlazeDS của Adobe (đã khai tử https://flex.apache.org/about-history.html)
- Flex BlazeDS của Apache (https://flex.apache.org/)
- Flamingo AMF Serializer của Exadel
- GraniteDS
Mỗi thư viện này đều đã bị ảnh hưởng bởi một trong số các kiểu tấn công:
- XML external entity (XXE)
- Tạo đối tượng và thiết lập thuộc tính tùy ý
- Deserialization thông qua RMI
Một trong những tính năng mới của các đối tượng AMF3 là bổ sung hai đặc điểm nhất định, được gọi là Externalizable
và Dynamic
:
- Externalizable: Một instance của class implement
flash.utils.IExternalizable
và kiểm soát hoàn toàn việc serialization các thành phần của nó. Nó tương tự vớijava.io.Externalizable
interface. Trên thực tế, tất cả thư viện nêu trên đều xây dựngflash.utils.IExternalizable
từ đặc điểm kỹ thuật tương đương vớijava.io.Externalizable
của Java, cho phép tái tạo bất kỳ lớp nào implementjava.io.Externalizable
. Nó sử dụng 2 method làreadExternal(java.io.ObjectInput)
vàwriteExternal(java.io.ObjectInput)
để deserialize/serialize object. - Dynamic: Một instance của các class definition với các đặc điểm được khai báo động, các public variable và method có thể được thêm và bớt trong runtime. Nó tương tự với chức năng của JavaBean: cho phép khởi tạo đối tượng bằng cách chỉ định tên lớp, tên và giá trị của thuộc tính. Trên thực tế, nhiều triển khai sử dụng các tiện ích JavaBeans hiện có như
java.beans.Introspector
(Flamingo, Flex BlazeDS, WebORB) hoặc tự triển khai một chức năng tương tự (GraniteDS).
Trong trường hợp cụ thể này, lỗ hổng nằm ở thư viện Apache BlazeDS
- CVE-2017-5641 version <= 4.7.2
Phương thức readMessage
:
Trong đó readHeader
và readBody
:
Hàm readObject()
:
readObjectvalue()
:
readScriptObject()
:
Method Amf3Input.readScriptObject()
xác định class đang được xử lý có implement java.io.Externalizable
hay không.
Nếu ta dùng 1 Object có implement Externalizable
thì sẽ tới method readExternal()
của object đang được xử lý.
readExternalizable()
:
Như vậy đây là 1 hướng trong khai thác AMF Deserialization.
Nếu class không implement Externalizable
sẽ chuyển sang 1 nhánh khác lấy các property của class và sử dụng BeanProxy.setValue()
để set lại các giá trị này.
Khi đó BeanProxy
sẽ cố gắng tìm các setter method của class và invoke.
Đây là hướng khác trong khai thác AMF Deserialzation.
Như vậy, chain của lỗ hổng trong thư viện này có thể tóm tắt lại như sau:
Sau đây mình sẽ trình bày cách khai thác đối với lỗ hổng này.
2.5. Chuyển từ Externalizable.readExternal() sang ObjectInputStream.readObject()
Dùng MetaDataEntry.readExternal()
Tìm ra được class này bằng cách: tìm trong các class implement từ java.io.Externalizable
có phương thức readExternal()
call được sang readObject()
.
Trong org.apache.axis2.util.MetaDataEntry#readExternal
sẽ gọi tới SafeObjectInputStream.install
.
Khi đó nó sẽ khởi tạo bằng việc gọi tới org.apache.axis2.context.externalize.SafeObjectInputStream#readObject
.
Rồi readObjectOverride
thực hiện gọi readObject
của Java Native.
Trong lib của server có Commons BeanUtils 1.8.0
, vì vậy chain CommonsBeanutil1
sẽ hoạt động, tuy nhiên cần wrapper bởi MetaDataEntry
. Cách dễ nhất là download ysoserial về rồi build lại (nhớ đổi phiên bản từ 1,9.2 thành 1.8.0 của common-beanutils) sau đó wrapper MetaDataEntry.
POC:
Tham khảo
https://docs.google.com/presentation/d/116DwvGitgknoiq_AOLmRlrkjCrUWfi38t_u-GdU4k2k
All rights reserved