Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/CyberStrikeus/CyberStrike --skill wstg-authz-05-2명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
macOS post-exploitation for credential harvesting, DTrace monitoring, TCC bypass, and stealth operations via native tools
Windows userland post-exploitation for credential harvesting, monitoring, AMSI/ETW bypass, and stealth operations
Kubernetes post-exploitation for container escape, secret extraction, RBAC abuse, and cluster persistence
SKILL.md 표시 중
SOC 직업 분류 기준
| name | wstg-authz-05.2 |
| description | Testing OAuth Client Weaknesses |
| category | authorization |
| owasp_id | WSTG-AUTHZ-05.2 |
| version | 1.0.0 |
| author | cyberstrike-official |
| tags | ["authorization","access-control","privilege","wstg","authz"] |
| tech_stack | [] |
| cwe_ids | [] |
| chains_with | [] |
| prerequisites | [] |
| severity_boost | {} |
WSTG-AUTHZ-05.2
Testing OAuth Client Weaknesses
OAuth clients (also known as relying parties) are applications that use OAuth to access protected resources on behalf of users. Client-side vulnerabilities can lead to token theft, account takeover, and unauthorized access. This test focuses on identifying weaknesses in how the client application handles OAuth flows, stores tokens, validates responses, and protects against common attack vectors.
| Vulnerability | Description |
|---|---|
| Insecure token storage | Tokens in localStorage |
| Missing state validation | CSRF vulnerability |
| Token in URL | Referrer leakage |
| Missing PKCE | Authorization code interception |
| Open redirect | redirect_uri manipulation |
| XSS token theft | JavaScript access to tokens |
# Monitor the complete OAuth flow
# 1. Authorization request
# 2. User consent
# 3. Authorization response (code/token)
# 4. Token exchange (if authorization code)
# 5. Resource access
# Use browser devtools to capture:
# - Network requests
# - localStorage/sessionStorage
grep -iE browser_history.txt
// Browser console tests for token storage
// Check localStorage
console.log("localStorage tokens:")
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
if (key.toLowerCase().includes("token") || key.toLowerCase().includes("auth") || key.toLowerCase().includes("jwt")) {
console.log(`${key}: ${localStorage.getItem(key)}`)
}
}
// Check sessionStorage
console.log("\nsessionStorage tokens:")
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i)
if (key.toLowerCase().includes("token") || key.toLowerCase().includes("auth")) {
console.log(`${key}: ${sessionStorage.getItem(key)}`)
}
}
// Check if tokens are accessible via XSS
// If XSS exists, can tokens be stolen?
#!/bin/bash
# Test client-side state validation
# Capture legitimate OAuth flow
# 1. Start authorization
AUTH_URL="https://auth.example.com/oauth/authorize"
CLIENT_URL="https://client.example.com"
CLIENT_ID="client123"
# Get the state from client
initial_response=$(curl -s -c cookies.txt "$CLIENT_URL/login/oauth")
state=$(echo "$initial_response" | grep -oP 'state=\K[^&"]+')
echo "Original state: $state"
# Test callback with:
# 1. No state
curl -s "$CLIENT_URL/oauth/callback?code=AUTH_CODE"
# 2. Empty state
curl -s "$CLIENT_URL/oauth/callback?code=AUTH_CODE&state="
# 3. Modified state
curl -s "$CLIENT_URL/oauth/callback?code=AUTH_CODE&state=attacker_state"
# 4. State from different session
curl -s -b different_cookies.txt "$CLIENT_URL/oauth/callback?code=AUTH_CODE&state=$state"
#!/bin/bash
# Verify client implements PKCE correctly
# Check if client sends code_challenge in authorization request
# Monitor authorization URL for code_challenge parameter
# Test if code_verifier is properly used in token request
# Intercept token request and check for code_verifier
# Test if PKCE is enforced:
# - Capture code_challenge from auth request
# - Intercept token request
# - Try token request without code_verifier
# - Try token request with wrong code_verifier
curl -s -X POST "https://auth.example.com/oauth/token" \
-d "grant_type=authorization_code&code=AUTH_CODE&redirect_uri=REDIRECT&client_id=CLIENT"
# Should fail if PKCE was required
#!/bin/bash
# Test for open redirects in client's redirect handling
CLIENT_URL="https://client.example.com"
# Test post-login redirect parameter
redirects=(
"https://attacker.com"
"//attacker.com"
"/\\attacker.com"
"https://client.example.com.attacker.com"
"javascript:alert(1)"
"data:text/html,<script>alert(1)</script>"
)
for redirect in "${redirects[@]}"; do
# URL encode
encoded=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$redirect'))")
curl -s -I "$CLIENT_URL/oauth/callback?code=AUTH_CODE&state=VALID&next=$encoded"
curl -s -I "$CLIENT_URL/oauth/callback?code=AUTH_CODE&state=VALID&redirect=$encoded"
curl -s -I "$CLIENT_URL/oauth/callback?code=AUTH_CODE&state=VALID&return_to=$encoded"
done
# Check if tokens can leak via Referrer header
# After OAuth callback, check:
# 1. If access_token is in URL (implicit flow or error)
# 2. If any external resources are loaded
# 3. If Referrer-Policy is set
# Use browser devtools to check outgoing requests
# Look for access_token in Referer header
# Check for proper headers
curl -sI "https://client.example.com/oauth/callback" | grep -iE "referrer-policy|content-security-policy"
#!/usr/bin/env python3
import requests
import re
from urllib.parse import urlparse, parse_qs
class OAuthClientTester:
def __init__(self, client_url):
self.client_url = client_url
self.session = requests.Session()
self.vulnerabilities = []
def analyze_oauth_initiation(self, login_endpoint="/login/oauth"):
"""Analyze how client initiates OAuth flow"""
print("\n[*] Analyzing OAuth initiation...")
try:
response = self.session.get(
f"{self.client_url}{login_endpoint}",
allow_redirects=False
)
if response.status_code in [302, 303]:
location = response.headers.get('Location', '')
parsed = urlparse(location)
params = parse_qs(parsed.query)
print(f"Authorization URL: {parsed.scheme}://{parsed.netloc}{parsed.path}")
# Check for state parameter
if 'state' not in params:
print("[VULN] Missing state parameter")
self.vulnerabilities.append({
"type": "missing_state",
"location": "authorization_request"
})
else:
print(f"State: {params['state'][0][:20]}...")
# Check for PKCE
if 'code_challenge' not in params:
print("[WARN] No PKCE code_challenge (may be OK for confidential client)")
else:
print(f"PKCE: code_challenge present")
if params.get('code_challenge_method', [''])[0] != 'S256':
print("[WARN] PKCE should use S256 method")
# Check response_type
response_type = params.get('response_type', [''])[0]
if response_type == 'token':
print("[VULN] Using implicit flow (token in URL)")
self.vulnerabilities.append({
"type": "implicit_flow",
"description": "Tokens exposed in URL"
})
# Check redirect_uri
redirect_uri = params.get('redirect_uri', [''])[0]
if 'http://' in redirect_uri and 'localhost' not in redirect_uri:
print("[VULN] Non-HTTPS redirect_uri")
self.vulnerabilities.append({
"type": "http_redirect",
"uri": redirect_uri
})
except Exception as e:
print(f"[ERROR] {e}")
return self.vulnerabilities
def test_state_validation(self, callback_endpoint="/oauth/callback"):
"""Test if client properly validates state"""
print("\n[*] Testing state validation...")
# First get a valid state
init_response = self.session.get(
f"{self.client_url}/login/oauth",
allow_redirects=False
)
location = init_response.headers.get('Location', '')
params = parse_qs(urlparse(location).query)
valid_state = params.get('state', [''])[0]
# Test with no state
response = self.session.get(
f"{self.client_url}{callback_endpoint}",
params={"code": "test_code"},
allow_redirects=False
)
if response.status_code not in [400, 403]:
print("[VULN] Callback accepted without state")
self.vulnerabilities.append({
"type": "missing_state_validation",
"description": "Callback doesn't require state"
})
# Test with wrong state
response = self.session.get(
f"{self.client_url}{callback_endpoint}",
params={"code": "test_code", "state": "attacker_state"},
allow_redirects=False
)
if response.status_code not in [400, 403]:
print("[VULN] Callback accepted with invalid state")
self.vulnerabilities.append({
"type": "invalid_state_accepted",
"description": "State not properly validated"
})
return self.vulnerabilities
def test_token_storage(self):
"""Document token storage locations (requires browser testing)"""
print("\n[*] Token storage analysis (manual verification needed)")
storage_checks = """
// Run in browser console after OAuth login:
// Check localStorage
Object.keys(localStorage).filter(k =>
/token|auth|jwt|session/i.test(k)
).forEach(k => console.log('localStorage:', k, localStorage[k]));
// Check sessionStorage
Object.keys(sessionStorage).filter(k =>
/token|auth|jwt|session/i.test(k)
).forEach(k => console.log('sessionStorage:', k, sessionStorage[k]));
// Check cookies
document.cookie.split(';').filter(c =>
/token|auth|jwt|session/i.test(c)
).forEach(c => console.log('cookie:', c));
// Check if tokens are HttpOnly
// Tokens should NOT be accessible via JavaScript
"""
print(storage_checks)
def test_callback_error_handling(self, callback_endpoint="/oauth/callback"):
"""Test how client handles OAuth errors"""
print("\n[*] Testing callback error handling...")
errors = [
{"error": "access_denied", "error_description": "User denied access"},
{"error": "invalid_request", "error_description": "Invalid request"},
{"error": "server_error", "error_description": "Auth server error"},
]
for error in errors:
try:
response = self.session.get(
f"{self.client_url}{callback_endpoint}",
params=error
)
# Check if error details are reflected (XSS potential)
if error["error_description"] in response.text:
print(f"[WARN] Error description reflected in response")
# Test XSS in error_description
xss_payload = "<script>alert(1)</script>"
xss_response = self.session.get(
f"{self.client_url}{callback_endpoint}",
params={"error": "invalid_request",
"error_description": xss_payload}
)
if xss_payload in xss_response.text:
print("[VULN] XSS in error handling")
self.vulnerabilities.append({
"type": "xss_error_handling",
"payload": xss_payload
})
except Exception as e:
pass
return self.vulnerabilities
def test_logout_handling(self, logout_endpoint="/logout"):
"""Test logout and token revocation"""
print("\n[*] Testing logout handling...")
try:
# Get a session
self.session.get(f"{self.client_url}/login")
# Logout
logout_response = self.session.get(
f"{self.client_url}{logout_endpoint}",
allow_redirects=False
)
# Check if session is actually cleared
# Try accessing protected resource
protected_response = self.session.get(
f"{self.client_url}/dashboard",
allow_redirects=False
)
if protected_response.status_code == 200:
print("[VULN] Session not properly cleared on logout")
self.vulnerabilities.append({
"type": "session_not_cleared",
"description": "Logout doesn't invalidate session"
})
except Exception as e:
pass
return self.vulnerabilities
def generate_report(self):
"""Generate client security report"""
print("\n" + "="*60)
print("OAUTH CLIENT SECURITY REPORT")
print("="*60)
if not self.vulnerabilities:
print("\nNo vulnerabilities found.")
return
print(f"\nTotal findings: {len(self.vulnerabilities)}\n")
# Group by type
by_type = {}
for vuln in self.vulnerabilities:
t = vuln['type']
if t not in by_type:
by_type[t] = []
by_type[t].append(vuln)
for vuln_type, vulns in by_type.items():
print(f"\n[{vuln_type.upper()}] ({len(vulns)} finding(s))")
for v in vulns:
for k, val in v.items():
if k != 'type':
print(f" {k}: {val}")
# Usage
tester = OAuthClientTester("https://client.example.com")
tester.analyze_oauth_initiation()
tester.test_state_validation()
tester.test_callback_error_handling()
tester.test_logout_handling()
tester.generate_report()
# For mobile apps, additional checks:
# 1. Check if using custom URL schemes (vulnerable to hijacking)
# Look for: myapp://callback instead of https://
# 2. Check for PKCE (required for mobile/public clients)
# Intercept authorization request and verify code_challenge
# 3. Check token storage
# - iOS: Should use Keychain
# - Android: Should use EncryptedSharedPreferences or Keystore
# 4. Check for deep link hijacking
# - Verify app uses App Links (Android) or Universal Links (iOS)
# - Custom schemes are vulnerable to hijacking
# 5. Check for intent interception (Android)
# Malicious apps can intercept OAuth callbacks
| Tool | Description | Usage |
|---|---|---|
| Browser DevTools | Traffic analysis | Monitor OAuth flow |
| Burp Suite | Proxy | Intercept requests |
| OWASP ZAP | Security scanner | Automated testing |
| Tool | Description |
|---|---|
| Frida | Dynamic instrumentation |
| objection | Mobile exploration |
| MobSF | Static analysis |
// Use HttpOnly cookies for token storage (preferred)
// Server sets cookie:
// Set-Cookie: access_token=xxx; HttpOnly; Secure; SameSite=Strict; Path=/
// If client-side storage is needed, use sessionStorage (not localStorage)
// and encrypt sensitive data
class SecureTokenStorage {
constructor(encryptionKey) {
this.key = encryptionKey
}
async store(token) {
// Encrypt before storing
const encrypted = await this.encrypt(token)
sessionStorage.setItem("auth_token", encrypted)
}
async retrieve() {
const encrypted = sessionStorage.getItem("auth_token")
if (!encrypted) return null
return await this.decrypt(encrypted)
}
clear() {
sessionStorage.removeItem("auth_token")
}
async encrypt(data) {
const encoder = new TextEncoder()
const dataBuffer = encoder.encode(data)
const iv = crypto.getRandomValues(new Uint8Array(12))
const key = await crypto.subtle.importKey("raw", encoder.encode(this.key), "AES-GCM", false, ["encrypt"])
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, dataBuffer)
return btoa(String.fromCharCode(...iv) + String.fromCharCode(...new Uint8Array(encrypted)))
}
}
import secrets
import hashlib
from flask import session, redirect, abort
class OAuthStateManager:
@staticmethod
def generate_state(nonce=None):
"""Generate secure state parameter"""
state = secrets.token_urlsafe(32)
# Store in session with optional nonce
session['oauth_state'] = {
'value': hashlib.sha256(state.encode()).hexdigest(),
'nonce': nonce,
'created': time.time()
}
return state
@staticmethod
def validate_state(received_state):
"""Validate state from callback"""
stored = session.pop('oauth_state', None)
if not stored:
return False
# Check expiration (5 minute max)
if time.time() - stored['created'] > 300:
return False
received_hash = hashlib.sha256(received_state.encode()).hexdigest()
return secrets.compare_digest(stored['value'], received_hash)
# Usage in callback
@app.route('/oauth/callback')
def oauth_callback():
state = request.args.get('state')
if not state or not OAuthStateManager.validate_state(state):
abort(400, "Invalid state parameter")
code = request.args.get('code')
# Exchange code for token
class PKCEManager {
static async generateCodeVerifier() {
const array = new Uint8Array(32)
crypto.getRandomValues(array)
return this.base64URLEncode(array)
}
static async generateCodeChallenge(verifier) {
const encoder = new TextEncoder()
const data = encoder.encode(verifier)
const hash = await crypto.subtle.digest("SHA-256", data)
return this.base64URLEncode(new Uint8Array(hash))
}
static base64URLEncode(buffer) {
return btoa(String.fromCharCode(...buffer))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "")
}
static async initializeAuth() {
const codeVerifier = await this.generateCodeVerifier()
const codeChallenge = await this.generateCodeChallenge(codeVerifier)
// Store verifier in session
sessionStorage.setItem("code_verifier", codeVerifier)
return {
code_challenge: codeChallenge,
code_challenge_method: "S256",
}
}
static getCodeVerifier() {
const verifier = sessionStorage.getItem("code_verifier")
sessionStorage.removeItem("code_verifier")
return verifier
}
}
// Usage
async function startOAuth() {
const pkce = await PKCEManager.initializeAuth()
const authUrl = new URL("https://auth.example.com/authorize")
authUrl.searchParams.set("client_id", CLIENT_ID)
authUrl.searchParams.set("redirect_uri", REDIRECT_URI)
authUrl.searchParams.set("response_type", "code")
authUrl.searchParams.set("code_challenge", pkce.code_challenge)
authUrl.searchParams.set("code_challenge_method", pkce.code_challenge_method)
window.location = authUrl.toString()
}
from urllib.parse import urlparse
ALLOWED_REDIRECT_HOSTS = ['example.com', 'www.example.com']
def validate_redirect(redirect_uri):
"""Validate post-login redirect URL"""
if not redirect_uri:
return '/'
# Parse URL
parsed = urlparse(redirect_uri)
# Only allow relative URLs or specific hosts
if parsed.netloc:
if parsed.netloc not in ALLOWED_REDIRECT_HOSTS:
return '/'
# Block javascript: and data: URLs
if parsed.scheme in ['javascript', 'data', 'vbscript']:
return '/'
# Ensure path doesn't start with // (protocol-relative)
if redirect_uri.startswith('//'):
return '/'
return redirect_uri
@app.route('/oauth/callback')
def oauth_callback():
# ... validate state and exchange code ...
# Safely handle redirect
next_url = request.args.get('next', '/')
safe_redirect = validate_redirect(next_url)
return redirect(safe_redirect)
| Finding | CVSS | Severity |
|---|---|---|
| Token in localStorage (XSS accessible) | 6.5 | Medium |
| Missing state validation (CSRF) | 8.8 | High |
| Missing PKCE (public client) | 7.5 | High |
| Open redirect post-auth | 6.1 | Medium |
| Token in URL (referrer leak) | 6.5 | Medium |
| CWE ID | Title | Description |
|---|---|---|
| CWE-352 | CSRF | Missing state |
| CWE-601 | Open Redirect | Unsafe redirects |
| CWE-922 | Insecure Storage | Token storage |
| CWE-200 | Information Exposure | Token leakage |
[ ] OAuth initiation analyzed
[ ] State parameter validated
[ ] PKCE implementation checked
[ ] Token storage analyzed
[ ] Redirect handling tested
[ ] Error handling tested
[ ] Referrer leakage checked
[ ] Logout handling verified
[ ] Mobile-specific issues checked
[ ] XSS impact on tokens assessed
[ ] Findings documented
[ ] Remediation recommendations provided