Translated ['src/pentesting-web/hacking-with-cookies/README.md'] to uk

This commit is contained in:
Translator 2025-05-18 03:05:41 +00:00
parent 9a28c87778
commit 3242183e81
2 changed files with 169 additions and 148 deletions

View File

@ -8,7 +8,7 @@ Cookies мають кілька атрибутів, які контролюют
### Expires and Max-Age
Дата закінчення терміну дії cookie визначається атрибутом `Expires`. У свою чергу, атрибут `Max-age` визначає час у секундах, протягом якого cookie буде видалено. **Вибирайте `Max-age`, оскільки це відображає більш сучасні практики.**
Дата закінчення терміну дії cookie визначається атрибутом `Expires`. Натомість атрибут `Max-age` визначає час у секундах, протягом якого cookie буде видалено. **Вибирайте `Max-age`, оскільки це відображає більш сучасні практики.**
### Domain
@ -48,7 +48,7 @@ Table from [Invicti](https://www.netsparker.com/blog/web-security/same-site-cook
Cookie з атрибутом _**SameSite**_ **зменшить атаки CSRF**, де потрібна активна сесія.
**\*Зверніть увагу, що з Chrome80 (лютий 2019) стандартна поведінка cookie без атрибута cookie samesite** **буде lax** ([https://www.troyhunt.com/promiscuous-cookies-and-their-impending-death-via-the-samesite-policy/](https://www.troyhunt.com/promiscuous-cookies-and-their-impending-death-via-the-samesite-policy/)).\
Зверніть увагу, що тимчасово, після застосування цієї зміни, **cookie без політики SameSite** **в Chrome будуть** **оброблятися як None** протягом **перших 2 хвилин, а потім як Lax для запиту POST на верхньому рівні між сайтами.**
Зверніть увагу, що тимчасово, після застосування цієї зміни, **cookie без політики SameSite** **в Chrome будуть** **оброблятися як None** протягом **перших 2 хвилин, а потім як Lax для запиту POST на верхньому рівні з крос-доменом.**
## Cookies Flags
@ -58,9 +58,9 @@ Cookie з атрибутом _**SameSite**_ **зменшить атаки CSRF**
#### **Bypasses**
- Якщо сторінка **надсилає cookie у відповідь** на запити (наприклад, на сторінці **PHPinfo**), можливо зловживати XSS, щоб надіслати запит на цю сторінку та **вкрасти cookie** з відповіді (перевірте приклад на [https://hackcommander.github.io/posts/2022/11/12/bypass-httponly-via-php-info-page/](https://hackcommander.github.io/posts/2022/11/12/bypass-httponly-via-php-info-page/)).
- Якщо сторінка **надсилає cookie у відповідь** на запити (наприклад, на сторінці **PHPinfo**), можливо зловживати XSS, щоб надіслати запит на цю сторінку та **вкрасти cookie** з відповіді (перевірте приклад у [https://blog.hackcommander.com/posts/2022/11/12/bypass-httponly-via-php-info-page/](https://blog.hackcommander.com/posts/2022/11/12/bypass-httponly-via-php-info-page/)).
- Це можна обійти за допомогою **TRACE** **HTTP** запитів, оскільки відповідь сервера (якщо цей HTTP метод доступний) відобразить надіслані cookie. Цю техніку називають **Cross-Site Tracking**.
- Цю техніку уникають **сучасні браузери, не дозволяючи надсилати запит TRACE** з JS. Однак деякі обходи були знайдені в специфічному програмному забезпеченні, наприклад, надсилаючи `\r\nTRACE` замість `TRACE` до IE6.0 SP2.
- Цю техніку уникають **сучасні браузери, не дозволяючи надсилати TRACE** запит з JS. Однак деякі обходи цього були знайдені в специфічному програмному забезпеченні, наприклад, надсилаючи `\r\nTRACE` замість `TRACE` до IE6.0 SP2.
- Інший спосіб - це експлуатація вразливостей нульового дня браузерів.
- Можливо **перезаписати HttpOnly cookie**, виконуючи атаку переповнення Cookie Jar:
@ -68,11 +68,11 @@ Cookie з атрибутом _**SameSite**_ **зменшить атаки CSRF**
cookie-jar-overflow.md
{{#endref}}
- Можливо використовувати атаку [**Cookie Smuggling**](#cookie-smuggling) для ексфільтрації цих cookie
- Можливо використовувати [**Cookie Smuggling**](#cookie-smuggling) атаку для ексфільтрації цих cookie.
### Secure
Запит **тільки** надішле cookie в HTTP запиті, якщо запит передається через безпечний канал (зазвичай **HTTPS**).
Запит буде **тільки** надсилати cookie в HTTP запиті, якщо запит передається через захищений канал (зазвичай **HTTPS**).
## Cookies Prefixes
@ -89,11 +89,11 @@ Cookie, що починаються з `__Secure-`, повинні бути вс
### Overwriting cookies
Отже, одне з захистів cookie з префіксом `__Host-` - це запобігання їх перезапису з піддоменів. Запобігання, наприклад, [**Cookie Tossing attacks**](cookie-tossing.md). У доповіді [**Cookie Crumbles: Unveiling Web Session Integrity Vulnerabilities**](https://www.youtube.com/watch?v=F_wAzF4a7Xg) ([**paper**](https://www.usenix.org/system/files/usenixsecurity23-squarcina.pdf)) представлено, що було можливо встановити cookie з префіксом \_\_HOST- з піддомену, обманюючи парсер, наприклад, додаючи "=" на початку або на початку і в кінці...:
Отже, одне з захистів cookie з префіксом `__Host-` - запобігти їх перезапису з піддоменів. Запобігаючи, наприклад, [**Cookie Tossing attacks**](cookie-tossing.md). У доповіді [**Cookie Crumbles: Unveiling Web Session Integrity Vulnerabilities**](https://www.youtube.com/watch?v=F_wAzF4a7Xg) ([**paper**](https://www.usenix.org/system/files/usenixsecurity23-squarcina.pdf)) представлено, що було можливо встановити cookie з префіксом \_\_HOST- з піддомену, обманюючи парсер, наприклад, додаючи "=" на початку або на початку та в кінці...:
<figure><img src="../../images/image (6) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
Або в PHP було можливо додати **інші символи на початку** назви cookie, які будуть **замінені на символи підкреслення**, що дозволяє перезаписати cookie `__HOST-`:
Або в PHP було можливо додати **інші символи на початку** назви cookie, які будуть **замінені на символи підкреслення**, що дозволяє перезаписати cookie з префіксом `__HOST-`:
<figure><img src="../../images/image (7) (1) (1) (1) (1).png" alt="" width="373"><figcaption></figcaption></figure>
@ -103,15 +103,15 @@ Cookie, що починаються з `__Secure-`, повинні бути вс
### Decoding and Manipulating Cookies
Чутливі дані, вбудовані в cookie, завжди повинні бути перевірені. Cookie, закодовані в Base64 або подібних форматах, часто можуть бути декодовані. Ця вразливість дозволяє зловмисникам змінювати вміст cookie та видавати себе за інших користувачів, закодовуючи їх модифіковані дані назад у cookie.
Чутливі дані, вбудовані в cookie, завжди повинні бути перевірені. Cookie, закодовані в Base64 або подібних форматах, часто можуть бути декодовані. Ця вразливість дозволяє зловмисникам змінювати вміст cookie та видавати себе за інших користувачів, закодовуючи їх змінені дані назад у cookie.
### Session Hijacking
Ця атака передбачає крадіжку cookie користувача для отримання несанкціонованого доступу до їх облікового запису в додатку. Використовуючи вкрадену cookie, зловмисник може видавати себе за законного користувача.
Ця атака передбачає крадіжку cookie користувача для отримання несанкціонованого доступу до їх облікового запису в додатку. Використовуючи вкрадений cookie, зловмисник може видавати себе за законного користувача.
### Session Fixation
У цьому сценарії зловмисник обманює жертву, щоб вона використовувала конкретну cookie для входу. Якщо додаток не призначає нову cookie під час входу, зловмисник, маючи оригінальну cookie, може видавати себе за жертву. Ця техніка залежить від того, що жертва входить з cookie, наданою зловмисником.
У цьому сценарії зловмисник обманює жертву, щоб вона використовувала конкретний cookie для входу. Якщо додаток не призначає новий cookie під час входу, зловмисник, маючи оригінальний cookie, може видавати себе за жертву. Ця техніка залежить від того, що жертва входить з cookie, наданим зловмисником.
Якщо ви знайшли **XSS у піддомені** або **контролюєте піддомен**, прочитайте:
@ -131,13 +131,13 @@ cookie-tossing.md
### [JWT Cookies](../hacking-jwt-json-web-tokens.md)
Натисніть на попереднє посилання, щоб отримати доступ до сторінки, що пояснює можливі недоліки в JWT.
Натисніть на попереднє посилання, щоб отримати доступ до сторінки, що пояснює можливі вразливості в JWT.
JSON Web Tokens (JWT), що використовуються в cookie, також можуть мати вразливості. Для отримання детальної інформації про потенційні недоліки та способи їх експлуатації рекомендується звернутися до пов'язаного документа про злом JWT.
JSON Web Tokens (JWT), що використовуються в cookie, також можуть мати вразливості. Для отримання детальної інформації про потенційні вразливості та способи їх експлуатації рекомендується звернутися до пов'язаного документа про хакінг JWT.
### Cross-Site Request Forgery (CSRF)
Ця атака змушує увійшовшого користувача виконувати небажані дії на веб-додатку, в якому він наразі аутентифікований. Зловмисники можуть використовувати cookie, які автоматично надсилаються з кожним запитом до вразливого сайту.
Ця атака змушує увійшовшого користувача виконувати небажані дії на веб-додатку, в якому вони наразі аутентифіковані. Зловмисники можуть використовувати cookie, які автоматично надсилаються з кожним запитом до вразливого сайту.
### Empty Cookies
@ -147,7 +147,7 @@ document.cookie = "a=v1"
document.cookie = "=test value;" // Setting an empty named cookie
document.cookie = "b=v2"
```
Результат у заголовку cookie становить `a=v1; test value; b=v2;`. Цікаво, що це дозволяє маніпулювати cookie, якщо встановлено cookie з порожнім ім'ям, потенційно контролюючи інші cookie, встановлюючи порожній cookie на конкретне значення:
Результат у заголовку cookie `a=v1; test value; b=v2;`. Цікаво, що це дозволяє маніпулювати cookie, якщо встановлено cookie з порожнім ім'ям, потенційно контролюючи інші cookie, встановлюючи порожній cookie на конкретне значення:
```js
function setCookie(name, value) {
document.cookie = `${name}=${value}`
@ -155,11 +155,11 @@ document.cookie = `${name}=${value}`
setCookie("", "a=b") // Setting the empty cookie modifies another cookie's value
```
Це призводить до того, що браузер надсилає заголовок cookie, який кожен веб-сервер інтерпретує як cookie з ім'ям `a` та значенням `b`.
Це призводить до того, що браузер надсилає заголовок cookie, який кожен веб-сервер інтерпретує як cookie з назвою `a` та значенням `b`.
#### Chrome Bug: Проблема з кодовими точками сурогатів Unicode
#### Chrome Bug: Проблема з кодовими точками сурогатного Юнікоду
У Chrome, якщо кодова точка сурогата Unicode є частиною встановленого cookie, `document.cookie` стає пошкодженим, повертаючи порожній рядок надалі:
У Chrome, якщо кодова точка сурогату Юнікоду є частиною встановленого cookie, `document.cookie` стає пошкодженим, повертаючи порожній рядок наступним чином:
```js
document.cookie = "\ud800=meep"
```
@ -179,7 +179,7 @@ RENDER_TEXT="hello world; JSESSIONID=13371337; ASDF=end";
- Zope шукає кому, щоб почати парсинг наступної кукі.
- Класи кукі Python починають парсинг з символу пробілу.
Ця вразливість особливо небезпечна в веб-додатках, які покладаються на захист CSRF на основі кукі, оскільки це дозволяє зловмисникам ін'єктувати підроблені кукі CSRF-токенів, потенційно обходячи заходи безпеки. Проблема ускладнюється обробкою Python дублікатів імен кукі, де останнє входження перекриває попередні. Це також викликає занепокоєння щодо кукі `__Secure-` і `__Host-` в небезпечних контекстах і може призвести до обходу авторизації, коли кукі передаються на сервери, що піддаються підробці.
Ця вразливість особливо небезпечна в веб-додатках, які покладаються на захист CSRF на основі кукі, оскільки це дозволяє зловмисникам ін'єктувати підроблені кукі CSRF-токенів, потенційно обходячи заходи безпеки. Проблема ускладнюється обробкою Python дублікатів імен кукі, де останнє виникнення перекриває попередні. Це також викликає занепокоєння щодо кукі `__Secure-` і `__Host-` в небезпечних контекстах і може призвести до обходу авторизації, коли кукі передаються на сервери, що піддаються підробці.
### Кукі $version
@ -189,16 +189,15 @@ RENDER_TEXT="hello world; JSESSIONID=13371337; ASDF=end";
#### Атака "Кукі-сендвіч"
Згідно з [**цією статтею**](https://portswigger.net/research/stealing-httponly-cookies-with-the-cookie-sandwich-technique), можливо використовувати техніку кукі-сендвіча для викрадення HttpOnly кукі. Ось вимоги та кроки:
Згідно з [**цією статтею**](https://portswigger.net/research/stealing-httponly-cookies-with-the-cookie-sandwich-technique), можливо використовувати техніку "кукі-сендвіч" для викрадення HttpOnly кукі. Ось вимоги та кроки:
- Знайти місце, де очевидно безкорисна **кукі відображається у відповіді**
- **Створити кукі з назвою `$Version`** зі значенням `1` (ви можете зробити це в атаці XSS з JS) з більш специфічним шляхом, щоб вона отримала початкову позицію (деякі фреймворки, такі як Python, не потребують цього кроку)
- **Створити кукі, яка відображається** зі значенням, що залишає **відкриті подвійні лапки** і з конкретним шляхом, щоб вона була розташована в базі кукі після попередньої (`$Version`)
- Тоді легітимна кукі піде наступною в порядку
- Потім легітимна кукі буде йти наступною в порядку
- **Створити фальшиву кукі, яка закриває подвійні лапки** всередині свого значення
Таким чином, кукі жертви потрапляє в пастку всередині нової кукі версії 1 і буде відображатися щоразу, коли вона відображається.
e.g. з посту:
Таким чином, кукі жертви потрапляє в пастку всередині нової кукі версії 1 і буде відображатися щоразу, коли вона відображається.
```javascript
document.cookie = `$Version=1;`;
document.cookie = `param1="start`;
@ -209,7 +208,7 @@ document.cookie = `param2=end";`;
#### Cookies $version
Перевірте попередній розділ.
Перегляньте попередній розділ.
#### Обхід аналізу значень з кодуванням quoted-string
@ -220,7 +219,7 @@ document.cookie = `param2=end";`;
#### Обхід блокувань імен cookie
У RFC2109 вказано, що **кома може використовуватися як роздільник між значеннями cookie**. Також можливо додавати **пробіли та табуляції перед і після знака рівності**. Тому cookie, як-от `$Version=1; foo=bar, abc = qux`, не генерує cookie `"foo":"bar, admin = qux"`, а генерує cookie `foo":"bar"` та `"admin":"qux"`. Зверніть увагу, як генеруються 2 cookie і як з admin видалено пробіл перед і після знака рівності.
У RFC2109 вказано, що **кома може використовуватися як роздільник між значеннями cookie**. Також можливо додавати **пробіли та табуляції перед і після знака рівності**. Тому cookie на зразок `$Version=1; foo=bar, abc = qux` не генерує cookie `"foo":"bar, admin = qux"`, а генерує cookie `foo":"bar"` та `"admin":"qux"`. Зверніть увагу, як генеруються 2 cookie і як з admin видалено пробіл перед і після знака рівності.
#### Обхід аналізу значень з розділенням cookie
@ -242,23 +241,23 @@ Resulting cookie: name=eval('test//, comment') => allowed
#### **Основні перевірки**
- **cookie** є **однаковим** щоразу, коли ви **входите** в систему.
- Вийдіть з системи та спробуйте використати той самий cookie.
- Спробуйте увійти з 2 пристроїв (або браузерів) до одного й того ж облікового запису, використовуючи той самий cookie.
- Перевірте, чи містить cookie якусь інформацію, і спробуйте змінити її.
- **Кук** завжди **однаковий** щоразу, коли ви **увійдете**.
- Вийдіть з системи і спробуйте використати той самий кук.
- Спробуйте увійти з 2 пристроїв (або браузерів) до одного й того ж облікового запису, використовуючи той самий кук.
- Перевірте, чи містить кук будь-яку інформацію, і спробуйте змінити його.
- Спробуйте створити кілька облікових записів з майже однаковими іменами користувачів і перевірте, чи можете ви побачити подібності.
- Перевірте опцію "**запам'ятати мене**", якщо вона існує, щоб дізнатися, як вона працює. Якщо вона існує і може бути вразливою, завжди використовуйте cookie з **запам'ятати мене** без жодного іншого cookie.
- Перевірте, чи працює попередній cookie навіть після зміни пароля.
- Перевірте опцію "**запам'ятати мене**", якщо вона існує, щоб дізнатися, як вона працює. Якщо вона існує і може бути вразливою, завжди використовуйте кук з **запам'ятати мене** без жодного іншого кука.
- Перевірте, чи працює попередній кук навіть після зміни пароля.
#### **Складні атаки на куки**
Якщо cookie залишається таким же (або майже таким) під час входу, це, ймовірно, означає, що cookie пов'язаний з якимось полем вашого облікового запису (ймовірно, з ім'ям користувача). Тоді ви можете:
Якщо кук залишається таким же (або майже таким) при вході, це, ймовірно, означає, що кук пов'язаний з якимось полем вашого облікового запису (ймовірно, з ім'ям користувача). Тоді ви можете:
- Спробуйте створити багато **облікових записів** з дуже **схожими** іменами користувачів і спробуйте **вгадати**, як працює алгоритм.
- Спробуйте **брутфорсити ім'я користувача**. Якщо cookie зберігається лише як метод аутентифікації для вашого імені користувача, тоді ви можете створити обліковий запис з ім'ям користувача "**Bmin**" і **брутфорсити** кожен окремий **біт** вашого cookie, оскільки один з cookie, які ви спробуєте, буде належати "**admin**".
- Спробуйте **Padding** **Oracle** (ви можете розшифрувати вміст cookie). Використовуйте **padbuster**.
- Спробуйте **брутфорсити ім'я користувача**. Якщо кук зберігається лише як метод аутентифікації для вашого імені користувача, тоді ви можете створити обліковий запис з ім'ям користувача "**Bmin**" і **брутфорсити** кожен окремий **біт** вашого кука, тому що один з куків, які ви спробуєте, буде належати "**admin**".
- Спробуйте **Padding** **Oracle** (ви можете розшифрувати вміст кука). Використовуйте **padbuster**.
**Padding Oracle - приклади Padbuster**
**Padding Oracle - Приклади Padbuster**
```bash
padbuster <URL/path/when/successfully/login/with/cookie> <COOKIE> <PAD[8-16]>
# When cookies and regular Base64
@ -268,15 +267,15 @@ padbuster http://web.com/index.php u7bvLewln6PJPSAbMb5pFfnCHSEd6olf 8 -cookies a
padBuster http://web.com/home.jsp?UID=7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6
7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6 8 -encoding 2
```
Padbuster зробить кілька спроб і запитає вас, яка умова є умовою помилки (та, що не є дійсною).
Padbuster зробить кілька спроб і запитає вас, яка умова є умовою помилки (та, що є недійсною).
Потім він почне розшифровувати кукі (це може зайняти кілька хвилин).
Якщо атака була успішно виконана, ви можете спробувати зашифрувати рядок на ваш вибір. Наприклад, якщо ви хочете **зашифрувати** **user=administrator**.
Якщо атака була успішно виконана, ви можете спробувати зашифрувати рядок на ваш вибір. Наприклад, якщо ви хочете **encrypt** **user=administrator**
```
padbuster http://web.com/index.php 1dMjA5hfXh0jenxJQ0iW6QXKkzAGIWsiDAKV3UwJPT2lBP+zAD0D0w== 8 -cookies thecookie=1dMjA5hfXh0jenxJQ0iW6QXKkzAGIWsiDAKV3UwJPT2lBP+zAD0D0w== -plaintext user=administrator
```
Це виконання надасть вам cookie, правильно зашифрований та закодований зі строкою **user=administrator** всередині.
Це виконання надасть вам cookie, правильно зашифрований і закодований зі строкою **user=administrator** всередині.
**CBC-MAC**
@ -291,7 +290,7 @@ padbuster http://web.com/index.php 1dMjA5hfXh0jenxJQ0iW6QXKkzAGIWsiDAKV3UwJPT2lB
**ECB**
Якщо cookie зашифровано за допомогою ECB, він може бути вразливим.\
Коли ви входите, cookie, який ви отримуєте, завжди має бути однаковим.
Коли ви входите в систему, cookie, який ви отримуєте, завжди має бути однаковим.
**Як виявити та атакувати:**
@ -299,9 +298,9 @@ padbuster http://web.com/index.php 1dMjA5hfXh0jenxJQ0iW6QXKkzAGIWsiDAKV3UwJPT2lB
Створіть користувача, наприклад, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" і перевірте, чи є якийсь шаблон у cookie (оскільки ECB шифрує з одним і тим же ключем кожен блок, ті ж зашифровані байти можуть з'явитися, якщо ім'я користувача зашифровано).
Повинен бути шаблон (з розміром використаного блоку). Отже, знаючи, як зашифровано купу "a", ви можете створити ім'я користувача: "a"\*(розмір блоку)+"admin". Тоді ви зможете видалити зашифрований шаблон блоку "a" з cookie. І у вас буде cookie для імені користувача "admin".
Має бути шаблон (з розміром використаного блоку). Отже, знаючи, як зашифровано купу "a", ви можете створити ім'я користувача: "a"\*(розмір блоку)+"admin". Тоді ви зможете видалити зашифрований шаблон блоку "a" з cookie. І ви отримаєте cookie для імені користувача "admin".
## References
## Посилання
- [https://blog.ankursundara.com/cookie-bugs/](https://blog.ankursundara.com/cookie-bugs/)
- [https://www.linkedin.com/posts/rickey-martin-24533653_100daysofhacking-penetrationtester-ethicalhacking-activity-7016286424526180352-bwDd](https://www.linkedin.com/posts/rickey-martin-24533653_100daysofhacking-penetrationtester-ethicalhacking-activity-7016286424526180352-bwDd)

View File

@ -1,27 +1,28 @@
/**
* HackTricks AI Chat Widget v1.15 Markdown rendering + sanitised
* ------------------------------------------------------------------------
* Replaces the static placeholder with a three-dot **bouncing** loader
* Renders assistant replies as Markdown while purging any unsafe HTML
* (XSS-safe via DOMPurify)
* ------------------------------------------------------------------------
* HackTricks AI Chat Widget v1.16 resizable sidebar
* ---------------------------------------------------
* Markdown rendering + sanitised (same as before)
* NEW: dragtoresize panel, width persists via localStorage
*/
(function () {
const LOG = "[HackTricks-AI]";
/* ---------------- User-tunable constants ---------------- */
const MAX_CONTEXT = 3000; // highlighted-text char limit
const MAX_QUESTION = 500; // question char limit
const LOG = "[HackTricksAI]";
/* ---------------- Usertunable constants ---------------- */
const MAX_CONTEXT = 3000; // highlightedtext char limit
const MAX_QUESTION = 500; // question char limit
const MIN_W = 250; // ← resize limits →
const MAX_W = 600;
const DEF_W = 350; // default width (if nothing saved)
const TOOLTIP_TEXT =
"💡 Highlight any text on the page,\nthen click to ask HackTricks AI about it";
"💡 Highlight any text on the page,\nthen click to ask HackTricks AI about it";
const API_BASE = "https://www.hacktricks.ai/api/assistants/threads";
const BRAND_RED = "#b31328"; // HackTricks brand
const BRAND_RED = "#b31328";
/* ------------------------------ State ------------------------------ */
let threadId = null;
let isRunning = false;
/* ---------- helpers ---------- */
const $ = (sel, ctx = document) => ctx.querySelector(sel);
if (document.getElementById("ht-ai-btn")) {
console.warn(`${LOG} Widget already injected.`);
@ -31,44 +32,37 @@
? document.addEventListener("DOMContentLoaded", init)
: init());
/* ==================================================================== */
/* 🔗 1. 3rd-party libs → Markdown & sanitiser */
/* ==================================================================== */
/* =================================================================== */
/* 🔗 1. 3rdparty libs → Markdown & sanitiser */
/* =================================================================== */
function loadScript(src) {
return new Promise((resolve, reject) => {
return new Promise((res, rej) => {
const s = document.createElement("script");
s.src = src;
s.onload = resolve;
s.onerror = () => reject(new Error(`Failed to load ${src}`));
s.onload = res;
s.onerror = () => rej(new Error(`Failed to load ${src}`));
document.head.appendChild(s);
});
}
async function ensureDeps() {
const deps = [];
if (typeof marked === "undefined") {
if (typeof marked === "undefined")
deps.push(loadScript("https://cdn.jsdelivr.net/npm/marked/marked.min.js"));
}
if (typeof DOMPurify === "undefined") {
if (typeof DOMPurify === "undefined")
deps.push(
loadScript(
"https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.5/purify.min.js"
)
);
}
if (deps.length) await Promise.all(deps);
}
const mdToSafeHTML = (md) =>
DOMPurify.sanitize(marked.parse(md, { mangle: false, headerIds: false }), {
USE_PROFILES: { html: true }
});
function mdToSafeHTML(md) {
// 1⃣ Markdown → raw HTML
const raw = marked.parse(md, { mangle: false, headerIds: false });
// 2⃣ Purify
return DOMPurify.sanitize(raw, { USE_PROFILES: { html: true } });
}
/* ==================================================================== */
/* =================================================================== */
async function init() {
/* ----- make sure marked & DOMPurify are ready before anything else */
try {
await ensureDeps();
} catch (e) {
@ -76,14 +70,14 @@
return;
}
console.log(`${LOG} Injecting widget… v1.15`);
console.log(`${LOG} Injecting widget… v1.16`);
await ensureThreadId();
injectStyles();
const btn = createFloatingButton();
createTooltip(btn);
const panel = createSidebar();
const panel = createSidebar(); // ← panel with resizer
const chatLog = $("#ht-ai-chat");
const sendBtn = $("#ht-ai-send");
const inputBox = $("#ht-ai-question");
@ -100,15 +94,8 @@
function addMsg(text, cls) {
const b = document.createElement("div");
b.className = `ht-msg ${cls}`;
// ✨ assistant replies rendered as Markdown + sanitised
if (cls === "ht-ai") {
b.innerHTML = mdToSafeHTML(text);
} else {
// user / context bubbles stay plain-text
b.textContent = text;
}
b[cls === "ht-ai" ? "innerHTML" : "textContent"] =
cls === "ht-ai" ? mdToSafeHTML(text) : text;
chatLog.appendChild(b);
chatLog.scrollTop = chatLog.scrollHeight;
return b;
@ -116,30 +103,28 @@
const LOADER_HTML =
'<span class="ht-loading"><span></span><span></span><span></span></span>';
function setInputDisabled(d) {
const setInputDisabled = (d) => {
inputBox.disabled = d;
sendBtn.disabled = d;
}
function clearThreadCookie() {
};
const clearThreadCookie = () => {
document.cookie = "threadId=; Path=/; Max-Age=0";
threadId = null;
}
function resetConversation() {
threadId = null;
};
const resetConversation = () => {
chatLog.innerHTML = "";
clearThreadCookie();
panel.classList.remove("open");
}
};
/* ------------------- Panel open / close ------------------- */
btn.addEventListener("click", () => {
if (!savedSelection) {
alert("Please highlight some text first to then ask HackTricks AI about it.");
alert("Please highlight some text first.");
return;
}
if (savedSelection.length > MAX_CONTEXT) {
alert(
`Highlighted text is too long (${savedSelection.length} chars). Max allowed: ${MAX_CONTEXT}.`
);
alert(`Highlighted text is too long. Max ${MAX_CONTEXT} chars.`);
return;
}
chatLog.innerHTML = "";
@ -157,11 +142,10 @@
addMsg("Please wait until the current operation completes.", "ht-ai");
return;
}
isRunning = true;
setInputDisabled(true);
const loadingBubble = addMsg("", "ht-ai");
loadingBubble.innerHTML = LOADER_HTML;
const loading = addMsg("", "ht-ai");
loading.innerHTML = LOADER_HTML;
const content = context
? `### Context:\n${context}\n\n### Question to answer:\n${question}`
@ -178,43 +162,39 @@
try {
const e = await res.json();
if (e.error) err = `Error: ${e.error}`;
else if (res.status === 429)
err = "Rate limit exceeded. Please try again later.";
else if (res.status === 429) err = "Rate limit exceeded.";
} catch (_) {}
loadingBubble.textContent = err;
loading.textContent = err;
return;
}
const data = await res.json();
loadingBubble.remove();
loading.remove();
if (Array.isArray(data.response))
data.response.forEach((p) => {
data.response.forEach((p) =>
addMsg(
p.type === "text" && p.text && p.text.value
? p.text.value
: JSON.stringify(p),
"ht-ai"
);
});
)
);
else if (typeof data.response === "string")
addMsg(data.response, "ht-ai");
else addMsg(JSON.stringify(data, null, 2), "ht-ai");
} catch (e) {
console.error("Error sending message:", e);
loadingBubble.textContent = "An unexpected error occurred.";
loading.textContent = "An unexpected error occurred.";
} finally {
isRunning = false;
setInputDisabled(false);
chatLog.scrollTop = chatLog.scrollHeight;
}
}
async function handleSend() {
const q = inputBox.value.trim();
if (!q) return;
if (q.length > MAX_QUESTION) {
alert(
`Your question is too long (${q.length} chars). Max allowed: ${MAX_QUESTION}.`
);
alert(`Question too long (${q.length}). Max ${MAX_QUESTION}.`);
return;
}
inputBox.value = "";
@ -228,9 +208,9 @@
handleSend();
}
});
}
} /* end init */
/* ==================================================================== */
/* =================================================================== */
async function ensureThreadId() {
const m = document.cookie.match(/threadId=([^;]+)/);
if (m && m[1]) {
@ -241,62 +221,67 @@
const r = await fetch(API_BASE, { method: "POST", credentials: "include" });
const d = await r.json();
if (!r.ok || !d.threadId) throw new Error(`${r.status} ${r.statusText}`);
threadId = d.threadId;
threadId = d.threadId;
document.cookie =
`threadId=${threadId}; Path=/; Secure; SameSite=Strict; Max-Age=7200`;
} catch (e) {
console.error("Error creating threadId:", e);
alert("Failed to initialise the conversation. Please refresh and try again.");
alert("Failed to initialise the conversation. Please refresh.");
throw e;
}
}
/* ==================================================================== */
/* =================================================================== */
function injectStyles() {
const css = `
#ht-ai-btn{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);width:60px;height:60px;border-radius:50%;background:#1e1e1e;color:#fff;font-size:28px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:99999;box-shadow:0 2px 8px rgba(0,0,0,.4);transition:opacity .2s}
#ht-ai-btn:hover{opacity:.85}
@media(max-width:768px){#ht-ai-btn{display:none}}
#ht-ai-tooltip{position:fixed;padding:6px 8px;background:#111;color:#fff;border-radius:4px;font-size:13px;white-space:pre-wrap;pointer-events:none;opacity:0;transform:translate(-50%,-8px);transition:opacity .15s ease,transform .15s ease;z-index:100000}
#ht-ai-tooltip.show{opacity:1;transform:translate(-50%,-12px)}
#ht-ai-panel{position:fixed;top:0;right:0;height:100%;width:350px;max-width:90vw;background:#000;color:#fff;display:flex;flex-direction:column;transform:translateX(100%);transition:transform .3s ease;z-index:100000;font-family:system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial,sans-serif}
#ht-ai-panel.open{transform:translateX(0)}
@media(max-width:768px){#ht-ai-panel{display:none}}
#ht-ai-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #333}
#ht-ai-header .ht-actions{display:flex;gap:8px;align-items:center}
#ht-ai-close,#ht-ai-reset{cursor:pointer;font-size:18px;background:none;border:none;color:#fff;padding:0}
#ht-ai-close:hover,#ht-ai-reset:hover{opacity:.7}
#ht-ai-chat{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px;font-size:14px}
.ht-msg{max-width:90%;line-height:1.4;padding:10px 12px;border-radius:8px;white-space:pre-wrap;word-wrap:break-word}
.ht-user{align-self:flex-end;background:${BRAND_RED}}
.ht-ai{align-self:flex-start;background:#222}
.ht-context{align-self:flex-start;background:#444;font-style:italic;font-size:13px}
#ht-ai-input{display:flex;gap:8px;padding:12px 16px;border-top:1px solid #333}
#ht-ai-question{flex:1;min-height:40px;max-height:120px;resize:vertical;padding:8px;border-radius:6px;border:none;font-size:14px}
#ht-ai-send{padding:0 18px;border:none;border-radius:6px;background:${BRAND_RED};color:#fff;font-size:14px;cursor:pointer}
#ht-ai-send:disabled{opacity:.5;cursor:not-allowed}
/* Loader animation */
.ht-loading{display:inline-flex;align-items:center;gap:4px}
.ht-loading span{width:6px;height:6px;border-radius:50%;background:#888;animation:ht-bounce 1.2s infinite ease-in-out}
.ht-loading span:nth-child(2){animation-delay:0.2s}
.ht-loading span:nth-child(3){animation-delay:0.4s}
@keyframes ht-bounce{0%,80%,100%{transform:scale(0);}40%{transform:scale(1);} }
::selection{background:#ffeb3b;color:#000}
::-moz-selection{background:#ffeb3b;color:#000}`;
#ht-ai-btn{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);min-width:60px;height:60px;border-radius:30px;background:linear-gradient(45deg, #b31328, #d42d3f, #2d5db4, #3470e4);background-size:300% 300%;animation:gradientShift 8s ease infinite;color:#fff;font-size:18px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:99999;box-shadow:0 2px 8px rgba(0,0,0,.4);transition:opacity .2s;padding:0 20px}
#ht-ai-btn span{margin-left:8px;font-weight:bold}
@keyframes gradientShift{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
#ht-ai-btn:hover{opacity:.85}
@media(max-width:768px){#ht-ai-btn{display:none}}
#ht-ai-tooltip{position:fixed;padding:6px 8px;background:#111;color:#fff;border-radius:4px;font-size:13px;white-space:pre-wrap;pointer-events:none;opacity:0;transform:translate(-50%,-8px);transition:opacity .15s ease,transform .15s ease;z-index:100000}
#ht-ai-tooltip.show{opacity:1;transform:translate(-50%,-12px)}
#ht-ai-panel{position:fixed;top:0;right:0;height:100%;max-width:90vw;background:#000;color:#fff;display:flex;flex-direction:column;transform:translateX(100%);transition:transform .3s ease;z-index:100000;font-family:system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial,sans-serif}
#ht-ai-panel.open{transform:translateX(0)}
@media(max-width:768px){#ht-ai-panel{display:none}}
#ht-ai-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #333}
#ht-ai-header .ht-actions{display:flex;gap:8px;align-items:center}
#ht-ai-close,#ht-ai-reset{cursor:pointer;font-size:18px;background:none;border:none;color:#fff;padding:0}
#ht-ai-close:hover,#ht-ai-reset:hover{opacity:.7}
#ht-ai-chat{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px;font-size:14px}
.ht-msg{max-width:90%;line-height:1.4;padding:10px 12px;border-radius:8px;white-space:pre-wrap;word-wrap:break-word}
.ht-user{align-self:flex-end;background:${BRAND_RED}}
.ht-ai{align-self:flex-start;background:#222}
.ht-context{align-self:flex-start;background:#444;font-style:italic;font-size:13px}
#ht-ai-input{display:flex;gap:8px;padding:12px 16px;border-top:1px solid #333}
#ht-ai-question{flex:1;min-height:40px;max-height:120px;resize:vertical;padding:8px;border-radius:6px;border:none;font-size:14px}
#ht-ai-send{padding:0 18px;border:none;border-radius:6px;background:${BRAND_RED};color:#fff;font-size:14px;cursor:pointer}
#ht-ai-send:disabled{opacity:.5;cursor:not-allowed}
/* Loader */
.ht-loading{display:inline-flex;align-items:center;gap:4px}
.ht-loading span{width:6px;height:6px;border-radius:50%;background:#888;animation:ht-bounce 1.2s infinite ease-in-out}
.ht-loading span:nth-child(2){animation-delay:0.2s}
.ht-loading span:nth-child(3){animation-delay:0.4s}
@keyframes ht-bounce{0%,80%,100%{transform:scale(0);}40%{transform:scale(1);} }
::selection{background:#ffeb3b;color:#000}
::-moz-selection{background:#ffeb3b;color:#000}
/* NEW: resizer handle */
#ht-ai-resizer{position:absolute;left:0;top:0;width:6px;height:100%;cursor:ew-resize;background:transparent}
#ht-ai-resizer:hover{background:rgba(255,255,255,.05)}`;
const s = document.createElement("style");
s.id = "ht-ai-style";
s.textContent = css;
document.head.appendChild(s);
}
/* =================================================================== */
function createFloatingButton() {
const d = document.createElement("div");
d.id = "ht-ai-btn";
d.textContent = "🤖";
d.innerHTML = "🤖<span>HackTricksAI</span>";
document.body.appendChild(d);
return d;
}
function createTooltip(btn) {
const t = document.createElement("div");
t.id = "ht-ai-tooltip";
@ -311,11 +296,16 @@
btn.addEventListener("mouseleave", () => t.classList.remove("show"));
}
/* =================================================================== */
function createSidebar() {
const saved = parseInt(localStorage.getItem("htAiWidth") || DEF_W, 10);
const width = Math.min(Math.max(saved, MIN_W), MAX_W);
const p = document.createElement("div");
p.id = "ht-ai-panel";
p.style.width = width + "px"; // ← applied width
p.innerHTML = `
<div id="ht-ai-header"><strong>HackTricks AI Chat</strong>
<div id="ht-ai-header"><strong>HackTricks AI Chat</strong>
<div class="ht-actions">
<button id="ht-ai-reset" title="Reset"></button>
<span id="ht-ai-close" title="Close"></span>
@ -326,7 +316,39 @@
<textarea id="ht-ai-question" placeholder="Type your question…"></textarea>
<button id="ht-ai-send">Send</button>
</div>`;
/* NEW: resizer strip */
const resizer = document.createElement("div");
resizer.id = "ht-ai-resizer";
p.appendChild(resizer);
document.body.appendChild(p);
addResizeLogic(resizer, p);
return p;
}
/* ---------------- resize behaviour ---------------- */
function addResizeLogic(handle, panel) {
let startX, startW, dragging = false;
const onMove = (e) => {
if (!dragging) return;
const dx = startX - e.clientX; // dragging leftwards ⇒ +dx
let newW = startW + dx;
newW = Math.min(Math.max(newW, MIN_W), MAX_W);
panel.style.width = newW + "px";
};
const onUp = () => {
if (!dragging) return;
dragging = false;
localStorage.setItem("htAiWidth", parseInt(panel.style.width, 10));
document.removeEventListener("mousemove", onMove);
document.removeEventListener("mouseup", onUp);
};
handle.addEventListener("mousedown", (e) => {
dragging = true;
startX = e.clientX;
startW = parseInt(window.getComputedStyle(panel).width, 10);
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp);
});
}
})();