mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/pentesting-web/hacking-with-cookies/README.md'] to pl
This commit is contained in:
parent
b3d4c38025
commit
09cf872cd2
@ -1,4 +1,4 @@
|
||||
# Cookies Hacking
|
||||
# Hacking Cookies
|
||||
|
||||
{{#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:
|
||||
|
||||
### 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.**
|
||||
|
||||
### 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
|
||||
|
||||
@ -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.
|
||||
|
||||
**\*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
|
||||
|
||||
@ -58,9 +58,9 @@ To uniemożliwia **klientowi** dostęp do ciasteczka (np. za pomocą **Javascrip
|
||||
|
||||
#### **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/)).
|
||||
- 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.
|
||||
- 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**.
|
||||
- Ta technika jest unikaną 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.
|
||||
- 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
|
||||
{{#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
|
||||
|
||||
Żą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
|
||||
|
||||
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:
|
||||
|
||||
@ -89,7 +89,7 @@ Ważne jest, aby zauważyć, że ciasteczka z prefiksem `__Host-` nie mogą być
|
||||
|
||||
### 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>
|
||||
|
||||
@ -103,13 +103,13 @@ Jeśli niestandardowe ciasteczko zawiera wrażliwe dane, sprawdź je (szczególn
|
||||
|
||||
### 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.
|
||||
|
||||
### 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.
|
||||
|
||||
@ -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.
|
||||
|
||||
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)
|
||||
|
||||
@ -147,7 +147,7 @@ document.cookie = "a=v1"
|
||||
document.cookie = "=test value;" // Setting an empty named cookie
|
||||
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
|
||||
function setCookie(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
|
||||
```
|
||||
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:
|
||||
```js
|
||||
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";
|
||||
```
|
||||
#### 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.
|
||||
- Zope szuka przecinka, aby rozpocząć analizowanie następnego ciasteczka.
|
||||
- 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
|
||||
|
||||
#### 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.
|
||||
|
||||
@ -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:
|
||||
|
||||
- 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`)
|
||||
- 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
|
||||
@ -230,7 +230,7 @@ Host: example.com
|
||||
Cookie: param1=value1;
|
||||
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: comment')
|
||||
@ -245,16 +245,16 @@ Resulting cookie: name=eval('test//, comment') => allowed
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
- 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 z **zapamiętaj mnie** bez żadnego innego ciasteczka.
|
||||
- Sprawdź, czy poprzednie ciasteczko działa nawet po zmianie hasła.
|
||||
|
||||
#### **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ć **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ć 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, 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**.
|
||||
|
||||
**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).
|
||||
|
||||
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
|
||||
```
|
||||
@ -279,7 +279,7 @@ To wykonanie da ci ciasteczko poprawnie zaszyfrowane i zakodowane z ciągiem **u
|
||||
|
||||
**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**
|
||||
|
||||
@ -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).
|
||||
|
||||
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://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)
|
||||
|
236
theme/ai.js
236
theme/ai.js
@ -1,27 +1,28 @@
|
||||
/**
|
||||
* HackTricks AI Chat Widget v1.15 – Markdown rendering + sanitised
|
||||
* ------------------------------------------------------------------------
|
||||
* • Replaces the static “…” placeholder with a three-dot **bouncing** loader
|
||||
* • Renders assistant replies as Markdown while purging any unsafe HTML
|
||||
* (XSS-safe via DOMPurify)
|
||||
* ------------------------------------------------------------------------
|
||||
* HackTricks AI Chat Widget v1.16 – resizable sidebar
|
||||
* ---------------------------------------------------
|
||||
* ❶ Markdown rendering + sanitised (same as before)
|
||||
* ❷ NEW: drag‑to‑resize panel, width persists via localStorage
|
||||
*/
|
||||
(function () {
|
||||
const LOG = "[HackTricks-AI]";
|
||||
|
||||
/* ---------------- User-tunable constants ---------------- */
|
||||
const MAX_CONTEXT = 3000; // highlighted-text char limit
|
||||
const MAX_QUESTION = 500; // question char limit
|
||||
const LOG = "[HackTricks‑AI]";
|
||||
/* ---------------- User‑tunable constants ---------------- */
|
||||
const MAX_CONTEXT = 3000; // highlighted‑text char limit
|
||||
const MAX_QUESTION = 500; // question char limit
|
||||
const MIN_W = 250; // ← resize limits →
|
||||
const MAX_W = 600;
|
||||
const DEF_W = 350; // default width (if nothing saved)
|
||||
const TOOLTIP_TEXT =
|
||||
"💡 Highlight any text on the page,\nthen click to ask HackTricks AI about it";
|
||||
"💡 Highlight any text on the page,\nthen click to ask HackTricks AI about it";
|
||||
|
||||
const API_BASE = "https://www.hacktricks.ai/api/assistants/threads";
|
||||
const BRAND_RED = "#b31328"; // HackTricks brand
|
||||
const BRAND_RED = "#b31328";
|
||||
|
||||
/* ------------------------------ State ------------------------------ */
|
||||
let threadId = null;
|
||||
let isRunning = false;
|
||||
|
||||
/* ---------- helpers ---------- */
|
||||
const $ = (sel, ctx = document) => ctx.querySelector(sel);
|
||||
if (document.getElementById("ht-ai-btn")) {
|
||||
console.warn(`${LOG} Widget already injected.`);
|
||||
@ -31,44 +32,37 @@
|
||||
? document.addEventListener("DOMContentLoaded", init)
|
||||
: init());
|
||||
|
||||
/* ==================================================================== */
|
||||
/* 🔗 1. 3rd-party libs → Markdown & sanitiser */
|
||||
/* ==================================================================== */
|
||||
/* =================================================================== */
|
||||
/* 🔗 1. 3rd‑party libs → Markdown & sanitiser */
|
||||
/* =================================================================== */
|
||||
function loadScript(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((res, rej) => {
|
||||
const s = document.createElement("script");
|
||||
s.src = src;
|
||||
s.onload = resolve;
|
||||
s.onerror = () => reject(new Error(`Failed to load ${src}`));
|
||||
s.onload = res;
|
||||
s.onerror = () => rej(new Error(`Failed to load ${src}`));
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureDeps() {
|
||||
const deps = [];
|
||||
if (typeof marked === "undefined") {
|
||||
if (typeof marked === "undefined")
|
||||
deps.push(loadScript("https://cdn.jsdelivr.net/npm/marked/marked.min.js"));
|
||||
}
|
||||
if (typeof DOMPurify === "undefined") {
|
||||
if (typeof DOMPurify === "undefined")
|
||||
deps.push(
|
||||
loadScript(
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.5/purify.min.js"
|
||||
)
|
||||
);
|
||||
}
|
||||
if (deps.length) await Promise.all(deps);
|
||||
}
|
||||
const mdToSafeHTML = (md) =>
|
||||
DOMPurify.sanitize(marked.parse(md, { mangle: false, headerIds: false }), {
|
||||
USE_PROFILES: { html: true }
|
||||
});
|
||||
|
||||
function mdToSafeHTML(md) {
|
||||
// 1️⃣ Markdown → raw HTML
|
||||
const raw = marked.parse(md, { mangle: false, headerIds: false });
|
||||
// 2️⃣ Purify
|
||||
return DOMPurify.sanitize(raw, { USE_PROFILES: { html: true } });
|
||||
}
|
||||
|
||||
/* ==================================================================== */
|
||||
/* =================================================================== */
|
||||
async function init() {
|
||||
/* ----- make sure marked & DOMPurify are ready before anything else */
|
||||
try {
|
||||
await ensureDeps();
|
||||
} catch (e) {
|
||||
@ -76,14 +70,14 @@
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`${LOG} Injecting widget… v1.15`);
|
||||
console.log(`${LOG} Injecting widget… v1.16`);
|
||||
|
||||
await ensureThreadId();
|
||||
injectStyles();
|
||||
|
||||
const btn = createFloatingButton();
|
||||
createTooltip(btn);
|
||||
const panel = createSidebar();
|
||||
const panel = createSidebar(); // ← panel with resizer
|
||||
const chatLog = $("#ht-ai-chat");
|
||||
const sendBtn = $("#ht-ai-send");
|
||||
const inputBox = $("#ht-ai-question");
|
||||
@ -100,15 +94,8 @@
|
||||
function addMsg(text, cls) {
|
||||
const b = document.createElement("div");
|
||||
b.className = `ht-msg ${cls}`;
|
||||
|
||||
// ✨ assistant replies rendered as Markdown + sanitised
|
||||
if (cls === "ht-ai") {
|
||||
b.innerHTML = mdToSafeHTML(text);
|
||||
} else {
|
||||
// user / context bubbles stay plain-text
|
||||
b.textContent = text;
|
||||
}
|
||||
|
||||
b[cls === "ht-ai" ? "innerHTML" : "textContent"] =
|
||||
cls === "ht-ai" ? mdToSafeHTML(text) : text;
|
||||
chatLog.appendChild(b);
|
||||
chatLog.scrollTop = chatLog.scrollHeight;
|
||||
return b;
|
||||
@ -116,30 +103,28 @@
|
||||
const LOADER_HTML =
|
||||
'<span class="ht-loading"><span></span><span></span><span></span></span>';
|
||||
|
||||
function setInputDisabled(d) {
|
||||
const setInputDisabled = (d) => {
|
||||
inputBox.disabled = d;
|
||||
sendBtn.disabled = d;
|
||||
}
|
||||
function clearThreadCookie() {
|
||||
};
|
||||
const clearThreadCookie = () => {
|
||||
document.cookie = "threadId=; Path=/; Max-Age=0";
|
||||
threadId = null;
|
||||
}
|
||||
function resetConversation() {
|
||||
threadId = null;
|
||||
};
|
||||
const resetConversation = () => {
|
||||
chatLog.innerHTML = "";
|
||||
clearThreadCookie();
|
||||
panel.classList.remove("open");
|
||||
}
|
||||
};
|
||||
|
||||
/* ------------------- Panel open / close ------------------- */
|
||||
btn.addEventListener("click", () => {
|
||||
if (!savedSelection) {
|
||||
alert("Please highlight some text first to then ask HackTricks AI about it.");
|
||||
alert("Please highlight some text first.");
|
||||
return;
|
||||
}
|
||||
if (savedSelection.length > MAX_CONTEXT) {
|
||||
alert(
|
||||
`Highlighted text is too long (${savedSelection.length} chars). Max allowed: ${MAX_CONTEXT}.`
|
||||
);
|
||||
alert(`Highlighted text is too long. Max ${MAX_CONTEXT} chars.`);
|
||||
return;
|
||||
}
|
||||
chatLog.innerHTML = "";
|
||||
@ -157,11 +142,10 @@
|
||||
addMsg("Please wait until the current operation completes.", "ht-ai");
|
||||
return;
|
||||
}
|
||||
|
||||
isRunning = true;
|
||||
setInputDisabled(true);
|
||||
const loadingBubble = addMsg("", "ht-ai");
|
||||
loadingBubble.innerHTML = LOADER_HTML;
|
||||
const loading = addMsg("", "ht-ai");
|
||||
loading.innerHTML = LOADER_HTML;
|
||||
|
||||
const content = context
|
||||
? `### Context:\n${context}\n\n### Question to answer:\n${question}`
|
||||
@ -178,43 +162,39 @@
|
||||
try {
|
||||
const e = await res.json();
|
||||
if (e.error) err = `Error: ${e.error}`;
|
||||
else if (res.status === 429)
|
||||
err = "Rate limit exceeded. Please try again later.";
|
||||
else if (res.status === 429) err = "Rate limit exceeded.";
|
||||
} catch (_) {}
|
||||
loadingBubble.textContent = err;
|
||||
loading.textContent = err;
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
loadingBubble.remove();
|
||||
loading.remove();
|
||||
if (Array.isArray(data.response))
|
||||
data.response.forEach((p) => {
|
||||
data.response.forEach((p) =>
|
||||
addMsg(
|
||||
p.type === "text" && p.text && p.text.value
|
||||
? p.text.value
|
||||
: JSON.stringify(p),
|
||||
"ht-ai"
|
||||
);
|
||||
});
|
||||
)
|
||||
);
|
||||
else if (typeof data.response === "string")
|
||||
addMsg(data.response, "ht-ai");
|
||||
else addMsg(JSON.stringify(data, null, 2), "ht-ai");
|
||||
} catch (e) {
|
||||
console.error("Error sending message:", e);
|
||||
loadingBubble.textContent = "An unexpected error occurred.";
|
||||
loading.textContent = "An unexpected error occurred.";
|
||||
} finally {
|
||||
isRunning = false;
|
||||
setInputDisabled(false);
|
||||
chatLog.scrollTop = chatLog.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSend() {
|
||||
const q = inputBox.value.trim();
|
||||
if (!q) return;
|
||||
if (q.length > MAX_QUESTION) {
|
||||
alert(
|
||||
`Your question is too long (${q.length} chars). Max allowed: ${MAX_QUESTION}.`
|
||||
);
|
||||
alert(`Question too long (${q.length}). Max ${MAX_QUESTION}.`);
|
||||
return;
|
||||
}
|
||||
inputBox.value = "";
|
||||
@ -228,9 +208,9 @@
|
||||
handleSend();
|
||||
}
|
||||
});
|
||||
}
|
||||
} /* end init */
|
||||
|
||||
/* ==================================================================== */
|
||||
/* =================================================================== */
|
||||
async function ensureThreadId() {
|
||||
const m = document.cookie.match(/threadId=([^;]+)/);
|
||||
if (m && m[1]) {
|
||||
@ -241,62 +221,67 @@
|
||||
const r = await fetch(API_BASE, { method: "POST", credentials: "include" });
|
||||
const d = await r.json();
|
||||
if (!r.ok || !d.threadId) throw new Error(`${r.status} ${r.statusText}`);
|
||||
threadId = d.threadId;
|
||||
threadId = d.threadId;
|
||||
document.cookie =
|
||||
`threadId=${threadId}; Path=/; Secure; SameSite=Strict; Max-Age=7200`;
|
||||
} catch (e) {
|
||||
console.error("Error creating threadId:", e);
|
||||
alert("Failed to initialise the conversation. Please refresh and try again.");
|
||||
alert("Failed to initialise the conversation. Please refresh.");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================================================================== */
|
||||
/* =================================================================== */
|
||||
function injectStyles() {
|
||||
const css = `
|
||||
#ht-ai-btn{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);width:60px;height:60px;border-radius:50%;background:#1e1e1e;color:#fff;font-size:28px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:99999;box-shadow:0 2px 8px rgba(0,0,0,.4);transition:opacity .2s}
|
||||
#ht-ai-btn:hover{opacity:.85}
|
||||
@media(max-width:768px){#ht-ai-btn{display:none}}
|
||||
#ht-ai-tooltip{position:fixed;padding:6px 8px;background:#111;color:#fff;border-radius:4px;font-size:13px;white-space:pre-wrap;pointer-events:none;opacity:0;transform:translate(-50%,-8px);transition:opacity .15s ease,transform .15s ease;z-index:100000}
|
||||
#ht-ai-tooltip.show{opacity:1;transform:translate(-50%,-12px)}
|
||||
#ht-ai-panel{position:fixed;top:0;right:0;height:100%;width:350px;max-width:90vw;background:#000;color:#fff;display:flex;flex-direction:column;transform:translateX(100%);transition:transform .3s ease;z-index:100000;font-family:system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial,sans-serif}
|
||||
#ht-ai-panel.open{transform:translateX(0)}
|
||||
@media(max-width:768px){#ht-ai-panel{display:none}}
|
||||
#ht-ai-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #333}
|
||||
#ht-ai-header .ht-actions{display:flex;gap:8px;align-items:center}
|
||||
#ht-ai-close,#ht-ai-reset{cursor:pointer;font-size:18px;background:none;border:none;color:#fff;padding:0}
|
||||
#ht-ai-close:hover,#ht-ai-reset:hover{opacity:.7}
|
||||
#ht-ai-chat{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px;font-size:14px}
|
||||
.ht-msg{max-width:90%;line-height:1.4;padding:10px 12px;border-radius:8px;white-space:pre-wrap;word-wrap:break-word}
|
||||
.ht-user{align-self:flex-end;background:${BRAND_RED}}
|
||||
.ht-ai{align-self:flex-start;background:#222}
|
||||
.ht-context{align-self:flex-start;background:#444;font-style:italic;font-size:13px}
|
||||
#ht-ai-input{display:flex;gap:8px;padding:12px 16px;border-top:1px solid #333}
|
||||
#ht-ai-question{flex:1;min-height:40px;max-height:120px;resize:vertical;padding:8px;border-radius:6px;border:none;font-size:14px}
|
||||
#ht-ai-send{padding:0 18px;border:none;border-radius:6px;background:${BRAND_RED};color:#fff;font-size:14px;cursor:pointer}
|
||||
#ht-ai-send:disabled{opacity:.5;cursor:not-allowed}
|
||||
/* Loader animation */
|
||||
.ht-loading{display:inline-flex;align-items:center;gap:4px}
|
||||
.ht-loading span{width:6px;height:6px;border-radius:50%;background:#888;animation:ht-bounce 1.2s infinite ease-in-out}
|
||||
.ht-loading span:nth-child(2){animation-delay:0.2s}
|
||||
.ht-loading span:nth-child(3){animation-delay:0.4s}
|
||||
@keyframes ht-bounce{0%,80%,100%{transform:scale(0);}40%{transform:scale(1);} }
|
||||
::selection{background:#ffeb3b;color:#000}
|
||||
::-moz-selection{background:#ffeb3b;color:#000}`;
|
||||
#ht-ai-btn{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);min-width:60px;height:60px;border-radius:30px;background:linear-gradient(45deg, #b31328, #d42d3f, #2d5db4, #3470e4);background-size:300% 300%;animation:gradientShift 8s ease infinite;color:#fff;font-size:18px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:99999;box-shadow:0 2px 8px rgba(0,0,0,.4);transition:opacity .2s;padding:0 20px}
|
||||
#ht-ai-btn span{margin-left:8px;font-weight:bold}
|
||||
@keyframes gradientShift{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
|
||||
#ht-ai-btn:hover{opacity:.85}
|
||||
@media(max-width:768px){#ht-ai-btn{display:none}}
|
||||
#ht-ai-tooltip{position:fixed;padding:6px 8px;background:#111;color:#fff;border-radius:4px;font-size:13px;white-space:pre-wrap;pointer-events:none;opacity:0;transform:translate(-50%,-8px);transition:opacity .15s ease,transform .15s ease;z-index:100000}
|
||||
#ht-ai-tooltip.show{opacity:1;transform:translate(-50%,-12px)}
|
||||
#ht-ai-panel{position:fixed;top:0;right:0;height:100%;max-width:90vw;background:#000;color:#fff;display:flex;flex-direction:column;transform:translateX(100%);transition:transform .3s ease;z-index:100000;font-family:system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial,sans-serif}
|
||||
#ht-ai-panel.open{transform:translateX(0)}
|
||||
@media(max-width:768px){#ht-ai-panel{display:none}}
|
||||
#ht-ai-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #333}
|
||||
#ht-ai-header .ht-actions{display:flex;gap:8px;align-items:center}
|
||||
#ht-ai-close,#ht-ai-reset{cursor:pointer;font-size:18px;background:none;border:none;color:#fff;padding:0}
|
||||
#ht-ai-close:hover,#ht-ai-reset:hover{opacity:.7}
|
||||
#ht-ai-chat{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px;font-size:14px}
|
||||
.ht-msg{max-width:90%;line-height:1.4;padding:10px 12px;border-radius:8px;white-space:pre-wrap;word-wrap:break-word}
|
||||
.ht-user{align-self:flex-end;background:${BRAND_RED}}
|
||||
.ht-ai{align-self:flex-start;background:#222}
|
||||
.ht-context{align-self:flex-start;background:#444;font-style:italic;font-size:13px}
|
||||
#ht-ai-input{display:flex;gap:8px;padding:12px 16px;border-top:1px solid #333}
|
||||
#ht-ai-question{flex:1;min-height:40px;max-height:120px;resize:vertical;padding:8px;border-radius:6px;border:none;font-size:14px}
|
||||
#ht-ai-send{padding:0 18px;border:none;border-radius:6px;background:${BRAND_RED};color:#fff;font-size:14px;cursor:pointer}
|
||||
#ht-ai-send:disabled{opacity:.5;cursor:not-allowed}
|
||||
/* Loader */
|
||||
.ht-loading{display:inline-flex;align-items:center;gap:4px}
|
||||
.ht-loading span{width:6px;height:6px;border-radius:50%;background:#888;animation:ht-bounce 1.2s infinite ease-in-out}
|
||||
.ht-loading span:nth-child(2){animation-delay:0.2s}
|
||||
.ht-loading span:nth-child(3){animation-delay:0.4s}
|
||||
@keyframes ht-bounce{0%,80%,100%{transform:scale(0);}40%{transform:scale(1);} }
|
||||
::selection{background:#ffeb3b;color:#000}
|
||||
::-moz-selection{background:#ffeb3b;color:#000}
|
||||
/* NEW: resizer handle */
|
||||
#ht-ai-resizer{position:absolute;left:0;top:0;width:6px;height:100%;cursor:ew-resize;background:transparent}
|
||||
#ht-ai-resizer:hover{background:rgba(255,255,255,.05)}`;
|
||||
const s = document.createElement("style");
|
||||
s.id = "ht-ai-style";
|
||||
s.textContent = css;
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
/* =================================================================== */
|
||||
function createFloatingButton() {
|
||||
const d = document.createElement("div");
|
||||
d.id = "ht-ai-btn";
|
||||
d.textContent = "🤖";
|
||||
d.innerHTML = "🤖<span>HackTricksAI</span>";
|
||||
document.body.appendChild(d);
|
||||
return d;
|
||||
}
|
||||
|
||||
function createTooltip(btn) {
|
||||
const t = document.createElement("div");
|
||||
t.id = "ht-ai-tooltip";
|
||||
@ -311,11 +296,16 @@
|
||||
btn.addEventListener("mouseleave", () => t.classList.remove("show"));
|
||||
}
|
||||
|
||||
/* =================================================================== */
|
||||
function createSidebar() {
|
||||
const saved = parseInt(localStorage.getItem("htAiWidth") || DEF_W, 10);
|
||||
const width = Math.min(Math.max(saved, MIN_W), MAX_W);
|
||||
|
||||
const p = document.createElement("div");
|
||||
p.id = "ht-ai-panel";
|
||||
p.style.width = width + "px"; // ← applied width
|
||||
p.innerHTML = `
|
||||
<div id="ht-ai-header"><strong>HackTricks AI Chat</strong>
|
||||
<div id="ht-ai-header"><strong>HackTricks AI Chat</strong>
|
||||
<div class="ht-actions">
|
||||
<button id="ht-ai-reset" title="Reset">↺</button>
|
||||
<span id="ht-ai-close" title="Close">✖</span>
|
||||
@ -326,7 +316,39 @@
|
||||
<textarea id="ht-ai-question" placeholder="Type your question…"></textarea>
|
||||
<button id="ht-ai-send">Send</button>
|
||||
</div>`;
|
||||
/* NEW: resizer strip */
|
||||
const resizer = document.createElement("div");
|
||||
resizer.id = "ht-ai-resizer";
|
||||
p.appendChild(resizer);
|
||||
document.body.appendChild(p);
|
||||
addResizeLogic(resizer, p);
|
||||
return p;
|
||||
}
|
||||
|
||||
/* ---------------- resize behaviour ---------------- */
|
||||
function addResizeLogic(handle, panel) {
|
||||
let startX, startW, dragging = false;
|
||||
|
||||
const onMove = (e) => {
|
||||
if (!dragging) return;
|
||||
const dx = startX - e.clientX; // dragging leftwards ⇒ +dx
|
||||
let newW = startW + dx;
|
||||
newW = Math.min(Math.max(newW, MIN_W), MAX_W);
|
||||
panel.style.width = newW + "px";
|
||||
};
|
||||
const onUp = () => {
|
||||
if (!dragging) return;
|
||||
dragging = false;
|
||||
localStorage.setItem("htAiWidth", parseInt(panel.style.width, 10));
|
||||
document.removeEventListener("mousemove", onMove);
|
||||
document.removeEventListener("mouseup", onUp);
|
||||
};
|
||||
handle.addEventListener("mousedown", (e) => {
|
||||
dragging = true;
|
||||
startX = e.clientX;
|
||||
startW = parseInt(window.getComputedStyle(panel).width, 10);
|
||||
document.addEventListener("mousemove", onMove);
|
||||
document.addEventListener("mouseup", onUp);
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
Loading…
x
Reference in New Issue
Block a user