HIPAA Technical Safeguards: Web Application Security Testing Guide

HIPAA Technical Safeguards (45 CFR §164.312) require covered entities and business associates to implement technical policies protecting electronic Protected Health Information (ePHI). Web application security testing for HIPAA focuses on four required implementation specifications: access controls (unique user identification, emergency access, automatic logoff, encryption/decryption), audit controls (activity logging), integrity controls (ePHI alteration detection), and transmission security (TLS, encryption in transit). This guide covers practical assessment of each safeguard category.

Table of Contents

  1. §164.312(a) — Access Control Testing
  2. §164.312(b) — Audit Controls and Log Review
  3. §164.312(c) — Integrity Controls Testing
  4. §164.312(e) — Transmission Security Assessment
  5. HIPAA Technical Safeguards Hardening Checklist

§164.312(a) — Access Control Testing

# HIPAA §164.312(a) Access Control — web application assessment
TARGET="https://ehealthapp.example.com"

# 1. Unique User Identification (Required)
# Each user must have a unique identifier — shared/generic accounts are a HIPAA violation
# Test for shared/generic accounts
for USER in "admin" "root" "test" "demo" "shared" "staff" "nurse" "doctor" "clinician"; do
  RESP=$(curl -s -o /dev/null -w "%{http_code}" -X POST "${TARGET}/api/auth/login" \
    -H "Content-Type: application/json" \
    -d "{\"username\":\"${USER}\",\"password\":\"${USER}\"}" 2>/dev/null)
  echo "${USER}: HTTP ${RESP}"
done
# Shared accounts (admin/admin, staff/staff) that succeed are a §164.312(a)(2)(i) violation

# 2. Emergency Access Procedure (Required)
# Verify emergency access is a defined procedure, not a permanent backdoor
# Look for emergency/break-glass endpoints that bypass normal authentication
for EP in "/emergency-access" "/break-glass" "/override" "/emergency" "/admin/emergency"; do
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${TARGET}${EP}" 2>/dev/null)
  echo "${EP}: HTTP ${STATUS}"
done

# 3. Automatic Logoff (Addressable)
# Session must auto-expire after a defined period of inactivity
# Extract session cookie and test if it remains valid after the defined timeout
SESSION_COOKIE=$(curl -s -c /tmp/hipaa_cookies.txt -X POST "${TARGET}/api/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"testuser","password":"testpass"}' 2>/dev/null | \
  python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('sessionToken',''))" 2>/dev/null)

# Check session cookie attributes
curl -s -v "${TARGET}/api/user/profile" -H "Authorization: Bearer ${SESSION_COOKIE}" 2>&1 | \
  grep -i "set-cookie\|expires\|max-age\|secure\|httponly\|samesite"
# Expected: Secure; HttpOnly; SameSite=Strict; max-age set to session timeout value
# Missing Secure flag = ePHI session token transmitted in plaintext (HIPAA violation)

# 4. Encryption and Decryption (Addressable)
# ePHI at rest must be encrypted — test for plaintext PHI in API responses
curl -s "${TARGET}/api/patients/search?q=doe" \
  -H "Authorization: Bearer ${SESSION_COOKIE}" 2>/dev/null | \
  python3 -c "
import json, sys, re
try:
    d = json.load(sys.stdin)
    text = json.dumps(d)
    # Look for common PHI field patterns in responses
    phi_fields = ['ssn', 'social_security', 'dob', 'date_of_birth', 'mrn', 'medical_record',
                  'diagnosis', 'icd', 'medication', 'prescription', 'insurance_id', 'npi']
    for f in phi_fields:
        if f in text.lower():
            print(f'PHI FIELD PRESENT: {f}')
    # SSN pattern check
    ssn = re.findall(r'\b\d{3}-\d{2}-\d{4}\b', text)
    if ssn: print(f'UNMASKED SSN: {ssn[0]}')
except: pass
" 2>/dev/null

