Skip to content

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

CommandDescription
roadrecon authAuthenticate to Azure AD
roadrecon auth --device-codeUse device code flow
roadrecon auth --refresh-tokenUse refresh token
roadrecon auth --access-tokenUse access token

Data Gathering Commands

CommandDescription
roadrecon gatherGather Azure AD data
roadrecon gather --usersGather user data only
roadrecon gather --groupsGather group data only
roadrecon gather --applicationsGather application data
roadrecon gather --devicesGather device data

Analysis Commands

CommandDescription
roadrecon guiStart web interface
roadrecon pluginRun analysis plugins
roadrecon dumpExport 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
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


This cheat sheet provides a comprehensive reference for using ROADtools. Always ensure you have proper authorization before conducting Azure AD assessments.