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 ### Expires and Max-Age
Дата закінчення терміну дії cookie визначається атрибутом `Expires`. У свою чергу, атрибут `Max-age` визначає час у секундах, протягом якого cookie буде видалено. **Вибирайте `Max-age`, оскільки це відображає більш сучасні практики.** Дата закінчення терміну дії cookie визначається атрибутом `Expires`. Натомість атрибут `Max-age` визначає час у секундах, протягом якого cookie буде видалено. **Вибирайте `Max-age`, оскільки це відображає більш сучасні практики.**
### Domain ### Domain
@ -48,7 +48,7 @@ Table from [Invicti](https://www.netsparker.com/blog/web-security/same-site-cook
Cookie з атрибутом _**SameSite**_ **зменшить атаки CSRF**, де потрібна активна сесія. 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/)).\ **\*Зверніть увагу, що з 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 ## Cookies Flags
@ -58,9 +58,9 @@ Cookie з атрибутом _**SameSite**_ **зменшить атаки CSRF**
#### **Bypasses** #### **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** **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: - Можливо **перезаписати HttpOnly cookie**, виконуючи атаку переповнення Cookie Jar:
@ -68,11 +68,11 @@ Cookie з атрибутом _**SameSite**_ **зменшить атаки CSRF**
cookie-jar-overflow.md cookie-jar-overflow.md
{{#endref}} {{#endref}}
- Можливо використовувати атаку [**Cookie Smuggling**](#cookie-smuggling) для ексфільтрації цих cookie - Можливо використовувати [**Cookie Smuggling**](#cookie-smuggling) атаку для ексфільтрації цих cookie.
### Secure ### Secure
Запит **тільки** надішле cookie в HTTP запиті, якщо запит передається через безпечний канал (зазвичай **HTTPS**). Запит буде **тільки** надсилати cookie в HTTP запиті, якщо запит передається через захищений канал (зазвичай **HTTPS**).
## Cookies Prefixes ## Cookies Prefixes
@ -89,11 +89,11 @@ Cookie, що починаються з `__Secure-`, повинні бути вс
### Overwriting cookies ### 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> <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> <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 ### Decoding and Manipulating Cookies
Чутливі дані, вбудовані в cookie, завжди повинні бути перевірені. Cookie, закодовані в Base64 або подібних форматах, часто можуть бути декодовані. Ця вразливість дозволяє зловмисникам змінювати вміст cookie та видавати себе за інших користувачів, закодовуючи їх модифіковані дані назад у cookie. Чутливі дані, вбудовані в cookie, завжди повинні бути перевірені. Cookie, закодовані в Base64 або подібних форматах, часто можуть бути декодовані. Ця вразливість дозволяє зловмисникам змінювати вміст cookie та видавати себе за інших користувачів, закодовуючи їх змінені дані назад у cookie.
### Session Hijacking ### Session Hijacking
Ця атака передбачає крадіжку cookie користувача для отримання несанкціонованого доступу до їх облікового запису в додатку. Використовуючи вкрадену cookie, зловмисник може видавати себе за законного користувача. Ця атака передбачає крадіжку cookie користувача для отримання несанкціонованого доступу до їх облікового запису в додатку. Використовуючи вкрадений cookie, зловмисник може видавати себе за законного користувача.
### Session Fixation ### Session Fixation
У цьому сценарії зловмисник обманює жертву, щоб вона використовувала конкретну cookie для входу. Якщо додаток не призначає нову cookie під час входу, зловмисник, маючи оригінальну cookie, може видавати себе за жертву. Ця техніка залежить від того, що жертва входить з cookie, наданою зловмисником. У цьому сценарії зловмисник обманює жертву, щоб вона використовувала конкретний cookie для входу. Якщо додаток не призначає новий cookie під час входу, зловмисник, маючи оригінальний cookie, може видавати себе за жертву. Ця техніка залежить від того, що жертва входить з cookie, наданим зловмисником.
Якщо ви знайшли **XSS у піддомені** або **контролюєте піддомен**, прочитайте: Якщо ви знайшли **XSS у піддомені** або **контролюєте піддомен**, прочитайте:
@ -131,13 +131,13 @@ cookie-tossing.md
### [JWT Cookies](../hacking-jwt-json-web-tokens.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) ### Cross-Site Request Forgery (CSRF)
Ця атака змушує увійшовшого користувача виконувати небажані дії на веб-додатку, в якому він наразі аутентифікований. Зловмисники можуть використовувати cookie, які автоматично надсилаються з кожним запитом до вразливого сайту. Ця атака змушує увійшовшого користувача виконувати небажані дії на веб-додатку, в якому вони наразі аутентифіковані. Зловмисники можуть використовувати cookie, які автоматично надсилаються з кожним запитом до вразливого сайту.
### Empty Cookies ### Empty Cookies
@ -147,7 +147,7 @@ document.cookie = "a=v1"
document.cookie = "=test value;" // Setting an empty named cookie document.cookie = "=test value;" // Setting an empty named cookie
document.cookie = "b=v2" 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 ```js
function setCookie(name, value) { function setCookie(name, value) {
document.cookie = `${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 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 ```js
document.cookie = "\ud800=meep" document.cookie = "\ud800=meep"
``` ```
@ -179,7 +179,7 @@ RENDER_TEXT="hello world; JSESSIONID=13371337; ASDF=end";
- Zope шукає кому, щоб почати парсинг наступної кукі. - Zope шукає кому, щоб почати парсинг наступної кукі.
- Класи кукі Python починають парсинг з символу пробілу. - Класи кукі Python починають парсинг з символу пробілу.
Ця вразливість особливо небезпечна в веб-додатках, які покладаються на захист CSRF на основі кукі, оскільки це дозволяє зловмисникам ін'єктувати підроблені кукі CSRF-токенів, потенційно обходячи заходи безпеки. Проблема ускладнюється обробкою Python дублікатів імен кукі, де останнє входження перекриває попередні. Це також викликає занепокоєння щодо кукі `__Secure-` і `__Host-` в небезпечних контекстах і може призвести до обходу авторизації, коли кукі передаються на сервери, що піддаються підробці. Ця вразливість особливо небезпечна в веб-додатках, які покладаються на захист CSRF на основі кукі, оскільки це дозволяє зловмисникам ін'єктувати підроблені кукі CSRF-токенів, потенційно обходячи заходи безпеки. Проблема ускладнюється обробкою Python дублікатів імен кукі, де останнє виникнення перекриває попередні. Це також викликає занепокоєння щодо кукі `__Secure-` і `__Host-` в небезпечних контекстах і може призвести до обходу авторизації, коли кукі передаються на сервери, що піддаються підробці.
### Кукі $version ### Кукі $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` (ви можете зробити це в атаці XSS з JS) з більш специфічним шляхом, щоб вона отримала початкову позицію (деякі фреймворки, такі як Python, не потребують цього кроку)
- **Створити кукі, яка відображається** зі значенням, що залишає **відкриті подвійні лапки** і з конкретним шляхом, щоб вона була розташована в базі кукі після попередньої (`$Version`) - **Створити кукі, яка відображається** зі значенням, що залишає **відкриті подвійні лапки** і з конкретним шляхом, щоб вона була розташована в базі кукі після попередньої (`$Version`)
- Тоді легітимна кукі піде наступною в порядку - Потім легітимна кукі буде йти наступною в порядку
- **Створити фальшиву кукі, яка закриває подвійні лапки** всередині свого значення - **Створити фальшиву кукі, яка закриває подвійні лапки** всередині свого значення
Таким чином, кукі жертви потрапляє в пастку всередині нової кукі версії 1 і буде відображатися щоразу, коли вона відображається. Таким чином, кукі жертви потрапляє в пастку всередині нової кукі версії 1 і буде відображатися щоразу, коли вона відображається.
e.g. з посту:
```javascript ```javascript
document.cookie = `$Version=1;`; document.cookie = `$Version=1;`;
document.cookie = `param1="start`; document.cookie = `param1="start`;
@ -209,7 +208,7 @@ document.cookie = `param2=end";`;
#### Cookies $version #### Cookies $version
Перевірте попередній розділ. Перегляньте попередній розділ.
#### Обхід аналізу значень з кодуванням quoted-string #### Обхід аналізу значень з кодуванням quoted-string
@ -220,7 +219,7 @@ document.cookie = `param2=end";`;
#### Обхід блокувань імен cookie #### Обхід блокувань імен 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 #### Обхід аналізу значень з розділенням cookie
@ -242,23 +241,23 @@ Resulting cookie: name=eval('test//, comment') => allowed
#### **Основні перевірки** #### **Основні перевірки**
- **cookie** є **однаковим** щоразу, коли ви **входите** в систему. - **Кук** завжди **однаковий** щоразу, коли ви **увійдете**.
- Вийдіть з системи та спробуйте використати той самий cookie. - Вийдіть з системи і спробуйте використати той самий кук.
- Спробуйте увійти з 2 пристроїв (або браузерів) до одного й того ж облікового запису, використовуючи той самий cookie. - Спробуйте увійти з 2 пристроїв (або браузерів) до одного й того ж облікового запису, використовуючи той самий кук.
- Перевірте, чи містить cookie якусь інформацію, і спробуйте змінити її. - Перевірте, чи містить кук будь-яку інформацію, і спробуйте змінити його.
- Спробуйте створити кілька облікових записів з майже однаковими іменами користувачів і перевірте, чи можете ви побачити подібності. - Спробуйте створити кілька облікових записів з майже однаковими іменами користувачів і перевірте, чи можете ви побачити подібності.
- Перевірте опцію "**запам'ятати мене**", якщо вона існує, щоб дізнатися, як вона працює. Якщо вона існує і може бути вразливою, завжди використовуйте cookie з **запам'ятати мене** без жодного іншого cookie. - Перевірте опцію "**запам'ятати мене**", якщо вона існує, щоб дізнатися, як вона працює. Якщо вона існує і може бути вразливою, завжди використовуйте кук з **запам'ятати мене** без жодного іншого кука.
- Перевірте, чи працює попередній cookie навіть після зміни пароля. - Перевірте, чи працює попередній кук навіть після зміни пароля.
#### **Складні атаки на куки** #### **Складні атаки на куки**
Якщо cookie залишається таким же (або майже таким) під час входу, це, ймовірно, означає, що cookie пов'язаний з якимось полем вашого облікового запису (ймовірно, з ім'ям користувача). Тоді ви можете: Якщо кук залишається таким же (або майже таким) при вході, це, ймовірно, означає, що кук пов'язаний з якимось полем вашого облікового запису (ймовірно, з ім'ям користувача). Тоді ви можете:
- Спробуйте створити багато **облікових записів** з дуже **схожими** іменами користувачів і спробуйте **вгадати**, як працює алгоритм. - Спробуйте створити багато **облікових записів** з дуже **схожими** іменами користувачів і спробуйте **вгадати**, як працює алгоритм.
- Спробуйте **брутфорсити ім'я користувача**. Якщо cookie зберігається лише як метод аутентифікації для вашого імені користувача, тоді ви можете створити обліковий запис з ім'ям користувача "**Bmin**" і **брутфорсити** кожен окремий **біт** вашого cookie, оскільки один з cookie, які ви спробуєте, буде належати "**admin**". - Спробуйте **брутфорсити ім'я користувача**. Якщо кук зберігається лише як метод аутентифікації для вашого імені користувача, тоді ви можете створити обліковий запис з ім'ям користувача "**Bmin**" і **брутфорсити** кожен окремий **біт** вашого кука, тому що один з куків, які ви спробуєте, буде належати "**admin**".
- Спробуйте **Padding** **Oracle** (ви можете розшифрувати вміст cookie). Використовуйте **padbuster**. - Спробуйте **Padding** **Oracle** (ви можете розшифрувати вміст кука). Використовуйте **padbuster**.
**Padding Oracle - приклади Padbuster** **Padding Oracle - Приклади Padbuster**
```bash ```bash
padbuster <URL/path/when/successfully/login/with/cookie> <COOKIE> <PAD[8-16]> padbuster <URL/path/when/successfully/login/with/cookie> <COOKIE> <PAD[8-16]>
# When cookies and regular Base64 # 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 padBuster http://web.com/home.jsp?UID=7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6
7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6 8 -encoding 2 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 padbuster http://web.com/index.php 1dMjA5hfXh0jenxJQ0iW6QXKkzAGIWsiDAKV3UwJPT2lBP+zAD0D0w== 8 -cookies thecookie=1dMjA5hfXh0jenxJQ0iW6QXKkzAGIWsiDAKV3UwJPT2lBP+zAD0D0w== -plaintext user=administrator
``` ```
Це виконання надасть вам cookie, правильно зашифрований та закодований зі строкою **user=administrator** всередині. Це виконання надасть вам cookie, правильно зашифрований і закодований зі строкою **user=administrator** всередині.
**CBC-MAC** **CBC-MAC**
@ -291,7 +290,7 @@ padbuster http://web.com/index.php 1dMjA5hfXh0jenxJQ0iW6QXKkzAGIWsiDAKV3UwJPT2lB
**ECB** **ECB**
Якщо cookie зашифровано за допомогою ECB, він може бути вразливим.\ Якщо cookie зашифровано за допомогою ECB, він може бути вразливим.\
Коли ви входите, cookie, який ви отримуєте, завжди має бути однаковим. Коли ви входите в систему, cookie, який ви отримуєте, завжди має бути однаковим.
**Як виявити та атакувати:** **Як виявити та атакувати:**
@ -299,9 +298,9 @@ padbuster http://web.com/index.php 1dMjA5hfXh0jenxJQ0iW6QXKkzAGIWsiDAKV3UwJPT2lB
Створіть користувача, наприклад, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" і перевірте, чи є якийсь шаблон у cookie (оскільки ECB шифрує з одним і тим же ключем кожен блок, ті ж зашифровані байти можуть з'явитися, якщо ім'я користувача зашифровано). Створіть користувача, наприклад, "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://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) - [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 * HackTricks AI Chat Widget v1.16 resizable sidebar
* ------------------------------------------------------------------------ * ---------------------------------------------------
* Replaces the static placeholder with a three-dot **bouncing** loader * Markdown rendering + sanitised (same as before)
* Renders assistant replies as Markdown while purging any unsafe HTML * NEW: dragtoresize panel, width persists via localStorage
* (XSS-safe via DOMPurify)
* ------------------------------------------------------------------------
*/ */
(function () { (function () {
const LOG = "[HackTricks-AI]"; const LOG = "[HackTricksAI]";
/* ---------------- Usertunable constants ---------------- */
/* ---------------- User-tunable constants ---------------- */ const MAX_CONTEXT = 3000; // highlightedtext char limit
const MAX_CONTEXT = 3000; // highlighted-text char limit const MAX_QUESTION = 500; // question 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 = 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 API_BASE = "https://www.hacktricks.ai/api/assistants/threads";
const BRAND_RED = "#b31328"; // HackTricks brand const BRAND_RED = "#b31328";
/* ------------------------------ State ------------------------------ */ /* ------------------------------ State ------------------------------ */
let threadId = null; let threadId = null;
let isRunning = false; let isRunning = false;
/* ---------- helpers ---------- */
const $ = (sel, ctx = document) => ctx.querySelector(sel); const $ = (sel, ctx = document) => ctx.querySelector(sel);
if (document.getElementById("ht-ai-btn")) { if (document.getElementById("ht-ai-btn")) {
console.warn(`${LOG} Widget already injected.`); console.warn(`${LOG} Widget already injected.`);
@ -31,44 +32,37 @@
? document.addEventListener("DOMContentLoaded", init) ? document.addEventListener("DOMContentLoaded", init)
: init()); : init());
/* ==================================================================== */ /* =================================================================== */
/* 🔗 1. 3rd-party libs → Markdown & sanitiser */ /* 🔗 1. 3rdparty libs → Markdown & sanitiser */
/* ==================================================================== */ /* =================================================================== */
function loadScript(src) { function loadScript(src) {
return new Promise((resolve, reject) => { return new Promise((res, rej) => {
const s = document.createElement("script"); const s = document.createElement("script");
s.src = src; s.src = src;
s.onload = resolve; s.onload = res;
s.onerror = () => reject(new Error(`Failed to load ${src}`)); s.onerror = () => rej(new Error(`Failed to load ${src}`));
document.head.appendChild(s); document.head.appendChild(s);
}); });
} }
async function ensureDeps() { async function ensureDeps() {
const deps = []; const deps = [];
if (typeof marked === "undefined") { if (typeof marked === "undefined")
deps.push(loadScript("https://cdn.jsdelivr.net/npm/marked/marked.min.js")); deps.push(loadScript("https://cdn.jsdelivr.net/npm/marked/marked.min.js"));
} if (typeof DOMPurify === "undefined")
if (typeof DOMPurify === "undefined") {
deps.push( deps.push(
loadScript( loadScript(
"https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.5/purify.min.js" "https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.5/purify.min.js"
) )
); );
}
if (deps.length) await Promise.all(deps); 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() { async function init() {
/* ----- make sure marked & DOMPurify are ready before anything else */
try { try {
await ensureDeps(); await ensureDeps();
} catch (e) { } catch (e) {
@ -76,14 +70,14 @@
return; return;
} }
console.log(`${LOG} Injecting widget… v1.15`); console.log(`${LOG} Injecting widget… v1.16`);
await ensureThreadId(); await ensureThreadId();
injectStyles(); injectStyles();
const btn = createFloatingButton(); const btn = createFloatingButton();
createTooltip(btn); createTooltip(btn);
const panel = createSidebar(); const panel = createSidebar(); // ← panel with resizer
const chatLog = $("#ht-ai-chat"); const chatLog = $("#ht-ai-chat");
const sendBtn = $("#ht-ai-send"); const sendBtn = $("#ht-ai-send");
const inputBox = $("#ht-ai-question"); const inputBox = $("#ht-ai-question");
@ -100,15 +94,8 @@
function addMsg(text, cls) { function addMsg(text, cls) {
const b = document.createElement("div"); const b = document.createElement("div");
b.className = `ht-msg ${cls}`; b.className = `ht-msg ${cls}`;
b[cls === "ht-ai" ? "innerHTML" : "textContent"] =
// ✨ assistant replies rendered as Markdown + sanitised cls === "ht-ai" ? mdToSafeHTML(text) : text;
if (cls === "ht-ai") {
b.innerHTML = mdToSafeHTML(text);
} else {
// user / context bubbles stay plain-text
b.textContent = text;
}
chatLog.appendChild(b); chatLog.appendChild(b);
chatLog.scrollTop = chatLog.scrollHeight; chatLog.scrollTop = chatLog.scrollHeight;
return b; return b;
@ -116,30 +103,28 @@
const LOADER_HTML = const LOADER_HTML =
'<span class="ht-loading"><span></span><span></span><span></span></span>'; '<span class="ht-loading"><span></span><span></span><span></span></span>';
function setInputDisabled(d) { const setInputDisabled = (d) => {
inputBox.disabled = d; inputBox.disabled = d;
sendBtn.disabled = d; sendBtn.disabled = d;
} };
function clearThreadCookie() { const clearThreadCookie = () => {
document.cookie = "threadId=; Path=/; Max-Age=0"; document.cookie = "threadId=; Path=/; Max-Age=0";
threadId = null; threadId = null;
} };
function resetConversation() { const resetConversation = () => {
chatLog.innerHTML = ""; chatLog.innerHTML = "";
clearThreadCookie(); clearThreadCookie();
panel.classList.remove("open"); panel.classList.remove("open");
} };
/* ------------------- Panel open / close ------------------- */ /* ------------------- Panel open / close ------------------- */
btn.addEventListener("click", () => { btn.addEventListener("click", () => {
if (!savedSelection) { if (!savedSelection) {
alert("Please highlight some text first to then ask HackTricks AI about it."); alert("Please highlight some text first.");
return; return;
} }
if (savedSelection.length > MAX_CONTEXT) { if (savedSelection.length > MAX_CONTEXT) {
alert( alert(`Highlighted text is too long. Max ${MAX_CONTEXT} chars.`);
`Highlighted text is too long (${savedSelection.length} chars). Max allowed: ${MAX_CONTEXT}.`
);
return; return;
} }
chatLog.innerHTML = ""; chatLog.innerHTML = "";
@ -157,11 +142,10 @@
addMsg("Please wait until the current operation completes.", "ht-ai"); addMsg("Please wait until the current operation completes.", "ht-ai");
return; return;
} }
isRunning = true; isRunning = true;
setInputDisabled(true); setInputDisabled(true);
const loadingBubble = addMsg("", "ht-ai"); const loading = addMsg("", "ht-ai");
loadingBubble.innerHTML = LOADER_HTML; loading.innerHTML = LOADER_HTML;
const content = context const content = context
? `### Context:\n${context}\n\n### Question to answer:\n${question}` ? `### Context:\n${context}\n\n### Question to answer:\n${question}`
@ -178,43 +162,39 @@
try { try {
const e = await res.json(); const e = await res.json();
if (e.error) err = `Error: ${e.error}`; if (e.error) err = `Error: ${e.error}`;
else if (res.status === 429) else if (res.status === 429) err = "Rate limit exceeded.";
err = "Rate limit exceeded. Please try again later.";
} catch (_) {} } catch (_) {}
loadingBubble.textContent = err; loading.textContent = err;
return; return;
} }
const data = await res.json(); const data = await res.json();
loadingBubble.remove(); loading.remove();
if (Array.isArray(data.response)) if (Array.isArray(data.response))
data.response.forEach((p) => { data.response.forEach((p) =>
addMsg( addMsg(
p.type === "text" && p.text && p.text.value p.type === "text" && p.text && p.text.value
? p.text.value ? p.text.value
: JSON.stringify(p), : JSON.stringify(p),
"ht-ai" "ht-ai"
); )
}); );
else if (typeof data.response === "string") else if (typeof data.response === "string")
addMsg(data.response, "ht-ai"); addMsg(data.response, "ht-ai");
else addMsg(JSON.stringify(data, null, 2), "ht-ai"); else addMsg(JSON.stringify(data, null, 2), "ht-ai");
} catch (e) { } catch (e) {
console.error("Error sending message:", e); console.error("Error sending message:", e);
loadingBubble.textContent = "An unexpected error occurred."; loading.textContent = "An unexpected error occurred.";
} finally { } finally {
isRunning = false; isRunning = false;
setInputDisabled(false); setInputDisabled(false);
chatLog.scrollTop = chatLog.scrollHeight; chatLog.scrollTop = chatLog.scrollHeight;
} }
} }
async function handleSend() { async function handleSend() {
const q = inputBox.value.trim(); const q = inputBox.value.trim();
if (!q) return; if (!q) return;
if (q.length > MAX_QUESTION) { if (q.length > MAX_QUESTION) {
alert( alert(`Question too long (${q.length}). Max ${MAX_QUESTION}.`);
`Your question is too long (${q.length} chars). Max allowed: ${MAX_QUESTION}.`
);
return; return;
} }
inputBox.value = ""; inputBox.value = "";
@ -228,9 +208,9 @@
handleSend(); handleSend();
} }
}); });
} } /* end init */
/* ==================================================================== */ /* =================================================================== */
async function ensureThreadId() { async function ensureThreadId() {
const m = document.cookie.match(/threadId=([^;]+)/); const m = document.cookie.match(/threadId=([^;]+)/);
if (m && m[1]) { if (m && m[1]) {
@ -241,62 +221,67 @@
const r = await fetch(API_BASE, { method: "POST", credentials: "include" }); const r = await fetch(API_BASE, { method: "POST", credentials: "include" });
const d = await r.json(); const d = await r.json();
if (!r.ok || !d.threadId) throw new Error(`${r.status} ${r.statusText}`); if (!r.ok || !d.threadId) throw new Error(`${r.status} ${r.statusText}`);
threadId = d.threadId; threadId = d.threadId;
document.cookie = document.cookie =
`threadId=${threadId}; Path=/; Secure; SameSite=Strict; Max-Age=7200`; `threadId=${threadId}; Path=/; Secure; SameSite=Strict; Max-Age=7200`;
} catch (e) { } catch (e) {
console.error("Error creating threadId:", 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; throw e;
} }
} }
/* ==================================================================== */ /* =================================================================== */
function injectStyles() { function injectStyles() {
const css = ` 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{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:hover{opacity:.85} #ht-ai-btn span{margin-left:8px;font-weight:bold}
@media(max-width:768px){#ht-ai-btn{display:none}} @keyframes gradientShift{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
#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-btn:hover{opacity:.85}
#ht-ai-tooltip.show{opacity:1;transform:translate(-50%,-12px)} @media(max-width:768px){#ht-ai-btn{display:none}}
#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-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-panel.open{transform:translateX(0)} #ht-ai-tooltip.show{opacity:1;transform:translate(-50%,-12px)}
@media(max-width:768px){#ht-ai-panel{display:none}} #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-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #333} #ht-ai-panel.open{transform:translateX(0)}
#ht-ai-header .ht-actions{display:flex;gap:8px;align-items:center} @media(max-width:768px){#ht-ai-panel{display:none}}
#ht-ai-close,#ht-ai-reset{cursor:pointer;font-size:18px;background:none;border:none;color:#fff;padding:0} #ht-ai-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #333}
#ht-ai-close:hover,#ht-ai-reset:hover{opacity:.7} #ht-ai-header .ht-actions{display:flex;gap:8px;align-items:center}
#ht-ai-chat{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px;font-size:14px} #ht-ai-close,#ht-ai-reset{cursor:pointer;font-size:18px;background:none;border:none;color:#fff;padding:0}
.ht-msg{max-width:90%;line-height:1.4;padding:10px 12px;border-radius:8px;white-space:pre-wrap;word-wrap:break-word} #ht-ai-close:hover,#ht-ai-reset:hover{opacity:.7}
.ht-user{align-self:flex-end;background:${BRAND_RED}} #ht-ai-chat{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px;font-size:14px}
.ht-ai{align-self:flex-start;background:#222} .ht-msg{max-width:90%;line-height:1.4;padding:10px 12px;border-radius:8px;white-space:pre-wrap;word-wrap:break-word}
.ht-context{align-self:flex-start;background:#444;font-style:italic;font-size:13px} .ht-user{align-self:flex-end;background:${BRAND_RED}}
#ht-ai-input{display:flex;gap:8px;padding:12px 16px;border-top:1px solid #333} .ht-ai{align-self:flex-start;background:#222}
#ht-ai-question{flex:1;min-height:40px;max-height:120px;resize:vertical;padding:8px;border-radius:6px;border:none;font-size:14px} .ht-context{align-self:flex-start;background:#444;font-style:italic;font-size:13px}
#ht-ai-send{padding:0 18px;border:none;border-radius:6px;background:${BRAND_RED};color:#fff;font-size:14px;cursor:pointer} #ht-ai-input{display:flex;gap:8px;padding:12px 16px;border-top:1px solid #333}
#ht-ai-send:disabled{opacity:.5;cursor:not-allowed} #ht-ai-question{flex:1;min-height:40px;max-height:120px;resize:vertical;padding:8px;border-radius:6px;border:none;font-size:14px}
/* Loader animation */ #ht-ai-send{padding:0 18px;border:none;border-radius:6px;background:${BRAND_RED};color:#fff;font-size:14px;cursor:pointer}
.ht-loading{display:inline-flex;align-items:center;gap:4px} #ht-ai-send:disabled{opacity:.5;cursor:not-allowed}
.ht-loading span{width:6px;height:6px;border-radius:50%;background:#888;animation:ht-bounce 1.2s infinite ease-in-out} /* Loader */
.ht-loading span:nth-child(2){animation-delay:0.2s} .ht-loading{display:inline-flex;align-items:center;gap:4px}
.ht-loading span:nth-child(3){animation-delay:0.4s} .ht-loading span{width:6px;height:6px;border-radius:50%;background:#888;animation:ht-bounce 1.2s infinite ease-in-out}
@keyframes ht-bounce{0%,80%,100%{transform:scale(0);}40%{transform:scale(1);} } .ht-loading span:nth-child(2){animation-delay:0.2s}
::selection{background:#ffeb3b;color:#000} .ht-loading span:nth-child(3){animation-delay:0.4s}
::-moz-selection{background:#ffeb3b;color:#000}`; @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"); const s = document.createElement("style");
s.id = "ht-ai-style"; s.id = "ht-ai-style";
s.textContent = css; s.textContent = css;
document.head.appendChild(s); document.head.appendChild(s);
} }
/* =================================================================== */
function createFloatingButton() { function createFloatingButton() {
const d = document.createElement("div"); const d = document.createElement("div");
d.id = "ht-ai-btn"; d.id = "ht-ai-btn";
d.textContent = "🤖"; d.innerHTML = "🤖<span>HackTricksAI</span>";
document.body.appendChild(d); document.body.appendChild(d);
return d; return d;
} }
function createTooltip(btn) { function createTooltip(btn) {
const t = document.createElement("div"); const t = document.createElement("div");
t.id = "ht-ai-tooltip"; t.id = "ht-ai-tooltip";
@ -311,11 +296,16 @@
btn.addEventListener("mouseleave", () => t.classList.remove("show")); btn.addEventListener("mouseleave", () => t.classList.remove("show"));
} }
/* =================================================================== */
function createSidebar() { 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"); const p = document.createElement("div");
p.id = "ht-ai-panel"; p.id = "ht-ai-panel";
p.style.width = width + "px"; // ← applied width
p.innerHTML = ` 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"> <div class="ht-actions">
<button id="ht-ai-reset" title="Reset"></button> <button id="ht-ai-reset" title="Reset"></button>
<span id="ht-ai-close" title="Close"></span> <span id="ht-ai-close" title="Close"></span>
@ -326,7 +316,39 @@
<textarea id="ht-ai-question" placeholder="Type your question…"></textarea> <textarea id="ht-ai-question" placeholder="Type your question…"></textarea>
<button id="ht-ai-send">Send</button> <button id="ht-ai-send">Send</button>
</div>`; </div>`;
/* NEW: resizer strip */
const resizer = document.createElement("div");
resizer.id = "ht-ai-resizer";
p.appendChild(resizer);
document.body.appendChild(p); document.body.appendChild(p);
addResizeLogic(resizer, p);
return 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);
});
}
})(); })();