CVE-2020–2950 — Turning AMF Deserialize bug to Java Deserialize bug

Peterjson
11 min readJul 25, 2020

--

Bug này của một anh nickname là GreenDog chứ không phải của mình nhé :>

INTRO

Hi các bạn ! Cũng lâu rồi mình chưa có viết lách gì về kỹ thuật thì lần này mình sẽ write-up về 1 case pentest liên quan đến bug CVE-2020–2950 . Mình reproduce lại bug này khi đọc được bài blog phân tích của ZDI: https://www.zerodayinitiative.com/blog/2020/5/8/details-on-the-oracle-weblogic-vulnerability-being-exploited-in-the-wild.
Lúc này nhìn lại trong internal của công ty mình thì thấy có 1 site dùng Oracle Business Intelligence nên mình quyết định reproduce bug này để tiện cho việc pentest.

TARGET

Tóm tắt về bug này thì Oracle Business Intelligence handle AMF data mà attack send lên qua 1 servlet gọi là BIRemotingServlet. Đa phần AMF deserialize sẽ trigger setter hoặc readExternal() (mình sẽ nói kĩ hơn về cách AMF deserialize để recover lại một Object như thế nào), mà đa phần các chain về AMF này thì khi exploit đều cần outbound connection để connect tới attacker server (như JRMP call, hay JNDI call chẳng hạn). Nhưng cuộc đời đâu dễ đến thế, đâu phải reproduce PoC là ăn ngay được, đa phần khi pentest bạn sẽ phải gặp những case server được config không có outbound connection ra internet, hoặc nằm sau nginx, để có internet thì server bên trong cần đi qua 1 proxy internal như Squid chẳng hạn. Thế nên đa phần những case mình gặp phải để phải improve lại PoC đã public để có thể stable hơn khi exploit mà không cần outbound connection.

SOME CASES

Như trước đây mình có làm 1 case về CVE-2019–3396 (bug này của Confluence, đại loại là load remote template về render để từ đó RCE được). Nhưng nếu server không có outbound connection để load remote template thì sao ? Ở bước này một là bạn tìm cách làm tiếp hai là để đó sau xem lại :v, thì mình cũng chia sẻ luôn về cách improve bug này mà mình tìm được như thế này. Khi dựng và debug bug này ở local thì mình thấy Confluence log lại header của user vào log, đến đây thì mình nảy ra ý tưởng là inject template vào log của confluence sau đó render ra và RCE. Nhưng mà làm sao biết được path của file log? Thì lúc này file:/// protocol handler trong java lại phát huy tác dụng, có tác dụng như thế nào thì để lại cho các bạn tìm hiểu nhé :> . Tới bước này thì giả sử như load được template mà mình inject vào rồi nhưng vẫn ko RCE đc do trong log file có những kí tự mà khi parser parse template dẫn đến fail. Thường thì chức năng log file của 1 app sẽ rotate log khi file log đạt dung lượng bao nhiêu đó như 20mb sẽ rotate thành 1 file log khác. Tới đây thì ý tưởng cuối cùng là spam cho tràn log vừa qua file log mới, inject template RCE vào kèm theo comment hết tất cả phần sau của file log, dùng file:/// protocol handler để tìm path của file log và cuối cùng là RCE :).

Do đó công việc của mình từ khi chuyển việc đa phần là research, tìm hiểu technique mới, reproduce 1 day, improve PoC cho từng case cụ thể, quá trình này giúp mình học được rất nhiều thứ, nếu PoC của bạn sau khi improve có thể exploit được nhiều case như không có outbound connection thì bạn có thể lấy PoC đó đi tìm bounty chẳng hạn, như lần trước làm Liferay mình cũng được reward một ít coi như là đáng công sức bỏ ra. Hướng đi này mình cảm giác khá hiệu quả cho người mới tiếp cận với 1 attack surface nào đó, có thể vừa học, vừa cải thiện, thậm chí sau khi analysis những 1 day bug có thể hiểu flow, luồng của app để sau có thể audit tìm 0day chẳng hạn.

ANALYSIS

Quay lại bug Oracle Business Intelligence,để tiện cho việc reproduce thì chắc chắn phải build và debug được Oracle Business Intelligence rồi. Phần setup cũng khá dễ, chỉ cần build bản mới nhất của Oracle Business Intelligence nhưng không cài bản patch là bạn đã có 1 version có lỗi rồi (nói chứ vì phải cần license mới down được patch =]]] ), phần setup mình sẽ để lại cho mọi người nếu muốn reproduce lại thử bug này. Có thể tham khảo tham ở đây:
http://www.catgovind.com/obiee/step-by-step-obiee-12c-installation-on-windows-oracle-bi-publisher-installation-and-oracle-bi-analytic-installation/

