Appearance
ROADtools Azure AD Assessment Framework Cheat Sheet
Overview
ROADtools (The Azure AD exploration framework) is a collection of tools developed by Dirk-Jan Mollema for Azure Active Directory reconnaissance and assessment. It provides comprehensive capabilities for exploring Azure AD environments, analyzing configurations, and identifying security issues.
⚠️ Warning: This tool is intended for authorized penetration testing and security assessments only. Ensure you have proper authorization before using in any environment.
Installation
pip Installation
bash
# Install ROADtools
pip3 install roadtools
# Install specific components
pip3 install roadrecon roadlib
# Install from GitHub (latest)
pip3 install git+https://github.com/dirkjanm/ROADtools
Manual Installation
bash
# Clone repository
git clone https://github.com/dirkjanm/ROADtools.git
cd ROADtools
# Install dependencies
pip3 install -r requirements.txt
# Install ROADtools
python3 setup.py install
Docker Installation
bash
# Build Docker image
git clone https://github.com/dirkjanm/ROADtools.git
cd ROADtools
docker build -t roadtools .
# Run ROADtools in Docker
docker run -it -v $(pwd):/data roadtools
Basic Usage
Authentication Methods
bash
# Username/password authentication
roadrecon auth -u user@domain.com -p password
# Device code authentication
roadrecon auth --device-code
# Access token authentication
roadrecon auth --access-token <token>
# Refresh token authentication
roadrecon auth --refresh-token <token>
# Certificate authentication
roadrecon auth --cert-thumbprint <thumbprint> --cert-file cert.pem --key-file key.pem
Data Gathering
bash
# Gather all available data
roadrecon gather
# Gather specific data types
roadrecon gather --users --groups --applications
# Gather with specific authentication
roadrecon gather --tokens roadtokens.json
# Gather with rate limiting
roadrecon gather --rate-limit 10
Command Reference
Authentication Commands
Command | Description |
---|---|
roadrecon auth | Authenticate to Azure AD |
roadrecon auth --device-code | Use device code flow |
roadrecon auth --refresh-token | Use refresh token |
roadrecon auth --access-token | Use access token |
Data Gathering Commands
Command | Description |
---|---|
roadrecon gather | Gather Azure AD data |
roadrecon gather --users | Gather user data only |
roadrecon gather --groups | Gather group data only |
roadrecon gather --applications | Gather application data |
roadrecon gather --devices | Gather device data |
Analysis Commands
Command | Description |
---|---|
roadrecon gui | Start web interface |
roadrecon plugin | Run analysis plugins |
roadrecon dump | Export data to files |
Data Collection
User Enumeration
bash
# Collect all users
roadrecon gather --users
# Collect users with specific attributes
roadrecon gather --users --include-attributes displayName,mail,userPrincipalName
# Collect guest users only
roadrecon gather --users --filter "userType eq 'Guest'"
# Collect privileged users
roadrecon gather --users --filter "assignedLicenses/any(x:x/skuId eq guid'6fd2c87f-b296-42f0-b197-1e91e994b900')"
Group Enumeration
bash
# Collect all groups
roadrecon gather --groups
# Collect security groups only
roadrecon gather --groups --filter "securityEnabled eq true"
# Collect groups with members
roadrecon gather --groups --expand members
# Collect administrative groups
roadrecon gather --groups --filter "displayName eq 'Global Administrators'"
Application Enumeration
bash
# Collect all applications
roadrecon gather --applications
# Collect service principals
roadrecon gather --servicePrincipals
# Collect application permissions
roadrecon gather --applications --expand appRoles,oauth2PermissionScopes
# Collect enterprise applications
roadrecon gather --servicePrincipals --filter "servicePrincipalType eq 'Application'"
Device Enumeration
bash
# Collect all devices
roadrecon gather --devices
# Collect managed devices
roadrecon gather --devices --filter "isManaged eq true"
# Collect compliant devices
roadrecon gather --devices --filter "isCompliant eq true"
# Collect device owners
roadrecon gather --devices --expand registeredOwners
Advanced Data Collection
Custom Queries
bash
# Custom OData filter
roadrecon gather --users --filter "department eq 'IT'"
# Multiple filters
roadrecon gather --users --filter "department eq 'IT' and accountEnabled eq true"
# Select specific attributes
roadrecon gather --users --select displayName,mail,department,jobTitle
# Expand related objects
roadrecon gather --users --expand manager,directReports
Bulk Operations
bash
# Gather all data types
roadrecon gather --all
# Gather with pagination
roadrecon gather --users --top 100 --skip 0
# Gather with retry logic
roadrecon gather --retry-count 3 --retry-delay 5
# Parallel gathering
roadrecon gather --threads 5
Token Management
bash
# Save tokens for reuse
roadrecon auth --save-tokens tokens.json
# Load saved tokens
roadrecon gather --tokens tokens.json
# Refresh expired tokens
roadrecon auth --refresh-token <token> --save-tokens tokens.json
Web Interface Analysis
Starting the GUI
bash
# Start web interface
roadrecon gui
# Start on specific port
roadrecon gui --port 8080
# Start with specific database
roadrecon gui --database roadrecon.db
# Start with authentication
roadrecon gui --auth
Navigation and Analysis
bash
# Access web interface
# http://localhost:5000
# Main sections:
# - Users: User accounts and attributes
# - Groups: Group memberships and roles
# - Applications: App registrations and permissions
# - Devices: Device information and compliance
# - Roles: Administrative roles and assignments
Custom Queries in GUI
sql
-- Find users with administrative roles
SELECT u.displayName, u.userPrincipalName, r.displayName as role
FROM users u
JOIN roleAssignments ra ON u.id = ra.principalId
JOIN directoryRoles r ON ra.roleDefinitionId = r.id
-- Find applications with high privileges
SELECT sp.displayName, sp.appId, p.value as permission
FROM servicePrincipals sp
JOIN appRoleAssignments ara ON sp.id = ara.resourceId
JOIN appRoles p ON ara.appRoleId = p.id
WHERE p.value LIKE '%All%'
-- Find guest users with group memberships
SELECT u.displayName, u.userPrincipalName, g.displayName as groupName
FROM users u
JOIN groupMembers gm ON u.id = gm.memberId
JOIN groups g ON gm.groupId = g.id
WHERE u.userType = 'Guest'
Plugin System
Available Plugins
bash
# List available plugins
roadrecon plugin --list
# Run specific plugin
roadrecon plugin --name privileged-users
# Run all plugins
roadrecon plugin --all
# Run plugin with parameters
roadrecon plugin --name custom-plugin --param value
Custom Plugin Development
python
# Example plugin structure
from roadtools.roadrecon.plugins import PluginBase
class CustomPlugin(PluginBase):
name = "custom-analysis"
description = "Custom Azure AD analysis"
def run(self):
# Access database
users = self.db.query("SELECT * FROM users WHERE accountEnabled = 1")
# Perform analysis
results = []
for user in users:
if self.is_privileged_user(user):
results.append({
'user': user['displayName'],
'upn': user['userPrincipalName'],
'risk': 'High'
})
return results
def is_privileged_user(self, user):
# Custom logic to identify privileged users
privileged_roles = ['Global Administrator', 'Security Administrator']
user_roles = self.get_user_roles(user['id'])
return any(role in privileged_roles for role in user_roles)
Data Export and Reporting
Export Options
bash
# Export to JSON
roadrecon dump --format json --output azure_data.json
# Export to CSV
roadrecon dump --format csv --output azure_data.csv
# Export specific data types
roadrecon dump --users --format json --output users.json
# Export with filters
roadrecon dump --users --filter "department eq 'IT'" --format csv
Custom Reports
python
# Generate custom reports
import json
import sqlite3
def generate_security_report(db_path):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
report = {
'summary': {},
'findings': [],
'recommendations': []
}
# Count users
cursor.execute("SELECT COUNT(*) FROM users WHERE accountEnabled = 1")
report['summary']['active_users'] = cursor.fetchone()[0]
# Count guest users
cursor.execute("SELECT COUNT(*) FROM users WHERE userType = 'Guest'")
report['summary']['guest_users'] = cursor.fetchone()[0]
# Find users without MFA
cursor.execute("""
SELECT displayName, userPrincipalName
FROM users
WHERE accountEnabled = 1
AND id NOT IN (
SELECT userId FROM strongAuthenticationMethods
)
""")
no_mfa_users = cursor.fetchall()
if no_mfa_users:
report['findings'].append({
'title': 'Users without MFA',
'severity': 'High',
'count': len(no_mfa_users),
'users': no_mfa_users
})
return report
# Usage
report = generate_security_report('roadrecon.db')
print(json.dumps(report, indent=2))
Visualization
python
# Create visualizations
import matplotlib.pyplot as plt
import sqlite3
def create_user_distribution_chart(db_path):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Get user distribution by department
cursor.execute("""
SELECT department, COUNT(*) as count
FROM users
WHERE department IS NOT NULL
GROUP BY department
ORDER BY count DESC
LIMIT 10
""")
data = cursor.fetchall()
departments = [row[0] for row in data]
counts = [row[1] for row in data]
plt.figure(figsize=(12, 6))
plt.bar(departments, counts)
plt.title('User Distribution by Department')
plt.xlabel('Department')
plt.ylabel('Number of Users')
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('user_distribution.png')
Security Analysis
Privilege Escalation Paths
sql
-- Find potential privilege escalation paths
WITH RECURSIVE privilege_paths AS (
-- Base case: direct role assignments
SELECT
u.displayName as user_name,
u.userPrincipalName,
r.displayName as role_name,
1 as depth,
u.userPrincipalName as path
FROM users u
JOIN roleAssignments ra ON u.id = ra.principalId
JOIN directoryRoles r ON ra.roleDefinitionId = r.id
UNION ALL
-- Recursive case: group memberships leading to roles
SELECT
pp.user_name,
pp.userPrincipalName,
r.displayName as role_name,
pp.depth + 1,
pp.path || ' -> ' || g.displayName || ' -> ' || r.displayName
FROM privilege_paths pp
JOIN groupMembers gm ON pp.userPrincipalName = (
SELECT userPrincipalName FROM users WHERE id = gm.memberId
)
JOIN groups g ON gm.groupId = g.id
JOIN roleAssignments ra ON g.id = ra.principalId
JOIN directoryRoles r ON ra.roleDefinitionId = r.id
WHERE pp.depth < 5
)
SELECT * FROM privilege_paths ORDER BY depth, user_name;
Application Permission Analysis
sql
-- Find applications with excessive permissions
SELECT
sp.displayName as app_name,
sp.appId,
COUNT(DISTINCT ara.appRoleId) as permission_count,
GROUP_CONCAT(ar.value) as permissions
FROM servicePrincipals sp
JOIN appRoleAssignments ara ON sp.id = ara.principalId
JOIN appRoles ar ON ara.appRoleId = ar.id
GROUP BY sp.id, sp.displayName, sp.appId
HAVING permission_count > 10
ORDER BY permission_count DESC;
Conditional Access Analysis
python
# Analyze Conditional Access policies
def analyze_conditional_access(db_path):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Get Conditional Access policies
cursor.execute("SELECT * FROM conditionalAccessPolicies")
policies = cursor.fetchall()
analysis = {
'total_policies': len(policies),
'enabled_policies': 0,
'mfa_policies': 0,
'location_policies': 0,
'device_policies': 0
}
for policy in policies:
if policy['state'] == 'enabled':
analysis['enabled_policies'] += 1
# Check for MFA requirements
if 'mfa' in policy['grantControls'].lower():
analysis['mfa_policies'] += 1
# Check for location conditions
if policy['conditions'] and 'locations' in policy['conditions']:
analysis['location_policies'] += 1
# Check for device conditions
if policy['conditions'] and 'devices' in policy['conditions']:
analysis['device_policies'] += 1
return analysis
Evasion and Stealth
Rate Limiting
bash
# Use rate limiting to avoid detection
roadrecon gather --rate-limit 5 --delay 2
# Random delays between requests
roadrecon gather --random-delay 1-5
# Spread requests over time
roadrecon gather --duration 3600 # 1 hour
User Agent Rotation
python
# Custom user agent rotation
import random
user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
]
def get_random_user_agent():
return random.choice(user_agents)
# Use with roadrecon
roadrecon gather --user-agent "$(python3 -c 'import random; print(random.choice(["Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"]))')"
Proxy Usage
bash
# Use proxy for requests
roadrecon gather --proxy http://proxy:8080
# Use SOCKS proxy
roadrecon gather --proxy socks5://proxy:1080
# Rotate proxies
roadrecon gather --proxy-list proxies.txt
Troubleshooting
Authentication Issues
bash
# Clear cached tokens
rm -rf ~/.roadtools/
# Debug authentication
roadrecon auth --debug
# Check token validity
roadrecon auth --validate-token
# Manual token refresh
roadrecon auth --refresh-token <token>
API Rate Limiting
bash
# Handle rate limiting
roadrecon gather --rate-limit 1 --retry-count 5
# Monitor rate limit headers
roadrecon gather --debug | grep -i "rate\|limit"
# Use exponential backoff
roadrecon gather --backoff-strategy exponential
Data Collection Issues
bash
# Verify permissions
roadrecon auth --check-permissions
# Test connectivity
roadrecon gather --test-connection
# Debug API calls
roadrecon gather --debug --verbose
# Check for API changes
roadrecon gather --api-version beta
Database Issues
bash
# Repair database
sqlite3 roadrecon.db "PRAGMA integrity_check;"
# Backup database
cp roadrecon.db roadrecon_backup.db
# Reset database
rm roadrecon.db
roadrecon gather
Integration with Other Tools
BloodHound Integration
python
# Convert ROADtools data to BloodHound format
def convert_to_bloodhound(db_path):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Get users and groups
cursor.execute("SELECT * FROM users")
users = cursor.fetchall()
cursor.execute("SELECT * FROM groups")
groups = cursor.fetchall()
# Create BloodHound JSON
bloodhound_data = {
'users': [],
'groups': [],
'computers': [],
'domains': []
}
for user in users:
bloodhound_data['users'].append({
'ObjectIdentifier': user['id'],
'PrimaryGroupSID': user['primaryGroupId'],
'Properties': {
'name': user['userPrincipalName'],
'displayname': user['displayName'],
'enabled': user['accountEnabled']
}
})
return bloodhound_data
PowerShell Integration
powershell
# Use ROADtools data in PowerShell
$roadData = Get-Content -Path "azure_data.json" | ConvertFrom-Json
# Find privileged users
$privilegedUsers = $roadData.users | Where-Object {
$_.assignedRoles -contains "Global Administrator"
}
# Export to CSV
$privilegedUsers | Export-Csv -Path "privileged_users.csv" -NoTypeInformation
Resources
- ROADtools GitHub Repository
- ROADtools Documentation
- Dirk-Jan Mollema Blog
- Azure AD Security Research
- Azure AD Attack and Defense
This cheat sheet provides a comprehensive reference for using ROADtools. Always ensure you have proper authorization before conducting Azure AD assessments.