بلاگ آزمایشگاه امنیت وایت لب بلاگ آزمایشگاه امنیت وایت لب

بررسی اجرای کد از راه دور روی Drupal

| زمان مطالعه : 5 دقیقه
بررسی اجرای کد از راه دور روی Drupal

در ۱۱ آپریل سال ۲۰۱۹، بلاگ ZDI مقاله‌ای با عنوان A SERIES OF UNFORTUNATE IMAGES: DRUPAL 1-CLICK TO RCE EXPLOIT CHAIN DETAILED منتشر کرد. چیزی که در این مقاله بسیار هوشمندانه است استفاده از ۳ آسیب‌پذیری در کنار هم به صورت زنجیره‌ای و در نهایت کمی خلافیت است. حال نگاهی به این آسیب‌پذیری‌ها می‌اندازیم.

نوشتن یک فایل بدون پسوند

در مکانیزم و ساختار دروپال قوانینی وجود دارد. یکی از آن‌ها حفظ نام تصاویر بارگذاری (upload) شده توسط کاربر است. اگر تصاویری که بارگذاری می‌شوند هم‌نام باشند به ترتیب در انتهای آن‌ها _0 و _1 اضافه خواهد شد. همچنین دروپال به منظور حفظ هماهنگی با encodingهای مختلف اقدام به تعویض نویسه‌هایی (charecters) می‌نماید که مقدار آن‌ها کمتر از 0x20 باشد.

اگرچه، اگر در نام پرونده (file) یک نویسه در بازه x80\ و xff\ باشد، PHP یک PREG_BAD_UTF8_ERROR ارسال می‌نماید. اگر یک خطا رخ بدهد، تابع preg_replace مقدار NULL باز می‌گرداند. و در نهایت مقدار $basename مقدار NULL خواهد بود.

هنگامی که basename مقدار NULL داشته باشد، محتوای پرونده زیر در یک پرونده با نام _0 نوشته خواهد شد.

طبق توضیحات داده شده، تصویر در محل زیر بارگذاری می‌شود.


/sites/default/files/pictures/<YYYY-MM>/

لذا آدرس نهایی تصویر به صورت زیر خواهد بود.

/sites/default/files/pictures/<YYYY-MM>/_0

حال زمانی که اجازه بارگذاری تصویر از سمت کارگزار (server) فراهم می‌شود و یا کاربر دسترسی نویسنده داشته باشد، حمله کننده می‌تواند با بارگذاری یک تصویر gif و همان تصویر با نویسه‌های آلوده حمله را شروع کند. چرا که محتوای تصویر آلوده با مقدار _0 در همان محل ذخیره می‌گردد.

ولی اگر دسترسی به این پرونده، مستقیم باشد، احتمال دارد که اجرا نشود. علت این امر به دلایل زیر است:

  1. مرورگر ابتدا بر اساس content-type ای که از طریق کارگزار اعلام شده است، اقدام به نمایش محتوا می‌کند. این درحالی است که مقدار application/octet-stream برای پرونده‌هایی بدون پسوند اعلام می‌شود.
  2. مرورگرها در برخورد با پرونده‌ها از روی محتوا تصمیم گیری می‌کنند. به طور مثال اگر پرونده با مقدار <html> شروع شده باشد، برخی مرورگر‌ها آن را به صورت پرونده html در نظر گرفته و نمایش می‌دهند.
  3. بعضی مرورگرها به صورت پیشفرض مقداری را برای content-type در نظر می‌گیرند ولی اکثر مرورگرها آن‌ها را نادیده می‌گیرند و عملیات parse را انجام نمی‌دهند.

در این لحظه، نیاز داریم تا از یک خلاقیت خاص استفاده نماییم. تگ a می‌تواند نوع محتوا پرونده مورد نظر برای باز شدن را مشخص سازد (تنها برای مرورگر کروم این چنین نیست)

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

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<head>
</head>
<body>
<a id='a' href="http://127.0.0.1/drupal-8.6.2/sites/default/files/2019-04/_6" type="text/html">321321</a>

<script type="text/javascript">
var a = document.getElementById('a')
a.click()
</script>
</body>
</html>

زمانی که قربانی به این صفحه دسترسی پیدا کند، می‌توان یک حمله از نوع xss انجام داد. که یک موقعیت خوب برای حمله را ایجاد می‌کند. که در این صورت یک محیط خارجی برای اجرای js خواهیم داشت.

اجرای کد از راه دور با استفاده از phar deserialization

در کنفرانس BlackHat سال 2018، سم توماس به یک جریان api یا Stream API در PHP اشاره کرده بود. Phar یک Wrapper برای PHP است که از این جریان داده‌ای استفاده می‌کند.

محقق امنیتی Seaii در Knownsec تیم ۴۰۴ اشاره کرده بود که تمامی پرونده‌های توابع از stream wrapperها پیشتیبانی می‌کنند. همچنین اشاره کرد، اگر بتوان یک پرونده تابعی قابل کنترل و دستکاری یافت که بتوان مقادیر ورودی آن را تحت کنترل در آورد، می‌توان به وسیله پرونده phar، دستور دلخواه خود را با استفاده از deserializer اجرا کرد.

در دروپال، یک تابع سیستمی وجود دارد که درباره آدرس‌های ورودی تصمیم گیری می‌کند. is_dir باعث بروز مشکل امنیتی در این قسمت خواهد شد.

با استفاده از کدهای زیر کد مخرب را تولید می‌کنیم

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
<?php

namespace GuzzleHttp\Psr7{
class FnStream{

public $_fn_close = "phpinfo";

public function __destruct()
{
if (isset($this->_fn_close)) {
call_user_func($this->_fn_close);
}
}
}
}

namespace{
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$o = new \GuzzleHttp\Psr7\FnStream();
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
}
?>

پس از ایجاد تغییر در پسوند به png و ارسال این تصویر به کارگزار، اقدام به تنظیم آن به عنوان یک file system می‌نماییم.


phar://./sites/default/files/2019-04/drupal.png

و می‌توان آن را اجرا کرد.