# Test for PHI exposure in URL parameters (PHI in URLs logged in proxy/server logs)
curl -s "${TARGET}/api/patient?ssn=123-45-6789" -I 2>/dev/null | head -10
# PHI in URL query params = HIPAA violation (server logs, browser history, referrer headers)

§164.312(b) — Audit Controls and Log Review

# HIPAA §164.312(b) Audit Controls — activity logging assessment
TARGET="https://ehealthapp.example.com"
SESSION_COOKIE="valid-session-token"

# 1. Verify audit logging is active for ePHI access
# Access a patient record — the action must be logged
PATIENT_ID="12345"
curl -s "${TARGET}/api/patients/${PATIENT_ID}" \
  -H "Authorization: Bearer ${SESSION_COOKIE}" 2>/dev/null | head -5

# 2. Test audit log accessibility — logs should NOT be accessible via the web app
# (audit logs must be protected from unauthorized modification)
for LOG_PATH in "/audit-logs" "/logs/audit" "/api/audit" "/admin/audit" \
    "/logs" "/api/logs" "/audit" "/_logs" "/log.txt" "/access.log"; do
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${TARGET}${LOG_PATH}" \
    -H "Authorization: Bearer ${SESSION_COOKIE}" 2>/dev/null)
  echo "${LOG_PATH}: HTTP ${STATUS}"
done
# Any 200 for unauthenticated access to logs = HIPAA violation

# 3. Log tampering test — audit log integrity
# Logs must be append-only and tamper-evident
# Check if log entries can be modified or deleted via API
curl -s -X DELETE "${TARGET}/api/audit/logs/latest" \
  -H "Authorization: Bearer ${SESSION_COOKIE}" 2>/dev/null | head -5
# Should return 403 or 405 — never 200/204

curl -s -X PUT "${TARGET}/api/audit/logs/12345" \
  -H "Authorization: Bearer ${SESSION_COOKIE}" \
  -H "Content-Type: application/json" \
  -d '{"action":"modified_after_fact"}' 2>/dev/null | head -5
# Should return 403 or 404

# 4. Test that failed login attempts are logged (required for access control)
for i in {1..5}; do
  curl -s -X POST "${TARGET}/api/auth/login" \
    -H "Content-Type: application/json" \
    -d '{"username":"admin","password":"wrongpassword"}' 2>/dev/null | head -3
done
# Verify: after 5 failed attempts, account should be locked (§164.312(a)(2)(i) implementation)
# and all failed attempts must appear in the audit log

# 5. Database-level audit log check (if shell access)
# PostgreSQL — check if pg_audit is enabled
psql -h db.internal -U hipaa_user -d ehr_db -c "
SELECT name, setting FROM pg_settings
WHERE name IN ('log_statement', 'log_duration', 'log_connections',
               'log_disconnections', 'log_min_duration_statement');
" 2>/dev/null

# MySQL — check general query log and audit plugin
mysql -h db.internal -u root -p -e "
SHOW VARIABLES LIKE 'general_log%';
SHOW VARIABLES LIKE 'audit_log%';
SHOW PLUGINS WHERE Status='ACTIVE' AND Name LIKE '%audit%';
" 2>/dev/null

§164.312(c) — Integrity Controls Testing

# HIPAA §164.312(c) Integrity — ePHI alteration and destruction prevention
TARGET="https://ehealthapp.example.com"
SESSION_COOKIE="valid-session-token"

# 1. ePHI integrity — test for unauthorized modification of patient records
# Attempt to modify a patient record without appropriate authorization
curl -s -X PATCH "${TARGET}/api/patients/99999" \
  -H "Authorization: Bearer ${SESSION_COOKIE}" \
  -H "Content-Type: application/json" \
  -d '{"diagnosis":"modified_entry","icd_code":"Z00.00"}' 2>/dev/null | head -5
# Should enforce role-based authorization — only treating providers may modify records

