WannaCTF WriteUp JaEcho (Web)

Jang
6 min readDec 10, 2022

Hồi sáng đi học về thì mấy thằng em có rủ vào chơi CTF, cũng lâu lắm rồi chưa chơi CTF nên tiện cũng tạt qua chơi cùng luôn.

Giải này theo mình biết được tổ chức bởi CNCS gì đó thuộc UIT (nghe cứ như rep 1:1 của NCSC nhỉ ಠ_ಠ), nhìn chung chất lượng đề thì cũng khá tốt, chỉ có điều là nhồi nhét quá nhiều thứ vào nên trông nó như cái nồi lẩu vậy.

Mình bắt đầu join từ lúc 11h, lúc đó Web chỉ có vỏn vẹn 2 bài, biết làm mỗi bài Christmas Card còn mấy bài khác thì chịu chết ¯\(°_o)/¯.

Tới tầm 2h, mình F5 scoreboard và nhận ra có thêm tận 2 chall mới, trong đó có 1 chall về Java Deserialization. Mình tập trung vào làm chall này cho đến tận cuối giờ (vẫn ko kịp).

#Writeup

Chall: http://45.122.249.68:20003/chall

Material: https://drive.google.com/file/d/1inqZGveRNQrpJFlxp28Bx8X8DC4CDSpG/view?usp=sharing

Về nội dung của chall này thì cũng ko có gì nhiều lắm, web app nằm phía sau con proxy nginx.

Proxy này config để chặn các request tới /chall/something cũng như các kỹ thuật bypass proxy phổ biến như .;/, ..;/

Tuy nhiên nếu để ý kỹ vào phần config proxy_pass, ta có thể thấy location nhận vào là /chall, còn proxy_pass target là http://web:8080/chall/

location /chall {
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://web:8080/chall/;
}

Có nghĩa là tất cả các request bắt đầu bằng “/chall” sẽ được forward tới “/chall/”, ví dụ như: /challABCD sẽ được forward tới/chall/ABCD

¯\_(ツ)_/¯ Nói đơn giản là không cần bypass gì hết, url để access tới phần đã bị filter ở proxy là target/challsomething

Tiếp tục với phần web backend, phần file docker không có gì khác biệt lắm so với thông thường, tuy nhiên cần chú ý tới file: java.security

Nội dung của file này có 1 dòng đề cập tới việc filter Serialization, các class sau sẽ bị reject khi deserialize ở bất kỳ nơi đâu:

jdk.serialFilter=!java.util.HashMap;!java.util.HashTable;!com.rometools.rome.feed.impl.ObjectBean;!sun.print.UnixPrintService;!javax.management.BadAttributeValueExpException;
  • java.util.HashMap
  • java.util.HashTable
  • com.rometools.rome.feed.impl.ObjectBean
  • sun.print.UnixPrintService
  • javax.management.BadAttributeValueExpException

( ͡° ͜ʖ ͡°) nhớ chi tiết bôi đậm nhé

Quay trở lại với path “/chall/something”, chúng ta có SomethingServlet, sẽ check parameter strxem có hashCode = hashCode của to^_with_<3 hay không. Sau đó sẽ đưa vào Deserialize.

Ở đây có 2 phần cần khai thác, phần 1 là làm sao tìm được chuỗi collision và build deserialize payload.

Sau khi dạo một vòng google, thằng em có tìm được link sau: https://goodapple.top/archives/964

Với ý tưởng y chang, cũng là tìm hash collision và deserialize, tham khảo writeup này mình có tìm được chuỗi phù hợp là:

uP^_with_<3

Tiếp tục là tới phần Build payload deserialize, dựa vào list lib có trong class path, ta có thể thấy gadgetchain ROME có thể được sử dụng:

Tuy nhiên, như đã đề cập phía trên, một số class đã bị đưa vào blacklist, và tiêu biểu là class HashMap, và ObjectBean của gadgetchain ROME.

Sau khi giành thời gian tham khảo link writeup phía trên, mình mới nhận ra là không cần sử dụng ObjectBean cũng có thể build payload deser -> RCE thoải mái (do JDK thấp chưa có giới hạn gì ở chỗ class TemplatesHandlerImpl)

