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

This commit is contained in:
Translator 2025-04-27 16:32:54 +00:00
parent 05048c4490
commit 2a51837a28
5 changed files with 251 additions and 90 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

@ -21,18 +21,18 @@
### 域名变体技术
- **关键词**域名 **包含** 原始域名的重要 **关键词** (例如zelster.com-management.com)。
- **带连字符的子域**将子域的 **点替换为连字符** (例如www-zelster.com)。
- **新 TLD**使用 **新 TLD** 的相同域名 (例如zelster.org)
- **同形异义字**:它 **用看起来相似的字母替换** 域名中的一个字母 (例如zelfser.com)。
- **置换****交换域名中的两个字母** (例如zelsetr.com)。
- **单数/复数化**:在域名末尾添加或删除“s” (例如zeltsers.com)。
- **省略****删除域名中的一个字母** (例如zelser.com)。
- **重复****重复域名中的一个字母** (例如zeltsser.com)。
- **替换**:类似于同形异义字,但不太隐蔽。它替换域名中的一个字母,可能是与原字母在键盘上相邻的字母 (例如zektser.com)。
- **子域化**在域名中引入一个 **点** (例如ze.lster.com)。
- **插入****在域名中插入一个字母** (例如zerltser.com)。
- **缺失点**将 TLD 附加到域名上。 (例如zelstercom.com)
- **关键词**: 域名 **包含** 原始域名的重要 **关键词** (例如zelster.com-management.com)。
- **带连字符的子域**: 将子域的 **点替换为连字符** (例如www-zelster.com)。
- **新 TLD**: 使用 **新 TLD** 的相同域名 (例如zelster.org)
- **同形异义字**: 它 **替换** 域名中的一个字母为 **看起来相似的字母** (例如zelfser.com)。
- **置换**: **交换域名中的两个字母** (例如zelsetr.com)。
- **单数/复数化**: 在域名末尾添加或删除 “s” (例如zeltsers.com)。
- **省略**: **删除域名中的一个字母** (例如zelser.com)。
- **重复**: **重复域名中的一个字母** (例如zeltsser.com)。
- **替换**: 类似于同形异义字,但不那么隐蔽。它替换域名中的一个字母,可能是与原字母在键盘上相邻的字母 (例如zektser.com)。
- **子域化**: 在域名中引入一个 **点** (例如ze.lster.com)。
- **插入**: **在域名中插入一个字母** (例如zerltser.com)。
- **缺失点**: 将 TLD 附加到域名上。 (例如zelstercom.com)
**自动工具**
@ -47,20 +47,20 @@
### 位翻转
由于太阳耀斑、宇宙射线或硬件错误等各种因素,**存储或通信中的某些位可能会自动翻转**
存在 **某些存储或通信中的位可能会因各种因素而自动翻转的可能性**,例如太阳耀斑、宇宙射线或硬件错误
当这个概念 **应用于 DNS 请求** 时,**DNS 服务器接收到的域名** 可能与最初请求的域名不同。
当这个概念 **应用于 DNS 请求** 时,可能 **DNS 服务器接收到的域名** 与最初请求的域名不同。
例如,域名 "windows.com" 中的单个位修改可以将其更改为 "windnws.com"。
攻击者可能 **利用这一点注册多个位翻转域名**,这些域名与受害者的域名相似。他们的目的是将合法用户重定向到他们自己的基础设施。
攻击者可能 **利用这一点注册多个位翻转域名**,这些域名与受害者的域名相似。他们的意图是将合法用户重定向到他们自己的基础设施。
有关更多信息,请阅读 [https://www.bleepingcomputer.com/news/security/hijacking-traffic-to-microsoft-s-windowscom-with-bitflipping/](https://www.bleepingcomputer.com/news/security/hijacking-traffic-to-microsoft-s-windowscom-with-bitflipping/)
### 购买受信任的域名
你可以在 [https://www.expireddomains.net/](https://www.expireddomains.net) 搜索可以使用的过期域名。\
为了确保你要购买的过期域名 **已经有良好的 SEO**,你可以搜索它在以下网站的分类:
为了确保你要购买的过期域名 **已经有良好的 SEO**,你可以搜索它在以下网站的分类:
- [http://www.fortiguard.com/webfilter](http://www.fortiguard.com/webfilter)
- [https://urlfiltering.paloaltonetworks.com/query/](https://urlfiltering.paloaltonetworks.com/query/)
@ -73,7 +73,7 @@
- [https://hunter.io/](https://hunter.io)
- [https://anymailfinder.com/](https://anymailfinder.com)
为了 **发现更多** 有效的电子邮件地址或 **验证你已经发现的地址**,你可以检查是否可以对受害者的 smtp 服务器进行暴力破解。 [在这里了解如何验证/发现电子邮件地址](../../network-services-pentesting/pentesting-smtp/index.html#username-bruteforce-enumeration)。\
为了 **发现更多** 有效的电子邮件地址或 **验证你已经发现的地址**,你可以检查是否可以对受害者的 smtp 服务器进行暴力破解。 [在这里学习如何验证/发现电子邮件地址](../../network-services-pentesting/pentesting-smtp/index.html#username-bruteforce-enumeration)。\
此外,不要忘记,如果用户使用 **任何网络门户访问他们的邮件**,你可以检查它是否容易受到 **用户名暴力破解**,并在可能的情况下利用该漏洞。
## 配置 GoPhish
@ -82,7 +82,7 @@
你可以从 [https://github.com/gophish/gophish/releases/tag/v0.11.0](https://github.com/gophish/gophish/releases/tag/v0.11.0) 下载它。
下载并解压到 `/opt/gophish`并执行 `/opt/gophish/gophish`\
下载并解压到 `/opt/gophish` 中并执行 `/opt/gophish/gophish`\
你将在输出中获得端口 3333 的管理员用户密码。因此,访问该端口并使用这些凭据更改管理员密码。你可能需要将该端口隧道到本地:
```bash
ssh -L 3333:127.0.0.1:3333 <user>@<ip>
@ -91,7 +91,7 @@ ssh -L 3333:127.0.0.1:3333 <user>@<ip>
**TLS 证书配置**
此步骤之前,您应该**已经购买了您将要使用的域名**,并且它必须**指向**您正在配置**gophish**的**VPS 的 IP**。
这一步之前,您应该已经**购买了您将要使用的域名**,并且它必须**指向**您配置**gophish**的**VPS 的 IP**。
```bash
DOMAIN="<domain>"
wget https://dl.eff.org/certbot-auto
@ -122,9 +122,9 @@ cp "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" /opt/gophish/ssl_keys/key.crt
`myhostname = <domain>`\
`mydestination = $myhostname, <domain>, localhost.com, localhost`
最后将文件 **`/etc/hostname`** 和 **`/etc/mailname`** 修改为您的域名并 **重启您的 VPS。**
最后将文件 **`/etc/hostname`** 和 **`/etc/mailname`** 修改为您的域名并 **重启您的 VPS。**
现在,创建一个指向 VPS **ip 地址****DNS A 记录** `mail.<domain>` 和一个指向 `mail.<domain>`**DNS MX** 记录
现在,创建一个指向 VPS **ip 地址****DNS A 记录** `mail.<domain>` 和一个指向 `mail.<domain>` 的 **DNS MX 记录**
现在让我们测试发送电子邮件:
```bash
@ -208,7 +208,7 @@ case $1 in
start|stop|status) "$1" ;;
esac
```
完成配置服务并检查它方法是:
完成配置服务并检查它方法是:
```bash
mkdir /var/log/gophish
chmod +x /etc/init.d/gophish
@ -264,7 +264,7 @@ v=DMARC1; p=none
> v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0wPibdqPtzYk81njjQCrChIcHzxOp8a1wjbsoNtka2X9QXCZs+iXkvw++QsWDtdYu3q0Ofnr0Yd/TmG/Y2bBGoEgeE+YTUG2aEgw8Xx42NLJq2D1pB2lRQPW4IxefROnXu5HfKSm7dyzML1gZ1U0pR5X4IZCH0wOPhIq326QjxJZm79E1nTh3xj" "Y9N/Dt3+fVnIbMupzXE216TdFuifKM6Tl6O/axNsbswMS1TH812euno8xRpsdXJzFlB9q3VbMkVWig4P538mHolGzudEBg563vv66U8D7uuzGYxYT4WS8NVm3QBMg0QKPWZaKp+bADLkOSB9J2nUpk4Aj9KB5swIDAQAB
> ```
### 测试您的电子邮件配置分
### 测试您的电子邮件配置
您可以使用[https://www.mail-tester.com/](https://www.mail-tester.com)\
只需访问该页面并将电子邮件发送到他们提供的地址:
@ -291,7 +291,7 @@ dkim=pass header.i=@example.com;
```
### 从Spamhouse黑名单中移除
页面 [www.mail-tester.com](https://www.mail-tester.com) 可以指示您的域名是否被spamhouse阻止。您可以在以下地址请求移除您的域名/IP: [https://www.spamhaus.org/lookup/](https://www.spamhaus.org/lookup/)
页面 [www.mail-tester.com](https://www.mail-tester.com) 可以指示您的域名是否被spamhouse阻止。您可以在: [https://www.spamhaus.org/lookup/](https://www.spamhaus.org/lookup/) 请求移除您的域名/IP。
### 从Microsoft黑名单中移除
@ -305,7 +305,7 @@ dkim=pass header.i=@example.com;
- 决定您将从哪个账户发送钓鱼邮件。建议_noreply, support, servicedesk, salesforce..._
- 您可以将用户名和密码留空,但请确保勾选忽略证书错误
![](<../../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>)
![](<../../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) (1) (10) (15) (2).png>)
> [!NOTE]
> 建议使用“**发送测试邮件**”功能来测试一切是否正常。\
@ -357,15 +357,15 @@ WRITE HERE SOME SIGNATURE OF SOMEONE FROM THE COMPANY
> [!NOTE]
> 通常,您需要修改页面的 HTML 代码并在本地进行一些测试(可能使用某些 Apache 服务器)**直到您满意结果。** 然后,将该 HTML 代码写入框中。\
> 请注意,如果您需要**使用些静态资源**用于 HTML可能是一些 CSS 和 JS 页面),您可以将它们保存在 _**/opt/gophish/static/endpoint**_ 中,然后从 _**/static/\<filename>**_ 访问它们。
> 请注意,如果您需要**使用些静态资源**用于 HTML可能是一些 CSS 和 JS 页面),您可以将它们保存在 _**/opt/gophish/static/endpoint**_ 中,然后从 _**/static/\<filename>**_ 访问它们。
> [!NOTE]
> 对于重定向,您可以**将用户重定向到受害者的合法主网页**,或者例如将他们重定向到 _/static/migration.html_,放置一些**旋转轮****[https://loading.io/](https://loading.io)**5 秒钟,然后指示该过程成功。
> 对于重定向,您可以**将用户重定向到受害者的合法主网页**,或者例如重定向到 _/static/migration.html_,放置一些**旋转轮****[https://loading.io/](https://loading.io)**5 秒钟,然后指示该过程成功。
### 用户与组
- 设置一个名称
- **导入数据**(请注意,为了使用示例模板,您需要每个用户的名字、姓氏和电子邮件地址)
- **导入数据**(请注意,为了使用示例模板,您需要每个用户的名字、姓氏和电子邮件地址)
![](<../../images/image (163).png>)
@ -373,14 +373,14 @@ WRITE HERE SOME SIGNATURE OF SOMEONE FROM THE COMPANY
最后创建一个活动选择一个名称、电子邮件模板、登陆页面、URL、发送配置文件和组。请注意URL 将是发送给受害者的链接。
注意,**发送配置文件允许发送测试电子邮件以查看最终钓鱼电子邮件的外观**
注意,**发送配置文件允许发送测试电子邮件以查看最终的钓鱼电子邮件的样子**
![](<../../images/image (192).png>)
> [!NOTE]
> 我建议**将测试电子邮件发送到 10min 邮件地址**以避免在测试中被列入黑名单。
一切准备就绪后,只需启动活动!
一切准备就绪后,启动活动!
## 网站克隆
@ -403,18 +403,18 @@ phishing-documents.md
### 通过代理 MitM
之前的攻击非常聪明,因为您伪造了一个真实的网站并收集了用户输入的信息。不幸的是,如果用户没有输入正确的密码,或者您伪造的应用程序配置了 2FA**这些信息将无法让您冒充被欺骗的用户**。
之前的攻击相当聪明,因为您伪造了一个真实的网站并收集了用户输入的信息。不幸的是,如果用户没有输入正确的密码,或者您伪造的应用程序配置了 2FA**这些信息将无法让您冒充被欺骗的用户**。
这就是像 [**evilginx2**](https://github.com/kgretzky/evilginx2)**、** [**CredSniper**](https://github.com/ustayready/CredSniper) 和 [**muraena**](https://github.com/muraenateam/muraena) 这样的工具有用的地方。工具将允许您生成类似 MitM 的攻击。基本上,攻击的工作方式如下:
这就是像 [**evilginx2**](https://github.com/kgretzky/evilginx2)**、** [**CredSniper**](https://github.com/ustayready/CredSniper) 和 [**muraena**](https://github.com/muraenateam/muraena) 这样的工具有用的地方。这个工具将允许您生成类似 MitM 的攻击。基本上,攻击的工作方式如下:
1. 您**冒充真实网页的登录**表单。
2. 用户**发送**他的**凭据**到您的假页面,工具将这些发送到真实网页,**检查凭据是否有效**。
3. 如果账户配置了**2FA**MitM 页面将要求输入,一旦**用户输入**,工具将其发送到真实网页。
4. 一旦用户通过身份验证,您(作为攻击者)将**捕获凭据、2FA、cookie 和任何信息**,在工具执行 MitM 时的每次交互
4. 一旦用户通过身份验证,您(作为攻击者)将**捕获凭据、2FA、cookie 和每次交互的任何信息**,同时工具正在执行 MitM
### 通过 VNC
如果您不是**将受害者发送到一个与原始页面外观相同的恶意页面**,而是将他发送到一个**与真实网页连接的浏览器的 VNC 会话**?您将能够看到他所做的事情,窃取密码、使用的 MFA、cookie...\
如果您不是**将受害者发送到一个与原始页面外观相同的恶意页面**,而是将他发送到一个**与真实网页连接的浏览器的 VNC 会话**,会怎么样?您将能够看到他所做的事情,窃取密码、使用的 MFA、cookie...\
您可以使用 [**EvilnVNC**](https://github.com/JoelGMSec/EvilnoVNC) 来做到这一点。
## 检测检测
@ -422,13 +422,13 @@ phishing-documents.md
显然,知道您是否被发现的最佳方法之一是**在黑名单中搜索您的域**。如果它被列出,您的域以某种方式被检测为可疑。\
检查您的域是否出现在任何黑名单中的一种简单方法是使用 [https://malwareworld.com/](https://malwareworld.com)。
然而,还有其他方法可以知道受害者是否**积极寻找可疑的钓鱼活动**,如以下所述:
然而,还有其他方法可以知道受害者是否**积极寻找可疑的钓鱼活动**,如以下所述:
{{#ref}}
detecting-phising.md
{{#endref}}
您可以**购买一个与受害者域名非常相似的域名****和/或为您控制的域的**子域**生成证书****包含**受害者域名的**关键字**。如果**受害者**与它们进行任何类型的**DNS 或 HTTP 交互**,您将知道**他在积极寻找**可疑域,您需要非常隐蔽。
您可以**购买一个与受害者域名非常相似的域名****和/或为您控制的域的**一个**子域生成证书****包含**受害者域的**关键字**。如果**受害者**与它们进行任何类型的**DNS 或 HTTP 交互**,您将知道**他在积极寻找**可疑域,您需要非常隐蔽。
### 评估钓鱼

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);
}
})();