Phân tích CVE-2017-3066 - AMF Deserialization trong Adobe ColdFusion
Bài đăng này đã không được cập nhật trong 2 năm
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
ServletRequestobject để collect data request của client và dùngServletResponseobject để 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.IExternalizablevà 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.Externalizableinterface. Trên thực tế, tất cả thư viện nêu trên đều xây dựngflash.utils.IExternalizabletừ đặc điểm kỹ thuật tương đương vớijava.io.Externalizablecủ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