mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/network-services-pentesting/pentesting-web/nextjs-1.md'
This commit is contained in:
parent
6ead1a6db0
commit
77fa73a16c
@ -393,8 +393,6 @@
|
|||||||
- [Electron contextIsolation RCE via Electron internal code](network-services-pentesting/pentesting-web/electron-desktop-apps/electron-contextisolation-rce-via-electron-internal-code.md)
|
- [Electron contextIsolation RCE via Electron internal code](network-services-pentesting/pentesting-web/electron-desktop-apps/electron-contextisolation-rce-via-electron-internal-code.md)
|
||||||
- [Electron contextIsolation RCE via IPC](network-services-pentesting/pentesting-web/electron-desktop-apps/electron-contextisolation-rce-via-ipc.md)
|
- [Electron contextIsolation RCE via IPC](network-services-pentesting/pentesting-web/electron-desktop-apps/electron-contextisolation-rce-via-ipc.md)
|
||||||
- [Flask](network-services-pentesting/pentesting-web/flask.md)
|
- [Flask](network-services-pentesting/pentesting-web/flask.md)
|
||||||
- [NextJS](network-services-pentesting/pentesting-web/nextjs.md)
|
|
||||||
- [NodeJS Express](network-services-pentesting/pentesting-web/nodejs-express.md)
|
|
||||||
- [Git](network-services-pentesting/pentesting-web/git.md)
|
- [Git](network-services-pentesting/pentesting-web/git.md)
|
||||||
- [Golang](network-services-pentesting/pentesting-web/golang.md)
|
- [Golang](network-services-pentesting/pentesting-web/golang.md)
|
||||||
- [GWT - Google Web Toolkit](network-services-pentesting/pentesting-web/gwt-google-web-toolkit.md)
|
- [GWT - Google Web Toolkit](network-services-pentesting/pentesting-web/gwt-google-web-toolkit.md)
|
||||||
@ -409,8 +407,9 @@
|
|||||||
- [JSP](network-services-pentesting/pentesting-web/jsp.md)
|
- [JSP](network-services-pentesting/pentesting-web/jsp.md)
|
||||||
- [Laravel](network-services-pentesting/pentesting-web/laravel.md)
|
- [Laravel](network-services-pentesting/pentesting-web/laravel.md)
|
||||||
- [Moodle](network-services-pentesting/pentesting-web/moodle.md)
|
- [Moodle](network-services-pentesting/pentesting-web/moodle.md)
|
||||||
|
- [NextJS](network-services-pentesting/pentesting-web/nextjs.md)
|
||||||
- [Nginx](network-services-pentesting/pentesting-web/nginx.md)
|
- [Nginx](network-services-pentesting/pentesting-web/nginx.md)
|
||||||
- [NextJS](network-services-pentesting/pentesting-web/nextjs-1.md)
|
- [NodeJS Express](network-services-pentesting/pentesting-web/nodejs-express.md)
|
||||||
- [PHP Tricks](network-services-pentesting/pentesting-web/php-tricks-esp/README.md)
|
- [PHP Tricks](network-services-pentesting/pentesting-web/php-tricks-esp/README.md)
|
||||||
- [PHP - Useful Functions & disable_functions/open_basedir bypass](network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable_functions-open_basedir-bypass/README.md)
|
- [PHP - Useful Functions & disable_functions/open_basedir bypass](network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable_functions-open_basedir-bypass/README.md)
|
||||||
- [disable_functions bypass - php-fpm/FastCGI](network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable_functions-open_basedir-bypass/disable_functions-bypass-php-fpm-fastcgi.md)
|
- [disable_functions bypass - php-fpm/FastCGI](network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable_functions-open_basedir-bypass/disable_functions-bypass-php-fpm-fastcgi.md)
|
||||||
@ -439,6 +438,7 @@
|
|||||||
- [Symfony](network-services-pentesting/pentesting-web/symphony.md)
|
- [Symfony](network-services-pentesting/pentesting-web/symphony.md)
|
||||||
- [Tomcat](network-services-pentesting/pentesting-web/tomcat/README.md)
|
- [Tomcat](network-services-pentesting/pentesting-web/tomcat/README.md)
|
||||||
- [Uncovering CloudFlare](network-services-pentesting/pentesting-web/uncovering-cloudflare.md)
|
- [Uncovering CloudFlare](network-services-pentesting/pentesting-web/uncovering-cloudflare.md)
|
||||||
|
- [Vuejs](network-services-pentesting/pentesting-web/vuejs.md)
|
||||||
- [VMWare (ESX, VCenter...)](network-services-pentesting/pentesting-web/vmware-esx-vcenter....md)
|
- [VMWare (ESX, VCenter...)](network-services-pentesting/pentesting-web/vmware-esx-vcenter....md)
|
||||||
- [Web API Pentesting](network-services-pentesting/pentesting-web/web-api-pentesting.md)
|
- [Web API Pentesting](network-services-pentesting/pentesting-web/web-api-pentesting.md)
|
||||||
- [WebDav](network-services-pentesting/pentesting-web/put-method-webdav.md)
|
- [WebDav](network-services-pentesting/pentesting-web/put-method-webdav.md)
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
# NextJS
|
|
||||||
|
|
||||||
{{#include ../../banners/hacktricks-training.md}}
|
|
||||||
|
|
||||||
{{#include ../../banners/hacktricks-training.md}}
|
|
128
src/network-services-pentesting/pentesting-web/vuejs.md
Normal file
128
src/network-services-pentesting/pentesting-web/vuejs.md
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# Vue.js
|
||||||
|
|
||||||
|
{{#include ../../banners/hacktricks-training.md}}
|
||||||
|
|
||||||
|
## Vue.js में XSS Sinks
|
||||||
|
|
||||||
|
### v-html निर्देश
|
||||||
|
`v-html` निर्देश **कच्चा** HTML प्रस्तुत करता है, इसलिए कोई भी `<script>` (या `onerror` जैसी कोई विशेषता) जो अस्वच्छ उपयोगकर्ता इनपुट में निहित है, तुरंत निष्पादित हो जाती है।
|
||||||
|
```html
|
||||||
|
<div id="app">
|
||||||
|
<div v-html="htmlContent"></div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
htmlContent: '<img src=x onerror=alert(1)>'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
### v-bind with src or href
|
||||||
|
एक उपयोगकर्ता स्ट्रिंग को URL-bearing attributes (`href`, `src`, `xlink:href`, `formaction` …) से बाइंड करना payloads जैसे कि `javascript:alert(1)` को चलाने की अनुमति देता है जब लिंक का पालन किया जाता है।
|
||||||
|
```html
|
||||||
|
<div id="app">
|
||||||
|
<a v-bind:href="userInput">Click me</a>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
userInput: 'javascript:alert(1)'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
### v-on के साथ उपयोगकर्ता-नियंत्रित हैंडलर्स
|
||||||
|
`v-on` अपने मान को `new Function` के साथ संकलित करता है; यदि वह मान उपयोगकर्ता से आता है, तो आप उन्हें कोड-निष्पादन एक प्लेट पर देते हैं।
|
||||||
|
```html
|
||||||
|
<div id="app">
|
||||||
|
<button v-on:click="malicious">Click me</button>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: { malicious: 'alert(1)' }
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
### Dynamic attribute / event names
|
||||||
|
उपयोगकर्ता द्वारा प्रदान किए गए नाम `v-bind:[attr]` या `v-on:[event]` में हमलावरों को किसी भी विशेषता या इवेंट हैंडलर को बनाने की अनुमति देते हैं, स्थिर विश्लेषण और कई CSP नियमों को बायपास करते हुए।
|
||||||
|
```html
|
||||||
|
<img v-bind:[userAttr]="payload">
|
||||||
|
<!-- userAttr = 'onerror', payload = 'alert(1)' -->
|
||||||
|
```
|
||||||
|
### Dynamic component (`<component :is>`)
|
||||||
|
`:is` में उपयोगकर्ता स्ट्रिंग्स की अनुमति देना मनमाने घटकों या इनलाइन टेम्पलेट्स को माउंट कर सकता है—ब्राउज़र में खतरनाक और SSR में विनाशकारी।
|
||||||
|
```html
|
||||||
|
<component :is="userChoice"></component>
|
||||||
|
<!-- userChoice = '<script>alert(1)</script>' -->
|
||||||
|
```
|
||||||
|
### Untrusted templates in SSR
|
||||||
|
सर्वर-साइड रेंडरिंग के दौरान, टेम्पलेट **आपके सर्वर पर** चलता है; उपयोगकर्ता HTML को इंजेक्ट करना XSS को पूर्ण रिमोट कोड निष्पादन (RCE) में बढ़ा सकता है। `vue-template-compiler` में CVEs जोखिम को साबित करते हैं।
|
||||||
|
```js
|
||||||
|
// DANGER – never do this
|
||||||
|
const app = createSSRApp({ template: userProvidedHtml })
|
||||||
|
```
|
||||||
|
### Filters / render functions that eval
|
||||||
|
पुरानी फ़िल्टर जो रेंडर स्ट्रिंग बनाती हैं या उपयोगकर्ता डेटा पर `eval`/`new Function` को कॉल करती हैं, एक और XSS वेक्टर हैं—इन्हें संगणित गुणों से बदलें।
|
||||||
|
```js
|
||||||
|
Vue.filter('run', code => eval(code)) // DANGER
|
||||||
|
```
|
||||||
|
---
|
||||||
|
|
||||||
|
## Other Common Vulnerabilities in Vue Projects
|
||||||
|
|
||||||
|
### Prototype pollution in plugins
|
||||||
|
कुछ प्लगइन्स (जैसे, **vue-i18n**) में डीप-मर्ज हेल्पर्स ने हमलावरों को `Object.prototype` में लिखने की अनुमति दी है।
|
||||||
|
```js
|
||||||
|
import merge from 'deepmerge'
|
||||||
|
merge({}, JSON.parse('{ "__proto__": { "polluted": true } }'))
|
||||||
|
```
|
||||||
|
### Open redirects with vue-router
|
||||||
|
Unchecked उपयोगकर्ता URLs को `router.push` या `<router-link>` में पास करना `javascript:` URIs या फ़िशिंग डोमेन पर रीडायरेक्ट कर सकता है।
|
||||||
|
```js
|
||||||
|
this.$router.push(this.$route.query.next) // DANGER
|
||||||
|
```
|
||||||
|
### CSRF in Axios / fetch
|
||||||
|
SPAs को अभी भी सर्वर-साइड CSRF टोकन की आवश्यकता होती है; केवल SameSite कुकीज़ स्वचालित रूप से प्रस्तुत किए गए क्रॉस-ओरिजिन POST को रोक नहीं सकती हैं।
|
||||||
|
```js
|
||||||
|
axios.post('/api/transfer', data, {
|
||||||
|
headers: { 'X-CSRF-TOKEN': token }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
### Click-jacking
|
||||||
|
Vue ऐप्स फ्रेम करने योग्य होते हैं जब तक आप दोनों `X-Frame-Options: DENY` और `Content-Security-Policy: frame-ancestors 'none'` नहीं भेजते।
|
||||||
|
```http
|
||||||
|
X-Frame-Options: DENY
|
||||||
|
Content-Security-Policy: frame-ancestors 'none';
|
||||||
|
```
|
||||||
|
### Content-Security-Policy pitfalls
|
||||||
|
पूर्ण Vue निर्माण को `unsafe-eval` की आवश्यकता होती है; आप रनटाइम निर्माण या पूर्व-निर्मित टेम्पलेट्स पर स्विच करें ताकि आप उस खतरनाक स्रोत को हटा सकें।
|
||||||
|
```http
|
||||||
|
Content-Security-Policy: default-src 'self'; script-src 'self';
|
||||||
|
```
|
||||||
|
### Supply-chain attacks (node-ipc – March 2022)
|
||||||
|
**node-ipc** का सबोटाज—जो Vue CLI द्वारा खींचा गया—ने दिखाया कि एक ट्रांजिटिव डिपेंडेंसी कैसे डेवलपमेंट मशीनों पर मनमाना कोड चला सकती है। संस्करणों को पिन करें और अक्सर ऑडिट करें।
|
||||||
|
```shell
|
||||||
|
npm ci --ignore-scripts # safer install
|
||||||
|
```
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hardening Checklist
|
||||||
|
|
||||||
|
1. **हर स्ट्रिंग को `v-html` पर पहुँचने से पहले साफ करें** (DOMPurify)।
|
||||||
|
2. **अनुमत स्कीम, विशेषताएँ, घटक, और घटनाओं की सफेद सूची बनाएं**।
|
||||||
|
3. **`eval` और गतिशील टेम्पलेट्स से पूरी तरह बचें**।
|
||||||
|
4. **साप्ताहिक रूप से निर्भरताओं को पैच करें** और सलाहों की निगरानी करें।
|
||||||
|
5. **मजबूत HTTP हेडर भेजें** (CSP, HSTS, XFO, CSRF)।
|
||||||
|
6. **ऑडिट, लॉकफाइल, और साइन किए गए कमिट्स के साथ अपनी सप्लाई चेन को लॉक करें**।
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [https://www.stackhawk.com/blog/vue-xss-guide-examples-and-prevention/](https://www.stackhawk.com/blog/vue-xss-guide-examples-and-prevention/)
|
||||||
|
- [https://medium.com/@isaacwangethi30/vue-js-security-6e246a7613da](https://medium.com/@isaacwangethi30/vue-js-security-6e246a7613da)
|
||||||
|
- [https://vuejs.org/guide/best-practices/security](https://vuejs.org/guide/best-practices/security)
|
||||||
|
|
||||||
|
{{#include ../../banners/hacktricks-training.md}}
|
403
theme/ai.js
403
theme/ai.js
@ -1,108 +1,259 @@
|
|||||||
/**
|
/**
|
||||||
* HackTricks AI Chat Widget v1.14 – animated typing indicator
|
* HackTricks AI Chat Widget v1.15 – Markdown rendering + sanitised
|
||||||
* ------------------------------------------------------------------------
|
* ------------------------------------------------------------------------
|
||||||
* • Replaces the static “…” placeholder with a three‑dot **bouncing** loader
|
* • Replaces the static “…” placeholder with a three-dot **bouncing** loader
|
||||||
* while waiting for the assistant’s response.
|
* • Renders assistant replies as Markdown while purging any unsafe HTML
|
||||||
|
* (XSS-safe via DOMPurify)
|
||||||
* ------------------------------------------------------------------------
|
* ------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
const LOG = "[HackTricks-AI]";
|
const LOG = "[HackTricks-AI]";
|
||||||
|
|
||||||
/* ---------------- User‑tunable constants ---------------- */
|
/* ---------------- User-tunable constants ---------------- */
|
||||||
const MAX_CONTEXT = 3000; // highlighted‑text char limit
|
const MAX_CONTEXT = 3000; // highlighted-text char limit
|
||||||
const MAX_QUESTION = 500; // question char limit
|
const MAX_QUESTION = 500; // question char limit
|
||||||
const TOOLTIP_TEXT =
|
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 API_BASE = "https://www.hacktricks.ai/api/assistants/threads";
|
||||||
const BRAND_RED = "#b31328"; // HackTricks brand
|
const BRAND_RED = "#b31328"; // HackTricks brand
|
||||||
|
|
||||||
/* ------------------------------ State ------------------------------ */
|
/* ------------------------------ State ------------------------------ */
|
||||||
let threadId = null;
|
let threadId = null;
|
||||||
let isRunning = false;
|
let isRunning = false;
|
||||||
|
|
||||||
const $ = (sel, ctx = document) => ctx.querySelector(sel);
|
const $ = (sel, ctx = document) => ctx.querySelector(sel);
|
||||||
if (document.getElementById("ht-ai-btn")) { console.warn(`${LOG} Widget already injected.`); return; }
|
if (document.getElementById("ht-ai-btn")) {
|
||||||
(document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", init) : init());
|
console.warn(`${LOG} Widget already injected.`);
|
||||||
|
return;
|
||||||
/* ==================================================================== */
|
}
|
||||||
async function init() {
|
(document.readyState === "loading"
|
||||||
console.log(`${LOG} Injecting widget… v1.14`);
|
? document.addEventListener("DOMContentLoaded", init)
|
||||||
await ensureThreadId();
|
: init());
|
||||||
injectStyles();
|
|
||||||
|
/* ==================================================================== */
|
||||||
const btn = createFloatingButton();
|
/* 🔗 1. 3rd-party libs → Markdown & sanitiser */
|
||||||
createTooltip(btn);
|
/* ==================================================================== */
|
||||||
const panel = createSidebar();
|
function loadScript(src) {
|
||||||
const chatLog = $("#ht-ai-chat");
|
return new Promise((resolve, reject) => {
|
||||||
const sendBtn = $("#ht-ai-send");
|
const s = document.createElement("script");
|
||||||
const inputBox = $("#ht-ai-question");
|
s.src = src;
|
||||||
const resetBtn = $("#ht-ai-reset");
|
s.onload = resolve;
|
||||||
const closeBtn = $("#ht-ai-close");
|
s.onerror = () => reject(new Error(`Failed to load ${src}`));
|
||||||
|
document.head.appendChild(s);
|
||||||
/* ------------------- Selection snapshot ------------------- */
|
});
|
||||||
let savedSelection = "";
|
}
|
||||||
btn.addEventListener("pointerdown", () => { savedSelection = window.getSelection().toString().trim(); });
|
|
||||||
|
async function ensureDeps() {
|
||||||
/* ------------------- Helpers ------------------------------ */
|
const deps = [];
|
||||||
function addMsg(text, cls) {
|
if (typeof marked === "undefined") {
|
||||||
const b = document.createElement("div");
|
deps.push(loadScript("https://cdn.jsdelivr.net/npm/marked/marked.min.js"));
|
||||||
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(); } });
|
|
||||||
}
|
}
|
||||||
|
if (typeof DOMPurify === "undefined") {
|
||||||
/* ==================================================================== */
|
deps.push(
|
||||||
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; }}
|
loadScript(
|
||||||
|
"https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.5/purify.min.js"
|
||||||
/* ==================================================================== */
|
)
|
||||||
function injectStyles(){ const css=`
|
);
|
||||||
|
}
|
||||||
|
if (deps.length) await Promise.all(deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
console.error(`${LOG} Could not load dependencies`, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${LOG} Injecting widget… v1.15`);
|
||||||
|
|
||||||
|
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}`;
|
||||||
|
|
||||||
|
// ✨ assistant replies rendered as Markdown + sanitised
|
||||||
|
if (cls === "ht-ai") {
|
||||||
|
b.innerHTML = mdToSafeHTML(text);
|
||||||
|
} else {
|
||||||
|
// user / context bubbles stay plain-text
|
||||||
|
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{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}
|
#ht-ai-btn:hover{opacity:.85}
|
||||||
@media(max-width:768px){#ht-ai-btn{display:none}}
|
@media(max-width:768px){#ht-ai-btn{display:none}}
|
||||||
@ -132,10 +283,50 @@
|
|||||||
@keyframes ht-bounce{0%,80%,100%{transform:scale(0);}40%{transform:scale(1);} }
|
@keyframes ht-bounce{0%,80%,100%{transform:scale(0);}40%{transform:scale(1);} }
|
||||||
::selection{background:#ffeb3b;color:#000}
|
::selection{background:#ffeb3b;color:#000}
|
||||||
::-moz-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);}
|
const s = document.createElement("style");
|
||||||
|
s.id = "ht-ai-style";
|
||||||
function createFloatingButton(){ const d=document.createElement("div"); d.id="ht-ai-btn"; d.textContent="🤖"; document.body.appendChild(d); return d; }
|
s.textContent = css;
|
||||||
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")); }
|
document.head.appendChild(s);
|
||||||
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; }
|
}
|
||||||
})();
|
|
||||||
|
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>HackTricks AI 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;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user