ROADtools Azure AD Assessment Framework hoja de trucos
Overview
ROADtools (The Azure AD exploration framework) is a collection of tools developed by Dirk-Jan Mollema for Azure Active Directory reconocimiento and assessment. It provides comprehensive capabilities for exploring Azure AD environments, analyzing configuracións, and identifying security issues.
⚠️ Warning: This tool is intended for authorized pruebas de penetración and security assessments only. Ensure you have proper autorización before using in any environment.
instalación
pip instalación
# 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 instalación
# 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 instalación
# 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 uso
autenticación Methods
# nombre de usuario/contraseña autenticación
roadrecon auth -u user@domain.com -p contraseña
# Device code autenticación
roadrecon auth --device-code
# Access token autenticación
roadrecon auth --access-token <token>
# Refresh token autenticación
roadrecon auth --refresh-token <token>
# certificado autenticación
roadrecon auth --cert-thumbprint <thumbprint> --cert-file cert.pem --clave-file clave.pem
Data Gathering
# Gather all available data
roadrecon gather
# Gather specific data types
roadrecon gather --users --groups --applications
# Gather with specific autenticación
roadrecon gather --tokens roadtokens.json
# Gather with rate limiting
roadrecon gather --rate-limit 10
comando Reference
autenticación comandos
| | comando | Descripción | |
| --- | --- |
| | 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 comandos
| | comando | Descripción | |
| --- | --- |
| | 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 comandos
| | comando | Descripción | |
| --- | --- |
| | roadrecon gui
| Start web interface | |
| | roadrecon plugin
| Run analysis plugins | |
| | roadrecon dump
| Expuerto data to files | |
Data Collection
User enumeración
# 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 enumeración
# 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 enumeración
# Collect all applications
roadrecon gather --applications
# Collect servicio principals
roadrecon gather --servicioPrincipals
# Collect application permissions
roadrecon gather --applications --expand appRoles,oauth2PermissionScopes
# Collect enterprise applications
roadrecon gather --servicioPrincipals --filter "servicioPrincipalType eq 'Application'"
Device enumeración
# 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
# 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,directRepuertos
Bulk Operations
# 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 --hilos 5
token Management
# 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
# Start web interface
roadrecon gui
# Start on specific puerto
roadrecon gui --puerto 8080
# Start with specific database
roadrecon gui --database roadrecon.db
# Start with autenticación
roadrecon gui --auth
Navigation and Analysis
# 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
-- 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 servicioPrincipals 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
# List available plugins
roadrecon plugin --list
# Run specific plugin
roadrecon plugin --name privileged-users
# Run all plugins
roadrecon plugin --all
# Run plugin with parámetros
roadrecon plugin --name custom-plugin --param value
Custom Plugin Development
# ejemplo plugin structure
from roadtools.roadrecon.plugins impuerto PluginBase
class CustomPlugin(PluginBase):
name = "custom-analysis"
Descripción = "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 Expuerto and Repuertoing
Expuerto opcións
# Expuerto to JSON
roadrecon dump --format json --output azure_data.json
# Expuerto to CSV
roadrecon dump --format csv --output azure_data.csv
# Expuerto specific data types
roadrecon dump --users --format json --output users.json
# Expuerto with filters
roadrecon dump --users --filter "department eq 'IT'" --format csv
Custom Repuertos
# Generate custom repuertos
impuerto json
impuerto sqlite3
def generate_security_repuerto(db_path):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
repuerto = \\\\{
'summary': \\\\{\\\\},
'findings': [],
'recommendations': []
\\\\}
# Count users
cursor.execute("SELECT COUNT(*) FROM users WHERE accountEnabled = 1")
repuerto['summary']['active_users'] = cursor.fetchone()[0]
# Count guest users
cursor.execute("SELECT COUNT(*) FROM users WHERE userType = 'Guest'")
repuerto['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 strongautenticaciónMethods
)
""")
no_mfa_users = cursor.fetchall()
if no_mfa_users:
repuerto['findings'].append(\\\\{
'title': 'Users without MFA',
'severity': 'High',
'count': len(no_mfa_users),
'users': no_mfa_users
\\\\})
return repuerto
# uso
repuerto = generate_security_repuerto('roadrecon.db')
print(json.dumps(repuerto, indent=2))
Visualization
# Create visualizations
impuerto matplotlib.pyplot as plt
impuerto 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
escalada de privilegios Paths
-- Find potential escalada de privilegios 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
-- 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 servicioPrincipals 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
# 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
# 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
# Custom user agent rotation
impuerto 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 'impuerto 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 uso
# 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
solución de problemas
autenticación Issues
# Clear cached tokens
rm -rf ~/.roadtools/
# Debug autenticación
roadrecon auth --debug
# Check token validity
roadrecon auth --validate-token
# Manual token refresh
roadrecon auth --refresh-token <token>
API Rate Limiting
# 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
# Verify permissions
roadrecon auth --check-permissions
# Test connectivity
roadrecon gather --test-conexión
# Debug API calls
roadrecon gather --debug --verbose
# Check for API changes
roadrecon gather --api-version beta
Database Issues
# 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
# 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
# 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"
\\\\}
# Expuerto to CSV
$privilegedUsers|Expuerto-Csv -Path "privileged_users.csv" -NoTypeInformation
Resources
- ROADtools GitHub Repository
- ROADtools documentación
- Dirk-Jan Mollema Blog
- Azure AD Security Research
- Azure AD Attack and Defense
This hoja de trucos provides a comprehensive reference for using ROADtools. Always ensure you have proper autorización before conducting Azure AD assessments.