AMF Deserialize

Nói đến series AMF này thì chắc nhiều bạn cũng ít biết nó là gì, ngoài Java Deserialize các bạn hay thấy gần đây thì vẫn còn các stream data khác ngoài Java Native như XML, JSON hay AMF. Thì mỗi dạng sẽ map với 1 kiểu bug khác nhau nên hướng tiếp cận từng cái thì cần hiểu ra cách stream data đó recover 1 Objet từ 1 stream data như thế nào. Với AMF các bạn có thể đọc qua 2 bài blog sau của CodeWhiteSec Team để hiểu rõ hơn về nó:

https://codewhitesec.blogspot.com/2017/04/amf.html
(bài này thì phân tích kĩ AMF hoạt động như thế nào)

https://codewhitesec.blogspot.com/2018/03/exploiting-adobe-coldfusion.html
(bài này thì là một hướng đi của tác giả khi pentest gặp phải trường hợp như mình là server không có outbound connection, để khắc phục thì tác giả tìm thêm trong library những setter, readExternal() mà convert stream AMF về ObjectInputStream để từ đó có thể lợi dụng gadget chain có sẵn trong ColdFusion để exploit RCE)

Mình cũng có 1 case study về bug này, cách improve nó để exploit khi không có outbound connection, các bạn có thể tìm đọc tại đây: https://docs.google.com/presentation/d/116DwvGitgknoiq_AOLmRlrkjCrUWfi38t_u-GdU4k2k/edit?usp=sharing . Trong slide này mình đi rất kĩ từ endpoint có bug đến trace trong những config của coldfusion để tới được đoạn code có lỗi !

CVE 2020 2950

Quay lại phần debug, trace lỗi, thì cách mình thông thường làm nhất đó là dump hết jars trên home directory của webapp, sau đó thì add hết vào một project IntelliJ IDEA và setup remote debug. Ở weblogic thì enable remote debug khá dễ, chỉ cần vào console add thêm arg jdwp (Java Wire Debug Protocol) vào restart lại hết là xong, còn với các app thông thường thì sẽ add trực tiếp arg jdwp vào script startup để run.

Trong bài blog của ZDI chỉ để cần endpoint bug nằm ở đâu nên việc chúng ta cần làm là phải trace ra đoạn code mapping với endpoint đó

Các bạn có thể nhảy thẳng vào Class BIRemotingServlet xem cũng được, nhưng mình sẽ nói sơ mà cách tìm webroot của từng application trong weblogic

Với mỗi application sẽ có 1 file là application.xml để mapping với từng app được deploy lên weblogic, nên dễ nhất các bạn có thể tìm hết tất cả applicatoin.xml trong home directory của weblogic xong rồi grep với endpoint bạn muốn acess tới, từ đó biết được file war deploy nằm ở đâu, xem config khi cần. Trong từng file war cũng có web.xml để define servlet, filter của app đó, như file web.xml của app jbips này như sau

