Aller au contenu

ROADtools Azure AD Assessment Framework aide-mémoire

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 tests de pénétration and security assessments only. Ensure you have proper autorisation before using in any environment.

Installation

pip Installation

# 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

# 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

# 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 utilisation

authentification Methods

# nom d'utilisateur/mot de passe authentification
roadrecon auth -u user@domain.com -p mot de passe

# Device code authentification
roadrecon auth --device-code

# Access jeton authentification
roadrecon auth --access-jeton <jeton>

# Refresh jeton authentification
roadrecon auth --refresh-jeton <jeton>

# certificat authentification
roadrecon auth --cert-thumbprint <thumbprint> --cert-file cert.pem --clé-file clé.pem

Data Gathering

# Gather all available data
roadrecon gather

# Gather specific data types
roadrecon gather --users --groups --applications

# Gather with specific authentification
roadrecon gather --jetons roadjetons.json

# Gather with rate limiting
roadrecon gather --rate-limit 10

commande Reference

authentification commandes

| | commande | Description | | | --- | --- | | | roadrecon auth | Authenticate to Azure AD | | | | roadrecon auth --device-code | Use device code flow | | | | roadrecon auth --refresh-jeton | Use refresh jeton | | | | roadrecon auth --access-jeton | Use access jeton | |

Data Gathering commandes

| | commande | 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 commandes

| | commande | Description | | | --- | --- | | | roadrecon gui | Start web interface | | | | roadrecon plugin | Run analysis plugins | | | | roadrecon dump | Export data to files | |

Data Collection

User énumération

# 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 énumération

# 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 énumération

# 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 énumération

# 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,directReports

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 --threads 5

jeton Management

# Save jetons for reuse
roadrecon auth --save-jetons jetons.json

# Load saved jetons
roadrecon gather --jetons jetons.json

# Refresh expired jetons
roadrecon auth --refresh-jeton <jeton> --save-jetons jetons.json

Web Interface Analysis

Starting the GUI

# Start web interface
roadrecon gui

# Start on specific port
roadrecon gui --port 8080

# Start with specific database
roadrecon gui --database roadrecon.db

# Start with authentification
roadrecon gui --auth
# Access web interface
# http://localhôte: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 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

# List available plugins
roadrecon plugin --list

# Run specific plugin
roadrecon plugin --name privileged-users

# Run all plugins
roadrecon plugin --all

# Run plugin with paramètres
roadrecon plugin --name custom-plugin --param value

Custom Plugin Development

# exemple 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

# 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

# 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 strongauthentificationMethods
        )
    """)

    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

# utilisation
report = generate_security_report('roadrecon.db')
print(json.dumps(report, indent=2))

Visualization

# 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

escalade de privilèges Paths

-- Find potential escalade de privilèges 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 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

# 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
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 utilisation

# 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

dépannage

authentification Issues

# Clear cached jetons
rm -rf ~/.roadtools/

# Debug authentification
roadrecon auth --debug

# Check jeton validity
roadrecon auth --validate-jeton

# Manual jeton refresh
roadrecon auth --refresh-jeton <jeton>

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-connexion

# 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"
\\\\}

# Export to CSV
$privilegedUsers|Export-Csv -Path "privileged_users.csv" -NoTypeInformation

Resources


This aide-mémoire provides a comprehensive reference for using ROADtools. Always ensure you have proper autorisation before conducting Azure AD assessments.