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

This commit is contained in:
Translator 2025-05-18 03:07:38 +00:00
parent d2a1871aa2
commit 9bb3c1416a
2 changed files with 146 additions and 124 deletions

View File

@ -8,7 +8,7 @@ Les cookies possèdent plusieurs attributs qui contrôlent leur comportement dan
### Expires et Max-Age
La date d'expiration d'un cookie est déterminée par l'attribut `Expires`. En revanche, l'attribut `Max-age` définit le temps en secondes avant qu'un cookie ne soit supprimé. **Optez pour `Max-age` car il reflète des pratiques plus modernes.**
La date d'expiration d'un cookie est déterminée par l'attribut `Expires`. En revanche, l'attribut `Max-age` définit le temps en secondes jusqu'à ce qu'un cookie soit supprimé. **Optez pour `Max-age` car il reflète des pratiques plus modernes.**
### Domaine
@ -16,7 +16,7 @@ Les hôtes qui reçoivent un cookie sont spécifiés par l'attribut `Domain`. Pa
### Chemin
Un chemin URL spécifique qui doit être présent dans l'URL demandée pour que l'en-tête `Cookie` soit envoyé est indiqué par l'attribut `Path`. Cet attribut considère le caractère `/` comme un séparateur de répertoire, permettant des correspondances dans les sous-répertoires également.
Un chemin d'URL spécifique qui doit être présent dans l'URL demandée pour que l'en-tête `Cookie` soit envoyé est indiqué par l'attribut `Path`. Cet attribut considère le caractère `/` comme un séparateur de répertoire, permettant des correspondances dans les sous-répertoires également.
### Règles de Commande
@ -48,7 +48,7 @@ Tableau provenant de [Invicti](https://www.netsparker.com/blog/web-security/same
Un cookie avec l'attribut _**SameSite**_ **atténuera les attaques CSRF** où une session connectée est nécessaire.
**\*Notez qu'à partir de Chrome80 (février 2019), le comportement par défaut d'un cookie sans attribut SameSite** **sera 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/)).\
Notez qu'après avoir appliqué ce changement, les **cookies sans politique SameSite** dans Chrome seront **traités comme None** pendant les **premières 2 minutes, puis comme Lax pour les requêtes POST de niveau supérieur entre sites.**
Notez qu'après avoir appliqué ce changement, les **cookies sans politique SameSite** dans Chrome seront **traités comme None** pendant les **premières 2 minutes, puis comme Lax pour les requêtes POST inter-domaines de niveau supérieur.**
## Drapeaux des Cookies
@ -56,9 +56,9 @@ Notez qu'après avoir appliqué ce changement, les **cookies sans politique Same
Cela empêche le **client** d'accéder au cookie (via **Javascript**, par exemple : `document.cookie`)
#### **Contournements**
#### **Bypasses**
- Si la page **envoie les cookies en réponse** à une requête (par exemple dans une page **PHPinfo**), il est possible d'abuser de l'XSS pour envoyer une requête à cette page et **voler les cookies** de la réponse (voir un exemple dans [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/)).
- Si la page **envoie les cookies en réponse** à des requêtes (par exemple dans une page **PHPinfo**), il est possible d'abuser de l'XSS pour envoyer une requête à cette page et **voler les cookies** de la réponse (voir un exemple dans [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/)).
- Cela pourrait être contourné avec des requêtes **TRACE** **HTTP** car la réponse du serveur (si cette méthode HTTP est disponible) reflétera les cookies envoyés. Cette technique est appelée **Cross-Site Tracking**.
- Cette technique est évitée par **les navigateurs modernes en ne permettant pas l'envoi d'une requête TRACE** depuis JS. Cependant, certains contournements ont été trouvés dans des logiciels spécifiques comme l'envoi de `\r\nTRACE` au lieu de `TRACE` à IE6.0 SP2.
- Une autre méthode est l'exploitation de vulnérabilités zero-day des navigateurs.
@ -72,7 +72,7 @@ cookie-jar-overflow.md
### Secure
La requête n'enverra **que** le cookie dans une requête HTTP uniquement si la requête est transmise sur un canal sécurisé (typiquement **HTTPS**).
La requête n'enverra **que** le cookie dans une requête HTTP si la requête est transmise sur un canal sécurisé (typiquement **HTTPS**).
## Préfixes des Cookies
@ -87,9 +87,9 @@ Pour les cookies préfixés par `__Host-`, plusieurs conditions doivent être re
Il est important de noter que les cookies préfixés par `__Host-` ne sont pas autorisés à être envoyés à des superdomaines ou sous-domaines. Cette restriction aide à isoler les cookies d'application. Ainsi, utiliser le préfixe `__Host-` pour tous les cookies d'application peut être considéré comme une bonne pratique pour améliorer la sécurité et l'isolement.
### Surcharger les cookies
### Surcharge des cookies
Ainsi, l'une des protections des cookies préfixés par `__Host-` est d'empêcher leur écrasement depuis des sous-domaines. Prévenir par exemple les [**attaques de Cookie Tossing**](cookie-tossing.md). Dans la présentation [**Cookie Crumbles: Unveiling Web Session Integrity Vulnerabilities**](https://www.youtube.com/watch?v=F_wAzF4a7Xg) ([**document**](https://www.usenix.org/system/files/usenixsecurity23-squarcina.pdf)), il est présenté qu'il était possible de définir des cookies préfixés par __HOST- depuis un sous-domaine, en trompant le parseur, par exemple, en ajoutant "=" au début ou à la fin... :
Ainsi, l'une des protections des cookies préfixés par `__Host-` est d'empêcher leur écrasement depuis des sous-domaines. Prévenant par exemple [**les attaques de Cookie Tossing**](cookie-tossing.md). Dans la présentation [**Cookie Crumbles: Unveiling Web Session Integrity Vulnerabilities**](https://www.youtube.com/watch?v=F_wAzF4a7Xg) ([**papier**](https://www.usenix.org/system/files/usenixsecurity23-squarcina.pdf)), il est présenté qu'il était possible de définir des cookies préfixés par \_\_HOST- depuis un sous-domaine, en trompant le parseur, par exemple, en ajoutant "=" au début ou au début et à la fin... :
<figure><img src="../../images/image (6) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
@ -111,7 +111,7 @@ Cette attaque consiste à voler le cookie d'un utilisateur pour obtenir un accè
### Fixation de Session
Dans ce scénario, un attaquant trompe une victime pour qu'elle utilise un cookie spécifique pour se connecter. Si l'application n'assigne pas un nouveau cookie lors de la connexion, l'attaquant, possédant le cookie original, peut usurper l'identité de la victime. Cette technique repose sur le fait que la victime se connecte avec un cookie fourni par l'attaquant.
Dans ce scénario, un attaquant trompe une victime pour qu'elle utilise un cookie spécifique pour se connecter. Si l'application n'attribue pas un nouveau cookie lors de la connexion, l'attaquant, possédant le cookie original, peut usurper l'identité de la victime. Cette technique repose sur le fait que la victime se connecte avec un cookie fourni par l'attaquant.
Si vous avez trouvé un **XSS dans un sous-domaine** ou si vous **contrôlez un sous-domaine**, lisez :
@ -147,7 +147,7 @@ document.cookie = "a=v1"
document.cookie = "=test value;" // Setting an empty named cookie
document.cookie = "b=v2"
```
Le résultat dans l'en-tête de cookie envoyé est `a=v1; test value; b=v2;`. Intriguement, cela permet la manipulation des cookies si un cookie avec un nom vide est défini, contrôlant potentiellement d'autres cookies en définissant le cookie vide à une valeur spécifique :
Le résultat dans l'en-tête de cookie envoyé est `a=v1; test value; b=v2;`. Il est intéressant de noter que cela permet la manipulation des cookies si un cookie avec un nom vide est défini, contrôlant potentiellement d'autres cookies en définissant le cookie vide à une valeur spécifique :
```js
function setCookie(name, value) {
document.cookie = `${name}=${value}`
@ -163,17 +163,17 @@ Dans Chrome, si un codepoint de surrogate Unicode fait partie d'un cookie défin
```js
document.cookie = "\ud800=meep"
```
Cela entraîne `document.cookie` renvoyant une chaîne vide, indiquant une corruption permanente.
Cela entraîne que `document.cookie` renvoie une chaîne vide, indiquant une corruption permanente.
#### Cookie Smuggling en raison de problèmes de parsing
(Voir plus de détails dans la[recherche originale](https://blog.ankursundara.com/cookie-bugs/)) Plusieurs serveurs web, y compris ceux de Java (Jetty, TomCat, Undertow) et Python (Zope, cherrypy, web.py, aiohttp, bottle, webob), gèrent mal les chaînes de cookies en raison d'un support obsolète de RFC2965. Ils lisent une valeur de cookie entre guillemets comme une seule valeur même si elle inclut des points-virgules, qui devraient normalement séparer les paires clé-valeur :
(Voir plus de détails dans la [recherche originale](https://blog.ankursundara.com/cookie-bugs/)) Plusieurs serveurs web, y compris ceux de Java (Jetty, TomCat, Undertow) et Python (Zope, cherrypy, web.py, aiohttp, bottle, webob), gèrent mal les chaînes de cookies en raison d'un support obsolète de RFC2965. Ils lisent une valeur de cookie entre guillemets comme une seule valeur même si elle inclut des points-virgules, qui devraient normalement séparer les paires clé-valeur :
```
RENDER_TEXT="hello world; JSESSIONID=13371337; ASDF=end";
```
#### Vulnérabilités d'injection de cookies
(Voir plus de détails dans la [recherche originale](https://blog.ankursundara.com/cookie-bugs/)) Le parsing incorrect des cookies par les serveurs, notamment Undertow, Zope, et ceux utilisant `http.cookie.SimpleCookie` et `http.cookie.BaseCookie` de Python, crée des opportunités pour des attaques par injection de cookies. Ces serveurs ne parviennent pas à délimiter correctement le début de nouveaux cookies, permettant aux attaquants de falsifier des cookies :
(Voir plus de détails dans la [recherche originale](https://blog.ankursundara.com/cookie-bugs/)) Le parsing incorrect des cookies par les serveurs, notamment Undertow, Zope, et ceux utilisant `http.cookie.SimpleCookie` et `http.cookie.BaseCookie` de Python, crée des opportunités pour des attaques par injection de cookies. Ces serveurs échouent à délimiter correctement le début de nouveaux cookies, permettant aux attaquants de falsifier des cookies :
- Undertow s'attend à un nouveau cookie immédiatement après une valeur entre guillemets sans point-virgule.
- Zope recherche une virgule pour commencer à parser le cookie suivant.
@ -210,7 +210,7 @@ document.cookie = `param2=end";`;
Consultez la section précédente.
#### Analyse de contournement des valeurs avec l'encodage de chaînes entre guillemets
#### Contournement de l'analyse des valeurs avec l'encodage de chaînes citées
Cette analyse indique de déséchapper les valeurs échappées à l'intérieur des cookies, donc "\a" devient "a". Cela peut être utile pour contourner les WAFS car :
@ -221,9 +221,9 @@ Cette analyse indique de déséchapper les valeurs échappées à l'intérieur d
Dans le RFC2109, il est indiqué qu'une **virgule peut être utilisée comme séparateur entre les valeurs des cookies**. Et il est également possible d'ajouter **des espaces et des tabulations avant et après le signe égal**. Par conséquent, un cookie comme `$Version=1; foo=bar, abc = qux` ne génère pas le cookie `"foo":"bar, admin = qux"` mais les cookies `foo":"bar"` et `"admin":"qux"`. Remarquez comment 2 cookies sont générés et comment l'espace avant et après le signe égal a été supprimé pour admin.
#### Contournement de l'analyse des valeurs avec le fractionnement des cookies
#### Contournement de l'analyse des valeurs avec la séparation des cookies
Enfin, différentes portes dérobées pourraient se joindre dans une chaîne de différents cookies passés dans différents en-têtes de cookies comme dans :
Enfin, différentes portes dérobées pourraient se rejoindre dans une chaîne de différents cookies passés dans différents en-têtes de cookies comme dans :
```
GET / HTTP/1.1
Host: example.com
@ -254,7 +254,7 @@ Resulting cookie: name=eval('test//, comment') => allowed
Si le cookie reste le même (ou presque) lorsque vous vous connectez, cela signifie probablement que le cookie est lié à un champ de votre compte (probablement le nom d'utilisateur). Ensuite, vous pouvez :
- Essayer de créer beaucoup de **comptes** avec des noms d'utilisateur très **similaires** et essayer de **deviner** comment l'algorithme fonctionne.
- Essayer de **bruteforcer le nom d'utilisateur**. Si le cookie est uniquement enregistré comme méthode d'authentification pour votre nom d'utilisateur, alors vous pouvez créer un compte avec le nom d'utilisateur "**Bmin**" et **bruteforcer** chaque **bit** de votre cookie car l'un des cookies que vous allez essayer sera celui appartenant à "**admin**".
- Essayer de **bruteforcer le nom d'utilisateur**. Si le cookie est enregistré uniquement comme méthode d'authentification pour votre nom d'utilisateur, alors vous pouvez créer un compte avec le nom d'utilisateur "**Bmin**" et **bruteforcer** chaque **bit** de votre cookie car l'un des cookies que vous allez essayer sera celui appartenant à "**admin**".
- Essayer **Padding** **Oracle** (vous pouvez déchiffrer le contenu du cookie). Utilisez **padbuster**.
**Padding Oracle - Exemples de Padbuster**

View File

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