# 2. Mass assignment — test for ePHI field injection via API
curl -s -X PUT "${TARGET}/api/patients/12345/profile" \
  -H "Authorization: Bearer ${SESSION_COOKIE}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Test Patient",
    "is_admin": true,
    "role": "admin",
    "clearance_level": "all",
    "patient_id": "99999",
    "verified": true,
    "consent_status": "granted"
  }' 2>/dev/null | python3 -c "
import json, sys
try:
    d = json.load(sys.stdin)
    print('Role change:', d.get('role'))
    print('Admin flag:', d.get('is_admin'))
    print('Consent:', d.get('consent_status'))
except: pass
" 2>/dev/null

# 3. Hash integrity verification — ePHI must not be alterable without detection
# Check if the API returns checksums, ETags, or version numbers for patient records
curl -s -I "${TARGET}/api/patients/12345" \
  -H "Authorization: Bearer ${SESSION_COOKIE}" 2>/dev/null | \
  grep -i "etag\|x-content-hash\|x-record-hash\|x-checksum\|last-modified"
# Good: ETag header allows integrity verification
# Missing: no integrity mechanism for ePHI records

# 4. Soft-delete vs hard-delete — deleted ePHI must remain recoverable for 6 years
curl -s -X DELETE "${TARGET}/api/patients/12345/records/456" \
  -H "Authorization: Bearer ${SESSION_COOKIE}" 2>/dev/null | head -5
# Good: 200 with {"deleted":true,"recoverable":true,"retention_days":2190}
# Bad: 204 No Content with permanent deletion (HIPAA retention violation)

# Verify the record is soft-deleted (still accessible to admin)
curl -s "${TARGET}/api/admin/patients/12345/records/456?include_deleted=true" \
  -H "Authorization: Bearer ${SESSION_COOKIE}" 2>/dev/null | \
  python3 -c "
import json, sys
try:
    d = json.load(sys.stdin)
    print('Deleted flag:', d.get('deleted', d.get('is_deleted')))
    print('Deletion date:', d.get('deleted_at'))
    print('Record still present:', 'id' in d)
except: pass
" 2>/dev/null

§164.312(e) — Transmission Security Assessment

# HIPAA §164.312(e) Transmission Security — TLS and encryption in transit
TARGET_HOST="ehealthapp.example.com"

# 1. TLS version and cipher assessment
# HIPAA requires encryption — TLS 1.2 minimum, TLS 1.3 recommended
nmap --script ssl-enum-ciphers -p 443 "${TARGET_HOST}" 2>/dev/null | \
  grep -A 5 "TLSv\|SSLv\|least strength"

# Check for deprecated TLS versions
openssl s_client -connect "${TARGET_HOST}:443" -tls1 2>&1 | grep "handshake\|CONNECTED\|ERROR"
openssl s_client -connect "${TARGET_HOST}:443" -tls1_1 2>&1 | grep "handshake\|CONNECTED\|ERROR"
openssl s_client -connect "${TARGET_HOST}:443" -tls1_2 2>&1 | grep "handshake\|CONNECTED"
openssl s_client -connect "${TARGET_HOST}:443" -tls1_3 2>&1 | grep "handshake\|CONNECTED"
# TLS 1.0 or 1.1 connected = HIPAA transmission security failure

# 2. Certificate validation
openssl s_client -connect "${TARGET_HOST}:443" 2>/dev/null | \
  openssl x509 -noout -text | \
  grep -E "Subject:|Issuer:|Not Before:|Not After:|Subject Alternative Name"
# Check: certificate is not self-signed, not expired, covers all subdomains

# 3. HTTP Strict Transport Security (HSTS)
curl -s -I "https://${TARGET_HOST}" 2>/dev/null | grep -i "strict-transport\|hsts"
# Required: Strict-Transport-Security: max-age=31536000; includeSubDomains
# Missing HSTS means browsers may connect without TLS on first visit

