diff --git a/.env.example.ini b/.env.example.ini index 356f7d4..2a85ab7 100644 --- a/.env.example.ini +++ b/.env.example.ini @@ -1,2 +1 @@ -; Save request details in a SQLite database at this location -DB_POT="" \ No newline at end of file +LOG_ENABLED = true \ No newline at end of file diff --git a/.env.ini b/.env.ini new file mode 100644 index 0000000..ac1b58f --- /dev/null +++ b/.env.ini @@ -0,0 +1 @@ +LOG_ENABLED = false \ No newline at end of file diff --git a/.gitignore b/.gitignore index 16bb1cd..3fccee3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,51 +1,4 @@ -# Bootstrapping # -################# -/node_modules -/public/hot -/public/storage -/storage/*.key -/vendor -.env -.env.ini -.env.backup -.phpunit.result.cache -Homestead.json -Homestead.yaml -npm-debug.log -yarn-error.log -public/robots.txt +logs/* +!logs/.gitkeep - -# OS generated files # -###################### -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -Icon? -ehthumbs.db -Thumbs.db -.directory - -# Tool specific files # -####################### -# vim -*~ -*.swp -*.swo -# sublime text & textmate -*.sublime-* -*.stTheme.cache -*.tmlanguage.cache -*.tmPreferences.cache -# Eclipse -.settings/* -# JetBrains, aka PHPStorm, IntelliJ IDEA -.idea/* -# NetBeans -nbproject/* -# Visual Studio Code -.vscode -# Sass preprocessor -.sass-cache/ +vendor \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 61d2e41..3b5234f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "database/9f50ea1a5be726e610dc2fe134926869"] - path = database/9f50ea1a5be726e610dc2fe134926869 - url = https://gist.github.com/9f50ea1a5be726e610dc2fe134926869.git +[submodule "vegvisir"] + path = vegvisir + url = https://codeberg.org/vegvisir/vegvisir diff --git a/README.md b/README.md index 16deeb9..7193c74 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,29 @@ +# Work in progress +I'm reviving this project. [The last commit was pushed over 2½ years ago](https://codeberg.org/vlw/honeypot/commit/2e4cc7e31ccc4190abfb327ecee0bdb553c3f565) at the time of writing! It was created (and not finished either) in the second generation of [my web framework](https://vegvisir.vlw.se). A lot has changes since then and I think it would be fun to revive this project. + # 🍯 Honeypot -Can the IP, HTTP Headers and more from anyone trying to log in to this site. Everything entered into this page will be saved to an SQLite database. +Yoink mouse clicks, mouse moves, keyboard pressed from the client, and the whole `$_SERVER` superglobal from the server on this website that is intentionally slow, and old-school looking to mimic the `D-Link DIR-880L` WiFi router's web interface. This is a typical WiFi router that you might find in a standard family home so maybe we can assume since the site isn't proxied, that someone hasn't changed the default credentials either? ;) + +**Logs are saved as timestamped JSON-files in the `logs/` directory grouped under a subdirectory for each client using a [best-effort] client fingerprint.** + +Logging only starts when the sneaky starts. Interact with the fake login screen input elements, and we're off.  -> **Note** This project is not related to D-Link in any form or fashion. I just chose their mydlink portal as it features both IP camera and WiFi router controls from the same interface, which I intend to implement fun dummies of in the future. +By the way, this project has nothing to do with D-Link specifically. The "mydlink" software apparently features both "WiFi-router" and IP-camera configuration from the same web interface - which adds additional ## Installation - -1. **Install Pragma** - - This website is built on the [Pragma web framwork](https://github.com/VictorWesterlund/pragma), and is meant as a showcase of some of it's features. - [**Install the Pragma framwork**](https://github.com/VictorWesterlund/pragma) - -2. **Clone this repo** - - Clone this repo (with submodules) anywhere Pragma can read its contents. +1. **Clone this repo** ``` - $ git clone http://github.com/VictorWesterlund/honeypot --recurse-submodules + $ git clone https://codeberg.org/vlw/honeypot ``` - -3. **Set env var** - Make a copy of the `.env.example.ini` file +2. **Install the [Vegvisir](https://vegvisir.vlw.se) framework** + + [**Run the Vegvisir install script**](https://codeberg.org/vegvisir/install#get-started) - ``` - $ cp -p .env.example.ini .env.ini - ``` +3. **Set write permission** - and set the `DB_POT` variable to an absolute path to the SQLite database to create - - ```ini - DB_POT="/home/me/pot.sql" - ```` - -## Data saved - -The database will dump the contents of `$_POST` and `$_SERVER` into a JSON string along with the timestamp since Unix epoch the request was received. + Make sure that the user your PHP configuration runs as has write access to the `logs/` directory in this repository. \ No newline at end of file diff --git a/assets/css/pages/dashboard.css b/assets/css/pages/dashboard.css new file mode 100644 index 0000000..c08c9c4 --- /dev/null +++ b/assets/css/pages/dashboard.css @@ -0,0 +1,5 @@ +vv-shell { + display: grid; + justify-items: center; + align-items: center; +} \ No newline at end of file diff --git a/assets/css/pages/document.css b/assets/css/pages/document.css deleted file mode 100644 index 293c2de..0000000 --- a/assets/css/pages/document.css +++ /dev/null @@ -1,104 +0,0 @@ -:root { - --padding: 20px; - --color-accent: #00b0d0; -} - -* { - font-family: "Verdana", "Arial", sans-serif; - font-size: 12px; -} - -body { - margin: 0; - background: url("/assets/media/Inner-page_cut_02.png") repeat-x right top; -} - -a { - color: inherit; - text-decoration: none; -} - -/* ---- */ - -header, -section { - width: 100%; - display: grid; - align-items: center; - justify-items: center; -} - -header { - height: 100px; -} - -.container { - width: 100%; - max-width: 1000px; -} - -/* ---- */ - -header .container { - display: flex; - justify-content: space-between; -} - -header nav { - margin-left: auto; - display: flex; - align-items: flex-end; -} - -header nav p { - position: relative; - margin: 0; - padding: 5px 10px; - border-radius: 10px; - color: var(--color-accent); -} - -/* --- */ - -#title h1 { - color: white; - font-size: 17px; - margin-left: var(--padding); -} - -.content { - background-color: white; - box-sizing: border-box; - padding: var(--padding); - border-radius: 6px; - border: solid 1px #eee; - min-height: 450px; - box-shadow: 0 0 10px 5px #00000017; - border: solid 1px #e6e6e6; -} - -.content * { - margin: 0; -} - -@media (hover: hover) { - header nav p:hover { - background-color: var(--color-accent); - color: white; - } - - header nav p:hover::after { - --size: 7px; - content: ""; - position: absolute; - top: 100%; - left: 50%; - transform: translateX(-50%); - width: 0; - height: 0; - border-left: var(--size) solid transparent; - border-right: var(--size) solid transparent; - - border-top: var(--size) solid var(--color-accent); - } -} \ No newline at end of file diff --git a/assets/css/pages/index.css b/assets/css/pages/index.css index 46bc5a8..c08c9c4 100644 --- a/assets/css/pages/index.css +++ b/assets/css/pages/index.css @@ -1,48 +1,5 @@ -.content#login { - display: grid; - grid-template-columns: 1fr 300px; -} - -.content#login aside { - background-color: #f7f7f7; - box-sizing: border-box; - padding: var(--padding); - display: flex; - flex-direction: column; - gap: var(--padding); - border-radius: 6px; -} - -.content#login aside h2 { - color: var(--color-accent); - font-size: 17px; -} - -.content#login form { - max-width: 400px; - display: flex; - flex-direction: column; - gap: var(--padding); -} - -.content#login .error { - background-color: #ff000010; - color: red; - padding: 10px; -} - -.content#login input[type="submit"] { - width: 100px; - padding: 7px; - background: linear-gradient(0deg, rgba(0,134,167,1) 0%, rgba(0,176,208,1) 100%); - border-radius: 3px; - border: none; - color: white; - cursor: pointer; -} - -@media (hover: hover) { - .content#login input[type="submit"]:hover { - background: rgba(0,134,167,1); - } +vv-shell { + display: grid; + justify-items: center; + align-items: center; } \ No newline at end of file diff --git a/assets/css/pages/login.css b/assets/css/pages/login.css new file mode 100644 index 0000000..95782a7 --- /dev/null +++ b/assets/css/pages/login.css @@ -0,0 +1,26 @@ +vv-shell { + display: grid; + align-items: baseline; + grid-template-columns: 1fr 300px; +} + +form { + gap: 10px; + display: flex; + flex-direction: column; + + button { + margin-top: 20px; + } +} + +aside { + height: 100%; + padding: 20px; + border-radius: 6px; + background-color: var(--color-grey-light); + + > * { + margin-bottom: 10px; + } +} \ No newline at end of file diff --git a/assets/css/pages/partials/footer.css b/assets/css/pages/partials/footer.css deleted file mode 100644 index c499231..0000000 --- a/assets/css/pages/partials/footer.css +++ /dev/null @@ -1,25 +0,0 @@ -footer { - margin-top: var(--padding); -} - -footer #footer_list { - --color: #888; - display: grid; - grid-template-columns: repeat(4, 1fr); - color: var(--color); -} - -footer #footer_list > div { - display: flex; - flex-direction: column; - padding-left: var(--padding); - color: var(--color); -} - -footer #footer_list > div p { - font-weight: bold; -} - -footer #footer_list > div:not(:first-child) { - border-left: solid 1px var(--color); -} \ No newline at end of file diff --git a/assets/css/shell.css b/assets/css/shell.css new file mode 100644 index 0000000..0703b47 --- /dev/null +++ b/assets/css/shell.css @@ -0,0 +1,151 @@ +:root { + --color-grey: #888888; + --color-dlink: #00B0D0; + --color-grey-dark: #424242; + --color-grey-light: #F7F7F7; +} + +* { + color: inherit; + margin: 0; + box-sizing: border-box; + font-family: Arial, Helvetica, sans-serif; +} + +html { + display: grid; + justify-items: center; +} + +body { + width: 1000px; + display: grid; + justify-items: center; + background-image: url("/assets/media/Inner-page_cut_02.png"); + background-size: 1200px; + background-repeat: no-repeat; + grid-template-rows: 70px 1fr 200px; + background-position: 50% -30px; + grid-template-columns: 1fr; +} + +/* Components */ + +h1, h2, h3 { + color: var(--color-dlink); +} + +p, label, a { + font-size: 13px; +} + +button { + color: white; + height: 30px; + cursor: pointer; + border: solid 1px var(--color-grey-light); + min-width: 100px; + align-self: baseline; + background: linear-gradient(180deg,rgba(0, 176, 208, 1) 0%, rgba(0, 134, 167, 1) 100%); + justify-self: baseline; + border-radius: 4px; + + &:hover { + border-color: var(--color-dlink); + } + + &:active { + background: linear-gradient(180deg,rgba(0, 176, 208, 1) 0%, rgba(0, 134, 167, 1) 0%); + } +} + +dialog { + margin: auto; +} + +/* Sections */ + +vv-shell { + width: calc(100% - 30px); + margin: 40px 0; + padding: 20px; + position: relative; + min-height: 400px; + box-shadow: 0 0 9px 3px #00000026; + border-radius: 9px; + background-color: white; + + &[vv-loading="true"] ::not(dialog) { + pointer-events: none; + } + + &[vv-loading="true"]::after { + --size: 150px; + + top: 50%; + left: 50%; + color: var(--color-dlink); + width: var(--size); + height: var(--size); + padding: 15px; + content: ""; + position: absolute; + transform: translate(-50%, -50%); + font-weight: bolder; + background-size: contain; + background-image: url("/assets/media/spinner.gif"); + } +} + +header { + width: 100%; + display: flex; + align-items: end; + justify-content: space-between; + + img { + height: 60px; + } + + nav ul { + gap: 20px; + display: flex; + list-style: none; + + a { + color: var(--color-dlink); + font-weight: bolder; + text-decoration: none; + } + } +} + +footer { + width: 100%; + display: grid; + margin-top: 100px; + color: var(--color-grey); + grid-template-columns: repeat(4, 1fr); + + section { + padding: 20px; + + &:not(:first-child) { + border-left: solid 1px var(--color-grey); + } + + p { + font-weight: bolder; + margin-bottom: 10px; + } + + ul { + padding: unset; + list-style: none; + + & a { + text-decoration: none; + } + } + } +} \ No newline at end of file diff --git a/assets/js/modules/Logger.js b/assets/js/modules/Logger.js new file mode 100644 index 0000000..97ad174 --- /dev/null +++ b/assets/js/modules/Logger.js @@ -0,0 +1,99 @@ +const MOUSE_MOVE_TIMEOUT_MS = 100; + +globalThis.Logger = class Logger { + #abort; + #mouseMoveTimeout; + + static get url() { + const url = new URL(window.location); + url.pathname = "/log"; + + return url; + } + + /** + * Return a best-effort unique string that identifies this client + * @returns {String} + */ + static async #fingerprint() { + // Create a hash from various identifying browser data + const buffer = await window.crypto.subtle.digest("SHA-1", new TextEncoder().encode(JSON.stringify([ + navigator.userAgent, + navigator.buildId, + navigator.languages + ]))); + + return new DataView(buffer).getBigUint64().toString(); + } + + /** + * Extract desired MouseEvent data into an object literal + * @param {MouseEvent} event + * @returns {Object} + */ + static #mouseEvent(event) { + return { + e: event.type, + w: window.innerWidth, + h: window.innerHeight, + x: event.x, + y: event.y + } + } + + /** + * Extract desired KeyboardEvent data into an object literal + * @param {KeyboardEvent} event + * @returns {Object} + */ + static #keyEvent(event) { + return { + e: event.type, + c: event.key, + s: event.shiftKey + } + } + + constructor() {} + + /** + * Start logging user activities + */ + start() { + this.#abort = new AbortController(); + + document.addEventListener("keyup", (event) => this.log(Logger.#keyEvent(event)), { signal: this.#abort.signal }); + document.addEventListener("keydown", (event) => this.log(Logger.#keyEvent(event)), { signal: this.#abort.signal }); + document.addEventListener("click", (event) => this.log(Logger.#mouseEvent(event)), { signal: this.#abort.signal }); + document.addEventListener("mousemove", (event) => { + // Throttle mousemove events + clearTimeout(this.#mouseMoveTimeout); + //this.#mouseMoveTimeout = setTimeout(() => this.#log(mouseEvent(event)), MOUSE_MOVE_TIMEOUT_MS); + }, { signal: this.#abort.signal }); + } + + /** + * Stop logging user activitiers + */ + stop() { + this.#abort.abort(); + } + + /** + * Log user data + * @param {Object} data + * @returns {Response} + */ + async log(data) { + return await fetch(Logger.url, { + body: JSON.stringify({ + client: data, + fingerprint: (await Logger.#fingerprint()) + }), + method: "POST", + headers: Object.assign({ + "Content-Type": "application/json" + }, VV.header) + }); + }; +} \ No newline at end of file diff --git a/assets/js/pages/dashboard.js b/assets/js/pages/dashboard.js new file mode 100644 index 0000000..06f766e --- /dev/null +++ b/assets/js/pages/dashboard.js @@ -0,0 +1,5 @@ +// Clear all content and display the loading spinner for now. I want to add more stuff here later! +setTimeout(() => { + VV.shell.innerHTML = ""; + VV.shell.setAttribute("vv-loading", true); +}, VV.delay); \ No newline at end of file diff --git a/assets/js/pages/document.js b/assets/js/pages/document.js deleted file mode 100644 index ed7003d..0000000 --- a/assets/js/pages/document.js +++ /dev/null @@ -1 +0,0 @@ -globalThis.pragma.Interactions("document", {}); \ No newline at end of file diff --git a/assets/js/pages/login.js b/assets/js/pages/login.js new file mode 100644 index 0000000..d0d3588 --- /dev/null +++ b/assets/js/pages/login.js @@ -0,0 +1,57 @@ +// Simulate a fake login page +{ + const WHITELIST_USERNAMES = [ + "user", + "root", + "admin", + "mydlink" + ]; + const WHITELIST_PASSWORDS = [ + "root", + "admin", + "12345", + "mydlink", + "password", + "123456789" + ]; + const INPUT_NAME_USERNAME = "username"; + const INPUT_NAME_PASSWORD = "password"; + + document.querySelector("form button").addEventListener("click", (event) => { + event.preventDefault(); + + VV.shell.setAttribute("vv-loading", true); + const form = new FormData(event.target.closest("form")); + + // Invalid fake username or password derp + if ( + !WHITELIST_USERNAMES.includes(form.get(INPUT_NAME_USERNAME)) + || !WHITELIST_PASSWORDS.includes(form.get(INPUT_NAME_PASSWORD)) + ) { + // Show "incorrect credentials" dialog after global Vegvisir delay + setTimeout(() => { + VV.shell.setAttribute("vv-loading", false); + document.querySelector("dialog").showModal(); + }, VV.delay); + + return; + } + + new VV().navigate("/dashboard"); + }); +} + +// Only start logging if the user does something with the input fields +{ + const abortInitialInputChange = new AbortController(); + + const startLogging = () =>{ + abortInitialInputChange.abort(); + new globalThis.Logger().start(); + }; + + document.querySelector("button").addEventListener("click", () => startLogging(), { signal: abortInitialInputChange.signal }); + document.querySelectorAll("input").forEach(element => element.addEventListener("click", () => startLogging(), { signal: abortInitialInputChange.signal })); + document.querySelectorAll("input").forEach(element => element.addEventListener("keydown", () => startLogging(), { signal: abortInitialInputChange.signal })); + document.querySelectorAll("input").forEach(element => element.addEventListener("change", () => startLogging(), { signal: abortInitialInputChange.signal })); +} \ No newline at end of file diff --git a/assets/js/shell.js b/assets/js/shell.js new file mode 100644 index 0000000..c3df959 --- /dev/null +++ b/assets/js/shell.js @@ -0,0 +1,30 @@ +const LOGIN_PAGE = "/login"; +const STORAGE_KEY_LOGGEDIN = "mydlink_dashboard_login"; + +// Set a generous global navigation delay to simulate crappy web software +VV.delay = 3500; + +// Redirect the user to the login page if session storage key is not set +if (!sessionStorage.getItem(STORAGE_KEY_LOGGEDIN) && window.location.pathname !== LOGIN_PAGE) { + const getRandomString = (length = 16) => { + const CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let string = ""; + + for (let i = 0; i < length; i++) string += CHARSET[Math.floor(Math.random() * CHARSET.length)]; + + return string; + }; + + const url = new URL(window.location); + + // Set some legit looking overcomplicated search parameters + url.searchParams.set("mydl_sid", getRandomString()); + // This is our fake "user is logged in" Storage API key + url.searchParams.set("action", STORAGE_KEY_LOGGEDIN); + url.searchParams.set(`mydl_${getRandomString(3)}`, "dashboard"); + url.searchParams.set(`mydl_asas_${getRandomString(4)}_${getRandomString(8)}`, "login_cgi"); + + url.pathname = LOGIN_PAGE; + + new VV().navigate(url); +} \ No newline at end of file diff --git a/database/9f50ea1a5be726e610dc2fe134926869 b/database/9f50ea1a5be726e610dc2fe134926869 deleted file mode 160000 index ba34c57..0000000 --- a/database/9f50ea1a5be726e610dc2fe134926869 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ba34c5719fda3131a66ed9664ee182900c495bbd diff --git a/database/Pot.php b/database/Pot.php deleted file mode 100644 index 85f0e72..0000000 --- a/database/Pot.php +++ /dev/null @@ -1,33 +0,0 @@ - $_POST, - "SERVER" => $_SERVER - ]); - - // And save it! - $sql = "INSERT OR IGNORE INTO pot (id, data, version, created) VALUES (?, ?, ?, ?)"; - return $this->return_bool($sql, [ - crc32(uniqid($data, true)), - $data, - 1, - time() - ]); - } - } diff --git a/database/init/POT.sql b/database/init/POT.sql deleted file mode 100644 index 36e5bae..0000000 --- a/database/init/POT.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE pot ( - id TEXT PRIMARY KEY NOT NULL, - data TEXT, - version INT NOT NULL, - created INT NOT NULL -); \ No newline at end of file diff --git a/pages/EN_EN/document.php b/pages/EN_EN/document.php deleted file mode 100644 index 904b92c..0000000 --- a/pages/EN_EN/document.php +++ /dev/null @@ -1,31 +0,0 @@ - - -
- - -
-
-
+
\ No newline at end of file
diff --git a/public/error.php b/public/error.php
new file mode 100644
index 0000000..96d8eaa
--- /dev/null
+++ b/public/error.php
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/log.php b/public/log.php
new file mode 100644
index 0000000..90cecb0
--- /dev/null
+++ b/public/log.php
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/public/login.php b/public/login.php
new file mode 100644
index 0000000..1c4741c
--- /dev/null
+++ b/public/login.php
@@ -0,0 +1,24 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/public/shell.php b/public/shell.php
new file mode 100644
index 0000000..0d59f9d
--- /dev/null
+++ b/public/shell.php
@@ -0,0 +1,62 @@
+
+
+
+
+
+ DIR-880L
+ +