Radicale is a widely deployed lightweight self-hosted CalDAV and CardDAV server — it syncs contacts, calendars, and tasks between devices without third-party cloud services. Key assessment areas: Radicale defaults to no authentication in some configurations making all contact and calendar data publicly accessible; the htpasswd credential file supports weak hash formats including plaintext and MD5; all data is stored as plaintext vCard (.vcf) and iCalendar (.ics) files on the server filesystem; and the WebDAV protocol exposes a PROPFIND method for directory enumeration. This guide covers systematic Radicale security assessment.
# Radicale — authentication configuration and no-auth testing
RADICALE_URL="https://cal.example.com"
# Test if Radicale is running without authentication
# A PROPFIND to root reveals collections without credentials
curl -s -X PROPFIND "${RADICALE_URL}/" \
-H "Depth: 1" \
-H "Content-Type: text/xml" \
-d ' ' \
-o /dev/null -w "%{http_code}" 2>/dev/null
# 207 without credentials = auth=none configured (unauthenticated access)
# 401 = auth required (htpasswd or remote-user)
# Enumerate all users and their collections
curl -s -X PROPFIND "${RADICALE_URL}/" \
-H "Depth: 1" \
-H "Content-Type: text/xml" \
-d ' ' \
2>/dev/null | grep -oP '[^<]+ ' | sed 's|<[^>]*>||g'
# htpasswd credential testing — common weak passwords
cat /etc/radicale/htpasswd 2>/dev/null | head -10
# Format: username:$apr1$SALT$HASH (MD5) or username:PLAINTEXT
# MD5 format is crackable with hashcat -m 1600
# Read Radicale config to check auth type
cat /etc/radicale/config 2>/dev/null | grep -A5 '\[auth\]'
# type = none — no authentication
# type = htpasswd — file-based auth
# htpasswd_encryption = md5 or plain — weak formats
# Radicale CalDAV/CardDAV data enumeration
RADICALE_URL="https://cal.example.com"
USER="user"
PASS="password"
# Enumerate all calendars for a user
curl -s -X PROPFIND "${RADICALE_URL}/${USER}/" \
-u "${USER}:${PASS}" \
-H "Depth: 1" \
-H "Content-Type: text/xml" \
-d ' ' \
2>/dev/null | python3 -c "
import sys, re
data = sys.stdin.read()
hrefs = re.findall(r'<[^:]*:href>([^<]+)[^:]*:href>', data)
names = re.findall(r'<[^:]*:displayname>([^<]+)[^:]*:displayname>', data)
print('Collections:')
for h in hrefs:
print(f' {h}')
" 2>/dev/null
# Download all vCard contacts
curl -s "${RADICALE_URL}/${USER}/contacts/" \
-u "${USER}:${PASS}" \
-X REPORT \
-H "Content-Type: text/xml" \
-d ' ' \
2>/dev/null | python3 -c "
import sys, re
data = sys.stdin.read()
# Extract vCard data
vcards = re.findall(r'BEGIN:VCARD.*?END:VCARD', data, re.DOTALL)
print(f'Contacts: {len(vcards)}')
for vc in vcards[:5]:
fn = re.search(r'FN:(.*)', vc)
tel = re.findall(r'TEL[^:]*:(.*)', vc)
email = re.findall(r'EMAIL[^:]*:(.*)', vc)
print(f' Name: {fn.group(1) if fn else \"?\"}')
print(f' Phone: {tel[:2]}')
print(f' Email: {email[:2]}')
" 2>/dev/null
# Download all calendar events
curl -s "${RADICALE_URL}/${USER}/calendar/" \
-u "${USER}:${PASS}" \
-X REPORT \
-H "Content-Type: text/xml" \
-d ' ' \
2>/dev/null | python3 -c "
import sys, re
data = sys.stdin.read()
events = re.findall(r'BEGIN:VEVENT.*?END:VEVENT', data, re.DOTALL)
print(f'Calendar events: {len(events)}')
for ev in events[:5]:
summary = re.search(r'SUMMARY:(.*)', ev)
dtstart = re.search(r'DTSTART[^:]*:(.*)', ev)
location = re.search(r'LOCATION:(.*)', ev)
attendees = re.findall(r'ATTENDEE[^:]*:mailto:(.*)', ev)
print(f' Event: {summary.group(1) if summary else \"?\"}')
print(f' Start: {dtstart.group(1) if dtstart else \"?\"}')
print(f' Location: {location.group(1) if location else \"\"}')
print(f' Attendees: {attendees[:3]}')
" 2>/dev/null
# Radicale filesystem data extraction
# Radicale stores all data as plaintext files
COLLECTIONS="/var/lib/radicale/collections"
# Or: /config/radicale/ (Docker) / ~/.config/radicale/collections/
ls "${COLLECTIONS}/" 2>/dev/null
# Directories named after usernames or paths
# Read all vCard contact files
find "${COLLECTIONS}" -name "*.vcf" 2>/dev/null | while read vcf; do
echo "=== $vcf ==="
cat "$vcf" | grep -E "^(FN|TEL|EMAIL|ADR|ORG|BDAY):" | head -10
done
# Read all iCalendar event files
find "${COLLECTIONS}" -name "*.ics" 2>/dev/null | while read ics; do
echo "=== $ics ==="
cat "$ics" | grep -E "^(SUMMARY|DTSTART|DTEND|LOCATION|ATTENDEE|DESCRIPTION):" | head -10
done
# Docker Radicale configuration
docker inspect radicale 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for c in d:
for e in c.get('Config',{}).get('Env',[]):
print(e)
for m in c.get('Mounts',[]):
print(f'Volume: {m.get(\"Source\")} -> {m.get(\"Destination\")}')
" 2>/dev/null
# If using htpasswd — check hash format and crack
cat /etc/radicale/htpasswd 2>/dev/null
# $apr1$ = MD5-APR (hashcat -m 1600)
# {SHA} prefix = SHA1 base64 (hashcat -m 101)
# No prefix = plaintext
# Crack MD5-APR htpasswd
# hashcat -m 1600 /etc/radicale/htpasswd /usr/share/wordlists/rockyou.txt
| Security Test | Method | Risk |
|---|---|---|
| Unauthenticated PROPFIND collection enumeration | PROPFIND / with Depth: 1 and no credentials — if auth=none, complete list of all user collections; discovery of all users and their calendars/address books without authentication | Critical |
| vCard contact data extraction | REPORT with addressbook-query — all contacts with names, phone numbers, email addresses, home addresses, and birthdays; complete personal address book extraction without database access | High |
| iCalendar event extraction | REPORT with calendar-query — all calendar events with titles, times, locations, attendee email addresses, and private notes/descriptions; complete schedule and meeting history extraction | High |
| htpasswd MD5 hash offline cracking | Read /etc/radicale/htpasswd — MD5-APR hashes (hashcat -m 1600) are crackable with rockyou.txt in minutes for common passwords; SHA1 hashes even faster; bcrypt significantly more resistant | High |
| Filesystem plaintext .vcf and .ics file access | Read /var/lib/radicale/collections/ — all contact and calendar data in plaintext files; no encryption at rest; file system access gives complete data for all users | High |
Ironimo tests Radicale deployments for unauthenticated PROPFIND collection enumeration (auth=none detection), vCard contact data extraction with phone number and address harvesting, iCalendar event attendee email enumeration, htpasswd MD5/SHA1 hash format identification and cracking resistance assessment, filesystem .vcf and .ics plaintext file access, per-user collection isolation boundary testing, HTTP Basic Auth over plain HTTP credential transmission detection, rights module owner_only bypass testing, and Docker RADICALE_CONFIG authentication configuration assessment.
Start free scan