Translated ['src/generic-methodologies-and-resources/phishing-methodolog

This commit is contained in:
Translator 2025-04-27 16:32:46 +00:00
parent c78c8acf88
commit 36b1104b5f
5 changed files with 234 additions and 73 deletions

View File

@ -31,6 +31,7 @@ additional-js = [
"theme/tabs.js",
"theme/ht_searcher.js",
"theme/sponsor.js",
"theme/ai.js"
]
no-section-label = true
preferred-dark-theme = "hacktricks-dark"

View File

@ -561,6 +561,7 @@
- [CSRF (Cross Site Request Forgery)](pentesting-web/csrf-cross-site-request-forgery.md)
- [Dangling Markup - HTML scriptless injection](pentesting-web/dangling-markup-html-scriptless-injection/README.md)
- [SS-Leaks](pentesting-web/dangling-markup-html-scriptless-injection/ss-leaks.md)
- [DApps - Decentralized Applications](pentesting-web/dapps-DecentralizedApplications.md)
- [Dependency Confusion](pentesting-web/dependency-confusion.md)
- [Deserialization](pentesting-web/deserialization/README.md)
- [NodeJS - \_\_proto\_\_ & prototype Pollution](pentesting-web/deserialization/nodejs-proto-prototype-pollution/README.md)
@ -625,6 +626,7 @@
- [Regular expression Denial of Service - ReDoS](pentesting-web/regular-expression-denial-of-service-redos.md)
- [Reset/Forgotten Password Bypass](pentesting-web/reset-password.md)
- [Reverse Tab Nabbing](pentesting-web/reverse-tab-nabbing.md)
- [RSQL Injection](pentesting-web/rsql-injection.md)
- [SAML Attacks](pentesting-web/saml-attacks/README.md)
- [SAML Basics](pentesting-web/saml-attacks/saml-basics.md)
- [Server Side Inclusion/Edge Side Inclusion Injection](pentesting-web/server-side-inclusion-edge-side-inclusion-injection.md)

View File

@ -10,7 +10,7 @@
3. Usar **OSINT** para **encontrar correos electrónicos**.
2. Preparar el entorno
1. **Comprar el dominio** que vas a usar para la evaluación de phishing.
2. **Configurar el servicio de correo** registros relacionados (SPF, DMARC, DKIM, rDNS).
2. **Configurar el servicio de correo** relacionado (SPF, DMARC, DKIM, rDNS).
3. Configurar el VPS con **gophish**.
3. Preparar la campaña
1. Preparar la **plantilla de correo electrónico**.
@ -32,7 +32,7 @@
- **Reemplazo**: Como homoglyph pero menos sigiloso. Reemplaza una de las letras en el nombre de dominio, quizás con una letra cercana a la letra original en el teclado (por ejemplo, zektser.com).
- **Subdominado**: Introducir un **punto** dentro del nombre de dominio (por ejemplo, ze.lster.com).
- **Inserción**: **Inserta una letra** en el nombre de dominio (por ejemplo, zerltser.com).
- **Punto faltante**: Adjuntar el TLD al nombre de dominio. (por ejemplo, zelstercom.com)
- **Punto faltante**: Agregar el TLD al nombre de dominio. (por ejemplo, zelstercom.com)
**Herramientas Automáticas**
@ -74,7 +74,7 @@ Para asegurarte de que el dominio expirado que vas a comprar **ya tiene un buen
- [https://anymailfinder.com/](https://anymailfinder.com)
Para **descubrir más** direcciones de correo electrónico válidas o **verificar las que ya has descubierto**, puedes comprobar si puedes forzar por fuerza bruta los servidores smtp de la víctima. [Aprende cómo verificar/descubrir direcciones de correo electrónico aquí](../../network-services-pentesting/pentesting-smtp/index.html#username-bruteforce-enumeration).\
Además, no olvides que si los usuarios utilizan **cualquier portal web para acceder a sus correos**, puedes verificar si es vulnerable a **fuerza bruta de nombres de usuario**, y explotar la vulnerabilidad si es posible.
Además, no olvides que si los usuarios utilizan **cualquier portal web para acceder a sus correos**, puedes verificar si es vulnerable a **fuerza bruta de nombres de usuario** y explotar la vulnerabilidad si es posible.
## Configurando GoPhish
@ -82,7 +82,7 @@ Además, no olvides que si los usuarios utilizan **cualquier portal web para acc
Puedes descargarlo de [https://github.com/gophish/gophish/releases/tag/v0.11.0](https://github.com/gophish/gophish/releases/tag/v0.11.0)
Descarga y descomprime dentro de `/opt/gophish` y ejecuta `/opt/gophish/gophish`\
Descarga y descomprime en `/opt/gophish` y ejecuta `/opt/gophish/gophish`\
Se te dará una contraseña para el usuario administrador en el puerto 3333 en la salida. Por lo tanto, accede a ese puerto y usa esas credenciales para cambiar la contraseña del administrador. Puede que necesites tunelizar ese puerto a local:
```bash
ssh -L 3333:127.0.0.1:3333 <user>@<ip>
@ -126,7 +126,7 @@ Finalmente, modifica los archivos **`/etc/hostname`** y **`/etc/mailname`** a tu
Ahora, crea un **registro A de DNS** de `mail.<domain>` apuntando a la **dirección IP** del VPS y un **registro MX de DNS** apuntando a `mail.<domain>`
Ahora probemos enviar un correo electrónico:
Ahora probemos enviar un correo:
```bash
apt install mailutils
echo "This is the body of the email" | mail -s "This is the subject line" test@email.com
@ -161,7 +161,7 @@ Modifica `/opt/gophish/config.json` a lo siguiente (nota el uso de https):
```
**Configurar el servicio gophish**
Para crear el servicio gophish de modo que se pueda iniciar automáticamente y gestionar como un servicio, puedes crear el archivo `/etc/init.d/gophish` con el siguiente contenido:
Para crear el servicio gophish para que se inicie automáticamente y se gestione como un servicio, puedes crear el archivo `/etc/init.d/gophish` con el siguiente contenido:
```bash
#!/bin/bash
# /etc/init.d/gophish
@ -208,7 +208,7 @@ case $1 in
start|stop|status) "$1" ;;
esac
```
Termina de configurar el servicio y verifica que funcione:
Termina de configurar el servicio y verifica su funcionamiento haciendo:
```bash
mkdir /var/log/gophish
chmod +x /etc/init.d/gophish
@ -231,7 +231,7 @@ Ten en cuenta que incluso si tienes que esperar una semana, puedes terminar de c
Configura un registro rDNS (PTR) que resuelva la dirección IP del VPS al nombre de dominio.
### Registro de Sender Policy Framework (SPF)
### Registro de Marco de Políticas de Remitente (SPF)
Debes **configurar un registro SPF para el nuevo dominio**. Si no sabes qué es un registro SPF [**lee esta página**](../../network-services-pentesting/pentesting-smtp/index.html#spf).
@ -243,7 +243,7 @@ Este es el contenido que debe establecerse dentro de un registro TXT dentro del
```bash
v=spf1 mx a ip4:ip.ip.ip.ip ?all
```
### Registro de Autenticación, Informe y Conformidad de Mensajes Basado en Dominio (DMARC)
### Registro de Autenticación de Mensajes Basado en Dominio, Informes y Conformidad (DMARC)
Debes **configurar un registro DMARC para el nuevo dominio**. Si no sabes qué es un registro DMARC [**lee esta página**](../../network-services-pentesting/pentesting-smtp/index.html#dmarc).
@ -297,7 +297,7 @@ La página [www.mail-tester.com](https://www.mail-tester.com) puede indicarte si
Puedes solicitar la eliminación de tu dominio/IP en [https://sender.office.com/](https://sender.office.com).
## Crear y lanzar campaña GoPhish
## Crear y lanzar campaña de GoPhish
### Perfil de envío
@ -308,8 +308,8 @@ La página [www.mail-tester.com](https://www.mail-tester.com) puede indicarte si
![](<../../images/image (253) (1) (2) (1) (1) (2) (2) (3) (3) (5) (3) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (10) (15) (2).png>)
> [!NOTE]
> Se recomienda utilizar la funcionalidad "**Enviar correo de prueba**" para comprobar que todo está funcionando.\
> Recomendaría **enviar los correos de prueba a direcciones de 10min** para evitar ser incluido en la lista negra al hacer pruebas.
> Se recomienda utilizar la funcionalidad "**Enviar correo de prueba**" para verificar que todo esté funcionando.\
> Recomendaría **enviar los correos de prueba a direcciones de 10min** para evitar ser incluido en la lista negra durante las pruebas.
### Plantilla de correo electrónico
@ -337,7 +337,7 @@ WRITE HERE SOME SIGNATURE OF SOMEONE FROM THE COMPANY
```
Nota que **para aumentar la credibilidad del correo electrónico**, se recomienda usar alguna firma de un correo del cliente. Sugerencias:
- Envía un correo a una **dirección no existente** y verifica si la respuesta tiene alguna firma.
- Envía un correo a una **dirección inexistente** y verifica si la respuesta tiene alguna firma.
- Busca **correos públicos** como info@ex.com o press@ex.com o public@ex.com y envíales un correo y espera la respuesta.
- Intenta contactar **algún correo válido descubierto** y espera la respuesta.
@ -349,7 +349,7 @@ Nota que **para aumentar la credibilidad del correo electrónico**, se recomiend
### Página de Aterrizaje
- Escribe un **nombre**
- **Escribe el código HTML** de la página web. Ten en cuenta que puedes **importar** páginas web.
- **Escribe el código HTML** de la página web. Nota que puedes **importar** páginas web.
- Marca **Capturar Datos Enviados** y **Capturar Contraseñas**
- Establece una **redirección**
@ -357,7 +357,7 @@ Nota que **para aumentar la credibilidad del correo electrónico**, se recomiend
> [!NOTE]
> Generalmente necesitarás modificar el código HTML de la página y hacer algunas pruebas en local (quizás usando algún servidor Apache) **hasta que te gusten los resultados.** Luego, escribe ese código HTML en el cuadro.\
> Ten en cuenta que si necesitas **usar algunos recursos estáticos** para el HTML (quizás algunas páginas CSS y JS) puedes guardarlos en _**/opt/gophish/static/endpoint**_ y luego acceder a ellos desde _**/static/\<filename>**_
> Nota que si necesitas **usar algunos recursos estáticos** para el HTML (quizás algunas páginas CSS y JS) puedes guardarlos en _**/opt/gophish/static/endpoint**_ y luego acceder a ellos desde _**/static/\<filename>**_
> [!NOTE]
> Para la redirección podrías **redirigir a los usuarios a la página web principal legítima** de la víctima, o redirigirlos a _/static/migration.html_ por ejemplo, poner alguna **rueda giratoria (**[**https://loading.io/**](https://loading.io)**) durante 5 segundos y luego indicar que el proceso fue exitoso**.
@ -365,13 +365,13 @@ Nota que **para aumentar la credibilidad del correo electrónico**, se recomiend
### Usuarios y Grupos
- Establece un nombre
- **Importa los datos** (ten en cuenta que para usar la plantilla del ejemplo necesitas el nombre, apellido y dirección de correo electrónico de cada usuario)
- **Importa los datos** (nota que para usar la plantilla del ejemplo necesitas el nombre, apellido y dirección de correo electrónico de cada usuario)
![](<../../images/image (163).png>)
### Campaña
Finalmente, crea una campaña seleccionando un nombre, la plantilla de correo, la página de aterrizaje, la URL, el perfil de envío y el grupo. Ten en cuenta que la URL será el enlace enviado a las víctimas.
Finalmente, crea una campaña seleccionando un nombre, la plantilla de correo, la página de aterrizaje, la URL, el perfil de envío y el grupo. Nota que la URL será el enlace enviado a las víctimas.
Nota que el **Perfil de Envío permite enviar un correo de prueba para ver cómo se verá el correo de phishing final**:
@ -410,7 +410,7 @@ Aquí es donde herramientas como [**evilginx2**](https://github.com/kgretzky/evi
1. **Suplantas el formulario de inicio de sesión** de la página web real.
2. El usuario **envía** sus **credenciales** a tu página falsa y la herramienta envía esas credenciales a la página web real, **verificando si las credenciales funcionan**.
3. Si la cuenta está configurada con **2FA**, la página MitM lo pedirá y una vez que el **usuario lo introduzca**, la herramienta lo enviará a la página web real.
4. Una vez que el usuario esté autenticado, tú (como atacante) habrás **capturado las credenciales, el 2FA, la cookie y cualquier información** de cada interacción mientras la herramienta está realizando un MitM.
4. Una vez que el usuario esté autenticado, tú (como atacante) habrás **capturado las credenciales, el 2FA, la cookie y cualquier información** de cada interacción mientras la herramienta realiza un MitM.
### A través de VNC
@ -422,7 +422,7 @@ Puedes hacer esto con [**EvilnVNC**](https://github.com/JoelGMSec/EvilnoVNC)
Obviamente, una de las mejores maneras de saber si te han descubierto es **buscar tu dominio en listas negras**. Si aparece listado, de alguna manera tu dominio fue detectado como sospechoso.\
Una forma fácil de verificar si tu dominio aparece en alguna lista negra es usar [https://malwareworld.com/](https://malwareworld.com)
Sin embargo, hay otras formas de saber si la víctima está **buscando activamente actividad de phishing sospechosa en la naturaleza**, como se explica en:
Sin embargo, hay otras formas de saber si la víctima está **buscando activamente actividad de phishing sospechosa en la naturaleza** como se explica en:
{{#ref}}
detecting-phising.md

141
theme/ai.js Normal file
View File

@ -0,0 +1,141 @@
/**
* HackTricks AI Chat Widget v1.14 animated typing indicator
* ------------------------------------------------------------------------
* Replaces the static placeholder with a threedot **bouncing** loader
* while waiting for the assistants response.
* ------------------------------------------------------------------------
*/
(function () {
const LOG = "[HackTricks-AI]";
/* ---------------- Usertunable constants ---------------- */
const MAX_CONTEXT = 3000; // highlightedtext char limit
const MAX_QUESTION = 500; // question char limit
const TOOLTIP_TEXT =
"💡 Highlight any text on the page,\nthen click to ask HackTricks AI about it";
const API_BASE = "https://www.hacktricks.ai/api/assistants/threads";
const BRAND_RED = "#b31328"; // HackTricks brand
/* ------------------------------ State ------------------------------ */
let threadId = null;
let isRunning = false;
const $ = (sel, ctx = document) => ctx.querySelector(sel);
if (document.getElementById("ht-ai-btn")) { console.warn(`${LOG} Widget already injected.`); return; }
(document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", init) : init());
/* ==================================================================== */
async function init() {
console.log(`${LOG} Injecting widget… v1.14`);
await ensureThreadId();
injectStyles();
const btn = createFloatingButton();
createTooltip(btn);
const panel = createSidebar();
const chatLog = $("#ht-ai-chat");
const sendBtn = $("#ht-ai-send");
const inputBox = $("#ht-ai-question");
const resetBtn = $("#ht-ai-reset");
const closeBtn = $("#ht-ai-close");
/* ------------------- Selection snapshot ------------------- */
let savedSelection = "";
btn.addEventListener("pointerdown", () => { savedSelection = window.getSelection().toString().trim(); });
/* ------------------- Helpers ------------------------------ */
function addMsg(text, cls) {
const b = document.createElement("div");
b.className = `ht-msg ${cls}`;
b.textContent = text;
chatLog.appendChild(b);
chatLog.scrollTop = chatLog.scrollHeight;
return b;
}
const LOADER_HTML = '<span class="ht-loading"><span></span><span></span><span></span></span>';
function setInputDisabled(d) { inputBox.disabled = d; sendBtn.disabled = d; }
function clearThreadCookie() { document.cookie = "threadId=; Path=/; Max-Age=0"; threadId = null; }
function resetConversation() { chatLog.innerHTML=""; clearThreadCookie(); panel.classList.remove("open"); }
/* ------------------- Panel open / close ------------------- */
btn.addEventListener("click", () => {
if (!savedSelection) { alert("Please highlight some text first to then ask Hacktricks AI about it."); return; }
if (savedSelection.length > MAX_CONTEXT) { alert(`Highlighted text is too long (${savedSelection.length} chars). Max allowed: ${MAX_CONTEXT}.`); return; }
chatLog.innerHTML=""; addMsg(savedSelection, "ht-context"); panel.classList.add("open"); inputBox.focus();
});
closeBtn.addEventListener("click", resetConversation);
resetBtn.addEventListener("click", resetConversation);
/* --------------------------- Messaging --------------------------- */
async function sendMessage(question, context=null) {
if (!threadId) await ensureThreadId();
if (isRunning) { addMsg("Please wait until the current operation completes.", "ht-ai"); return; }
isRunning = true; setInputDisabled(true);
const loadingBubble = addMsg("", "ht-ai");
loadingBubble.innerHTML = LOADER_HTML;
const content = context ? `### Context:\n${context}\n\n### Question to answer:\n${question}` : question;
try {
const res = await fetch(`${API_BASE}/${threadId}/messages`, { method:"POST", credentials:"include", headers:{"Content-Type":"application/json"}, body:JSON.stringify({content}) });
if (!res.ok) {
let err=`Unknown error: ${res.status}`;
try { const e=await res.json(); if(e.error) err=`Error: ${e.error}`; else if(res.status===429) err="Rate limit exceeded. Please try again later."; } catch(_){}
loadingBubble.textContent = err; return; }
const data = await res.json();
loadingBubble.remove();
if (Array.isArray(data.response)) data.response.forEach(p=>{ addMsg( p.type==="text"&&p.text&&p.text.value ? p.text.value : JSON.stringify(p), "ht-ai"); });
else if (typeof data.response === "string") addMsg(data.response, "ht-ai");
else addMsg(JSON.stringify(data,null,2), "ht-ai");
} catch (e) { console.error("Error sending message:",e); loadingBubble.textContent="An unexpected error occurred."; }
finally { isRunning=false; setInputDisabled(false); chatLog.scrollTop=chatLog.scrollHeight; }
}
async function handleSend(){ const q=inputBox.value.trim(); if(!q)return; if(q.length>MAX_QUESTION){alert(`Your question is too long (${q.length} chars). Max allowed: ${MAX_QUESTION}.`); return;} inputBox.value=""; addMsg(q,"ht-user"); await sendMessage(q,savedSelection||null);}
sendBtn.addEventListener("click", handleSend);
inputBox.addEventListener("keydown", e=>{ if(e.key==="Enter"&&!e.shiftKey){ e.preventDefault(); handleSend(); } });
}
/* ==================================================================== */
async function ensureThreadId(){ const m=document.cookie.match(/threadId=([^;]+)/); if(m&&m[1]){threadId=m[1];return;} try{ const r=await fetch(API_BASE,{method:"POST",credentials:"include"}); const d=await r.json(); if(!r.ok||!d.threadId) throw new Error(`${r.status} ${r.statusText}`); threadId=d.threadId; document.cookie=`threadId=${threadId}; Path=/; Secure; SameSite=Strict; Max-Age=7200`; }catch(e){ console.error("Error creating threadId:",e); alert("Failed to initialise the conversation. Please refresh and try again."); throw e; }}
/* ==================================================================== */
function injectStyles(){ const css=`
#ht-ai-btn{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);width:60px;height:60px;border-radius:50%;background:#1e1e1e;color:#fff;font-size:28px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:99999;box-shadow:0 2px 8px rgba(0,0,0,.4);transition:opacity .2s}
#ht-ai-btn:hover{opacity:.85}
@media(max-width:768px){#ht-ai-btn{display:none}}
#ht-ai-tooltip{position:fixed;padding:6px 8px;background:#111;color:#fff;border-radius:4px;font-size:13px;white-space:pre-wrap;pointer-events:none;opacity:0;transform:translate(-50%,-8px);transition:opacity .15s ease,transform .15s ease;z-index:100000}
#ht-ai-tooltip.show{opacity:1;transform:translate(-50%,-12px)}
#ht-ai-panel{position:fixed;top:0;right:0;height:100%;width:350px;max-width:90vw;background:#000;color:#fff;display:flex;flex-direction:column;transform:translateX(100%);transition:transform .3s ease;z-index:100000;font-family:system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial,sans-serif}
#ht-ai-panel.open{transform:translateX(0)}
@media(max-width:768px){#ht-ai-panel{display:none}}
#ht-ai-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #333}
#ht-ai-header .ht-actions{display:flex;gap:8px;align-items:center}
#ht-ai-close,#ht-ai-reset{cursor:pointer;font-size:18px;background:none;border:none;color:#fff;padding:0}
#ht-ai-close:hover,#ht-ai-reset:hover{opacity:.7}
#ht-ai-chat{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px;font-size:14px}
.ht-msg{max-width:90%;line-height:1.4;padding:10px 12px;border-radius:8px;white-space:pre-wrap;word-wrap:break-word}
.ht-user{align-self:flex-end;background:${BRAND_RED}}
.ht-ai{align-self:flex-start;background:#222}
.ht-context{align-self:flex-start;background:#444;font-style:italic;font-size:13px}
#ht-ai-input{display:flex;gap:8px;padding:12px 16px;border-top:1px solid #333}
#ht-ai-question{flex:1;min-height:40px;max-height:120px;resize:vertical;padding:8px;border-radius:6px;border:none;font-size:14px}
#ht-ai-send{padding:0 18px;border:none;border-radius:6px;background:${BRAND_RED};color:#fff;font-size:14px;cursor:pointer}
#ht-ai-send:disabled{opacity:.5;cursor:not-allowed}
/* Loader animation */
.ht-loading{display:inline-flex;align-items:center;gap:4px}
.ht-loading span{width:6px;height:6px;border-radius:50%;background:#888;animation:ht-bounce 1.2s infinite ease-in-out}
.ht-loading span:nth-child(2){animation-delay:0.2s}
.ht-loading span:nth-child(3){animation-delay:0.4s}
@keyframes ht-bounce{0%,80%,100%{transform:scale(0);}40%{transform:scale(1);} }
::selection{background:#ffeb3b;color:#000}
::-moz-selection{background:#ffeb3b;color:#000}`;
const s=document.createElement("style"); s.id="ht-ai-style"; s.textContent=css; document.head.appendChild(s);}
function createFloatingButton(){ const d=document.createElement("div"); d.id="ht-ai-btn"; d.textContent="🤖"; document.body.appendChild(d); return d; }
function createTooltip(btn){ const t=document.createElement("div"); t.id="ht-ai-tooltip"; t.textContent=TOOLTIP_TEXT; document.body.appendChild(t); btn.addEventListener("mouseenter",()=>{const r=btn.getBoundingClientRect(); t.style.left=`${r.left+r.width/2}px`; t.style.top=`${r.top}px`; t.classList.add("show");}); btn.addEventListener("mouseleave",()=>t.classList.remove("show")); }
function createSidebar(){ const p=document.createElement("div"); p.id="ht-ai-panel"; p.innerHTML=`<div id="ht-ai-header"><strong>HackTricksAI Chat</strong><div class="ht-actions"><button id="ht-ai-reset" title="Reset">↺</button><span id="ht-ai-close" title="Close">✖</span></div></div><div id="ht-ai-chat"></div><div id="ht-ai-input"><textarea id="ht-ai-question" placeholder="Type your question…"></textarea><button id="ht-ai-send">Send</button></div>`; document.body.appendChild(p); return p; }
})();

View File

@ -1,3 +1,26 @@
/*
Polyfill so requestIdleCallback works everywhere (IE 11/Safari)
*/
if (typeof window.requestIdleCallback !== "function") {
window.requestIdleCallback = function (cb) {
const start = Date.now();
return setTimeout(function () {
cb({
didTimeout: false,
timeRemaining: function () {
return Math.max(0, 50 - (Date.now() - start));
}
});
}, 1);
};
window.cancelIdleCallback = window.clearTimeout;
}
/*
search.js
*/
"use strict";
window.search = window.search || {};
(function search(search) {
@ -471,64 +494,58 @@ window.search = window.search || {};
showResults(true);
}
(async function loadSearchIndex(lang = window.lang || 'en') {
/* ───────── paths ───────── */
const branch = lang === 'en' ? 'master' : lang;
const baseRemote = `https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/${branch}`;
const remoteJson = `${baseRemote}/searchindex.json`;
const remoteJs = `${baseRemote}/searchindex.js`;
const localJson = './searchindex.json';
const localJs = './searchindex.js';
const TIMEOUT_MS = 5_000;
/* ───────── helpers ───────── */
const fetchWithTimeout = (url, opt = {}) =>
Promise.race([
fetch(url, opt),
new Promise((_, r) => setTimeout(() => r(new Error('timeout')), TIMEOUT_MS))
]);
const loadScript = src =>
new Promise((resolve, reject) => {
const s = document.createElement('script');
s.src = src;
s.onload = resolve;
s.onerror = reject;
(async function loadSearchIndex(lang = window.lang || "en") {
const branch = lang === "en" ? "master" : lang;
const rawUrl =
`https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/refs/heads/${branch}/searchindex.js`;
const localJs = "/searchindex.js";
const TIMEOUT_MS = 10_000;
const injectScript = (src) =>
new Promise((resolve, reject) => {
const s = document.createElement("script");
s.src = src;
s.onload = () => resolve(src);
s.onerror = (e) => reject(e);
document.head.appendChild(s);
});
/* ───────── 1. remote JSON ───────── */
});
try {
const r = await fetchWithTimeout(remoteJson);
if (!r.ok) throw new Error(r.status);
return init(await r.json());
} catch (e) {
console.warn('Remote JSON failed →', e);
/* 1 — download raw JS from GitHub */
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
const res = await fetch(rawUrl, { signal: controller.signal });
clearTimeout(timer);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
/* 2 — wrap in a Blob so the browser sees application/javascript */
const code = await res.text();
const blobUrl = URL.createObjectURL(
new Blob([code], { type: "application/javascript" })
);
/* 3 — execute it */
await injectScript(blobUrl);
/* PATCH
heavy parsing now deferred to idle time
*/
requestIdleCallback(() => init(window.search));
return; // ✔ UI remains responsive
} catch (eRemote) {
console.warn("Remote JS failed →", eRemote);
}
/* ───────── 2. remote JS ───────── */
/* ───────── fallback: local copy ───────── */
try {
await loadScript(remoteJs);
return init(window.search);
} catch (e) {
console.warn('Remote JS failed →', e);
}
/* ───────── 3. local JSON ───────── */
try {
const r = await fetch(localJson);
if (!r.ok) throw new Error(r.status);
return init(await r.json());
} catch (e) {
console.warn('Local JSON failed →', e);
}
/* ───────── 4. local JS ───────── */
try {
await loadScript(localJs);
return init(window.search);
} catch (e) {
console.error('Local JS failed →', e);
await injectScript(localJs);
/* ───────────── PATCH ───────────── */
requestIdleCallback(() => init(window.search));
return;
} catch (eLocal) {
console.error("Local JS failed →", eLocal);
}
})();