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 04adf0533..3ced5d2c1 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 @@ -4,9 +4,9 @@ ## **Kutoka GCP hadi GWS** -### **Msingi wa Delegation ya Domain Wide** +### **Misingi ya Delegation ya Domain Wide** -Delegation ya Domain-Wide ya Google Workspace inaruhusu kituo cha utambulisho, iwe ni **programu ya nje** kutoka Google Workspace Marketplace au **GCP Service Account** ya ndani, **kupata data katika Workspace kwa niaba ya watumiaji**. +Delegation ya Domain-Wide ya Google Workspace inaruhusu kitu cha utambulisho, iwe ni **programu ya nje** kutoka Google Workspace Marketplace au **GCP Service Account** ya ndani, **kupata data katika Workspace kwa niaba ya watumiaji**. > [!NOTE] > Hii inamaanisha kwamba **akaunti za huduma** ndani ya miradi ya GCP ya shirika zinaweza kuwa na uwezo wa **kujifanya watumiaji wa Workspace** wa shirika hilo hilo (au hata kutoka shirika tofauti). @@ -19,16 +19,16 @@ gcp-understanding-domain-wide-delegation.md ### Kuathiri delegation iliyopo -Ikiwa mshambuliaji **ameathiri baadhi ya ufikiaji juu ya GCP** na **anajua barua pepe halali ya mtumiaji wa Workspace** (kama inavyopendelewa **super admin**) wa kampuni, anaweza **kuorodhesha miradi yote** ambayo ana ufikiaji nayo, **kuorodhesha SAs zote** za miradi, kuangalia ni **akaunti za huduma zipi ana ufikiaji nazo**, na **kurudia** hatua hizi zote na kila SA anayeweza kujifanya.\ -Kwa **orodha ya akaunti zote za huduma** alizo na **ufikiaji** nazo na orodha ya **barua pepe za Workspace**, mshambuliaji anaweza kujaribu **kujifanya mtumiaji kwa kila akaunti ya huduma**. +Ikiwa mshambuliaji **ameathiri baadhi ya ufikiaji juu ya GCP** na **anajua barua pepe halali ya mtumiaji wa Workspace** (kama inavyopendelewa **super admin**) wa kampuni, anaweza **kuorodhesha miradi yote** anayo ufikiaji, **kuorodhesha SAs zote** za miradi, kuangalia ni **akaunti za huduma zipi anazo ufikiaji**, na **kurudia** hatua hizi zote na kila SA anayeweza kujifanya.\ +Kwa **orodha ya akaunti zote za huduma** alizo **na ufikiaji** nazo na orodha ya **barua pepe za Workspace**, mshambuliaji anaweza kujaribu **kujifanya mtumiaji kwa kila akaunti ya huduma**. > [!CAUTION] -> Kumbuka kwamba wakati wa kusanidi delegation ya domain wide, mtumiaji yeyote wa Workspace haitajiki, kwa hivyo jua tu **mtumiaji mmoja halali inatosha na inahitajika kwa kujifanya**.\ +> Kumbuka kwamba unapoconfigure delegation ya domain wide mtumiaji yeyote wa Workspace haitajika, hivyo jua tu **mtumiaji mmoja halali inatosha na inahitajika kwa ajili ya kujifanya**.\ > Hata hivyo, **mamlaka ya mtumiaji anayejifananisha yatatumika**, hivyo ikiwa ni Super Admin utaweza kufikia kila kitu. Ikiwa haina ufikiaji wowote hii itakuwa haina maana. #### [GCP Generate Delegation Token](https://github.com/carlospolop/gcp_gen_delegation_token) -Hii ni skripti rahisi itakay **zalisha token ya OAuth kama mtumiaji aliyepewa mamlaka** ambayo unaweza kutumia kufikia API nyingine za Google kwa kutumia au bila `gcloud`: +Hii ni script rahisi itakay **zalisha token ya OAuth kama mtumiaji aliyepewa mamlaka** ambayo unaweza kutumia kufikia API nyingine za Google kwa kutumia au bila `gcloud`: ```bash # Impersonate indicated user python3 gen_delegation_token.py --user-email --key-file @@ -36,18 +36,22 @@ 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) + +Inategemea zana ya DeleFriend, lakini ikiwa na nyongeza kama uwezo wa kuorodhesha kikoa, diski, gmail, kalenda na kufanya operesheni nyingine. + #### [**DeleFriend**](https://github.com/axon-git/DeleFriend) -Hii ni zana inayoweza kufanya shambulio kwa kufuata hatua hizi: +Hii ni zana inayoweza kufanya shambulio ikifuatia hatua hizi: -1. **Enumerate GCP Projects** kwa kutumia Resource Manager API. -2. Itera kwenye kila rasilimali ya mradi, na **enumerate GCP Service account resources** ambazo mtumiaji wa IAM wa awali ana ufikiaji kwa kutumia _GetIAMPolicy_. -3. Itera kwenye **kila jukumu la akaunti ya huduma**, na pata majukumu ya ndani, ya msingi, na ya kawaida yenye ruhusa ya _**serviceAccountKeys.create**_ kwenye rasilimali ya akaunti ya huduma inayolengwa. Inapaswa kuzingatiwa kwamba jukumu la Mhariri kwa asili lina ruhusa hii. +1. **Orodhesha Miradi ya GCP** kwa kutumia Resource Manager API. +2. Pitia kila rasilimali ya mradi, na **orodhesha rasilimali za akaunti ya huduma ya GCP** ambazo mtumiaji wa IAM wa awali ana ufikiaji kwa kutumia _GetIAMPolicy_. +3. Pitia **kila jukumu la akaunti ya huduma**, na pata majukumu ya ndani, ya msingi, na ya kawaida yenye ruhusa ya _**serviceAccountKeys.create**_ kwenye rasilimali ya akaunti ya huduma inayolengwa. Inapaswa kuzingatiwa kwamba jukumu la Mhariri kwa asili lina ruhusa hii. 4. Unda **funguo mpya ya `KEY_ALG_RSA_2048`** ya faragha kwa kila rasilimali ya akaunti ya huduma ambayo imepatikana na ruhusa inayofaa katika sera ya IAM. -5. Itera kwenye **kila akaunti ya huduma mpya na unda `JWT`** **object** kwa ajili yake ambayo inajumuisha akidi za funguo za faragha za SA na eneo la OAuth. Mchakato wa kuunda _JWT_ mpya utachambua **mchanganyiko wote wa sasa wa maeneo ya OAuth** kutoka kwenye orodha ya **oauth_scopes.txt**, ili kupata uwezekano wote wa uwakilishi. Orodha ya **oauth_scopes.txt** inasasishwa na maeneo yote ya OAuth tuliyopata kuwa muhimu kwa kutumia vitambulisho vya Workspace. -6. Mbinu ya `_make_authorization_grant_assertion` inaonyesha umuhimu wa kutangaza mtumiaji wa **workspace wa lengo**, anayeitwa _subject_, kwa ajili ya kuunda JWTs chini ya DWD. Ingawa hii inaweza kuonekana inahitaji mtumiaji maalum, ni muhimu kutambua kwamba **DWD inaathiri kila kitambulisho ndani ya eneo**. Kwa hivyo, kuunda JWT kwa **mtumiaji yeyote wa eneo** kunaathiri vitambulisho vyote katika eneo hilo, kulingana na ukaguzi wetu wa mchanganyiko. Kwa kifupi, mtumiaji mmoja halali wa Workspace anatosha kuendelea.\ -Mtumiaji huyu anaweza kufafanuliwa katika faili ya _config.yaml_ ya DeleFriend. Ikiwa mtumiaji wa workspace wa lengo hajulikani tayari, zana inarahisisha utambuzi wa moja kwa moja wa watumiaji halali wa workspace kwa kuskan watumiaji wa eneo wenye majukumu kwenye miradi ya GCP. Ni muhimu kutambua (tena) kwamba JWTs ni maalum kwa eneo na hazizalishwi kwa kila mtumiaji; hivyo, mchakato wa moja kwa moja unalenga kitambulisho kimoja cha kipekee kwa kila eneo. -7. **Enumerate na unda token ya ufikiaji wa bearer mpya** kwa kila JWT na kuthibitisha token dhidi ya tokeninfo API. +5. Pitia **kila akaunti ya huduma mpya na unda `JWT`** **kitu** kwa ajili yake ambacho kinajumuisha akidi za funguo za faragha za SA na eneo la OAuth. Mchakato wa kuunda kitu kipya cha _JWT_ ut **apitia mchanganyiko wote wa maeneo ya OAuth** kutoka orodha ya **oauth_scopes.txt**, ili kupata uwezekano wote wa uwakilishi. Orodha ya **oauth_scopes.txt** inasasishwa na maeneo yote ya OAuth ambayo tumepata kuwa muhimu kwa kutumia vitambulisho vya Workspace. +6. Mbinu ya `_make_authorization_grant_assertion` inaonyesha umuhimu wa kutangaza mtumiaji wa t**arget workspace**, anayeitwa _subject_, kwa ajili ya kuzalisha JWTs chini ya DWD. Ingawa hii inaweza kuonekana inahitaji mtumiaji maalum, ni muhimu kutambua kwamba **DWD inaathiri kila kitambulisho ndani ya kikoa**. Kwa hivyo, kuunda JWT kwa **mtumiaji yeyote wa kikoa** kunaathiri vitambulisho vyote katika kikoa hicho, kulingana na ukaguzi wetu wa kuorodhesha mchanganyiko. Kwa maneno rahisi, mtumiaji mmoja halali wa Workspace ni wa kutosha kuendelea.\ +Mtumiaji huyu anaweza kufafanuliwa katika faili ya _config.yaml_ ya DeleFriend. Ikiwa mtumiaji wa kikoa anay target bado hajulikani, zana inarahisisha utambuzi wa moja kwa moja wa watumiaji halali wa workspace kwa kuskan watumiaji wa kikoa wenye majukumu kwenye miradi ya GCP. Ni muhimu kutambua (tena) kwamba JWTs ni maalum kwa kikoa na hazizalishwi kwa kila mtumiaji; hivyo, mchakato wa moja kwa moja unalenga kitambulisho kimoja cha kipekee kwa kila kikoa. +7. **Orodhesha na unda tokeni mpya ya ufikiaji wa bearer** kwa kila JWT na kuthibitisha tokeni hiyo dhidi ya tokeninfo API. #### [Gitlab's Python script](https://gitlab.com/gitlab-com/gl-security/threatmanagement/redteam/redteam-public/gcp_misc/-/blob/master/gcp_delegation.py) @@ -77,28 +81,28 @@ pip install --upgrade --user oauth2client Ni uwezekano wa **kuangalia Mamlaka ya Kikoa Kote katika** [**https://admin.google.com/u/1/ac/owl/domainwidedelegation**](https://admin.google.com/u/1/ac/owl/domainwidedelegation)**.** -Mshambuliaji mwenye uwezo wa **kuunda akaunti za huduma katika mradi wa GCP** na **haki za super admin kwa GWS anaweza kuunda mamlaka mpya inayoruhusu SAs kuiga watumiaji wengine wa GWS:** +Mshambuliaji mwenye uwezo wa **kuunda akaunti za huduma katika mradi wa GCP** na **haki za super admin kwa GWS anaweza kuunda mamlaka mpya inayoruhusu SAs kuiga baadhi ya watumiaji wa GWS:** -1. **Kuzalisha Akaunti ya Huduma Mpya na Mifano ya Funguo Inayohusiana:** Kwenye GCP, rasilimali mpya za akaunti za huduma zinaweza kuzalishwa ama kwa njia ya kuingiliana kupitia console au kwa njia ya programu kwa kutumia wito wa moja kwa moja wa API na zana za CLI. Hii inahitaji **jukumu `iam.serviceAccountAdmin`** au jukumu lolote la kawaida lililo na **ruhusa `iam.serviceAccounts.create`**. Mara akaunti ya huduma itakapoundwa, tutaendelea kuzalisha **mifano ya funguo inayohusiana** (**ruhusa `iam.serviceAccountKeys.create`**). -2. **Kuunda mamlaka mpya**: Ni muhimu kuelewa kwamba **ni jukumu la Super Admin pekee linaloweza kuanzisha mamlaka ya Kikoa Kote katika Google Workspace** na mamlaka ya Kikoa Kote **haiwezi kuanzishwa kwa njia ya programu,** Inaweza kuundwa na kurekebishwa **kwa mikono** kupitia **console** ya Google Workspace. +1. **Kuzalisha Akaunti ya Huduma Mpya na Jozi ya Funguo Inayohusiana:** Kwenye GCP, rasilimali mpya za akaunti za huduma zinaweza kuzalishwa ama kwa njia ya mwingiliano kupitia console au kwa njia ya programu kwa kutumia wito wa moja kwa moja wa API na zana za CLI. Hii inahitaji **jukumu `iam.serviceAccountAdmin`** au jukumu lolote la kawaida lililo na **`iam.serviceAccounts.create`** **idhini**. Mara akaunti ya huduma itakapoundwa, tutaendelea kuzalisha **jozi ya funguo inayohusiana** (**`iam.serviceAccountKeys.create`** ruhusa). +2. **Uundaji wa mamlaka mpya**: Ni muhimu kuelewa kwamba **ni jukumu la Super Admin pekee linaloweza kuanzisha mamlaka ya Kikoa Kote katika Google Workspace** na mamlaka ya Kikoa Kote **haiwezi kuanzishwa kwa njia ya programu,** Inaweza kuundwa na kurekebishwa **kwa mikono** kupitia **console** ya Google Workspace. - Uundaji wa sheria unaweza kupatikana chini ya ukurasa **API controls → Manage Domain-Wide delegation in Google Workspace Admin console**. -3. **Kuweka haki za OAuth scopes**: Wakati wa configuring mamlaka mpya, Google inahitaji tu vigezo 2, Kitambulisho cha Mteja, ambacho ni **Kitambulisho cha OAuth cha Rasilimali ya Akaunti ya Huduma ya GCP**, na **OAuth scopes** zinazofafanua ni wito gani wa API mamlaka inahitaji. +3. **Kuambatisha haki za OAuth scopes**: Wakati wa kuunda mamlaka mpya, Google inahitaji tu vigezo 2, Kitambulisho cha Mteja, ambacho ni **Kitambulisho cha OAuth cha Rasilimali ya Akaunti ya Huduma ya GCP**, na **OAuth scopes** zinazofafanua ni wito gani wa API mamlaka inahitaji. - **orodha kamili ya OAuth scopes** inaweza kupatikana [**hapa**](https://developers.google.com/identity/protocols/oauth2/scopes), lakini hapa kuna pendekezo: `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. **Kufanya kwa niaba ya kitambulisho cha lengo:** Katika hatua hii, tuna kitu kilichopewa mamlaka kinachofanya kazi katika GWS. Sasa, **tukitumia funguo ya faragha ya Akaunti ya Huduma ya GCP, tunaweza kufanya wito wa API** (katika wigo ulioainishwa katika vigezo vya OAuth scope) ili kuhamasisha na **kufanya kwa niaba ya kitambulisho chochote kilichopo katika Google Workspace**. Kama tulivyojifunza, akaunti ya huduma itazalisha tokeni za ufikiaji kulingana na mahitaji yake na kulingana na ruhusa aliyonayo kwa programu za REST API. -- Angalia **sehemu ya awali** kwa zana **za kutumia mamlaka hii**. +- Angalia **sehemu ya awali** kwa baadhi ya **zana** za kutumia mamlaka hii. #### Cross-Organizational delegation -Kitambulisho cha OAuth SA ni cha kimataifa na kinaweza kutumika kwa **mamlaka ya kuvuka mashirika**. Hakuna vizuizi vilivyowekwa kuzuia mamlaka ya kuvuka kimataifa. Kwa maneno rahisi, **akaunti za huduma kutoka mashirika tofauti ya GCP zinaweza kutumika kuunda mamlaka ya kikoa kote kwenye mashirika mengine ya Workspace**. Hii itasababisha **kuhitaji tu ufikiaji wa Super Admin kwa Workspace**, na sio ufikiaji wa akaunti hiyo hiyo ya GCP, kwani mpinzani anaweza kuunda Akaunti za Huduma na funguo za faragha kwenye akaunti yake ya GCP anayoitawala binafsi. +OAuth SA ID ni ya kimataifa na inaweza kutumika kwa **mamlaka ya kuvuka shirika**. Hakuna vizuizi vilivyowekwa kuzuia mamlaka ya kuvuka kimataifa. Kwa maneno rahisi, **akaunti za huduma kutoka mashirika tofauti ya GCP zinaweza kutumika kuunda mamlaka ya kikoa kote kwenye mashirika mengine ya Workspace**. Hii itasababisha **kuhitaji tu ufikiaji wa Super Admin kwa Workspace**, na si ufikiaji wa akaunti hiyo hiyo ya GCP, kwani mpinzani anaweza kuunda Akaunti za Huduma na funguo za faragha kwenye akaunti yake ya GCP anayoitawala binafsi. ### Creating a Project to enumerate Workspace Kwa **default** watumiaji wa Workspace **wana ruhusa ya** **kuunda miradi mipya**, na wakati mradi mpya unaundwa **mwandishi anapata jukumu la Mmiliki** juu yake. -Hivyo, mtumiaji anaweza **kuunda mradi**, **kuwezesha** **APIs** ili kuhesabu Workspace katika mradi wake mpya na kujaribu **kuhesabu**. +Hivyo, mtumiaji anaweza **kuunda mradi**, **kuwezesha** **APIs** ili kuhesabu Workspace katika mradi wake mpya na kujaribu **kuhesabu** hiyo. > [!CAUTION] -> Ili mtumiaji aweze kuhesabu Workspace anahitaji pia ruhusa za kutosha za Workspace (sio kila mtumiaji ataweza kuhesabu directory). +> Ili mtumiaji aweze kuhesabu Workspace inahitaji pia ruhusa za kutosha za Workspace (sio kila mtumiaji ataweza kuhesabu directory). ```bash # Create project gcloud projects create --name=proj-name @@ -130,17 +134,17 @@ You can find further information about the `gcloud` flow to login in: ../gcp-persistence/gcp-non-svc-persistence.md {{#endref}} -Kama ilivyoelezwa hapo, gcloud inaweza kuomba wigo **`https://www.googleapis.com/auth/drive`** ambayo itamruhusu mtumiaji kufikia drive ya mtumiaji.\ -Kama mshambuliaji, ikiwa umepata **kimwili** kompyuta ya mtumiaji na **mtumiaji bado ameingia** na akaunti yake unaweza kuingia kwa kuzalisha token yenye ufikiaji wa drive kwa kutumia: +Kama ilivyoelezwa hapo, gcloud inaweza kuomba upeo **`https://www.googleapis.com/auth/drive`** ambao utamruhusu mtumiaji kufikia drive ya mtumiaji.\ +Kama mshambuliaji, ikiwa umepata **kimwili** kompyuta ya mtumiaji na **mtumiaji bado ameingia** na akaunti yake unaweza kuingia kwa kuzalisha token yenye ufikiaji wa drive ukitumia: ```bash gcloud auth login --enable-gdrive-access ``` -Ikiwa mshambuliaji anapora kompyuta ya mtumiaji anaweza pia kubadilisha faili `google-cloud-sdk/lib/googlecloudsdk/core/config.py` na kuongeza katika **`CLOUDSDK_SCOPES`** upeo **`'https://www.googleapis.com/auth/drive'`**: +Ikiwa mshambuliaji atachafua kompyuta ya mtumiaji anaweza pia kubadilisha faili `google-cloud-sdk/lib/googlecloudsdk/core/config.py` na kuongeza katika **`CLOUDSDK_SCOPES`** upeo **`'https://www.googleapis.com/auth/drive'`**:
> [!WARNING] -> Hivyo, wakati mtumiaji atakapojisajili tena ataunda **token yenye ufikiaji wa drive** ambayo mshambuliaji anaweza kuitumia kuingia kwenye drive. Kwa wazi, kivinjari kitaonyesha kwamba token iliyoundwa itakuwa na ufikiaji wa drive, lakini kwa kuwa mtumiaji atajita mwenyewe **`gcloud auth login`**, labda **hatashuku chochote.** +> Hivyo, wakati mtumiaji atajiunga tena ataunda **token yenye ufikiaji wa drive** ambayo mshambuliaji anaweza kutumia vibaya ili kufikia drive. Kwa wazi, kivinjari kitaonyesha kwamba token iliyoundwa itakuwa na ufikiaji wa drive, lakini kwa kuwa mtumiaji atajitaja mwenyewe **`gcloud auth login`**, labda **hatashuku chochote.** > > Ili kuorodhesha faili za drive: **`curl -H "Authorization: Bearer $(gcloud auth print-access-token)" "https://www.googleapis.com/drive/v3/files"`** @@ -148,13 +152,13 @@ Ikiwa mshambuliaji anapora kompyuta ya mtumiaji anaweza pia kubadilisha faili `g ### Ufikiaji wa watumiaji wenye mamlaka ya GCP -Ikiwa mshambuliaji ana ufikiaji kamili juu ya GWS ataweza kufikia makundi yenye ufikiaji wa mamlaka juu ya GCP au hata watumiaji, hivyo kuhamia kutoka GWS hadi GCP kwa kawaida ni "rahisi" zaidi kwa sababu **watumiaji katika GWS wana mamlaka makubwa juu ya GCP**. +Ikiwa mshambuliaji ana ufikiaji kamili juu ya GWS ataweza kufikia vikundi vyenye ufikiaji wa mamlaka juu ya GCP au hata watumiaji, hivyo kuhamia kutoka GWS hadi GCP kwa kawaida ni "rahisi" zaidi kwa sababu **watumiaji katika GWS wana mamlaka makubwa juu ya GCP**. -### Kuinua Mamlaka ya Makundi ya Google +### Kuinua Mamlaka ya Vikundi vya Google -Kwa kawaida watumiaji wanaweza **kujiunga kwa uhuru na makundi ya Workspace ya Shirika** na makundi hayo **yanaweza kuwa na ruhusa za GCP** zilizotolewa (angalia makundi yako katika [https://groups.google.com/](https://groups.google.com/)). +Kwa kawaida watumiaji wanaweza **kujiunga kwa uhuru na vikundi vya Workspace vya Shirika** na vikundi hivyo **vinaweza kuwa na ruhusa za GCP** zilizotolewa (angalia vikundi vyako katika [https://groups.google.com/](https://groups.google.com/)). -Kwa kutumia **kuinua mamlaka ya makundi ya google** unaweza kuwa na uwezo wa kuinua hadi kundi lenye aina fulani ya ufikiaji wa mamlaka kwa GCP. +Kwa kutumia vibaya **kuinua mamlaka ya vikundi vya google** unaweza kuwa na uwezo wa kuinua hadi kundi lenye aina fulani ya ufikiaji wa mamlaka kwa GCP. ### Marejeleo 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); + }); + } })();