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

This commit is contained in:
Translator 2025-05-18 03:05:37 +00:00
parent b3d4c38025
commit 09cf872cd2
2 changed files with 168 additions and 146 deletions

View File

@ -1,4 +1,4 @@
# Cookies Hacking # Hacking Cookies
{{#include ../../banners/hacktricks-training.md}} {{#include ../../banners/hacktricks-training.md}}
@ -6,17 +6,17 @@
Ciasteczka mają kilka atrybutów, które kontrolują ich zachowanie w przeglądarce użytkownika. Oto przegląd tych atrybutów w bardziej pasywnej formie: Ciasteczka mają kilka atrybutów, które kontrolują ich zachowanie w przeglądarce użytkownika. Oto przegląd tych atrybutów w bardziej pasywnej formie:
### Wygasa i Max-Age ### Expires i Max-Age
Data wygaśnięcia ciasteczka jest określona przez atrybut `Expires`. Z kolei atrybut `Max-age` definiuje czas w sekundach, po którym ciasteczko zostanie usunięte. **Wybierz `Max-age`, ponieważ odzwierciedla to nowocześniejsze praktyki.** Data wygaśnięcia ciasteczka jest określona przez atrybut `Expires`. Z kolei atrybut `Max-age` definiuje czas w sekundach, po którym ciasteczko zostanie usunięte. **Wybierz `Max-age`, ponieważ odzwierciedla to nowocześniejsze praktyki.**
### Domeny ### Domain
Hosty, które mają otrzymać ciasteczko, są określone przez atrybut `Domain`. Domyślnie jest on ustawiony na hosta, który wydał ciasteczko, nie uwzględniając jego subdomen. Jednak gdy atrybut `Domain` jest wyraźnie ustawiony, obejmuje również subdomeny. To sprawia, że specyfikacja atrybutu `Domain` jest mniej restrykcyjną opcją, przydatną w scenariuszach, gdzie konieczne jest udostępnianie ciasteczek między subdomenami. Na przykład, ustawienie `Domain=mozilla.org` sprawia, że ciasteczka są dostępne na jego subdomenach, takich jak `developer.mozilla.org`. Hosty, które mają otrzymać ciasteczko, są określone przez atrybut `Domain`. Domyślnie jest to ustawione na hosta, który wydał ciasteczko, nie obejmując jego subdomen. Jednak gdy atrybut `Domain` jest wyraźnie ustawiony, obejmuje również subdomeny. To sprawia, że specyfikacja atrybutu `Domain` jest mniej restrykcyjną opcją, przydatną w scenariuszach, gdzie konieczne jest udostępnianie ciasteczek między subdomenami. Na przykład, ustawienie `Domain=mozilla.org` sprawia, że ciasteczka są dostępne na jego subdomenach, takich jak `developer.mozilla.org`.
### Ścieżka ### Path
Atrybut `Path` wskazuje konkretną ścieżkę URL, która musi być obecna w żądanym URL, aby nagłówek `Cookie` został wysłany. Atrybut ten traktuje znak `/` jako separator katalogów, co pozwala na dopasowania w podkatalogach. Specyficzna ścieżka URL, która musi być obecna w żądanym URL, aby nagłówek `Cookie` został wysłany, jest wskazana przez atrybut `Path`. Atrybut ten traktuje znak `/` jako separator katalogów, co pozwala na dopasowania w podkatalogach.
### Zasady porządkowania ### Zasady porządkowania
@ -48,7 +48,7 @@ Tabela z [Invicti](https://www.netsparker.com/blog/web-security/same-site-cookie
Ciasteczko z atrybutem _**SameSite**_ **łagodzi ataki CSRF**, gdzie potrzebna jest zalogowana sesja. Ciasteczko z atrybutem _**SameSite**_ **łagodzi ataki CSRF**, gdzie potrzebna jest zalogowana sesja.
**\*Zauważ, że od Chrome80 (lut/2019) domyślne zachowanie ciasteczka bez atrybutu cookie samesite** **będzie 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/)).\ **\*Zauważ, że od Chrome80 (lut/2019) domyślne zachowanie ciasteczka bez atrybutu cookie samesite** **będzie 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/)).\
Zauważ, że tymczasowo, po zastosowaniu tej zmiany, **ciasteczka bez polityki SameSite** **w Chrome będą** **traktowane jako None** przez **pierwsze 2 minuty, a następnie jako Lax dla głównych żądań POST między witrynami.** Zauważ, że tymczasowo, po zastosowaniu tej zmiany, **ciasteczka bez polityki SameSite** **w Chrome będą** **traktowane jako None** przez **pierwsze 2 minuty, a następnie jako Lax dla głównych żądań POST międzydomenowych.**
## Flagi ciasteczek ## Flagi ciasteczek
@ -58,9 +58,9 @@ To uniemożliwia **klientowi** dostęp do ciasteczka (np. za pomocą **Javascrip
#### **Obejścia** #### **Obejścia**
- Jeśli strona **wysyła ciasteczka jako odpowiedź** na żądania (na przykład na stronie **PHPinfo**), można wykorzystać XSS, aby wysłać żądanie do tej strony i **ukraść ciasteczka** z odpowiedzi (sprawdź przykład w [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/)). - Jeśli strona **wysyła ciasteczka jako odpowiedź** na żądania (na przykład na stronie **PHPinfo**), można wykorzystać XSS, aby wysłać żądanie do tej strony i **ukraść ciasteczka** z odpowiedzi (sprawdź przykład w [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/)).
- Można to obejść za pomocą **żądań TRACE** **HTTP**, ponieważ odpowiedź z serwera (jeśli ta metoda HTTP jest dostępna) odzwierciedli wysłane ciasteczka. Ta technika nazywa się **Cross-Site Tracking**. - Można to obejść za pomocą żądań **TRACE** **HTTP**, ponieważ odpowiedź z serwera (jeśli ta metoda HTTP jest dostępna) odzwierciedli wysłane ciasteczka. Ta technika nazywa się **Cross-Site Tracking**.
- Ta technika jest unika przez **nowoczesne przeglądarki, które nie pozwalają na wysyłanie żądania TRACE** z JS. Jednak w niektórych oprogramowaniach znaleziono obejścia, takie jak wysyłanie `\r\nTRACE` zamiast `TRACE` do IE6.0 SP2. - Ta technika jest unika przez **nowoczesne przeglądarki, które nie pozwalają na wysyłanie żądania TRACE** z JS. Jednak niektóre obejścia tego zostały znalezione w konkretnym oprogramowaniu, na przykład wysyłając `\r\nTRACE` zamiast `TRACE` do IE6.0 SP2.
- Innym sposobem jest wykorzystanie luk zero-day w przeglądarkach. - Innym sposobem jest wykorzystanie luk zero-day w przeglądarkach.
- Możliwe jest **nadpisanie ciasteczek HttpOnly** poprzez przeprowadzenie ataku Cookie Jar overflow: - Możliwe jest **nadpisanie ciasteczek HttpOnly** poprzez przeprowadzenie ataku Cookie Jar overflow:
@ -68,15 +68,15 @@ To uniemożliwia **klientowi** dostęp do ciasteczka (np. za pomocą **Javascrip
cookie-jar-overflow.md cookie-jar-overflow.md
{{#endref}} {{#endref}}
- Możliwe jest użycie ataku [**Cookie Smuggling**](#cookie-smuggling) do eksfiltracji tych ciasteczek. - Możliwe jest użycie ataku [**Cookie Smuggling**](#cookie-smuggling) do wykradzenia tych ciasteczek.
### Secure ### Secure
Żądanie **wyśle** ciasteczko tylko w żądaniu HTTP, jeśli żądanie jest przesyłane przez bezpieczny kanał (zwykle **HTTPS**). Żądanie **wyśle** ciasteczko w żądaniu HTTP tylko wtedy, gdy żądanie jest przesyłane przez bezpieczny kanał (zazwyczaj **HTTPS**).
## Prefiksy ciasteczek ## Prefiksy ciasteczek
Ciasteczka z prefiksem `__Secure-` muszą być ustawione wraz z flagą `secure` z stron, które są zabezpieczone przez HTTPS. Ciasteczka z prefiksem `__Secure-` muszą być ustawione wraz z flagą `secure` z stron zabezpieczonych przez HTTPS.
Dla ciasteczek z prefiksem `__Host-` musi być spełnionych kilka warunków: Dla ciasteczek z prefiksem `__Host-` musi być spełnionych kilka warunków:
@ -89,7 +89,7 @@ Ważne jest, aby zauważyć, że ciasteczka z prefiksem `__Host-` nie mogą być
### Nadpisywanie ciasteczek ### Nadpisywanie ciasteczek
Jedną z ochron prefiksowanych ciasteczek `__Host-` jest zapobieganie ich nadpisywaniu z subdomen. Zapobiega to na przykład [**atakom Cookie Tossing**](cookie-tossing.md). W wykładzie [**Cookie Crumbles: Unveiling Web Session Integrity Vulnerabilities**](https://www.youtube.com/watch?v=F_wAzF4a7Xg) ([**artykuł**](https://www.usenix.org/system/files/usenixsecurity23-squarcina.pdf)) przedstawiono, że możliwe było ustawienie ciasteczek z prefiksem \_\_HOST- z subdomen, oszukując parsera, na przykład dodając "=" na początku lub na końcu...: Jedną z ochron prefiksowanych ciasteczek `__Host-` jest zapobieganie ich nadpisywaniu z subdomen. Zapobiega to na przykład [**atakom Cookie Tossing**](cookie-tossing.md). W wykładzie [**Cookie Crumbles: Unveiling Web Session Integrity Vulnerabilities**](https://www.youtube.com/watch?v=F_wAzF4a7Xg) ([**artykuł**](https://www.usenix.org/system/files/usenixsecurity23-squarcina.pdf)) przedstawiono, że możliwe było ustawienie ciasteczek z prefiksem \_\_HOST- z subdomen, oszukując parser, na przykład dodając "=" na początku lub na końcu...:
<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>
@ -103,13 +103,13 @@ Jeśli niestandardowe ciasteczko zawiera wrażliwe dane, sprawdź je (szczególn
### Dekodowanie i manipulowanie ciasteczkami ### Dekodowanie i manipulowanie ciasteczkami
Wrażliwe dane osadzone w ciasteczkach powinny być zawsze dokładnie sprawdzane. Ciasteczka zakodowane w Base64 lub podobnych formatach można często dekodować. Ta luka pozwala atakującym na modyfikację zawartości ciasteczka i podszywanie się pod innych użytkowników, kodując ich zmodyfikowane dane z powrotem do ciasteczka. Wrażliwe dane osadzone w ciasteczkach powinny być zawsze dokładnie sprawdzane. Ciasteczka zakodowane w Base64 lub podobnych formatach mogą często być dekodowane. Ta luka pozwala atakującym na zmianę zawartości ciasteczka i podszywanie się pod innych użytkowników, kodując ich zmodyfikowane dane z powrotem do ciasteczka.
### Przechwytywanie sesji ### Kradzież sesji
Ten atak polega na kradzieży ciasteczka użytkownika, aby uzyskać nieautoryzowany dostęp do jego konta w aplikacji. Używając skradzionego ciasteczka, atakujący może podszyć się pod prawdziwego użytkownika. Ten atak polega na kradzieży ciasteczka użytkownika, aby uzyskać nieautoryzowany dostęp do jego konta w aplikacji. Używając skradzionego ciasteczka, atakujący może podszyć się pod prawdziwego użytkownika.
### Utrwalanie sesji ### Utrwalenie sesji
W tym scenariuszu atakujący oszukuje ofiarę, aby użyła konkretnego ciasteczka do logowania. Jeśli aplikacja nie przypisuje nowego ciasteczka po logowaniu, atakujący, posiadający oryginalne ciasteczko, może podszyć się pod ofiarę. Ta technika polega na tym, że ofiara loguje się za pomocą ciasteczka dostarczonego przez atakującego. W tym scenariuszu atakujący oszukuje ofiarę, aby użyła konkretnego ciasteczka do logowania. Jeśli aplikacja nie przypisuje nowego ciasteczka po logowaniu, atakujący, posiadający oryginalne ciasteczko, może podszyć się pod ofiarę. Ta technika polega na tym, że ofiara loguje się za pomocą ciasteczka dostarczonego przez atakującego.
@ -133,7 +133,7 @@ cookie-tossing.md
Kliknij na poprzedni link, aby uzyskać dostęp do strony wyjaśniającej możliwe luki w JWT. Kliknij na poprzedni link, aby uzyskać dostęp do strony wyjaśniającej możliwe luki w JWT.
JSON Web Tokens (JWT) używane w ciasteczkach mogą również przedstawiać luki. Aby uzyskać szczegółowe informacje na temat potencjalnych luk i sposobów ich wykorzystania, zaleca się dostęp do powiązanego dokumentu dotyczącego hakowania JWT. JSON Web Tokens (JWT) używane w ciasteczkach mogą również przedstawiać luki. Aby uzyskać szczegółowe informacje na temat potencjalnych luk i sposobów ich wykorzystania, zaleca się dostęp do powiązanego dokumentu dotyczącego hackowania JWT.
### Cross-Site Request Forgery (CSRF) ### Cross-Site Request Forgery (CSRF)
@ -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"
``` ```
Wynik w nagłówku cookie wysłanym to `a=v1; test value; b=v2;`. Intrygująco, umożliwia to manipulację cookie, jeśli ustawione jest cookie o pustej nazwie, potencjalnie kontrolując inne cookie poprzez ustawienie pustego cookie na określoną wartość: Wynik w nagłówku cookie wysłanym to `a=v1; test value; b=v2;`. Interesujące jest to, że umożliwia to manipulację cookie, jeśli ustawione jest cookie o pustej nazwie, potencjalnie kontrolując inne cookie poprzez ustawienie pustego cookie na określoną wartość:
```js ```js
function setCookie(name, value) { function setCookie(name, value) {
document.cookie = `${name}=${value}` document.cookie = `${name}=${value}`
@ -155,35 +155,35 @@ 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
``` ```
To prowadzi do wysłania przez przeglądarkę nagłówka cookie, który jest interpretowany przez każdy serwer WWW jako cookie o nazwie `a` z wartością `b`. To prowadzi do tego, że przeglądarka wysyła nagłówek cookie interpretowany przez każdy serwer WWW jako cookie o nazwie `a` z wartością `b`.
#### Chrome Bug: Problem z kodem zastępczym Unicode #### Błąd Chrome: Problem z kodem zastępczym Unicode
W Chrome, jeśli kod zastępczy Unicode jest częścią ustawionego cookie, `document.cookie` staje się uszkodzone, zwracając pusty ciąg w następstwie: W Chrome, jeśli kod zastępczy Unicode jest częścią ustawionego cookie, `document.cookie` staje się uszkodzone, zwracając pusty ciąg w następstwie:
```js ```js
document.cookie = "\ud800=meep" document.cookie = "\ud800=meep"
``` ```
To skutkuje tym, że `document.cookie` zwraca pusty ciąg, co wskazuje na trwałe uszkodzenie. To skutkuje tym, że `document.cookie` zwraca pusty ciąg, co wskazuje na trwałą korupcję.
#### Przechwytywanie ciasteczek z powodu problemów z analizą #### Przemyt ciasteczek z powodu problemów z analizą
(Sprawdź szczegóły w[oryginalnych badaniach](https://blog.ankursundara.com/cookie-bugs/)) Kilka serwerów internetowych, w tym te z Javy (Jetty, TomCat, Undertow) i Pythona (Zope, cherrypy, web.py, aiohttp, bottle, webob), niewłaściwie obsługuje ciągi ciasteczek z powodu przestarzałego wsparcia dla RFC2965. Odczytują wartość ciasteczka w podwójnych cudzysłowach jako jedną wartość, nawet jeśli zawiera średniki, które normalnie powinny oddzielać pary klucz-wartość: (Zobacz szczegóły w [oryginalnych badaniach](https://blog.ankursundara.com/cookie-bugs/)) Kilka serwerów internetowych, w tym te z Javy (Jetty, TomCat, Undertow) i Pythona (Zope, cherrypy, web.py, aiohttp, bottle, webob), niewłaściwie obsługuje ciągi ciasteczek z powodu przestarzałego wsparcia dla RFC2965. Odczytują wartość ciasteczka w podwójnych cudzysłowach jako jedną wartość, nawet jeśli zawiera średniki, które normalnie powinny oddzielać pary klucz-wartość:
``` ```
RENDER_TEXT="hello world; JSESSIONID=13371337; ASDF=end"; RENDER_TEXT="hello world; JSESSIONID=13371337; ASDF=end";
``` ```
#### Luki w Iniekcji Ciasteczek #### Luki w Iniekcji Ciasteczek
(Check further details in the[original research](https://blog.ankursundara.com/cookie-bugs/)) Nieprawidłowe analizowanie ciasteczek przez serwery, szczególnie Undertow, Zope oraz te korzystające z `http.cookie.SimpleCookie` i `http.cookie.BaseCookie` w Pythonie, stwarza możliwości ataków iniekcji ciasteczek. Serwery te nieprawidłowo delimitują początek nowych ciasteczek, co pozwala atakującym na fałszowanie ciasteczek: (Check further details in the[original research](https://blog.ankursundara.com/cookie-bugs/)) Nieprawidłowe analizowanie ciasteczek przez serwery, szczególnie Undertow, Zope oraz te korzystające z `http.cookie.SimpleCookie` i `http.cookie.BaseCookie` w Pythonie, stwarza możliwości ataków typu cookie injection. Serwery te nieprawidłowo delimitują początek nowych ciasteczek, co pozwala atakującym na fałszowanie ciasteczek:
- Undertow oczekuje nowego ciasteczka natychmiast po wartości w cudzysłowie bez średnika. - Undertow oczekuje nowego ciasteczka natychmiast po wartości w cudzysłowie bez średnika.
- Zope szuka przecinka, aby rozpocząć analizowanie następnego ciasteczka. - Zope szuka przecinka, aby rozpocząć analizowanie następnego ciasteczka.
- Klasy ciasteczek Pythona zaczynają analizowanie od znaku spacji. - Klasy ciasteczek Pythona zaczynają analizowanie od znaku spacji.
Ta luka jest szczególnie niebezpieczna w aplikacjach webowych polegających na ochronie CSRF opartej na ciasteczkach, ponieważ pozwala atakującym na wstrzykiwanie fałszywych ciasteczek z tokenami CSRF, potencjalnie omijając środki bezpieczeństwa. Problem ten jest zaostrzony przez sposób, w jaki Python obsługuje duplikaty nazw ciasteczek, gdzie ostatnie wystąpienie nadpisuje wcześniejsze. Budzi to również obawy dotyczące ciasteczek `__Secure-` i `__Host-` w niebezpiecznych kontekstach i może prowadzić do obejść autoryzacji, gdy ciasteczka są przekazywane do serwerów zaplecza podatnych na fałszowanie. Ta luka jest szczególnie niebezpieczna w aplikacjach webowych polegających na ochronie CSRF opartej na ciasteczkach, ponieważ pozwala atakującym na wstrzykiwanie fałszywych ciasteczek z tokenami CSRF, co potencjalnie omija środki bezpieczeństwa. Problem jest zaostrzony przez sposób, w jaki Python obsługuje duplikaty nazw ciasteczek, gdzie ostatnie wystąpienie nadpisuje wcześniejsze. Budzi to również obawy dotyczące ciasteczek `__Secure-` i `__Host-` w niebezpiecznych kontekstach i może prowadzić do obejść autoryzacji, gdy ciasteczka są przekazywane do serwerów zaplecza podatnych na fałszowanie.
### Ciasteczka $version ### Ciasteczka $version
#### Ominięcie WAF #### Obejście WAF
Zgodnie z [**tym wpisem na blogu**](https://portswigger.net/research/bypassing-wafs-with-the-phantom-version-cookie), możliwe jest użycie atrybutu ciasteczka **`$Version=1`**, aby backend używał starej logiki do analizy ciasteczka zgodnie z **RFC2109**. Ponadto, inne wartości takie jak **`$Domain`** i **`$Path`** mogą być używane do modyfikacji zachowania backendu z ciasteczkiem. Zgodnie z [**tym wpisem na blogu**](https://portswigger.net/research/bypassing-wafs-with-the-phantom-version-cookie), możliwe jest użycie atrybutu ciasteczka **`$Version=1`**, aby backend używał starej logiki do analizy ciasteczka zgodnie z **RFC2109**. Ponadto, inne wartości takie jak **`$Domain`** i **`$Path`** mogą być używane do modyfikacji zachowania backendu z ciasteczkiem.
@ -192,7 +192,7 @@ Zgodnie z [**tym wpisem na blogu**](https://portswigger.net/research/bypassing-w
Zgodnie z [**tym wpisem na blogu**](https://portswigger.net/research/stealing-httponly-cookies-with-the-cookie-sandwich-technique), możliwe jest użycie techniki ciasteczkowego sandwichu do kradzieży ciasteczek HttpOnly. Oto wymagania i kroki: Zgodnie z [**tym wpisem na blogu**](https://portswigger.net/research/stealing-httponly-cookies-with-the-cookie-sandwich-technique), możliwe jest użycie techniki ciasteczkowego sandwichu do kradzieży ciasteczek HttpOnly. Oto wymagania i kroki:
- Znajdź miejsce, w którym pozornie bezużyteczne **ciasteczko jest odzwierciedlane w odpowiedzi** - Znajdź miejsce, w którym pozornie bezużyteczne **ciasteczko jest odzwierciedlane w odpowiedzi**
- **Utwórz ciasteczko o nazwie `$Version`** z wartością `1` (możesz to zrobić w ataku XSS z JS) z bardziej specyficzną ścieżką, aby uzyskać początkową pozycję (niektóre frameworki, takie jak Python, nie potrzebują tego kroku) - **Utwórz ciasteczko o nazwie `$Version`** z wartością `1` (możesz to zrobić w ataku XSS z JS) z bardziej specyficzną ścieżką, aby zajęło początkową pozycję (niektóre frameworki, takie jak Python, nie potrzebują tego kroku)
- **Utwórz ciasteczko, które jest odzwierciedlane** z wartością, która pozostawia **otwarte podwójne cudzysłowy** i z określoną ścieżką, aby było umiejscowione w bazie ciasteczek po poprzednim (`$Version`) - **Utwórz ciasteczko, które jest odzwierciedlane** z wartością, która pozostawia **otwarte podwójne cudzysłowy** i z określoną ścieżką, aby było umiejscowione w bazie ciasteczek po poprzednim (`$Version`)
- Następnie, legalne ciasteczko będzie następne w kolejności - Następnie, legalne ciasteczko będzie następne w kolejności
- **Utwórz fałszywe ciasteczko, które zamyka podwójne cudzysłowy** wewnątrz swojej wartości - **Utwórz fałszywe ciasteczko, które zamyka podwójne cudzysłowy** wewnątrz swojej wartości
@ -230,7 +230,7 @@ Host: example.com
Cookie: param1=value1; Cookie: param1=value1;
Cookie: param2=value2; Cookie: param2=value2;
``` ```
Co może pozwolić na ominięcie WAF, jak w tym przykładzie: Co mogłoby pozwolić na ominięcie WAF, jak w tym przykładzie:
``` ```
Cookie: name=eval('test// Cookie: name=eval('test//
Cookie: comment') Cookie: comment')
@ -245,16 +245,16 @@ Resulting cookie: name=eval('test//, comment') => allowed
- Wyloguj się i spróbuj użyć tego samego ciasteczka. - Wyloguj się i spróbuj użyć tego samego ciasteczka.
- Spróbuj zalogować się z 2 urządzeń (lub przeglądarek) do tego samego konta, używając tego samego ciasteczka. - Spróbuj zalogować się z 2 urządzeń (lub przeglądarek) do tego samego konta, używając tego samego ciasteczka.
- Sprawdź, czy ciasteczko zawiera jakiekolwiek informacje i spróbuj je zmodyfikować. - Sprawdź, czy ciasteczko zawiera jakiekolwiek informacje i spróbuj je zmodyfikować.
- Spróbuj utworzyć kilka kont z prawie tym samym nazwiskiem użytkownika i sprawdź, czy możesz dostrzec podobieństwa. - Spróbuj utworzyć kilka kont z prawie tym samym nazwiskiem i sprawdź, czy możesz dostrzec podobieństwa.
- Sprawdź opcję "**zapamiętaj mnie**", jeśli istnieje, aby zobaczyć, jak działa. Jeśli istnieje i może być podatna, zawsze używaj ciasteczka **zapamiętaj mnie** bez żadnego innego ciasteczka. - Sprawdź opcję "**zapamiętaj mnie**", jeśli istnieje, aby zobaczyć, jak działa. Jeśli istnieje i może być podatna, zawsze używaj ciasteczka z **zapamiętaj mnie** bez żadnego innego ciasteczka.
- Sprawdź, czy poprzednie ciasteczko działa nawet po zmianie hasła. - Sprawdź, czy poprzednie ciasteczko działa nawet po zmianie hasła.
#### **Zaawansowane ataki na ciasteczka** #### **Zaawansowane ataki na ciasteczka**
Jeśli ciasteczko pozostaje takie samo (lub prawie takie samo) po zalogowaniu, prawdopodobnie oznacza to, że ciasteczko jest związane z jakimś polem twojego konta (prawdopodobnie nazwiskiem użytkownika). Wtedy możesz: Jeśli ciasteczko pozostaje takie samo (lub prawie takie samo) podczas logowania, prawdopodobnie oznacza to, że ciasteczko jest związane z jakimś polem twojego konta (prawdopodobnie nazwiskiem użytkownika). Wtedy możesz:
- Spróbować utworzyć wiele **kont** z bardzo **podobnymi** nazwiskami użytkowników i spróbować **zgadnąć**, jak działa algorytm. - Spróbować utworzyć wiele **kont** z nazwiskami użytkowników bardzo **podobnymi** i spróbować **zgadnąć**, jak działa algorytm.
- Spróbować **bruteforce'ować nazwisko użytkownika**. Jeśli ciasteczko jest zapisywane tylko jako metoda uwierzytelniania dla twojego nazwiska użytkownika, wtedy możesz utworzyć konto z nazwiskiem użytkownika "**Bmin**" i **bruteforce'ować** każdy pojedynczy **bit** swojego ciasteczka, ponieważ jedno z ciasteczek, które spróbujesz, będzie należało do "**admin**". - Spróbować **bruteforce'ować nazwisko użytkownika**. Jeśli ciasteczko jest zapisywane tylko jako metoda uwierzytelniania dla twojego nazwiska użytkownika, możesz utworzyć konto z nazwiskiem użytkownika "**Bmin**" i **bruteforce'ować** każdy pojedynczy **bit** swojego ciasteczka, ponieważ jedno z ciasteczek, które spróbujesz, będzie należało do "**admin**".
- Spróbuj **Padding** **Oracle** (możesz odszyfrować zawartość ciasteczka). Użyj **padbuster**. - Spróbuj **Padding** **Oracle** (możesz odszyfrować zawartość ciasteczka). Użyj **padbuster**.
**Padding Oracle - Przykłady Padbuster** **Padding Oracle - Przykłady Padbuster**
@ -269,9 +269,9 @@ padBuster http://web.com/home.jsp?UID=7B216A634951170FF851D6CC68FC9537858795A28E
``` ```
Padbuster pode podjąć kilka prób i zapyta cię, która z warunków jest warunkiem błędu (tym, który nie jest ważny). Padbuster pode podjąć kilka prób i zapyta cię, która z warunków jest warunkiem błędu (tym, który nie jest ważny).
Następnie zacznie deszyfrować ciasteczko (może to potrwać kilka minut). Następnie rozpocznie deszyfrowanie ciasteczka (może to potrwać kilka minut).
Jeśli atak został pomyślnie przeprowadzony, możesz spróbować zaszyfrować ciąg według własnego wyboru. Na przykład, jeśli chcesz **zaszyfrować** **user=administrator**. Jeśli atak został pomyślnie przeprowadzony, możesz spróbować zaszyfrować ciąg według własnego wyboru. Na przykład, jeśli chcesz **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
``` ```
@ -279,7 +279,7 @@ To wykonanie da ci ciasteczko poprawnie zaszyfrowane i zakodowane z ciągiem **u
**CBC-MAC** **CBC-MAC**
Może ciasteczko mogłoby mieć jakąś wartość i mogłoby być podpisane przy użyciu CBC. Wtedy integralność wartości to podpis stworzony przy użyciu CBC z tą samą wartością. Ponieważ zaleca się użycie jako IV wektora zerowego, ten typ sprawdzania integralności może być podatny. Może ciasteczko mogłoby mieć jakąś wartość i mogłoby być podpisane przy użyciu CBC. Wtedy integralność wartości to podpis utworzony przy użyciu CBC z tą samą wartością. Ponieważ zaleca się użycie jako IV wektora zerowego, ten typ sprawdzania integralności może być podatny.
**Atak** **Atak**
@ -298,9 +298,9 @@ Utwórz 2 użytkowników z prawie tymi samymi danymi (nazwa użytkownika, hasło
Utwórz użytkownika o nazwie na przykład "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" i sprawdź, czy w ciasteczku jest jakiś wzór (ponieważ ECB szyfruje z tym samym kluczem każdy blok, te same zaszyfrowane bajty mogą się pojawić, jeśli nazwa użytkownika jest szyfrowana). Utwórz użytkownika o nazwie na przykład "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" i sprawdź, czy w ciasteczku jest jakiś wzór (ponieważ ECB szyfruje z tym samym kluczem każdy blok, te same zaszyfrowane bajty mogą się pojawić, jeśli nazwa użytkownika jest szyfrowana).
Powinien być wzór (o rozmiarze używanego bloku). Zatem, wiedząc, jak jest zaszyfrowana masa "a", możesz stworzyć nazwę użytkownika: "a"\*(rozmiar bloku)+"admin". Następnie możesz usunąć zaszyfrowany wzór bloku "a" z ciasteczka. I będziesz miał ciasteczko dla nazwy użytkownika "admin". Powinien być wzór (o rozmiarze używanego bloku). Zatem, wiedząc, jak jest zaszyfrowana grupa "a", możesz utworzyć nazwę użytkownika: "a"\*(rozmiar bloku)+"admin". Następnie możesz usunąć zaszyfrowany wzór bloku "a" z ciasteczka. I będziesz miał ciasteczko dla nazwy użytkownika "admin".
## Referencje ## 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);
});
}
})(); })();