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 pt
This commit is contained in:
parent
b3f7a3d674
commit
13ab546624
@ -12,7 +12,7 @@ A data de expiração de um cookie é determinada pelo atributo `Expires`. Por o
|
|||||||
|
|
||||||
### Domínio
|
### Domínio
|
||||||
|
|
||||||
Os hosts que receberão um cookie são especificados pelo atributo `Domain`. Por padrão, isso é definido para o host que emitiu o cookie, não incluindo seus subdomínios. No entanto, quando o atributo `Domain` é explicitamente definido, ele abrange subdomínios também. Isso torna a especificação do atributo `Domain` uma opção menos restritiva, útil para cenários onde o compartilhamento de cookies entre subdomínios é necessário. Por exemplo, definir `Domain=mozilla.org` torna os cookies acessíveis em seus subdomínios como `developer.mozilla.org`.
|
Os hosts que recebem um cookie são especificados pelo atributo `Domain`. Por padrão, isso é definido para o host que emitiu o cookie, não incluindo seus subdomínios. No entanto, quando o atributo `Domain` é explicitamente definido, ele abrange subdomínios também. Isso torna a especificação do atributo `Domain` uma opção menos restritiva, útil para cenários onde o compartilhamento de cookies entre subdomínios é necessário. Por exemplo, definir `Domain=mozilla.org` torna os cookies acessíveis em seus subdomínios como `developer.mozilla.org`.
|
||||||
|
|
||||||
### Caminho
|
### Caminho
|
||||||
|
|
||||||
@ -58,10 +58,10 @@ Isso evita que o **cliente** acesse o cookie (via **Javascript**, por exemplo: `
|
|||||||
|
|
||||||
#### **Bypasses**
|
#### **Bypasses**
|
||||||
|
|
||||||
- Se a página **estiver enviando os cookies como resposta** a uma solicitação (por exemplo, em uma página **PHPinfo**), é possível abusar do XSS para enviar uma solicitação a essa página e **roubar os cookies** da resposta (ver um exemplo em [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/)).
|
- Se a página **estiver enviando os cookies como resposta** a uma solicitação (por exemplo, em uma página **PHPinfo**), é possível abusar do XSS para enviar uma solicitação a essa página e **roubar os cookies** da resposta (ver um exemplo em [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/)).
|
||||||
- Isso pode ser contornado com solicitações **TRACE** **HTTP**, pois a resposta do servidor (se esse método HTTP estiver disponível) refletirá os cookies enviados. Essa técnica é chamada de **Cross-Site Tracking**.
|
- Isso pode ser contornado com solicitações **TRACE** **HTTP**, pois a resposta do servidor (se esse método HTTP estiver disponível) refletirá os cookies enviados. Essa técnica é chamada de **Cross-Site Tracking**.
|
||||||
- Essa técnica é evitada por **navegadores modernos ao não permitir o envio de uma solicitação TRACE** a partir do JS. No entanto, alguns contornos para isso foram encontrados em softwares específicos, como enviar `\r\nTRACE` em vez de `TRACE` para IE6.0 SP2.
|
- Essa técnica é evitada por **navegadores modernos ao não permitir o envio de uma solicitação TRACE** a partir do JS. No entanto, alguns contornos para isso foram encontrados em softwares específicos, como enviar `\r\nTRACE` em vez de `TRACE` para IE6.0 SP2.
|
||||||
- Outra maneira é a exploração de vulnerabilidades zero-day dos navegadores.
|
- Outra maneira é a exploração de vulnerabilidades zero/day dos navegadores.
|
||||||
- É possível **sobrescrever cookies HttpOnly** realizando um ataque de transbordamento de Cookie Jar:
|
- É possível **sobrescrever cookies HttpOnly** realizando um ataque de transbordamento de Cookie Jar:
|
||||||
|
|
||||||
{{#ref}}
|
{{#ref}}
|
||||||
@ -133,7 +133,7 @@ cookie-tossing.md
|
|||||||
|
|
||||||
Clique no link anterior para acessar uma página que explica possíveis falhas em JWT.
|
Clique no link anterior para acessar uma página que explica possíveis falhas em JWT.
|
||||||
|
|
||||||
Tokens Web JSON (JWT) usados em cookies também podem apresentar vulnerabilidades. Para informações detalhadas sobre possíveis falhas e como explorá-las, é recomendável acessar o documento vinculado sobre hacking JWT.
|
JSON Web Tokens (JWT) usados em cookies também podem apresentar vulnerabilidades. Para informações detalhadas sobre falhas potenciais e como explorá-las, é recomendável acessar o documento vinculado sobre hacking JWT.
|
||||||
|
|
||||||
### Cross-Site Request Forgery (CSRF)
|
### Cross-Site Request Forgery (CSRF)
|
||||||
|
|
||||||
@ -167,7 +167,7 @@ Isso resulta em `document.cookie` retornando uma string vazia, indicando corrup
|
|||||||
|
|
||||||
#### Cookie Smuggling Devido a Problemas de Análise
|
#### Cookie Smuggling Devido a Problemas de Análise
|
||||||
|
|
||||||
(Confira mais detalhes na [pesquisa original](https://blog.ankursundara.com/cookie-bugs/)) Vários servidores web, incluindo os de Java (Jetty, TomCat, Undertow) e Python (Zope, cherrypy, web.py, aiohttp, bottle, webob), manipulam incorretamente strings de cookies devido ao suporte desatualizado do RFC2965. Eles leem um valor de cookie entre aspas duplas como um único valor, mesmo que inclua ponto e vírgulas, que normalmente deveriam separar pares chave-valor:
|
(Consulte mais detalhes na [pesquisa original](https://blog.ankursundara.com/cookie-bugs/)) Vários servidores web, incluindo os de Java (Jetty, TomCat, Undertow) e Python (Zope, cherrypy, web.py, aiohttp, bottle, webob), manipulam incorretamente strings de cookies devido ao suporte desatualizado ao RFC2965. Eles leem um valor de cookie entre aspas duplas como um único valor, mesmo que inclua ponto e vírgula, que normalmente deveria separar pares chave-valor:
|
||||||
```
|
```
|
||||||
RENDER_TEXT="hello world; JSESSIONID=13371337; ASDF=end";
|
RENDER_TEXT="hello world; JSESSIONID=13371337; ASDF=end";
|
||||||
```
|
```
|
||||||
@ -179,26 +179,25 @@ RENDER_TEXT="hello world; JSESSIONID=13371337; ASDF=end";
|
|||||||
- Zope procura uma vírgula para começar a analisar o próximo cookie.
|
- Zope procura uma vírgula para começar a analisar o próximo cookie.
|
||||||
- As classes de cookies do Python começam a análise em um caractere de espaço.
|
- As classes de cookies do Python começam a análise em um caractere de espaço.
|
||||||
|
|
||||||
Essa vulnerabilidade é particularmente perigosa em aplicações web que dependem de proteção CSRF baseada em cookies, pois permite que atacantes injetem cookies de token CSRF falsificados, potencialmente contornando medidas de segurança. O problema é agravado pelo tratamento de nomes de cookies duplicados pelo Python, onde a última ocorrência substitui as anteriores. Também levanta preocupações para cookies `__Secure-` e `__Host-` em contextos inseguros e pode levar a contornos de autorização quando cookies são passados para servidores de back-end suscetíveis à falsificação.
|
Essa vulnerabilidade é particularmente perigosa em aplicações web que dependem de proteção CSRF baseada em cookies, pois permite que atacantes injetem cookies de token CSRF falsificados, potencialmente contornando medidas de segurança. O problema é agravado pelo tratamento de nomes de cookies duplicados pelo Python, onde a última ocorrência substitui as anteriores. Também levanta preocupações para cookies `__Secure-` e `__Host-` em contextos inseguros e pode levar a contornos de autorização quando cookies são passados para servidores de back-end suscetíveis a falsificação.
|
||||||
|
|
||||||
### Cookies $version
|
### Cookies $version
|
||||||
|
|
||||||
#### Bypass de WAF
|
#### Bypass de WAF
|
||||||
|
|
||||||
De acordo com [**this blogpost**](https://portswigger.net/research/bypassing-wafs-with-the-phantom-version-cookie), pode ser possível usar o atributo de cookie **`$Version=1`** para fazer o back-end usar uma lógica antiga para analisar o cookie devido ao **RFC2109**. Além disso, outros valores como **`$Domain`** e **`$Path`** podem ser usados para modificar o comportamento do back-end com o cookie.
|
De acordo com [**este blogpost**](https://portswigger.net/research/bypassing-wafs-with-the-phantom-version-cookie), pode ser possível usar o atributo de cookie **`$Version=1`** para fazer o back-end usar uma lógica antiga para analisar o cookie devido ao **RFC2109**. Além disso, outros valores como **`$Domain`** e **`$Path`** podem ser usados para modificar o comportamento do back-end com o cookie.
|
||||||
|
|
||||||
#### Ataque de Sanduíche de Cookies
|
#### Ataque de Sanduíche de Cookies
|
||||||
|
|
||||||
De acordo com [**this blogpost**](https://portswigger.net/research/stealing-httponly-cookies-with-the-cookie-sandwich-technique), é possível usar a técnica de sanduíche de cookies para roubar cookies HttpOnly. Estes são os requisitos e passos:
|
De acordo com [**este blogpost**](https://portswigger.net/research/stealing-httponly-cookies-with-the-cookie-sandwich-technique), é possível usar a técnica de sanduíche de cookies para roubar cookies HttpOnly. Estes são os requisitos e passos:
|
||||||
|
|
||||||
- Encontre um lugar onde um **cookie aparentemente inútil é refletido na resposta**
|
- Encontre um lugar onde um **cookie aparentemente inútil é refletido na resposta**
|
||||||
- **Crie um cookie chamado `$Version`** com valor `1` (ou você pode fazer isso em um ataque XSS a partir do JS) com um caminho mais específico para que ele obtenha a posição inicial (alguns frameworks como o Python não precisam deste passo)
|
- **Crie um cookie chamado `$Version`** com valor `1` (ou você pode fazer isso em um ataque XSS a partir do JS) com um caminho mais específico para que ele obtenha a posição inicial (alguns frameworks como Python não precisam deste passo)
|
||||||
- **Crie o cookie que é refletido** com um valor que deixa uma **aspas duplas abertas** e com um caminho específico para que ele seja posicionado no banco de dados de cookies após o anterior (`$Version`)
|
- **Crie o cookie que é refletido** com um valor que deixa uma **aspas duplas abertas** e com um caminho específico para que ele seja posicionado no banco de dados de cookies após o anterior (`$Version`)
|
||||||
- Então, o cookie legítimo irá na sequência
|
- Então, o cookie legítimo irá na sequência
|
||||||
- **Crie um cookie fictício que fecha as aspas duplas** dentro de seu valor
|
- **Crie um cookie fictício que fecha as aspas duplas** dentro de seu valor
|
||||||
|
|
||||||
Dessa forma, o cookie da vítima fica preso dentro do novo cookie versão 1 e será refletido sempre que for refletido.
|
Dessa forma, o cookie da vítima fica preso dentro do novo cookie versão 1 e será refletido sempre que for refletido.
|
||||||
e.g. from the post:
|
|
||||||
```javascript
|
```javascript
|
||||||
document.cookie = `$Version=1;`;
|
document.cookie = `$Version=1;`;
|
||||||
document.cookie = `param1="start`;
|
document.cookie = `param1="start`;
|
||||||
@ -213,7 +212,7 @@ Verifique a seção anterior.
|
|||||||
|
|
||||||
#### Análise de valor de bypass com codificação de string entre aspas
|
#### Análise de valor de bypass com codificação de string entre aspas
|
||||||
|
|
||||||
Essa análise indica desescapar valores escapados dentro dos cookies, então "\a" se torna "a". Isso pode ser útil para contornar WAFS, como:
|
Essa análise indica desescapar valores escapados dentro dos cookies, então "\a" se torna "a". Isso pode ser útil para contornar WAFs, como:
|
||||||
|
|
||||||
- `eval('test') => proibido`
|
- `eval('test') => proibido`
|
||||||
- `"\e\v\a\l\(\'\t\e\s\t\'\)" => permitido`
|
- `"\e\v\a\l\(\'\t\e\s\t\'\)" => permitido`
|
||||||
@ -224,7 +223,7 @@ No RFC2109, é indicado que uma **vírgula pode ser usada como um separador entr
|
|||||||
|
|
||||||
#### Análise de valor de bypass com divisão de cookies
|
#### Análise de valor de bypass com divisão de cookies
|
||||||
|
|
||||||
Finalmente, diferentes backdoors se juntariam em uma string diferentes cookies passados em diferentes cabeçalhos de cookies como em:
|
Finalmente, diferentes backdoors se juntariam em uma string diferentes cookies passados em diferentes cabeçalhos de cookies, como em:
|
||||||
```
|
```
|
||||||
GET / HTTP/1.1
|
GET / HTTP/1.1
|
||||||
Host: example.com
|
Host: example.com
|
||||||
@ -247,7 +246,7 @@ Resulting cookie: name=eval('test//, comment') => allowed
|
|||||||
- Tente fazer login com 2 dispositivos (ou navegadores) na mesma conta usando o mesmo cookie.
|
- Tente fazer login com 2 dispositivos (ou navegadores) na mesma conta usando o mesmo cookie.
|
||||||
- Verifique se o cookie contém alguma informação e tente modificá-lo.
|
- Verifique se o cookie contém alguma informação e tente modificá-lo.
|
||||||
- Tente criar várias contas com nomes de usuário quase idênticos e verifique se consegue ver semelhanças.
|
- Tente criar várias contas com nomes de usuário quase idênticos e verifique se consegue ver semelhanças.
|
||||||
- Verifique a opção "**lembrar-me**" se existir para ver como funciona. Se existir e puder ser vulnerável, sempre use o cookie de **lembrar-me** sem nenhum outro cookie.
|
- Verifique a opção "**lembrar de mim**" se existir para ver como funciona. Se existir e puder ser vulnerável, sempre use o cookie de **lembrar de mim** sem nenhum outro cookie.
|
||||||
- Verifique se o cookie anterior funciona mesmo após você mudar a senha.
|
- Verifique se o cookie anterior funciona mesmo após você mudar a senha.
|
||||||
|
|
||||||
#### **Ataques avançados a cookies**
|
#### **Ataques avançados a cookies**
|
||||||
@ -255,7 +254,7 @@ Resulting cookie: name=eval('test//, comment') => allowed
|
|||||||
Se o cookie permanecer o mesmo (ou quase) quando você faz login, isso provavelmente significa que o cookie está relacionado a algum campo da sua conta (provavelmente o nome de usuário). Então você pode:
|
Se o cookie permanecer o mesmo (ou quase) quando você faz login, isso provavelmente significa que o cookie está relacionado a algum campo da sua conta (provavelmente o nome de usuário). Então você pode:
|
||||||
|
|
||||||
- Tentar criar muitas **contas** com nomes de usuário muito **semelhantes** e tentar **adivinhar** como o algoritmo está funcionando.
|
- Tentar criar muitas **contas** com nomes de usuário muito **semelhantes** e tentar **adivinhar** como o algoritmo está funcionando.
|
||||||
- Tentar **forçar o nome de usuário**. Se o cookie for salvo apenas como um método de autenticação para o seu nome de usuário, então você pode criar uma conta com o nome de usuário "**Bmin**" e **forçar** cada único **bit** do seu cookie porque um dos cookies que você tentará será o pertencente a "**admin**".
|
- Tentar **forçar o nome de usuário**. Se o cookie é salvo apenas como um método de autenticação para o seu nome de usuário, então você pode criar uma conta com o nome de usuário "**Bmin**" e **forçar** cada único **bit** do seu cookie porque um dos cookies que você tentará será o pertencente a "**admin**".
|
||||||
- Tente **Padding** **Oracle** (você pode descriptografar o conteúdo do cookie). Use **padbuster**.
|
- Tente **Padding** **Oracle** (você pode descriptografar o conteúdo do cookie). Use **padbuster**.
|
||||||
|
|
||||||
**Padding Oracle - Exemplos de Padbuster**
|
**Padding Oracle - Exemplos de Padbuster**
|
||||||
|
|||||||
178
theme/ai.js
178
theme/ai.js
@ -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: drag‑to‑resize panel, width persists via localStorage
|
||||||
* (XSS-safe via DOMPurify)
|
|
||||||
* ------------------------------------------------------------------------
|
|
||||||
*/
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
const LOG = "[HackTricks-AI]";
|
const LOG = "[HackTricks‑AI]";
|
||||||
|
/* ---------------- User‑tunable constants ---------------- */
|
||||||
/* ---------------- User-tunable constants ---------------- */
|
const MAX_CONTEXT = 3000; // highlighted‑text 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. 3rd‑party 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]) {
|
||||||
@ -246,20 +226,22 @@
|
|||||||
`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 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}
|
#ht-ai-btn:hover{opacity:.85}
|
||||||
@media(max-width:768px){#ht-ai-btn{display:none}}
|
@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{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-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{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)}
|
#ht-ai-panel.open{transform:translateX(0)}
|
||||||
@media(max-width:768px){#ht-ai-panel{display:none}}
|
@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{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #333}
|
||||||
@ -275,28 +257,31 @@
|
|||||||
#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-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{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}
|
#ht-ai-send:disabled{opacity:.5;cursor:not-allowed}
|
||||||
/* Loader animation */
|
/* Loader */
|
||||||
.ht-loading{display:inline-flex;align-items:center;gap:4px}
|
.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{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(2){animation-delay:0.2s}
|
||||||
.ht-loading span:nth-child(3){animation-delay:0.4s}
|
.ht-loading span:nth-child(3){animation-delay:0.4s}
|
||||||
@keyframes ht-bounce{0%,80%,100%{transform:scale(0);}40%{transform:scale(1);} }
|
@keyframes ht-bounce{0%,80%,100%{transform:scale(0);}40%{transform:scale(1);} }
|
||||||
::selection{background:#ffeb3b;color:#000}
|
::selection{background:#ffeb3b;color:#000}
|
||||||
::-moz-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);
|
||||||
|
});
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user