diff --git a/src/pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/README.md b/src/pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/README.md index 6127b3cd2..b842dfbd3 100644 --- a/src/pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/README.md +++ b/src/pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/README.md @@ -9,7 +9,7 @@ Google Workspace se domeinwye delegasie laat 'n identiteitsobjek, hetsy 'n **eksterne app** van Google Workspace Marketplace of 'n interne **GCP-diensrekening**, toe om **data oor die Workspace namens gebruikers te bekom**. > [!NOTE] -> Dit beteken basies dat **diensrekeninge** binne GCP-projekte van 'n organisasie dalk in staat mag wees om **Workspace-gebruikers** van dieselfde organisasie (of selfs van 'n ander) te **verpersoonlik**. +> Dit beteken basies dat **diensrekeninge** binne GCP-projekte van 'n organisasie dalk in staat is om **Workspace-gebruikers** van dieselfde organisasie (of selfs van 'n ander) te **verpersoonlik**. Vir meer inligting oor hoe dit presies werk, kyk: @@ -19,16 +19,16 @@ gcp-understanding-domain-wide-delegation.md ### Kompromitteer bestaande delegasie -As 'n aanvaller **toegang oor GCP gekompromitteer het** en **'n geldige Workspace-gebruiker e-pos** (verkieslik **super admin**) van die maatskappy ken, kan hy **alle projekte** wat hy toegang het, **opnoem**, **alle SA's** van die projekte **opnoem**, kyk na watter **diensrekeninge hy toegang het**, en **herhaal** al hierdie stappe met elke SA wat hy kan verpersoonlik.\ +As 'n aanvaller **toegang oor GCP gekompromitteer het** en **'n geldige Workspace-gebruiker e-pos** (verkieslik **super admin**) van die maatskappy ken, kan hy **alle projekte** wat hy toegang het tot, **alle SA's** van die projekte opnoem, kyk na watter **diensrekeninge hy toegang het tot**, en **herhaal** al hierdie stappe met elke SA wat hy kan verpersoonlik.\ Met 'n **lys van al die diensrekeninge** waartoe hy **toegang** het en die lys van **Workspace** **e-posse**, kan die aanvaller probeer om **gebruikers met elke diensrekening te verpersoonlik**. > [!CAUTION] -> Let daarop dat wanneer die domeinwye delegasie gekonfigureer word, geen Workspace-gebruiker benodig word nie, daarom is dit net nodig om **een geldige te hê wat genoeg is en vereis word vir die verpersoonliking**.\ +> Let daarop dat wanneer die domeinwye delegasie gekonfigureer word, geen Workspace-gebruiker benodig word nie, daarom is dit net nodig om **een geldige te ken wat genoeg is en vereis word vir die verpersoonliking**.\ > Die **privileges van die verpersoonlikte gebruiker sal egter gebruik word**, so as dit Super Admin is, sal jy toegang tot alles hê. As dit geen toegang het nie, sal dit nutteloos wees. #### [GCP Genereer Delegasie Token](https://github.com/carlospolop/gcp_gen_delegation_token) -Hierdie eenvoudige skrip sal **'n OAuth-token genereer as die gedelegeerde gebruiker** wat jy dan kan gebruik om ander Google API's met of sonder `gcloud` te benader: +Hierdie eenvoudige skrip sal **'n OAuth-token genereer as die gedelegeerde gebruiker** wat jy dan kan gebruik om ander Google API's mee te benader met of sonder `gcloud`: ```bash # Impersonate indicated user python3 gen_delegation_token.py --user-email --key-file @@ -36,22 +36,26 @@ python3 gen_delegation_token.py --user-email --key-file --key-file --scopes "https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/cloud-platform, https://www.googleapis.com/auth/admin.directory.group, https://www.googleapis.com/auth/admin.directory.user, https://www.googleapis.com/auth/admin.directory.domain, https://mail.google.com/, https://www.googleapis.com/auth/drive, openid" ``` +#### [**DelePwn**](https://github.com/n0tspam/delepwn) + +Gebaseer op die volgende DeleFriend tool, maar met 'n paar toevoegings soos die vermoë om die domein, skyf, gmail, kalender te enumereer en ander operasies uit te voer. + #### [**DeleFriend**](https://github.com/axon-git/DeleFriend) -Dit is 'n hulpmiddel wat die aanval kan uitvoer volgens hierdie stappe: +Dit is 'n tool wat die aanval kan uitvoer volgens die volgende stappe: -1. **Enumerate GCP Projects** met behulp van Resource Manager API. -2. Itereer oor elke projek hulpbron, en **enumerate GCP Service account resources** waartoe die aanvanklike IAM gebruiker toegang het met behulp van _GetIAMPolicy_. -3. Itereer oor **elke diensrekening rol**, en vind ingeboude, basiese, en pasgemaakte rolle met _**serviceAccountKeys.create**_ toestemming op die teiken diensrekening hulpbron. Dit moet opgemerk word dat die Editor rol inherent hierdie toestemming het. +1. **Enumereer GCP Projekte** met behulp van die Resource Manager API. +2. Herhaal op elke projek hulpbron, en **enumereer GCP Diensrekening hulpbronne** waartoe die aanvanklike IAM gebruiker toegang het met behulp van _GetIAMPolicy_. +3. Herhaal op **elke diensrekening rol**, en vind ingeboude, basiese, en pasgemaakte rolle met _**serviceAccountKeys.create**_ toestemming op die teiken diensrekening hulpbron. Dit moet opgemerk word dat die Editor rol inherent hierdie toestemming besit. 4. Skep 'n **nuwe `KEY_ALG_RSA_2048`** private sleutel vir elke diensrekening hulpbron wat gevind is met relevante toestemming in die IAM beleid. -5. Itereer oor **elke nuwe diensrekening en skep 'n `JWT`** **object** daarvoor wat saamgestel is uit die SA private sleutel akkrediteer en 'n OAuth omvang. Die proses om 'n nuwe _JWT_ object te skep sal **iterere oor al die bestaande kombinasies van OAuth omvange** van die **oauth_scopes.txt** lys, ten einde al die delegasie moontlikhede te vind. Die lys **oauth_scopes.txt** word opgedateer met al die OAuth omvange wat ons gevind het om relevant te wees vir die misbruik van Workspace identiteite. -6. Die `_make_authorization_grant_assertion` metode onthul die noodsaaklikheid om 'n t**arget workspace user** te verklaar, verwys as _subject_, vir die generering van JWTs onder DWD. Terwyl dit mag lyk asof dit 'n spesifieke gebruiker vereis, is dit belangrik om te besef dat **DWD elke identiteit binne 'n domein beïnvloed**. Gevolglik, die skep van 'n JWT vir **enige domein gebruiker** beïnvloed al die identiteite in daardie domein, in ooreenstemming met ons kombinasie enumerasie kontrole. Eenvoudig gestel, een geldige Workspace gebruiker is voldoende om vorentoe te beweeg.\ -Hierdie gebruiker kan in DeleFriend se _config.yaml_ lêer gedefinieer word. As 'n teiken workspace gebruiker nog nie bekend is nie, fasiliteer die hulpmiddel die outomatiese identifikasie van geldige workspace gebruikers deur domein gebruikers met rolle op GCP projekte te skandeer. Dit is belangrik om (weer) op te let dat JWTs domein-spesifiek is en nie vir elke gebruiker gegenereer word nie; daarom, die outomatiese proses teiken 'n enkele unieke identiteit per domein. -7. **Enumerate and create a new bearer access token** vir elke JWT en valideer die token teen tokeninfo API. +5. Herhaal op **elke nuwe diensrekening en skep 'n `JWT`** **objek** daarvoor wat saamgestel is uit die SA private sleutel akkrediteer en 'n OAuth omvang. Die proses om 'n nuwe _JWT_ objek te skep sal **herhaal op al die bestaande kombinasies van OAuth omvange** van die **oauth_scopes.txt** lys, ten einde al die delegasie moontlikhede te vind. Die lys **oauth_scopes.txt** word opgedateer met al die OAuth omvange wat ons gevind het om relevant te wees vir die misbruik van Workspace identiteite. +6. Die `_make_authorization_grant_assertion` metode onthul die noodsaaklikheid om 'n t**eiken werkruimte gebruiker** te verklaar, verwys as _subject_, vir die generering van JWTs onder DWD. Terwyl dit mag lyk asof dit 'n spesifieke gebruiker vereis, is dit belangrik om te besef dat **DWD elke identiteit binne 'n domein beïnvloed**. Gevolglik, die skep van 'n JWT vir **enige domein gebruiker** beïnvloed al die identiteite in daardie domein, ooreenkomstig met ons kombinasie enumering kontrole. Eenvoudig gestel, een geldige Workspace gebruiker is voldoende om vorentoe te beweeg.\ +Hierdie gebruiker kan gedefinieer word in DeleFriend se _config.yaml_ lêer. As 'n teiken werkruimte gebruiker nie reeds bekend is nie, fasiliteer die tool die outomatiese identifikasie van geldige werkruimte gebruikers deur domein gebruikers met rolle op GCP projekte te skandeer. Dit is belangrik om (weer) op te let dat JWTs domein-spesifiek is en nie vir elke gebruiker gegenereer word nie; daarom, die outomatiese proses teiken 'n enkele unieke identiteit per domein. +7. **Enumereer en skep 'n nuwe draer toegangstoken** vir elke JWT en valideer die token teen die tokeninfo API. -#### [Gitlab's Python script](https://gitlab.com/gitlab-com/gl-security/threatmanagement/redteam/redteam-public/gcp_misc/-/blob/master/gcp_delegation.py) +#### [Gitlab se Python skrip](https://gitlab.com/gitlab-com/gl-security/threatmanagement/redteam/redteam-public/gcp_misc/-/blob/master/gcp_delegation.py) -Gitlab het [hierdie Python skrip](https://gitlab.com/gitlab-com/gl-security/gl-redteam/gcp_misc/blob/master/gcp_delegation.py) geskep wat twee dinge kan doen - die gebruikersgids lys en 'n nuwe administratiewe rekening skep terwyl 'n json met SA akkrediteer en die gebruiker om te verpersoonlik aangedui word. Hier is hoe jy dit sou gebruik: +Gitlab het [hierdie Python skrip](https://gitlab.com/gitlab-com/gl-security/gl-redteam/gcp_misc/blob/master/gcp_delegation.py) geskep wat twee dinge kan doen - die gebruiker gids lys en 'n nuwe administratiewe rekening skep terwyl 'n json met SA akkrediteer en die gebruiker om te verpersoonlik aangedui word. Hier is hoe jy dit sou gebruik: ```bash # Install requirements pip install --upgrade --user oauth2client @@ -77,28 +81,28 @@ pip install --upgrade --user oauth2client Dit is moontlik om **Domein Wye Delegasies in** [**https://admin.google.com/u/1/ac/owl/domainwidedelegation**](https://admin.google.com/u/1/ac/owl/domainwidedelegation)** te kontroleer.** -'n Aanvaller met die vermoë om **diensrekeninge in 'n GCP-projek** te **skep en super admin voorregte in GWS** kan 'n nuwe delegasie skep wat SAs toelaat om as sommige GWS-gebruikers op te tree: +'n Aanvaller met die vermoë om **diensrekeninge in 'n GCP-projek te skep** en **super admin voorregte in GWS kan 'n nuwe delegasie skep wat SAs toelaat om as sommige GWS-gebruikers op te tree:** -1. **Genereer 'n Nuwe Diensrekening en Ooreenstemmende Sleutel Paar:** Op GCP kan nuwe diensrekening hulpbronne interaktief via die konsole of programmaties met direkte API-oproepe en CLI-gereedskap geproduseer word. Dit vereis die **rol `iam.serviceAccountAdmin`** of enige aangepaste rol toegerus met die **`iam.serviceAccounts.create`** **toestemming**. Sodra die diensrekening geskep is, sal ons voortgaan om 'n **verwante sleutel paar** (**`iam.serviceAccountKeys.create`** toestemming) te genereer. -2. **Skep van 'n nuwe delegasie**: Dit is belangrik om te verstaan dat **slegs die Super Admin rol die vermoë het om globale Domein-Wye delegasie in Google Workspace op te stel** en Domein-Wye delegasie **kan nie programmaties opgestel word nie,** Dit kan slegs **handmatig** deur die Google Workspace **konsole** geskep en aangepas word. +1. **Genereer 'n Nuwe Diensrekening en Ooreenstemmende Sleutel Paar:** Op GCP kan nuwe diensrekening hulpbronne interaktief via die konsole of programmaties met direkte API-oproepe en CLI-gereedskap geproduseer word. Dit vereis die **rol `iam.serviceAccountAdmin`** of enige aangepaste rol toegerus met die **`iam.serviceAccounts.create`** **toestemming**. Sodra die diensrekening geskep is, sal ons voortgaan om 'n **verwante sleutel paar** te genereer (**`iam.serviceAccountKeys.create`** toestemming). +2. **Skep van 'n nuwe delegasie**: Dit is belangrik om te verstaan dat **slegs die Super Admin rol die vermoë het om globale Domein-Wye delegasie in Google Workspace op te stel** en Domein-Wye delegasie **kan nie programmaties opgestel word nie,** dit kan slegs **handmatig** deur die Google Workspace **konsole** geskep en aangepas word. - Die skepping van die reël kan onder die bladsy **API kontroles → Bestuur Domein-Wye delegasie in Google Workspace Admin konsole** gevind word. 3. **Hechting van OAuth skope voorreg**: Wanneer 'n nuwe delegasie gekonfigureer word, vereis Google slegs 2 parameters, die Klient ID, wat die **OAuth ID van die GCP Diensrekening** hulpbron is, en **OAuth skope** wat definieer watter API-oproepe die delegasie benodig. - Die **volledige lys van OAuth skope** kan [**hier**](https://developers.google.com/identity/protocols/oauth2/scopes) gevind word, maar hier is 'n aanbeveling: `https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/cloud-platform, https://www.googleapis.com/auth/admin.directory.group, https://www.googleapis.com/auth/admin.directory.user, https://www.googleapis.com/auth/admin.directory.domain, https://mail.google.com/, https://www.googleapis.com/auth/drive, openid` -4. **Optree namens die teiken identiteit:** Op hierdie punt het ons 'n funksionerende gedelegeerde objek in GWS. Nou, **met die GCP Diensrekening private sleutel, kan ons API-oproepe uitvoer** (in die omvang gedefinieer in die OAuth skoop parameter) om dit te aktiveer en **namens enige identiteit wat in Google Workspace bestaan op te tree**. Soos ons geleer het, sal die diensrekening toegangstokens genereer volgens sy behoeftes en volgens die toestemming wat hy het vir REST API-toepassings. -- Kontroleer die **vorige afdeling** vir 'n paar **gereedskap** om hierdie delegasie te gebruik. +4. **Optree namens die teiken identiteit:** Op hierdie punt het ons 'n funksionerende gedelegeerde objek in GWS. Nou, **met die GCP Diensrekening private sleutel, kan ons API-oproepe uitvoer** (in die omvang gedefinieer in die OAuth skooparameter) om dit te aktiveer en **namens enige identiteit wat in Google Workspace bestaan op te tree**. Soos ons geleer het, sal die diensrekening toegangstokens genereer volgens sy behoeftes en volgens die toestemming wat hy het vir REST API-toepassings. +- Kontroleer die **vorige afdeling** vir sommige **gereedskap** om hierdie delegasie te gebruik. #### Kruis-Organisatoriese delegasie -OAuth SA ID is globaal en kan gebruik word vir **kruis-organisatoriese delegasie**. Daar is geen beperking geïmplementeer om kruis-globale delegasie te voorkom nie. In eenvoudige terme, **diensrekeninge van verskillende GCP-organisasies kan gebruik word om domein-wye delegasie op ander Workspace-organisasies te konfigureer**. Dit sou beteken dat **slegs Super Admin toegang tot Workspace** benodig word, en nie toegang tot dieselfde GCP-rekening nie, aangesien die teenstander diensrekeninge en private sleutels op sy persoonlik beheerde GCP-rekening kan skep. +OAuth SA ID is globaal en kan gebruik word vir **kruis-organisatoriese delegasie**. Daar is geen beperking geïmplementeer om kruis-globale delegasie te voorkom nie. In eenvoudige terme, **diensrekeninge van verskillende GCP-organisasies kan gebruik word om domein-wye delegasie op ander Workspace-organisasies te konfigureer**. Dit sou beteken dat **slegs Super Admin toegang tot Workspace benodig word**, en nie toegang tot dieselfde GCP-rekening nie, aangesien die teenstander diensrekeninge en private sleutels op sy persoonlik beheer GCP-rekening kan skep. ### Skep 'n Projek om Workspace te enumerate Deur **standaard** het Workspace **gebruikers** die toestemming om **nuwe projekte te skep**, en wanneer 'n nuwe projek geskep word, ontvang die **skepper die Eienaar rol** oor dit. -Daarom kan 'n gebruiker **'n projek skep**, **die** **API's** aktiveer om Workspace in sy nuwe projek te enumerate en probeer om dit te **enumerate**. +Daarom kan 'n gebruiker **'n projek skep**, **APIs** aktiveer om Workspace in sy nuwe projek te enumerate en probeer om dit te **enumerate**. > [!CAUTION] -> Om 'n gebruiker in staat te stel om Workspace te enumerate, benodig hy ook genoeg Workspace toestemming (nie elke gebruiker sal in staat wees om die gids te enumerate nie). +> Ten einde vir 'n gebruiker moontlik te wees om Workspace te enumerate, benodig hy ook genoeg Workspace toestemming (nie elke gebruiker sal in staat wees om die gids te enumerate nie). ```bash # Create project gcloud projects create --name=proj-name @@ -130,29 +134,29 @@ U kan verdere inligting oor die `gcloud` vloei om aan te meld vind in: ../gcp-persistence/gcp-non-svc-persistence.md {{#endref}} -Soos daar verduidelik is, kan gcloud die omvang **`https://www.googleapis.com/auth/drive`** aanvra wat 'n gebruiker in staat sou stel om toegang tot die gebruiker se drive te verkry.\ -As 'n aanvaller, as u die rekenaar van 'n gebruiker **fisies** gecompromitteer het en die **gebruiker is steeds aangemeld** met sy rekening, kan u aanmeld deur 'n token met toegang tot die drive te genereer met: +Soos daar verduidelik word, kan gcloud die omvang **`https://www.googleapis.com/auth/drive`** versoek wat 'n gebruiker in staat sou stel om toegang tot die gebruiker se skyf te verkry.\ +As 'n aanvaller, as u fisies die rekenaar van 'n gebruiker gecompromitteer het en die **gebruiker is steeds aangemeld** met sy rekening, kan u aanmeld deur 'n token met toegang tot die skyf te genereer met: ```bash gcloud auth login --enable-gdrive-access ``` -As 'n aanvaller die rekenaar van 'n gebruiker kompromitteer, kan hy ook die lêer `google-cloud-sdk/lib/googlecloudsdk/core/config.py` wysig en die **`CLOUDSDK_SCOPES`** die omvang **`'https://www.googleapis.com/auth/drive'`** byvoeg: +As 'n aanvaller die rekenaar van 'n gebruiker kompromitteer, kan hy ook die lêer `google-cloud-sdk/lib/googlecloudsdk/core/config.py` wysig en die **`CLOUDSDK_SCOPES`** die omvang **`'https://www.googleapisapis.com/auth/drive'`** byvoeg:
> [!WARNING] > Daarom, die volgende keer wanneer die gebruiker aanmeld, sal hy 'n **token met toegang tot drive** skep wat die aanvaller kan misbruik om toegang tot die drive te verkry. Dit is duidelik dat die blaaier sal aandui dat die gegenereerde token toegang tot drive sal hê, maar aangesien die gebruiker homself die **`gcloud auth login`** sal noem, sal hy waarskynlik **niks vermoed nie.** > -> Om drive lêers te lys: **`curl -H "Authorization: Bearer $(gcloud auth print-access-token)" "https://www.googleapis.com/drive/v3/files"`** +> Om drive-lêers te lys: **`curl -H "Authorization: Bearer $(gcloud auth print-access-token)" "https://www.googleapis.com/drive/v3/files"`** ## Van GWS na GCP -### Toegang tot bevoorregte GCP gebruikers +### Toegang tot bevoorregte GCP-gebruikers As 'n aanvaller volledige toegang oor GWS het, sal hy in staat wees om toegang te verkry tot groepe met bevoorregte toegang oor GCP of selfs gebruikers, daarom is dit gewoonlik "eenvoudiger" om van GWS na GCP te beweeg net omdat **gebruikers in GWS hoë voorregte oor GCP het**. ### Google Groups Bevoorregting Escalatie -Standaard kan gebruikers **vrylik by Workspace groepe van die Organisasie aansluit** en daardie groepe **kan GCP toestemmings** toegeken hê (kyk jou groepe in [https://groups.google.com/](https://groups.google.com/)). +Standaard kan gebruikers **vrylik by Workspace-groepe van die Organisasie aansluit** en daardie groepe **kan GCP-toestemmings** toegeken hê (kontroleer jou groepe in [https://groups.google.com/](https://groups.google.com/)). Deur die **google groups privesc** te misbruik, mag jy in staat wees om na 'n groep met 'n soort bevoorregte toegang tot GCP te eskaleer. diff --git a/theme/ai.js b/theme/ai.js index bb8af53b7..c94992d5f 100644 --- a/theme/ai.js +++ b/theme/ai.js @@ -1,27 +1,28 @@ /** - * HackTricks AI Chat Widget v1.15 – Markdown rendering + sanitised - * ------------------------------------------------------------------------ - * • Replaces the static “…” placeholder with a three-dot **bouncing** loader - * • Renders assistant replies as Markdown while purging any unsafe HTML - * (XSS-safe via DOMPurify) - * ------------------------------------------------------------------------ + * HackTricks AI Chat Widget v1.16 – resizable sidebar + * --------------------------------------------------- + * ❶ Markdown rendering + sanitised (same as before) + * ❷ NEW: drag‑to‑resize panel, width persists via localStorage */ (function () { - const LOG = "[HackTricks-AI]"; - - /* ---------------- User-tunable constants ---------------- */ - const MAX_CONTEXT = 3000; // highlighted-text char limit - const MAX_QUESTION = 500; // question char limit + const LOG = "[HackTricks‑AI]"; + /* ---------------- User‑tunable constants ---------------- */ + const MAX_CONTEXT = 3000; // highlighted‑text char limit + const MAX_QUESTION = 500; // question char limit + const MIN_W = 250; // ← resize limits → + const MAX_W = 600; + const DEF_W = 350; // default width (if nothing saved) const TOOLTIP_TEXT = - "💡 Highlight any text on the page,\nthen click to ask HackTricks AI about it"; + "💡 Highlight any text on the page,\nthen click to ask HackTricks AI about it"; const API_BASE = "https://www.hacktricks.ai/api/assistants/threads"; - const BRAND_RED = "#b31328"; // HackTricks brand + const BRAND_RED = "#b31328"; /* ------------------------------ State ------------------------------ */ let threadId = null; let isRunning = false; + /* ---------- helpers ---------- */ const $ = (sel, ctx = document) => ctx.querySelector(sel); if (document.getElementById("ht-ai-btn")) { console.warn(`${LOG} Widget already injected.`); @@ -31,44 +32,37 @@ ? document.addEventListener("DOMContentLoaded", init) : init()); - /* ==================================================================== */ - /* 🔗 1. 3rd-party libs → Markdown & sanitiser */ - /* ==================================================================== */ + /* =================================================================== */ + /* 🔗 1. 3rd‑party libs → Markdown & sanitiser */ + /* =================================================================== */ function loadScript(src) { - return new Promise((resolve, reject) => { + return new Promise((res, rej) => { const s = document.createElement("script"); s.src = src; - s.onload = resolve; - s.onerror = () => reject(new Error(`Failed to load ${src}`)); + s.onload = res; + s.onerror = () => rej(new Error(`Failed to load ${src}`)); document.head.appendChild(s); }); } - async function ensureDeps() { const deps = []; - if (typeof marked === "undefined") { + if (typeof marked === "undefined") deps.push(loadScript("https://cdn.jsdelivr.net/npm/marked/marked.min.js")); - } - if (typeof DOMPurify === "undefined") { + if (typeof DOMPurify === "undefined") deps.push( loadScript( "https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.5/purify.min.js" ) ); - } if (deps.length) await Promise.all(deps); } + const mdToSafeHTML = (md) => + DOMPurify.sanitize(marked.parse(md, { mangle: false, headerIds: false }), { + USE_PROFILES: { html: true } + }); - function mdToSafeHTML(md) { - // 1️⃣ Markdown → raw HTML - const raw = marked.parse(md, { mangle: false, headerIds: false }); - // 2️⃣ Purify - return DOMPurify.sanitize(raw, { USE_PROFILES: { html: true } }); - } - - /* ==================================================================== */ + /* =================================================================== */ async function init() { - /* ----- make sure marked & DOMPurify are ready before anything else */ try { await ensureDeps(); } catch (e) { @@ -76,14 +70,14 @@ return; } - console.log(`${LOG} Injecting widget… v1.15`); + console.log(`${LOG} Injecting widget… v1.16`); await ensureThreadId(); injectStyles(); const btn = createFloatingButton(); createTooltip(btn); - const panel = createSidebar(); + const panel = createSidebar(); // ← panel with resizer const chatLog = $("#ht-ai-chat"); const sendBtn = $("#ht-ai-send"); const inputBox = $("#ht-ai-question"); @@ -100,15 +94,8 @@ function addMsg(text, cls) { const b = document.createElement("div"); b.className = `ht-msg ${cls}`; - - // ✨ assistant replies rendered as Markdown + sanitised - if (cls === "ht-ai") { - b.innerHTML = mdToSafeHTML(text); - } else { - // user / context bubbles stay plain-text - b.textContent = text; - } - + b[cls === "ht-ai" ? "innerHTML" : "textContent"] = + cls === "ht-ai" ? mdToSafeHTML(text) : text; chatLog.appendChild(b); chatLog.scrollTop = chatLog.scrollHeight; return b; @@ -116,30 +103,28 @@ const LOADER_HTML = ''; - 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 = "🤖HackTricksAI"; 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 = ` -
HackTricks AI Chat +
HackTricks AI Chat
@@ -326,7 +316,39 @@
`; + /* 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); + }); + } })();