mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2025-12-12 15:50:19 -08:00
Translated ['src/pentesting-cloud/aws-security/aws-unauthenticated-enum-
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
## S3 Δημόσια Μπακέτα
|
||||
## S3 Δημόσιοι Κάδοι
|
||||
|
||||
Ένα μπακέτο θεωρείται **“δημόσιο”** αν **οποιοσδήποτε χρήστης μπορεί να καταγράψει το περιεχόμενο** του μπακέτου, και **“ιδιωτικό”** αν το περιεχόμενο του μπακέτου μπορεί **να καταγραφεί ή να γραφτεί μόνο από συγκεκριμένους χρήστες**.
|
||||
Ένας κάδος θεωρείται **“δημόσιος”** αν **οποιοσδήποτε χρήστης μπορεί να καταγράψει το περιεχόμενο** του κάδου, και **“ιδιωτικός”** αν το περιεχόμενο του κάδου μπορεί **να καταγραφεί ή να γραφτεί μόνο από συγκεκριμένους χρήστες**.
|
||||
|
||||
Οι εταιρείες μπορεί να έχουν **λανθασμένα ρυθμισμένα δικαιώματα μπακέτων** δίνοντας πρόσβαση είτε σε όλα είτε σε όλους τους πιστοποιημένους χρήστες στο AWS σε οποιονδήποτε λογαριασμό (δηλαδή σε οποιονδήποτε). Σημειώστε ότι ακόμη και με τέτοιες λανθασμένες ρυθμίσεις, ορισμένες ενέργειες μπορεί να μην είναι δυνατές καθώς τα μπακέτα μπορεί να έχουν τις δικές τους λίστες ελέγχου πρόσβασης (ACLs).
|
||||
Οι εταιρείες μπορεί να έχουν **κακώς ρυθμισμένα δικαιώματα κάδων** που δίνουν πρόσβαση είτε σε όλα είτε σε όλους τους αυθεντικοποιημένους χρήστες στο AWS σε οποιονδήποτε λογαριασμό (δηλαδή σε οποιονδήποτε). Σημειώστε ότι ακόμη και με τέτοιες κακές ρυθμίσεις, ορισμένες ενέργειες μπορεί να μην είναι δυνατές καθώς οι κάδοι μπορεί να έχουν τις δικές τους λίστες ελέγχου πρόσβασης (ACLs).
|
||||
|
||||
**Μάθετε για τις λανθασμένες ρυθμίσεις AWS-S3 εδώ:** [**http://flaws.cloud**](http://flaws.cloud/) **και** [**http://flaws2.cloud/**](http://flaws2.cloud)
|
||||
**Μάθετε για τις κακές ρυθμίσεις του AWS-S3 εδώ:** [**http://flaws.cloud**](http://flaws.cloud/) **και** [**http://flaws2.cloud/**](http://flaws2.cloud)
|
||||
|
||||
### Εύρεση Μπακετών AWS
|
||||
### Εύρεση Κάδων AWS
|
||||
|
||||
Διαφορετικές μέθοδοι για να βρείτε πότε μια ιστοσελίδα χρησιμοποιεί το AWS για την αποθήκευση κάποιων πόρων:
|
||||
|
||||
#### Καταμέτρηση & OSINT:
|
||||
#### Αρίθμηση & OSINT:
|
||||
|
||||
- Χρησιμοποιώντας το **wappalyzer** πρόσθετο του προγράμματος περιήγησης
|
||||
- Χρησιμοποιώντας burp (**spidering** του ιστού) ή πλοηγώντας χειροκίνητα μέσω της σελίδας, όλοι οι **πόροι** **που φορτώνονται** θα αποθηκευτούν στην Ιστορία.
|
||||
- Χρησιμοποιώντας το burp (**spidering** του ιστού) ή πλοηγώντας χειροκίνητα μέσω της σελίδας, όλοι οι **πόροι** **που φορτώνονται** θα αποθηκευτούν στην Ιστορία.
|
||||
- **Ελέγξτε για πόρους** σε τομείς όπως:
|
||||
|
||||
```
|
||||
@@ -26,18 +26,19 @@ http://[bucket_name].s3.amazonaws.com/
|
||||
```
|
||||
|
||||
- Ελέγξτε για **CNAMES** καθώς το `resources.domain.com` μπορεί να έχει το CNAME `bucket.s3.amazonaws.com`
|
||||
- Ελέγξτε [https://buckets.grayhatwarfare.com](https://buckets.grayhatwarfare.com/), μια ιστοσελίδα με ήδη **ανακαλυμμένα ανοιχτά μπακέτα**.
|
||||
- Το **όνομα μπακέτου** και το **όνομα τομέα μπακέτου** πρέπει να είναι **τα ίδια.**
|
||||
- **[s3dns](https://github.com/olizimmermann/s3dns)** – Ένας ελαφρύς DNS server που αναγνωρίζει παθητικά τους κάδους αποθήκευσης στο cloud (S3, GCP, Azure) αναλύοντας την κίνηση DNS. Ανιχνεύει CNAMEs, ακολουθεί αλυσίδες επίλυσης και ταιριάζει πρότυπα κάδων, προσφέροντας μια ήσυχη εναλλακτική λύση στην ανακάλυψη μέσω brute-force ή API. Ιδανικό για recon και ροές εργασίας OSINT.
|
||||
- Ελέγξτε [https://buckets.grayhatwarfare.com](https://buckets.grayhatwarfare.com/), μια ιστοσελίδα με ήδη **ανακαλυμμένους ανοιχτούς κάδους**.
|
||||
- Το **όνομα του κάδου** και το **όνομα τομέα του κάδου** πρέπει να είναι **τα ίδια.**
|
||||
- **flaws.cloud** είναι σε **IP** 52.92.181.107 και αν πάτε εκεί σας ανακατευθύνει στο [https://aws.amazon.com/s3/](https://aws.amazon.com/s3/). Επίσης, `dig -x 52.92.181.107` δίνει `s3-website-us-west-2.amazonaws.com`.
|
||||
- Για να ελέγξετε αν είναι μπακέτο μπορείτε επίσης να **επισκεφθείτε** [https://flaws.cloud.s3.amazonaws.com/](https://flaws.cloud.s3.amazonaws.com/).
|
||||
- Για να ελέγξετε αν είναι κάδος μπορείτε επίσης να **επισκεφθείτε** [https://flaws.cloud.s3.amazonaws.com/](https://flaws.cloud.s3.amazonaws.com/).
|
||||
|
||||
#### Brute-Force
|
||||
|
||||
Μπορείτε να βρείτε μπακέτα **δοκιμάζοντας ονόματα** που σχετίζονται με την εταιρεία που κάνετε pentesting:
|
||||
Μπορείτε να βρείτε κάδους **καταναγκαστικά** με ονόματα που σχετίζονται με την εταιρεία που κάνετε pentesting:
|
||||
|
||||
- [https://github.com/sa7mon/S3Scanner](https://github.com/sa7mon/S3Scanner)
|
||||
- [https://github.com/clario-tech/s3-inspector](https://github.com/clario-tech/s3-inspector)
|
||||
- [https://github.com/jordanpotti/AWSBucketDump](https://github.com/jordanpotti/AWSBucketDump) (Περιέχει μια λίστα με πιθανά ονόματα μπακέτων)
|
||||
- [https://github.com/jordanpotti/AWSBucketDump](https://github.com/jordanpotti/AWSBucketDump) (Περιέχει μια λίστα με πιθανά ονόματα κάδων)
|
||||
- [https://github.com/fellchase/flumberboozle/tree/master/flumberbuckets](https://github.com/fellchase/flumberboozle/tree/master/flumberbuckets)
|
||||
- [https://github.com/smaranchand/bucky](https://github.com/smaranchand/bucky)
|
||||
- [https://github.com/tomdev/teh_s3_bucketeers](https://github.com/tomdev/teh_s3_bucketeers)
|
||||
@@ -45,47 +46,47 @@ http://[bucket_name].s3.amazonaws.com/
|
||||
- [https://github.com/Eilonh/s3crets_scanner](https://github.com/Eilonh/s3crets_scanner)
|
||||
- [https://github.com/belane/CloudHunter](https://github.com/belane/CloudHunter)
|
||||
|
||||
<pre class="language-bash"><code class="lang-bash"># Generate a wordlist to create permutations
|
||||
<pre class="language-bash"><code class="lang-bash"># Δημιουργία μιας λίστας λέξεων για τη δημιουργία παραλλαγών
|
||||
curl -s https://raw.githubusercontent.com/cujanovic/goaltdns/master/words.txt > /tmp/words-s3.txt.temp
|
||||
curl -s https://raw.githubusercontent.com/jordanpotti/AWSBucketDump/master/BucketNames.txt >>/tmp/words-s3.txt.temp
|
||||
cat /tmp/words-s3.txt.temp | sort -u > /tmp/words-s3.txt
|
||||
|
||||
# Generate a wordlist based on the domains and subdomains to test
|
||||
## Write those domains and subdomains in subdomains.txt
|
||||
# Δημιουργία μιας λίστας λέξεων βασισμένης στους τομείς και υποτομείς για δοκιμή
|
||||
## Γράψτε αυτούς τους τομείς και υποτομείς στο subdomains.txt
|
||||
cat subdomains.txt > /tmp/words-hosts-s3.txt
|
||||
cat subdomains.txt | tr "." "-" >> /tmp/words-hosts-s3.txt
|
||||
cat subdomains.txt | tr "." "\n" | sort -u >> /tmp/words-hosts-s3.txt
|
||||
|
||||
# Create permutations based in a list with the domains and subdomains to attack
|
||||
# Δημιουργία παραλλαγών βασισμένων σε μια λίστα με τους τομείς και υποτομείς για επίθεση
|
||||
goaltdns -l /tmp/words-hosts-s3.txt -w /tmp/words-s3.txt -o /tmp/final-words-s3.txt.temp
|
||||
## The previous tool is specialized increating permutations for subdomains, lets filter that list
|
||||
<strong>### Remove lines ending with "."
|
||||
## Το προηγούμενο εργαλείο είναι εξειδικευμένο στη δημιουργία παραλλαγών για υποτομείς, ας φιλτράρουμε αυτή τη λίστα
|
||||
<strong>### Αφαιρέστε τις γραμμές που τελειώνουν με "."
|
||||
</strong>cat /tmp/final-words-s3.txt.temp | grep -Ev "\.$" > /tmp/final-words-s3.txt.temp2
|
||||
### Create list without TLD
|
||||
### Δημιουργία λίστας χωρίς TLD
|
||||
cat /tmp/final-words-s3.txt.temp2 | sed -E 's/\.[a-zA-Z0-9]+$//' > /tmp/final-words-s3.txt.temp3
|
||||
### Create list without dots
|
||||
### Δημιουργία λίστας χωρίς τελείες
|
||||
cat /tmp/final-words-s3.txt.temp3 | tr -d "." > /tmp/final-words-s3.txt.temp4http://phantom.s3.amazonaws.com/
|
||||
### Create list without hyphens
|
||||
### Δημιουργία λίστας χωρίς παύλες
|
||||
cat /tmp/final-words-s3.txt.temp3 | tr "." "-" > /tmp/final-words-s3.txt.temp5
|
||||
|
||||
## Generate the final wordlist
|
||||
## Δημιουργία της τελικής λίστας λέξεων
|
||||
cat /tmp/final-words-s3.txt.temp2 /tmp/final-words-s3.txt.temp3 /tmp/final-words-s3.txt.temp4 /tmp/final-words-s3.txt.temp5 | grep -v -- "-\." | awk '{print tolower($0)}' | sort -u > /tmp/final-words-s3.txt
|
||||
|
||||
## Call s3scanner
|
||||
## Κλήση s3scanner
|
||||
s3scanner --threads 100 scan --buckets-file /tmp/final-words-s3.txt | grep bucket_exists
|
||||
</code></pre>
|
||||
|
||||
#### Loot S3 Buckets
|
||||
#### Ληστεία S3 Κάδων
|
||||
|
||||
Δεδομένων των ανοιχτών μπακετών S3, [**BucketLoot**](https://github.com/redhuntlabs/BucketLoot) μπορεί αυτόματα να **αναζητήσει ενδιαφέροντες πληροφορίες**.
|
||||
Δεδομένων των ανοιχτών κάδων S3, [**BucketLoot**](https://github.com/redhuntlabs/BucketLoot) μπορεί αυτόματα να **αναζητήσει ενδιαφέροντα πληροφορίες**.
|
||||
|
||||
### Βρείτε την Περιοχή
|
||||
|
||||
Μπορείτε να βρείτε όλες τις υποστηριζόμενες περιοχές από το AWS στο [**https://docs.aws.amazon.com/general/latest/gr/s3.html**](https://docs.aws.amazon.com/general/latest/gr/s3.html)
|
||||
|
||||
#### Με DNS
|
||||
#### Μέσω DNS
|
||||
|
||||
Μπορείτε να αποκτήσετε την περιοχή ενός μπακέτου με ένα **`dig`** και **`nslookup`** κάνοντας ένα **DNS αίτημα της ανακαλυφθείσας IP**:
|
||||
Μπορείτε να αποκτήσετε την περιοχή ενός κάδου με ένα **`dig`** και **`nslookup`** κάνοντας ένα **DNS αίτημα της ανακαλυφθείσας IP**:
|
||||
```bash
|
||||
dig flaws.cloud
|
||||
;; ANSWER SECTION:
|
||||
@@ -99,21 +100,23 @@ Non-authoritative answer:
|
||||
Μπορείτε να αποκτήσετε πρόσβαση στον στατικό ιστότοπο πηγαίνοντας στο: `flaws.cloud.s3-website-us-west-2.amazonaws.com`\
|
||||
ή μπορείτε να αποκτήσετε πρόσβαση στον κάδο επισκεπτόμενοι: `flaws.cloud.s3-us-west-2.amazonaws.com`
|
||||
|
||||
|
||||
|
||||
#### Δοκιμάζοντας
|
||||
|
||||
Αν προσπαθήσετε να αποκτήσετε πρόσβαση σε έναν κάδο, αλλά στο **όνομα τομέα καθορίσετε άλλη περιοχή** (για παράδειγμα, ο κάδος είναι στο `bucket.s3.amazonaws.com` αλλά προσπαθείτε να αποκτήσετε πρόσβαση στο `bucket.s3-website-us-west-2.amazonaws.com`, τότε θα σας **υποδειχθεί η σωστή τοποθεσία**:
|
||||
Αν προσπαθήσετε να αποκτήσετε πρόσβαση σε έναν κάδο, αλλά στο **όνομα τομέα καθορίζετε άλλη περιοχή** (για παράδειγμα, ο κάδος είναι στο `bucket.s3.amazonaws.com` αλλά προσπαθείτε να αποκτήσετε πρόσβαση στο `bucket.s3-website-us-west-2.amazonaws.com`, τότε θα σας **υποδειχθεί η σωστή τοποθεσία**:
|
||||
|
||||
.png>)
|
||||
|
||||
### Αριθμητική του κάδου
|
||||
### Καταμέτρηση του κάδου
|
||||
|
||||
Για να δοκιμάσετε την ανοιχτότητα του κάδου, ένας χρήστης μπορεί απλά να εισάγει το URL στον περιηγητή του. Ένας ιδιωτικός κάδος θα απαντήσει με "Access Denied". Ένας δημόσιος κάδος θα απαριθμήσει τα πρώτα 1.000 αντικείμενα που έχουν αποθηκευτεί.
|
||||
Για να δοκιμάσετε την ανοιχτότητα του κάδου, ένας χρήστης μπορεί απλά να εισάγει το URL στον περιηγητή του. Ένας ιδιωτικός κάδος θα απαντήσει με "Access Denied". Ένας δημόσιος κάδος θα καταγράψει τα πρώτα 1.000 αντικείμενα που έχουν αποθηκευτεί.
|
||||
|
||||
Ανοιχτό σε όλους:
|
||||
|
||||
.png>)
|
||||
|
||||
Ιδιωτικό:
|
||||
Ιδιωτικός:
|
||||
|
||||
.png>)
|
||||
|
||||
@@ -133,7 +136,7 @@ https://{user_provided}.s3.amazonaws.com
|
||||
```
|
||||
### Λάβετε το ID λογαριασμού από δημόσιο Bucket
|
||||
|
||||
Είναι δυνατόν να προσδιορίσετε έναν λογαριασμό AWS εκμεταλλευόμενοι το νέο **`S3:ResourceAccount`** **Κλειδί Συνθήκης Πολιτικής**. Αυτή η συνθήκη **περιορίζει την πρόσβαση με βάση το S3 bucket** στο οποίο βρίσκεται ένας λογαριασμός (άλλες πολιτικές που βασίζονται σε λογαριασμούς περιορίζουν με βάση τον λογαριασμό στον οποίο βρίσκεται ο αιτών κύριος).\
|
||||
Είναι δυνατόν να προσδιορίσετε έναν λογαριασμό AWS εκμεταλλευόμενοι το νέο **`S3:ResourceAccount`** **Policy Condition Key**. Αυτή η συνθήκη **περιορίζει την πρόσβαση με βάση το S3 bucket** στο οποίο βρίσκεται ένας λογαριασμός (άλλες πολιτικές που βασίζονται σε λογαριασμούς περιορίζουν με βάση τον λογαριασμό στον οποίο βρίσκεται ο αιτών κύριος).\
|
||||
Και επειδή η πολιτική μπορεί να περιέχει **wildcards**, είναι δυνατόν να βρείτε τον αριθμό του λογαριασμού **μόνο έναν αριθμό τη φορά**.
|
||||
|
||||
Αυτό το εργαλείο αυτοματοποιεί τη διαδικασία:
|
||||
@@ -146,11 +149,11 @@ s3-account-search arn:aws:iam::123456789012:role/s3_read s3://my-bucket
|
||||
# With an object
|
||||
s3-account-search arn:aws:iam::123456789012:role/s3_read s3://my-bucket/path/to/object.ext
|
||||
```
|
||||
Αυτή η τεχνική λειτουργεί επίσης με URLs API Gateway, URLs Lambda, σύνολα δεδομένων Data Exchange και ακόμη και για να αποκτήσετε την τιμή ετικετών (αν γνωρίζετε το κλειδί της ετικέτας). Μπορείτε να βρείτε περισσότερες πληροφορίες στην [**αρχική έρευνα**](https://blog.plerion.com/conditional-love-for-aws-metadata-enumeration/) και το εργαλείο [**conditional-love**](https://github.com/plerionhq/conditional-love/) για να αυτοματοποιήσετε αυτή την εκμετάλλευση.
|
||||
Αυτή η τεχνική λειτουργεί επίσης με URLs API Gateway, URLs Lambda, σύνολα δεδομένων Data Exchange και ακόμη και για να αποκτήσετε την τιμή ετικετών (αν γνωρίζετε το κλειδί της ετικέτας). Μπορείτε να βρείτε περισσότερες πληροφορίες στην [**πρωτότυπη έρευνα**](https://blog.plerion.com/conditional-love-for-aws-metadata-enumeration/) και το εργαλείο [**conditional-love**](https://github.com/plerionhq/conditional-love/) για να αυτοματοποιήσετε αυτή την εκμετάλλευση.
|
||||
|
||||
### Επιβεβαίωση ότι ένα bucket ανήκει σε έναν λογαριασμό AWS
|
||||
|
||||
Όπως εξηγείται σε [**αυτή την ανάρτηση blog**](https://blog.plerion.com/things-you-wish-you-didnt-need-to-know-about-s3/)**, αν έχετε άδειες για να καταγράψετε ένα bucket** είναι δυνατόν να επιβεβαιώσετε ένα accountID στο οποίο ανήκει το bucket στέλνοντας ένα αίτημα όπως:
|
||||
Όπως εξηγείται σε [**αυτή την ανάρτηση στο blog**](https://blog.plerion.com/things-you-wish-you-didnt-need-to-know-about-s3/)**, αν έχετε άδειες για να καταγράψετε ένα bucket** είναι δυνατόν να επιβεβαιώσετε ένα accountID στο οποίο ανήκει το bucket στέλνοντας ένα αίτημα όπως:
|
||||
```bash
|
||||
curl -X GET "[bucketname].amazonaws.com/" \
|
||||
-H "x-amz-expected-bucket-owner: [correct-account-id]"
|
||||
@@ -160,7 +163,7 @@ curl -X GET "[bucketname].amazonaws.com/" \
|
||||
```
|
||||
Αν το σφάλμα είναι “Access Denied” σημαίνει ότι το ID του λογαριασμού ήταν λάθος.
|
||||
|
||||
### Χρησιμοποιούμενα Emails ως αρίθμηση λογαριασμού root
|
||||
### Χρησιμοποιούμενα Emails για την αρίθμηση λογαριασμού root
|
||||
|
||||
Όπως εξηγείται σε [**αυτή την ανάρτηση στο blog**](https://blog.plerion.com/things-you-wish-you-didnt-need-to-know-about-s3/), είναι δυνατόν να ελέγξετε αν μια διεύθυνση email σχετίζεται με οποιονδήποτε λογαριασμό AWS προσπαθώντας να **παραχωρήσετε άδειες σε μια διεύθυνση email** σε ένα S3 bucket μέσω ACLs. Αν αυτό δεν προκαλέσει σφάλμα, σημαίνει ότι το email είναι χρήστης root κάποιου λογαριασμού AWS:
|
||||
```python
|
||||
|
||||
332
theme/ai.js
Normal file
332
theme/ai.js
Normal file
@@ -0,0 +1,332 @@
|
||||
/**
|
||||
* 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)
|
||||
* ------------------------------------------------------------------------
|
||||
*/
|
||||
(function () {
|
||||
const LOG = "[HackTricks-AI]";
|
||||
|
||||
/* ---------------- User-tunable constants ---------------- */
|
||||
const MAX_CONTEXT = 3000; // highlighted-text 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());
|
||||
|
||||
/* ==================================================================== */
|
||||
/* 🔗 1. 3rd-party libs → Markdown & sanitiser */
|
||||
/* ==================================================================== */
|
||||
function loadScript(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const s = document.createElement("script");
|
||||
s.src = src;
|
||||
s.onload = resolve;
|
||||
s.onerror = () => reject(new Error(`Failed to load ${src}`));
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureDeps() {
|
||||
const deps = [];
|
||||
if (typeof marked === "undefined") {
|
||||
deps.push(loadScript("https://cdn.jsdelivr.net/npm/marked/marked.min.js"));
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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: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>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;
|
||||
}
|
||||
})();
|
||||
1030
theme/ht_searcher.js
1030
theme/ht_searcher.js
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user