Old men play SV ATTT (Writeup for “Isolate” challenge)

Jang
11 min readOct 19, 2021

Quả title nửa tây nửa ta cũng hơi khó nghe nhỉ, nhưng mình vẫn thích hơn là cái tên mới của cuộc thi này: acis, asis, ash*t gì gì đó mà mãi vẫn ko thể nhớ đc 🤷‍.

Mình cũng đã từng enjoy cái cuộc thi này for 3 năm, có lẽ là do trình độ chưa đủ nên trong 3 năm mình đi thi, chưa năm nào mang lại thành tích cao, chỉ toàn là thành tích bất hảo (unintend flag grep, leo root, sửa flag, fw traffic).

Giờ ngẫm lại có vẻ như mục đích của mình đi thi là để cho vui, chọc ngoáy người khác thì đúng hơn.

Năm nay SV ATTT vẫn diễn ra, với một cái tên mới, lứa thí sinh cũng mới hết, chỉ có BTC, người ra đề vẫn là những người cũ, những người vẫn còn tâm huyết với cuộc thi này.

Không tham gia nhưng, mình vẫn luôn theo dõi cuộc thi, và ngóng chờ một tia sáng len lói nào đó trong số hơn 2 trăm sinh viên kia. Đứng ở vị trí nằm ngoài cuộc thi đem lại một cái nhìn nào đó khác hẳn, khách quan hơn, và không còn những sự lo lắng, hồi hộp mỗi khi giờ thi bắt đầu nữa.

Và cũng vui thay, khi năm nay KMA có tới 2 cái giải nhất vòng loại … Tự dưng lại thấy trong lòng cay cay …

.

.

.

Thôi tâm tư cũng hơi dài, bắt đầu vào phần writeup chính nào,

Mình được nghe nói trong các chall web, có 1 challenge về java, và có xin đc source code + link chall để thử sức.

Link chall: http://167.172.85.253:8010/

Source code: http://transfer.sh/Eca9uB/isolate_starterpack_p7-3.zip

Trong source code đã bao gồm cả file docker, make nên việc setup rất nhẹ nhàng, chỉ cần make rồi docker-compose up là chạy,

Content của docker file như sau:

Env được xây dựng dựa trên docker image: tomcat:8.5.51-jdk8

Image này có phiên bản Java là JDK 8U242 (cần lưu ý chính xác version của java, nếu không quá trình debug sau đó sẽ bị lỗi nhiều)

Để xem xét kỹ lưỡng hơn thì mình deploy file war lên môi trường tomcat debug trước,

File war của challenge này rất đơn giản, chỉ có mỗi index.jsp và 1 class ISOServlet để handle các request:

Nội dung của ISOServlet cũng khá đơn giản, ta có thể nhìn thấy ra ngay lỗ hổng mà tác giả muốn người chơi tìm thấy:

Servlet này sẽ nhận vào một đoạn JSON, nếu trong nội dung không chứa chuỗi “@type” thì sẽ được đưa vào com.alibaba.fastjson.JSON.parse() để xử lý tiếp.

Đối với những người đã từng làm về Java, thì có thể nhận ra ngay việc sử dụng fastjson trực tiếp như vậy có thể dẫn đến việc “Insecure Deserialization”. Còn đối với các thí sinh chưa biết tới có thể sẽ tốn thêm chút thời gian, nhanh hay chậm sẽ dựa vào khả năng tối ưu hóa keyword google search, ví dụ như keyword đơn giản như vậy cũng ra được thứ đang cần tìm ở bài này:

*Bypass filter

Tuy nhiên có một vấn đề đầu tiên cần xử lý, đó là bị chặn chuỗi “@type”, nếu không thể truyền vào “@type” thì việc khai thác fastjson deserialization cũng coi như bỏ!

Khi hết giải, mình có đọc writeup của một số bạn về chall này, thấy rất nhiều trường hợp sử dụng các ký tự unicode, dạng “\u0040type” để bypass filter. Thật trùng hợp, mình cũng vừa mới viết 1 bài phân tích về OGNL Injection trên Confluence, cũng lợi dụng unicode để escape, không biết có sự liên hệ gì ở đây không ( ͡° ͜ʖ ͡°), mà khi đọc wu không thấy các bạn nói tại sao lại có thể sử dụng unicode cho chall này. Do vậy, nên ở đây mình cũng viết chi tiết luôn, tại sao lại có thể sử dụng unicode escape trong fastjson.

