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 c2f2e7924..1d59c3506 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 @@ -6,7 +6,7 @@ ### **Alan Genel Yetkilendirme Temelleri** -Google Workspace'in Alan Genel yetkilendirmesi, bir kimlik nesnesinin, ya bir **harici uygulama** Google Workspace Marketplace'ten ya da bir iç **GCP Hizmet Hesabı** olarak, **kullanıcılar adına Workspace'teki verilere erişmesine** olanak tanır. +Google Workspace'in Alan Genel Yetkilendirmesi, bir kimlik nesnesinin, ya bir **harici uygulama** Google Workspace Marketplace'ten ya da bir iç **GCP Hizmet Hesabı** olarak, **kullanıcılar adına Workspace'teki verilere erişmesine** olanak tanır. > [!NOTE] > Bu, temelde **GCP projeleri içindeki hizmet hesaplarının**, aynı organizasyondaki (veya farklı bir organizasyondaki) **Workspace kullanıcılarını taklit edebilme** yeteneğine sahip olabileceği anlamına gelir. @@ -17,18 +17,18 @@ Bu işlemin tam olarak nasıl çalıştığı hakkında daha fazla bilgi için k gcp-understanding-domain-wide-delegation.md {{#endref}} -### Mevcut yetkilendirmeyi ele geçirme +### Mevcut Yetkilendirmeyi Ele Geçirme -Eğer bir saldırgan **GCP üzerinde bazı erişimleri ele geçirmişse** ve şirketin **geçerli bir Workspace kullanıcı e-posta adresini** (tercihen **süper yönetici**) biliyorsa, **erişim sağladığı tüm projeleri listeleyebilir**, projelerin **tüm SA'lerini listeleyebilir**, hangi **hizmet hesaplarına erişimi olduğunu** kontrol edebilir ve **taklit edebileceği her SA ile bu adımları tekrarlayabilir**.\ -Elde ettiği **tüm hizmet hesaplarının listesi** ve **Workspace** **e-posta adresleri** ile saldırgan, **her hizmet hesabıyla kullanıcıyı taklit etmeye** çalışabilir. +Eğer bir saldırgan **GCP üzerinde bazı erişimleri ele geçirmişse** ve şirketin **geçerli bir Workspace kullanıcı e-posta adresini** (tercihen **süper admin**) biliyorsa, **erişim sağladığı tüm projeleri listeleyebilir**, projelerin **tüm SA'lerini listeleyebilir**, hangi **hizmet hesaplarına erişimi olduğunu kontrol edebilir** ve **taklit edebileceği her SA ile bu adımları tekrarlayabilir**.\ +Elde ettiği **tüm hizmet hesaplarının listesi** ve **Workspace** **e-posta adresleri** ile saldırgan, **her hizmet hesabı ile kullanıcıyı taklit etmeye** çalışabilir. > [!CAUTION] -> Alan genel yetkilendirme yapılandırılırken herhangi bir Workspace kullanıcısına ihtiyaç olmadığını unutmayın, bu nedenle sadece **bir geçerli kullanıcı bilmek yeterlidir ve taklit için gereklidir**.\ -> Ancak, **taklit edilen kullanıcının ayrıcalıkları kullanılacaktır**, bu nedenle eğer Süper Yönetici ise her şeye erişebileceksiniz. Eğer herhangi bir erişimi yoksa bu işe yaramaz. +> Alan genel yetkilendirme yapılandırılırken hiçbir Workspace kullanıcısına ihtiyaç olmadığını unutmayın, bu nedenle sadece **bir geçerli kullanıcı bilmek yeterlidir ve taklit için gereklidir**.\ +> Ancak, **taklit edilen kullanıcının ayrıcalıkları kullanılacaktır**, bu nedenle eğer Süper Admin ise her şeye erişebileceksiniz. Eğer hiçbir erişimi yoksa bu işe yaramaz. #### [GCP Yetkilendirme Token'ı Oluştur](https://github.com/carlospolop/gcp_gen_delegation_token) -Bu basit betik, **yetkilendirilmiş kullanıcı olarak bir OAuth token'ı oluşturacaktır** ve bunu `gcloud` ile ya da onsuz diğer Google API'lerine erişmek için kullanabilirsiniz: +Bu basit betik, **yetkilendirilmiş kullanıcı olarak bir OAuth token'ı oluşturur** ve bunu `gcloud` ile veya onsuz diğer Google API'lerine erişmek için kullanabilirsiniz: ```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) + +Aşağıdaki DeleFriend aracına dayanmaktadır, ancak alanı, sürücüyü, gmail'i, takvimi listeleme ve diğer işlemleri gerçekleştirme yeteneği gibi bazı eklemelerle birlikte. + #### [**DeleFriend**](https://github.com/axon-git/DeleFriend) Bu, saldırıyı şu adımları izleyerek gerçekleştirebilen bir araçtır: -1. **GCP Projelerini Sayma** Resource Manager API kullanarak. -2. Her proje kaynağı üzerinde yineleyin ve **GCP Hizmet hesabı kaynaklarını sayın**; başlangıç IAM kullanıcısının erişimi olan kaynakları _GetIAMPolicy_ kullanarak belirleyin. -3. **Her hizmet hesabı rolü üzerinde yineleyin** ve hedef hizmet hesabı kaynağında _**serviceAccountKeys.create**_ iznine sahip yerleşik, temel ve özel rolleri bulun. Editor rolünün bu izni doğası gereği sahip olduğunu belirtmek gerekir. +1. **GCP Projelerini Listele** Resource Manager API'sini kullanarak. +2. Her proje kaynağı üzerinde yineleme yapın ve **ilk IAM kullanıcısının erişim iznine sahip olduğu GCP Hizmet hesabı kaynaklarını listeleyin** _GetIAMPolicy_ kullanarak. +3. **Her hizmet hesabı rolü üzerinde yineleme yapın** ve hedef hizmet hesabı kaynağında _**serviceAccountKeys.create**_ iznine sahip yerleşik, temel ve özel rolleri bulun. Editör rolünün bu izni doğası gereği taşıdığına dikkat edilmelidir. 4. IAM politikasında ilgili izne sahip bulunan her hizmet hesabı kaynağı için **yeni bir `KEY_ALG_RSA_2048`** özel anahtar oluşturun. -5. **Her yeni hizmet hesabı üzerinde yineleyin ve bunun için bir `JWT`** **nesnesi** oluşturun; bu nesne SA özel anahtar kimlik bilgileri ve bir OAuth kapsamından oluşur. Yeni bir _JWT_ nesnesi oluşturma süreci, **oauth_scopes.txt** listesindeki tüm mevcut OAuth kapsamı kombinasyonları üzerinde **yineleme** yaparak tüm delege etme olasılıklarını bulmak için gerçekleştirilir. **oauth_scopes.txt** listesi, Workspace kimliklerini kötüye kullanmak için ilgili bulduğumuz tüm OAuth kapsamlarıyla güncellenir. -6. `_make_authorization_grant_assertion` yöntemi, DWD altında JWT'ler oluşturmak için bir t**arget workspace user** olarak adlandırılan _subject_ tanımlamanın gerekliliğini ortaya koyar. Bu, belirli bir kullanıcı gerektiriyormuş gibi görünse de, **DWD'nin bir alan içindeki her kimliği etkilediğini** anlamak önemlidir. Sonuç olarak, **herhangi bir alan kullanıcısı** için bir JWT oluşturmak, o alandaki tüm kimlikleri etkiler; bu, kombinasyon sayma kontrolümüzle tutarlıdır. Kısacası, ilerlemek için bir geçerli Workspace kullanıcısı yeterlidir.\ -Bu kullanıcı, DeleFriend’in _config.yaml_ dosyasında tanımlanabilir. Hedef bir workspace kullanıcısı henüz bilinmiyorsa, araç, GCP projelerinde rolleri olan alan kullanıcılarını tarayarak geçerli workspace kullanıcılarının otomatik olarak tanımlanmasını kolaylaştırır. JWT'lerin alan spesifik olduğunu ve her kullanıcı için üretilmediğini (tekrar) belirtmek önemlidir; bu nedenle, otomatik süreç her alan için tek bir benzersiz kimliği hedef alır. -7. **Her JWT için yeni bir bearer erişim token'ı sayın ve token'ı tokeninfo API'sine karşı doğrulayın.** +5. **Her yeni hizmet hesabı üzerinde yineleme yapın ve bunun için bir `JWT`** **nesnesi** oluşturun; bu nesne SA özel anahtar kimlik bilgileri ve bir OAuth kapsamından oluşur. Yeni bir _JWT_ nesnesi oluşturma süreci, **oauth_scopes.txt** listesindeki tüm mevcut OAuth kapsamı kombinasyonları üzerinde yineleme yaparak tüm delege etme olasılıklarını bulacaktır. **oauth_scopes.txt** listesi, Workspace kimliklerini kötüye kullanmak için ilgili bulduğumuz tüm OAuth kapsamlarıyla güncellenir. +6. `_make_authorization_grant_assertion` yöntemi, DWD altında JWT'ler oluşturmak için bir t**arget workspace user** olarak adlandırılan _subject_ belirtmenin gerekliliğini ortaya koyar. Bu, belirli bir kullanıcı gerektiriyormuş gibi görünse de, **DWD'nin bir alan içindeki her kimliği etkilediğini** anlamak önemlidir. Sonuç olarak, **herhangi bir alan kullanıcısı** için bir JWT oluşturmak, o alandaki tüm kimlikleri etkiler; bu, kombinasyon listeleme kontrolümüzle tutarlıdır. Kısacası, ilerlemek için bir geçerli Workspace kullanıcısı yeterlidir.\ +Bu kullanıcı, DeleFriend’in _config.yaml_ dosyasında tanımlanabilir. Hedef bir workspace kullanıcısı henüz bilinmiyorsa, araç, GCP projelerinde rolleri olan alan kullanıcılarını tarayarak geçerli workspace kullanıcılarının otomatik olarak tanımlanmasını kolaylaştırır. JWT'lerin alan spesifik olduğunu ve her kullanıcı için oluşturulmadığını (tekrar) belirtmek önemlidir; bu nedenle, otomatik süreç her alan için tek bir benzersiz kimliği hedef alır. +7. **Her JWT için yeni bir bearer erişim token'ı listeleyin ve token'ı tokeninfo API'si ile doğrulayın.** #### [Gitlab'ın Python scripti](https://gitlab.com/gitlab-com/gl-security/threatmanagement/redteam/redteam-public/gcp_misc/-/blob/master/gcp_delegation.py) -Gitlab, SA kimlik bilgileri ve taklit edilecek kullanıcıyı belirten bir json ile yeni bir yönetici hesabı oluştururken kullanıcı dizinini listeleyebilen [bu Python scriptini](https://gitlab.com/gitlab-com/gl-security/gl-redteam/gcp_misc/blob/master/gcp_delegation.py) oluşturmuştur. İşte nasıl kullanacağınız: +Gitlab, SA kimlik bilgileri ve taklit edilecek kullanıcı ile birlikte bir json belirterek kullanıcı dizinini listeleyip yeni bir yönetici hesabı oluşturabilen [bu Python scriptini](https://gitlab.com/gitlab-com/gl-security/gl-redteam/gcp_misc/blob/master/gcp_delegation.py) oluşturmuştur. İşte nasıl kullanacağınız: ```bash # Install requirements pip install --upgrade --user oauth2client @@ -77,19 +81,19 @@ pip install --upgrade --user oauth2client **Domain Wide Delegations'ı kontrol etmek mümkündür** [**https://admin.google.com/u/1/ac/owl/domainwidedelegation**](https://admin.google.com/u/1/ac/owl/domainwidedelegation)**.** -**GCP projesinde hizmet hesapları oluşturma yeteneğine** ve **GWS için süper yönetici ayrıcalığına sahip bir saldırgan, SAs'ın bazı GWS kullanıcılarını taklit etmesine izin veren yeni bir delegasyon oluşturabilir:** +**GCP projesinde hizmet hesapları oluşturma yeteneğine** ve **GWS için süper admin ayrıcalığına sahip bir saldırgan, bazı GWS kullanıcılarını taklit etmelerine izin veren yeni bir delegasyon oluşturabilir:** -1. **Yeni Bir Hizmet Hesabı ve İlgili Anahtar Çifti Oluşturma:** GCP'de, yeni hizmet hesabı kaynakları ya konsol aracılığıyla etkileşimli olarak ya da doğrudan API çağrıları ve CLI araçları kullanarak programlı olarak üretilebilir. Bu, **`iam.serviceAccountAdmin`** rolünü veya **`iam.serviceAccounts.create`** **izinine** sahip herhangi bir özel rolü gerektirir. Hizmet hesabı oluşturulduktan sonra, **ilişkili bir anahtar çifti** oluşturmak için devam edeceğiz (**`iam.serviceAccountKeys.create`** izni). -2. **Yeni delegasyonun oluşturulması:** **Sadece Süper Yönetici rolünün Google Workspace'te küresel Domain-Wide delegasyonu kurma yeteneğine sahip olduğunu** anlamak önemlidir ve Domain-Wide delegasyonu **programlı olarak kurulamaz,** yalnızca Google Workspace **konsolu** aracılığıyla **manuel olarak** oluşturulabilir ve ayarlanabilir. -- Kuralın oluşturulması, **API kontrolleri → Google Workspace Yönetici konsolunda Domain-Wide delegasyonu yönet** sayfasında bulunabilir. -3. **OAuth kapsamı ayrıcalığını ekleme:** Yeni bir delegasyon yapılandırırken, Google yalnızca 2 parametre gerektirir: **GCP Hizmet Hesabı** kaynağının **OAuth ID'si** olan İstemci Kimliği ve delegasyonun gerektirdiği API çağrılarını tanımlayan **OAuth kapsamları**. +1. **Yeni Bir Hizmet Hesabı ve İlgili Anahtar Çifti Oluşturma:** GCP'de, yeni hizmet hesabı kaynakları ya konsol aracılığıyla etkileşimli olarak ya da doğrudan API çağrıları ve CLI araçları kullanılarak programlı olarak üretilebilir. Bu, **`iam.serviceAccountAdmin`** rolünü veya **`iam.serviceAccounts.create`** **izinine** sahip herhangi bir özel rolü gerektirir. Hizmet hesabı oluşturulduktan sonra, **ilişkili bir anahtar çifti** oluşturmak için devam edeceğiz (**`iam.serviceAccountKeys.create`** izni). +2. **Yeni delegasyonun oluşturulması**: **Sadece Süper Admin rolünün Google Workspace'te küresel Domain-Wide delegasyonu kurma yeteneğine sahip olduğunu** anlamak önemlidir ve Domain-Wide delegasyonu **programlı olarak kurulamaz,** yalnızca Google Workspace **konsolu** aracılığıyla **manuel olarak** oluşturulabilir ve ayarlanabilir. +- Kuralın oluşturulması, **API kontrolleri → Google Workspace Admin konsolunda Domain-Wide delegasyonu yönet** sayfasında bulunabilir. +3. **OAuth kapsamı ayrıcalığını ekleme**: Yeni bir delegasyon yapılandırırken, Google yalnızca 2 parametre gerektirir: **GCP Hizmet Hesabı** kaynağının **OAuth ID'si** olan İstemci Kimliği ve delegasyonun gerektirdiği API çağrılarını tanımlayan **OAuth kapsamları**. - **OAuth kapsamlarının tam listesi** [**burada**](https://developers.google.com/identity/protocols/oauth2/scopes) bulunabilir, ancak burada bir öneri: `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. **Hedef kimliği adına hareket etme:** Bu noktada, GWS'de işlevsel bir delegasyon nesnesine sahibiz. Artık, **GCP Hizmet Hesabı özel anahtarını kullanarak API çağrıları gerçekleştirebiliriz** (OAuth kapsamı parametresinde tanımlanan kapsamda) ve **Google Workspace'te mevcut olan herhangi bir kimlik adına hareket edebiliriz**. Öğrendiğimiz gibi, hizmet hesabı ihtiyaçlarına göre ve REST API uygulamalarına sahip olduğu izinlere göre erişim belirteçleri üretecektir. +4. **Hedef kimlik adına hareket etme:** Bu noktada, GWS'de işlevsel bir delegasyon nesnesine sahibiz. Artık, **GCP Hizmet Hesabı özel anahtarını kullanarak API çağrıları gerçekleştirebiliriz** (OAuth kapsamı parametresinde tanımlanan kapsamda) ve **Google Workspace'te mevcut olan herhangi bir kimlik adına hareket edebiliriz**. Öğrendiğimiz gibi, hizmet hesabı ihtiyaçlarına göre ve REST API uygulamalarına sahip olduğu izinlere göre erişim jetonları üretecektir. - Bu delegasyonu kullanmak için bazı **araçlar** için **önceki bölüme** bakın. -#### Çapraz Kurumsal delegasyon +#### Kurumsal Dışı delegasyon -OAuth SA ID'si küreseldir ve **çapraz kurumsal delegasyon** için kullanılabilir. Çapraz küresel delegasyonu önlemek için herhangi bir kısıtlama uygulanmamıştır. Basitçe ifade etmek gerekirse, **farklı GCP organizasyonlarından hizmet hesapları, diğer Workspace organizasyonlarında domain-wide delegasyonu yapılandırmak için kullanılabilir**. Bu, **sadece Workspace için Süper Yönetici erişimine ihtiyaç duyulması** anlamına gelir ve aynı GCP hesabına erişim gerektirmez, çünkü saldırgan kendi kontrolündeki GCP hesabında Hizmet Hesapları ve özel anahtarlar oluşturabilir. +OAuth SA ID'si küreseldir ve **kurumsal dışı delegasyon** için kullanılabilir. Kurumsal dışı delegasyonu önlemek için herhangi bir kısıtlama uygulanmamıştır. Basitçe ifade etmek gerekirse, **farklı GCP organizasyonlarından hizmet hesapları, diğer Workspace organizasyonlarında domain-wide delegasyonu yapılandırmak için kullanılabilir**. Bu, **sadece Workspace için Süper Admin erişimine ihtiyaç duyulması** anlamına gelir ve aynı GCP hesabına erişim gerektirmez, çünkü saldırgan kendi kontrolündeki GCP hesabında Hizmet Hesapları ve özel anahtarlar oluşturabilir. ### Workspace'i listelemek için bir Proje Oluşturma @@ -97,7 +101,7 @@ OAuth SA ID'si küreseldir ve **çapraz kurumsal delegasyon** için kullanılabi Bu nedenle, bir kullanıcı **bir proje oluşturabilir**, yeni projesinde Workspace'i listelemek için **API'leri etkinleştirebilir** ve bunu **listelemeye** çalışabilir. -> [!CAUTION] +> [!DİKKAT] > Bir kullanıcının Workspace'i listeleyebilmesi için yeterli Workspace izinlerine de sahip olması gerekir (her kullanıcı dizini listeleyemez). ```bash # Create project @@ -131,16 +135,16 @@ Giriş yapmak için `gcloud` akışı hakkında daha fazla bilgi bulabilirsiniz: {{#endref}} Orada açıklandığı gibi, gcloud **`https://www.googleapis.com/auth/drive`** kapsamını talep edebilir, bu da bir kullanıcının sürücüsüne erişmesine izin verir.\ -Bir saldırgan olarak, eğer bir kullanıcının bilgisayarını **fiziksel olarak** ele geçirdiyseniz ve **kullanıcı hala** hesabıyla oturum açmışsa, sürüme erişim sağlayan bir token oluşturarak giriş yapabilirsiniz: +Bir saldırgan olarak, eğer bir kullanıcının bilgisayarını **fiziksel olarak** ele geçirdiyseniz ve **kullanıcı hala** hesabıyla oturum açmışsa, sürücüye erişim sağlayan bir token oluşturarak giriş yapabilirsiniz: ```bash gcloud auth login --enable-gdrive-access ``` -Eğer bir saldırgan bir kullanıcının bilgisayarını ele geçirirse, `google-cloud-sdk/lib/googlecloudsdk/core/config.py` dosyasını da değiştirebilir ve **`CLOUDSDK_SCOPES`** içine **`'https://www.googleapis.com/auth/drive'`** kapsamını ekleyebilir: +Eğer bir saldırgan bir kullanıcının bilgisayarını ele geçirirse, `google-cloud-sdk/lib/googlecloudsdk/core/config.py` dosyasını değiştirebilir ve **`CLOUDSDK_SCOPES`** içine **`'https://www.googleapis.com/auth/drive'`** kapsamını ekleyebilir:
> [!WARNING] -> Bu nedenle, kullanıcı bir sonraki oturum açtığında, saldırganın drive'a erişim sağlamak için kötüye kullanabileceği **drive'a erişim izni olan bir token oluşturacaktır**. Açıkça, tarayıcı oluşturulan token'ın drive'a erişim izni olduğunu gösterecektir, ancak kullanıcı **`gcloud auth login`** komutunu kendisi çağıracağı için muhtemelen **hiçbir şeyden şüphelenmeyecektir.** +> Bu nedenle, kullanıcı bir sonraki giriş yaptığında, saldırganın drive'a erişim sağlamak için kötüye kullanabileceği **drive'a erişim izni olan bir token oluşturacaktır**. Açıkça, tarayıcı oluşturulan token'ın drive'a erişim izni olduğunu gösterecektir, ancak kullanıcı **`gcloud auth login`** komutunu kendisi çağıracağı için, muhtemelen **hiçbir şeyden şüphelenmeyecektir.** > > Drive dosyalarını listelemek için: **`curl -H "Authorization: Bearer $(gcloud auth print-access-token)" "https://www.googleapis.com/drive/v3/files"`** 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); + }); + } })();