thì ta thấy BIRemotingServlet sẽ mappting vào /messagebroker/as/* nên endpoint cuối cùng là /analytics/jbips/messagebroker/cs/. Tiếp đến chúng ta sẽ trace vào code xử lý tại chỗ này

oracle.bi.nanserver.fwk.servlet.as.BIRemotingServlet.handleRequest()
oracle.bi.nanserver.fwk.servlet.as.RemotingSvs.processCall()
oracle.bi.nanserver.fwk.servlet.as.RemotingSvs.deserializePacket()

Đến đây thì các bạn đã thấy Filter này deserialize AMF stream data khi POST data vào endpoint /analytics/jbips/messagebroker/cs/

Trace tiếp thì ta thấy AMF deserialize sẽ chia làm 2 nhánh, nếu class chúng ta muốn deserialize có implements interfaces Externalizable thì sẽ trigger method readExternal() của class đó đã implement sẵn từ trước. còn không thì sẽ dùng setter để set lại các attribute trong class đó phục vụ cho việc recover lại Object từ stream data.

trigger readExternal()
trigger setter to restore properties of Object

Thông thường thì với chain readExternal() thì có 1 cách chuyển từ readExternal về readObject() qua chain JRMP, ý tưởng cơ bản như sau

readExternal() to readObject()

Nhưng chain này thì lại cần outbound connection để connect tới JRMP server của attacker, các bản có thể tham khảo lại bài blog về RMI của mình về chain này: https://medium.com/cdlabs/rmi-study-note-and-some-study-case-f8cfaec07857, hoặc mình cũng đã giải thích trong slides đã share ở phía trên

Phần tiếp theo chắc có lẽ là phần mình đã improve bug này như thế nào, chuyển từ readExternal() về readObject(). Ban đầu thì mình cũng khá stuck tìm đủ mọi cách, từ việc dùng lại những chain AMF được đề cập trong blog của CodeWhiteSec nhưng khá tiếc vì chain đó không có trong classpath của Oracle BI, nên mình phải tự tìm, vậy cách tìm như thế nào, mình thì không dùng tool gì, chỉ đọc từng method readExternal() trong đống jars đã dump ra. Hướng đi có thể khả quan hơn có thể là dùng modify lại gadget inspector để tìm, nhưng mình đã tìm ra trước khi phải dùng tool, lần sau sẽ thử tool xịn của a Jang :v.

THE GADGET

Và đây là chain mình đã tìm ra, chain này nằm trong library coherence, là một component caching trong bộ fusion middleware của Oracle, do là chúng ta cần cài Fusion Middleware trước khi cài Oracle BI nên library này chắc chắn có trong classpath của app jbips. Khi AMF trigger readExternal của từng class thì stream hiện tại đang làm AMF nên ý tưởng phải tìm method readExternal() xử lý wrap AMF stream về ObjectInputStream, chứ nều cho dùng trong readExternal() có call ra readObject() thì readObject() này cũng là readObject() của AMF stream chứ không phải ObjectInputStream mà mình mong muốn

com.tangosol.coherence.servlet.AttributeHolder
ExternalizableHelper.readObject()
readObjectInternal()
readSerializable()

chúng ta thấy stream AMF của chúng ta được xử quá qua getObjectInput(in, loader) trước khi gọi readObject()

Ở đây chúng ta thấy stream AMF được resolve thành stream khác qua class ResolvingObjectInputStream và khi debug thì stream AMF được convert thành stream khác là WLSObjectStreamFactory, đây cũng là stream mà Weblogic tạo ra để filter blacklist những bug deserialize đã có trong T3 protocol từ trước, và rất có khả năng là chưa filter gadget chain cve 2020 2555 hay cve 2020 2883

Như vậy thì đã xong chain, việc chuyển từ AMF stream về ObjectInputStream đã xong, từ đó kết hợp với gadget chain cve 2020 2555 hay cve 2020 2883 là đã có thể RCE. Đầu tiên generate payload cve 2020 2555 hay cve 2020 2883 sau đó wrap qua AMF stream và gửi lên server là xong !

Nhưng quay lại case server không có outbound connection. Giả sử RCE được thì cũng chỉ là Blind RCE.

Mà đa phần các chain trong ysoserial cuối cùng cũng gọi tới Runtime.getRuntime().exec(“____cmd____”)

Nên chúng ta phải modify lại một xíu là thay vì gọi Runtime.getRuntime().exec() thì gọi ra ScriptEngineManger để eval code, một phần ý tưởng này là có thể run Java code, để tiện cho việc control luồng xử lý sau này, ví dụ như tùy context trên server, nếu có trả Exception về cho client thì lợi dụng việc đó để throw ra output cmd của chúng ta, hoặc control luôn cách trả response của server bằng cách write output cmd qua http response

custom gadget to call ScriptEngineManager

Như vậy thì để PoC “xịn” hơn xíu nữa thì phải nắm bắt được context mà chúng ta đang làm. Ví dụ ở case này server sẽ nhả Exception về qua http response chúng ta có thể throw output cmd để đọc

Nhưng 1 số case server nằm sau nginx nên khi status code 500 sẽ return 1 trang 500 custom của bên đó nên sẽ không read được output cmd. Do đó chúng ta phải tìm cách khác đó là control luồng của weblogic để write output qua http response

Thông thường thì khi handle request các webserver sẽ có những object như request / response cho từng session, thì các object này thường được store trong Thread đang handle connection hiện tại hoặc lưu trong Thread Local của từng Thread. Như ví dụ trong slides Cold Fusion thì đó là một case về Tomcat, còn ở đây là weblogic thì có tý khác biệt. Mình dựa vào bài blog dưới đây mà follow theo:

Các anh Tàu đã tìm ra kịch bản là găm fileless backdoor vào như 1 filter trong webapp (đỉnh vãi nồi -__- ), có gì các bạn đọc thêm cho biết nhé :>

Ý tưởng cơ bản là lấy ra object request / response của Thread hoặc các Thread khác thông qua Thread Local của Thread hiện tại để từ đó có thể control luồng trả về của webserver bằng Java Reflection API !

Để tiện cho việc debug thì IntelliJ có chức năng khá hay ho khi debug là Evaluate Expression (Alt + F8) để Evaluate code trên server, bằng cách nằng có thể view được Thread hiện tại đang store những Object nào, Thread Local có gì, có attribute gì, …

RESULT

Cảm ơn các bạn đã đọc đến đây, chúc các bạn có một 2 ngày cuối tuần thật nhiều 🐞 🐞 🐞

--

--