# 4. HTTP to HTTPS redirect — all ePHI traffic must be encrypted
curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" \
  "http://${TARGET_HOST}/api/patients" 2>/dev/null
# Expected: 301 -> https://... (redirect to HTTPS)
# Bad: 200 (HTTP accepted without redirect — ePHI transmitted in plaintext)

# 5. API endpoints — verify all ePHI endpoints enforce TLS
for ENDPOINT in "/api/patients" "/api/records" "/api/prescriptions" \
    "/api/appointments" "/api/billing" "/api/lab-results"; do
  HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
    "http://${TARGET_HOST}${ENDPOINT}" 2>/dev/null)
  HTTPS_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
    "https://${TARGET_HOST}${ENDPOINT}" 2>/dev/null)
  echo "HTTP:${HTTP_CODE} HTTPS:${HTTPS_CODE} — ${ENDPOINT}"
done

# 6. API response headers — verify security headers for ePHI responses
curl -s -I "https://${TARGET_HOST}/api/patients/12345" \
  -H "Authorization: Bearer valid-token" 2>/dev/null | \
  grep -i "cache-control\|pragma\|x-frame\|content-security\|referrer-policy"
# ePHI API responses must include: Cache-Control: no-store, no-cache
# Missing Cache-Control = ePHI cached in browser/proxy (HIPAA violation)

# 7. Mixed content check — HTTPS page loading HTTP resources
curl -s "https://${TARGET_HOST}/" 2>/dev/null | \
  grep -Eo 'src="http://[^"]*"|href="http://[^"]*"' | head -10
# Any http:// resources on HTTPS pages = mixed content vulnerability

HIPAA Technical Safeguards Hardening Checklist

HIPAA §164.312 Technical Safeguards Implementation Checklist:
HIPAA Safeguard§164.312 ReferenceTest MethodRisk
Shared/generic accounts (admin, staff, nurse) bypassing unique user ID requirement§164.312(a)(2)(i)Login with common generic credentials; shared accounts mean ePHI access is not attributable to individualsCritical
Session tokens transmitted without Secure/HttpOnly flags, exposed to interception§164.312(a)(2)(iv) + §164.312(e)Review Set-Cookie headers; missing Secure = token sent over HTTP; missing HttpOnly = XSS token theftCritical
TLS 1.0 or 1.1 accepted — deprecated protocols with known vulnerabilities (POODLE, BEAST)§164.312(e)(2)(ii)openssl s_client -connect host:443 -tls1 — connected = violationHigh
ePHI responses missing Cache-Control: no-store, cached in browser or intermediary proxies§164.312(e)(1)curl -I API response; check Cache-Control header; absent = ePHI may be cached by browser, CDN, or ISP proxyHigh
Audit logs accessible or modifiable via web application API without restriction§164.312(b)GET/DELETE /api/audit/logs with low-privilege token; 200 = logs readable; 204 = logs deletable — both violationsHigh
PHI in URL query parameters logged in access logs, proxy logs, and browser history§164.312(a)(1)Check API endpoints for ?ssn=, ?mrn=, ?dob= parameters; PHI in URLs = logged in many places outside application controlHigh
Patient records permanently deleted instead of soft-deleted, violating 6-year retention§164.312(c)(2) + §164.530(j)DELETE /api/patients/{id}/records/{id} — verify record is still accessible to admin with include_deleted=trueMedium

Automate HIPAA Technical Safeguards Security Testing

Ironimo tests ePHI-handling web applications for HIPAA Technical Safeguards compliance: §164.312(a) access control including session cookie security attributes and automatic logoff timeout verification; §164.312(b) audit control accessibility testing to ensure logs are not readable or modifiable via the API; §164.312(e) transmission security including TLS version enumeration, HSTS header verification, HTTP-to-HTTPS redirect testing, and Cache-Control header validation on all ePHI API responses; plus PHI exposure in URL parameters and shared account detection.

Start free scan