ROADtools Azure AD Assessment Framework Cheat Sheet¶
Überblick¶
ROADtools (The Azure AD Exploration Framework) ist eine Sammlung von Werkzeugen, die von Dirk-Jan Mollema für die Erkundung und Bewertung von Azure Active Directory entwickelt wurden. Es bietet umfassende Möglichkeiten, um Azure AD Umgebungen zu erkunden, Konfigurationen zu analysieren und Sicherheitsprobleme zu identifizieren.
ZEIT Warnung: Dieses Tool ist nur für autorisierte Penetrationstests und Sicherheitsbewertungen gedacht. Stellen Sie sicher, dass Sie eine ordnungsgemäße Genehmigung vor der Verwendung in jeder Umgebung haben.
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 ```_
Manuelle 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 ```_
Basisnutzung¶
Authentifizierungsmethoden¶
```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
Refresh token authentication¶
roadrecon auth --refresh-token
Certificate authentication¶
roadrecon auth --cert-thumbprint
Daten sammeln¶
```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 ```_
Befehlsnummer¶
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 | | |
Daten sammeln Befehle¶
| | | 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 | | |
Analysekommandos¶
| | | Command | Description | | |
| --- | --- |
| | | roadrecon gui
| Start web interface | | |
| | | roadrecon plugin
| Run analysis plugins | | |
| | | roadrecon dump
| Export data to files | | |
Datenerhebung¶
Benutzeraufzählung¶
```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')" ```_
Gruppenaufzählung¶
```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'" ```_
Antragsnummer¶
```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'" ```_
Gerätezählung¶
```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 ```_
Erweiterte Datenerhebung¶
Kundenspezifische Abfragen¶
```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 ```_
Großbetrieb¶
```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
Web Interface Analyse¶
Beginn der 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 und Analyse¶
```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¶
```_
Benutzerdefinierte Abfragen 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¶
Verfügbare 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 ```_
Benutzerdefinierte Plugin Entwicklung¶
```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)
```_
Datenexport und Reporting¶
Exportoptionen¶
```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 ```_
Zollberichte¶
```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)) ```_
Visualisierung¶
```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')
```_
Sicherheitsanalyse¶
Vorrechte Eskalation Pfade¶
```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; ```_
Anwendungsberechtigungsanalyse¶
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;
_
Bedingte Zugriffsanalyse¶
```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 und Stealth¶
Grenzwerte¶
```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-Nutzung¶
```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 ```_
Fehlerbehebung¶
Authentifizierungsfragen¶
```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
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 ```_
Probleme der Datenerhebung¶
```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 ```_
Datenbankprobleme¶
```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 mit anderen Tools¶
BlutHound 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
```_
Integration von PowerShell¶
```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 ```_
Ressourcen¶
- ROADtools GitHub Repository
- ROADtools Dokumentation
- [Dirk-Jan Mollema Blog](LINK_5_ -%20(LINK_5_)
- Angriff und Verteidigung Azure AD
--
*Dieses Betrügereiblatt bietet eine umfassende Referenz für die Verwendung von ROADtools. Stellen Sie immer sicher, dass Sie eine ordnungsgemäße Genehmigung haben, bevor Sie Azure AD Bewertungen durchführen. *