Để hiểu rõ đoạn này, set 1 bp tại JSONLexerBase.scanSymbol(). Trong quá trình parse json string, khi bắt gặp các ký tự “\”, sẽ có một nhánh xử lý riêng biệt cho các ký tự sau nó:

Nếu tiếp theo sau “\” là các ký tự “u” hoặc “x” (\u, \x) thì các ký tự liền sau đó được đưa vào parse sang các dạng unicode hoặc hex,

Kết quả sau khi được xử lý:

Đó là lý do tại sao các bạn có thể sử dụng unicode escape để bypass filter ở đây,

Dựa vào logic của code trên thì, cũng có thể sử dụng các ký tự hex escape để bypass filter ¯\_(ツ)_/¯.

#BUGS

Ban đầu khi nghe tới fastjson, mình liền nhớ tới đợt DEFCON vừa rồi cũng có 1 bài talk về 1 cái chain mới để RCE trên fastjson 1.2.68 (https://media.defcon.org/DEF%20CON%2029/DEF%20CON%2029%20presentations/Hao%20Xing%20Zekai%20Wu%20-%20How%20I%20use%20a%20JSON%20Deserialization%200day%20to%20Steal%20Your%20Money%20On%20The%20Blockchain.pdf)

Tuy nhiên, khi áp dụng nguyên cái payload vào thì bị gặp ngay lỗi như sau:

¯\_(ツ)_/¯, đương nhiên rồi, làm sao có thể copy paste 1 phát ăn luon được chứ, có làm mới có ăn, không làm thì chỉ có …

Stacktrace của lỗi:

Sau một hồi trace, mình mới để ý tới phiên bản fastjson của challenge này khá thấp, chỉ là fastjson 1.2.24. Trong đó, method để lấy constructor từ dữ liệu chưa được hoàn thiện, nó chỉ process với các constructor có annotation @JSONCreator.

Payload trong bài talk BH áp dụng cho version 1.2.68, phiên bản này đã có bổ sung nhiều đoạn xử lý, cho phép khởi tạo các constructor được tự do thoải mái hơn:

Với phiên bản hiện tại là fastjson 1.2.24, mình có search qua 1 vòng google được một gadgetchain khả thi như sau:

Tuy nhiên, gadget này đòi hỏi phải có internet mới có thể sử dụng được, nên câu chuyện lại đi vào betak!

Sau đó, được thằng em xã hội vứt cho cái link sau https://blog.csdn.net/qq_27446553/article/details/79940923

Đại khái là trong này có sử dụng gadget defineClass -> RCE, không cần outbound gì cả, nghe cũng rất khả quan!

Dựa vào keyword BCEL này, mình tìm tiếp ra được 1 repo trên github, có chứa cả PoC ăn luôn (https://github.com/depycode/fastjson-local-echo)

(Khi cuộc thi kết thúc, mình mới biết là hóa ra tất cả các team solved chall này đều sử dụng PoC mẫu tại đây 🤣.)

Thực tế thì, cho tới thời điểm viết blog này mình cũng vẫn chưa hiểu hết về cách mà gadget trên có thể được trigger, do một số đoạn code không debug được, bị obfuscate hay một cách nào nào đó 🤷‍♀️.

Thứ mình có thể show ra ở đây tạm thời là cái stacktrace dẫn tới đoạn trigger gadget,

Theo cách hiểu của mình, tại đây, gadget được ép sử dụng thành key, nên method toString() đã được gọi -> trigger các getter method.

Ở đây, method getter cần được trigger là BasicDataSource.getConnection():

Từ createDataSource() sẽ gọi tới createConnectionFactory(), tại đây, Class.forName() được gọi với các tham số “driverClassName” và “driverClassLoader” đã bị thao túng:

Trong đó, driverClassLoader lúc này là: com.sun.org.apache.bcel.internal.util.ClassLoader

Class này chỉ tồn tại trên Java < 8u251, cho nên ngay từ đầu mình đã nhắc về tầm quan trọng của việc setup đúng version Java.

Tiếp tục soi kỹ thêm vào com.sun.org.apache.bcel.internal.util.ClassLoader.loadClass(), tại đây, nếu className được bắt đầu bằng “$$BCEL$$”, chương trình sẽ đi vào nhánh createClass() với className được truyền vào:

Nếu class vừa được define có các static method thì nó sẽ được process ngay khi define xong, và từ đó có thể được lợi dụng và RCE. (Khá giống một case của weblogic, đã được wu tại đây)

Để test RCE, sử dụng đoạn code sau để convert class file sang dạng BCEL:

public static String class2BCEL(String classFile) throws Exception{
Path path = Paths.get(classFile);
byte[] bytes = Files.readAllBytes(path);
String result = Utility.encode(bytes,true);
return "$$BCEL$$"+result;
}

Class trigger RCE:

Và kết quả là đã trigger được RCE trên lab:

.

.

Tuy nhiên có một vấn đề lớn hơn, là trên môi trường của chall không hề có internet, webroot cũng đã bị set thành Read Only.

Việc backconnect để cat flag hoàn toàn không khả thi, để lấy được flag cần phải có một cách khác, không sử dụng internet, ví dụ như leak qua DNS hoặc viết PoC Tomcat Echo.

Mình được biết trong quá trình thi, đa số đều sử dụng DNS để leak flag ra, cách làm này cũng không khó lắm nên mình sẽ không nhắc tới ở đây.

Trong bài này mình sẽ viết về cách để lấy được response trực tiếp mà không cần DNS hay outbound gì cả!

(*Có thể sử dụng Exception base để đẩy response, cách làm này đã được kiểm chứng bởi một thằng em xã hội).

#TomcatEchoInject

PoC này được kết hợp từ 2 payload sau TomcatEchoInject TomcatShellInject

Với TomcatEchoInject có nhiệm vụ là set giá trị cho field ApplicationDispatcher.WRAP_SAME_OBJECT

Trong Tomcat, field WRAP_SAME_OBJECT bắt buộc phải là true thì các field lastServicedRequest lastServicedResponse mới được set

Phải dụng thủ đoạn như vậy là vì với các Servlet thông thường, có override các method doPost(), doGet() là có thể sử dụng luôn các HttpServletRequest hoặc Response rồi. Trong bối cảnh hiện tại là đang thực thi code tại một nơi khác, nên phải sử dụng tới ApplicationFilterChain.lastServicedRequest để có thể tác động tới HttpRequest hiện tại.

Sau khi sửa một số thứ trong payload để phù hợp với trường hợp fastjson này, lắp vào thử và gặp ngay lỗi như sau:

Với Class mình sử dụng cho payload này:

Sau một hồi debug, mình mới phát hiện ra trong context hiện tại của BCEL, không có classloader nào có chứa các class của tomcat (ApplicationDispatcher, ApplicationFilterChain, …).

Lúc này trong đầu mình có 1 idea: nếu classloader hiện tại không có các class này, thì mình có thể add 1 cái classloader mới cho nó lookup trong đó là xong …

.

.

Việc load class khi đó sẽ trông như thế này:

Idea này đã được thực hiện và thất bại :(

Lý do lần này hơi loằn ngoằn 1 chút,

Đại thể là khi load 1 cái class từ classLoader X, sau đó mình xử lý các thao tác ở trên cái class đó thì các cái static value nó chỉ được lưu lại trên đúng cái classloader đó mà thôi,

Trong đó cái classloader lại được thêm bằng tay, sẽ bị mất đi khi đoạn code thực thi này đi qua,

Nếu muốn set đc cái static field WRAP_SAME_OBJECT kia, cần phải bằng cách nào đó lấy được chính cái ClassLoader của Tomcat …

Vấn đề này sau cuộc thi, mình có nói chuyện với tác giả và được biết có thể sử dụng Thread.currentThread().getContextClassLoader() để lấy được classloader của tomcat:

Như vậy đã xử lý được vấn đề mấu chốt, tuy nhiên vẫn là do vấn đề về classloader, cho nên tất cả các đoạn gọi method, field về sau phải chuyển sang dạng Reflection mới có thể hoạt động được, ví dụ:

Sẽ được chuyển sang dạng Reflection do việc ép kiểu sang HttpServletRequest không hợp lệ (HttpServletRequest không tồn tại trong context này):

Còn đây là thành quả sau khi chày cối 77 49 lần fix =))) …

Còn đây là mã khai thác mình sử dụng để exec & echo: https://gist.github.com/testanull/2f56dc549fc6aa5abd12e1b10bbea8e9

Cảm ơn các bạn đã theo dõi!

--

--