Và phần dưới đây sẽ là giải thích về cách hoạt động của Gadgetchain này.

Đi ngược từ dưới lên, ta sẽ bắt đầu tại phần sink ToStringBean.toString(String) (mặc dù thực tế sink là tại TemplatesHandlerImpl.getOutputProperties(), tuy nhiên nó đã xuất hiện quá nhiều trong các gadgetchain về java nên mình sẽ bỏ qua). Tại đây, các properties được lấy từ this.beanClass, và từ đó lấy ra các getter method tương ứng với properties, sau đó sẽ invoke getter method này:

private String toString(String prefix) {
StringBuffer sb = new StringBuffer(128);

try {
List<PropertyDescriptor> propertyDescriptors = BeanIntrospector.getPropertyDescriptorsWithGetters(this.beanClass);
Iterator var10 = propertyDescriptors.iterator();

while(var10.hasNext()) {
PropertyDescriptor propertyDescriptor = (PropertyDescriptor)var10.next();
String propertyName = propertyDescriptor.getName();
Method getter = propertyDescriptor.getReadMethod();
Object value = getter.invoke(this.obj, NO_PARAMS);
this.printProperty(sb, prefix + "." + propertyName, value);
}
} catch (Exception var9) {
LOG.error("Error while generating toString", var9);
Class<? extends Object> clazz = this.obj.getClass();
String errorMessage = var9.getMessage();
sb.append(String.format("\n\nEXCEPTION: Could not complete %s.toString(): %s\n", clazz, errorMessage));
}

Từ đây có thể lợi dụng để gọi tới TemplatesHandlerImpl.getOutputProperties() để trigger RCE.

Method gọi tới ToStringBean.toString(String) chính là ToStringBean.toString(), method này có thể được gọi tới từ nhiều nơi. Trước đó có 1 phương pháp phổ biến đó là dùng BadAttributeValueExpException.readObject() để gọi tới Object.toString(), tuy nhiên class này hiện đang bị chặn nên không thể dùng được!

Đây là lý do mà ta tiếp tục cần dùng tới EqualsBean, method EqualsBean.hashCode()gọi tới EqualsBean.beanHashCode() , từ đó gọi tiếp tới Object.toString()

Để trigger Object.hashCode() thì cũng có nhiều cách, trong đó cách phổ biến nhất đó là sử dụng tới HashMap, HashSet, hoặc các tính năng tương tự … Do HashMap đã bị chặn, mình quyết định sẽ tìm ra 1 class khác tương tự để lợi dụng.

Quay trở lại với phần blacklist đã đề cập phía trên, như mình đã note trước về class java.util.HashTable. Trong java không có class nào có tên như vậy cả, có lẽ class mà bạn tác giả muốn chặn là java.util.Hashtable

Dựa vào đây mình có thể tìm ra rất nhiều implementation của Hashtable mà có dùng tới hashCode() khi deserialize, tiêu biểu là java.security.Provider.readObject()

Khi deserialize, một HashMap sẽ được khởi tạo, sau đó sẽ lấy table entry từ serializable data và thực hiện put lần lượt các entry này vào HashMap. Method HashMap.put() sẽ tính hash của key bằng cách gọi tới key.hashCode()

Như vậy về lý thuyết hoạt động của gadgetchain này đã xong, chỉ cần tìm thêm class implement java.security.Provider là xong, ta có full chain như sau:

sun.security.provider.Sun.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
EqualsBean.hashCode()
ToStringBean.toString()
TemplatesHandlerImpl.getOutputProperties()
Method.invoke()

Payload generator:

Flag:

http://45.122.249.68:20003/challdaphuc.txt

Challenge này có lẽ sẽ hay hơn nếu được release từ sớm, với một người gọi là có thời gian làm việc với java hơi lâu một chút như mình cũng cảm thấy không đủ thời gian để thực hiện tất cả các bước trên,

Và mình cũng không chắc đây là intended solution của tác giả,

Thanks for reading!

--

--