Security in Odoo(中文翻译)


除了使用自定义代码手动管理访问之外,Odoo还提供了两种主要的数据驱动机制来管理或限制对数据的访问。

这两种机制都通过组链接到特定的用户:用户属于任意数量的组,安全机制与组相关联,从而将安全机制应用于用户

class res.groups

  • name
    作为用户可读的组标识(阐明组的角色/目的)
  • category_id
    模块类别用于将组与Odoo应用程序(~一组相关的业务模型)关联,并将它们转换为用户表单中的独家选择
  • implied_ids
    与此用户一起设置的其他组。这是一种方便的伪继承关系:可以显式地从用户中删除隐含组,而不删除隐含组。
  • comment
    关于小组的补充说明,例如

Access Rights(访问权限)

授予对给定操作集的整个模型的访问权限。如果没有访问权限与用户(通过其组)在模型上的操作匹配,则该用户没有访问权限。

访问权限是附加的,用户的访问权限是他们在所有组中访问权限的并集,例如,给定一个用户属于授予读和创建访问权限的组a,而属于授予更新访问权限的组B,则该用户将拥有创建、读和更新这三种权限。

class ir.model.access

  • name
    团体的目的或角色
  • model_id
    ACL控制其访问权限的模型
  • group_id
    授予访问权限的res.groups,空group_id表示将ACL授予每个用户(非雇员,例如门户或公共用户)

perm_method属性在设置时授予相应的CRUD访问权限,默认情况下它们都是不设置的

  • perm_create
  • perm_read
  • perm_write
  • perm_unlink

Record Rules(记录规则)

记录规则是为了允许某项操作而必须满足的条件。记录规则按照访问权限逐个记录进行评估。

记录规则为default-allow:如果访问权限允许访问,且该用户的操作和型号没有任何规则,则授予访问权限

class ir.rule

  • name
    规则的描述

  • model_id
    规则适用的模型

  • groups
    授予(或不授予)访问权限的res.group。可以指定多个组。如果没有指定组,则该规则是全局的,其处理方式与“组”规则不同(见下文)。

  • global
    以组为基础计算,提供对规则的全局状态(或非全局状态)的方便访问

  • domain_force
    作为指定为域的谓词,如果域与记录匹配,则允许所选操作,否则禁止所选操作

    域是一个python表达式,可以使用以下变量

    • time
      Python的时间模块
    • user
      当前用户,作为单例记录集
    • company_id
      当前用户当前选择的公司作为单个公司id(不是记录集)
    • company_ids
      当前用户可以访问的所有公司作为公司id列表(而不是记录集),请参阅安全性规则了解更多详细信息

    perm_method具有与ir.model.access完全不同的语义:对于规则,它们指定规则适用于哪个操作。如果未选择某个操作,则不会检查该规则,就像该规则不存在一样

    默认选中所有操作

  • perm_create

  • perm_read

  • perm_write

  • perm_unlink

Global rules versus group rules(全局规则与组规则)

全局规则和群体规则在组成和组合方式上有很大的不同

  • 全局规则相交,如果应用两个全局规则,则必须同时满足两个规则才能授予访问,这意味着添加全局规则总是进一步限制访问
  • 组规则统一,如果应用两个组规则,则可以满足任何一个来授予访问。这意味着添加组规则可以扩展访问,但不能超出全局规则定义的范围
  • 全局规则集和组规则集相交,这意味着添加到给定全局规则集的第一个组规则将限制访问

危险
创建多个全局规则是有风险的,因为可能会创建不重叠的规则集,这将删除所有访问权限

Field Access(字段访问 /域访问)

ORM字段可以具有groups属性,提供组列表(作为逗号分隔的外部标识符字符串)。

如果当前用户不在列出的组中,他将无权访问该字段:

  • 受限制的字段将自动从请求的视图中删除
  • 限制性字段将从fields_get()响应中删除
  • 尝试(显式地)读取或写入受限制的字段将导致访问错误

Security Pitfalls(安全隐患)

作为开发人员,了解安全机制并避免导致不安全代码的常见错误非常重要

Unsafe Public Methods(不安全的公共方法)

任何公共方法都可以通过带有所选参数的RPC调用来执行。以_开头的方法不能从动作按钮或外部API调用。

在公共方法上,不能信任执行方法的记录和参数,ACL仅在CRUD操作期间进行验证

# this method is public and its arguments can not be trusted
def action_done(self):
    if self.state == "draft" and self.user_has_groups('base.manager'):
        self._set_state("done")

# this method is private and can only be called from other python methods
def _set_state(self, new_state):
    self.sudo().write({"state": new_state})

将方法设为私有显然是不够的,必须注意正确地使用它。

Bypassing the ORM(绕过ORM)

当ORM可以做同样的事情时,永远不要直接使用数据库游标!通过这样做,您可以绕过所有ORM功能,可能是自动行为,如翻译、字段无效、活动、访问权限等。

而且,您还可能使代码更难阅读,并且可能不那么安全。

# very very wrong
self.env.cr.execute('SELECT id FROM auction_lots WHERE auction_id in (' + ','.join(map(str, ids))+') AND state=%s AND obj_price > 0', ('draft',))
auction_lots_ids = [x[0] for x in self.env.cr.fetchall()]

# no injection, but still wrong
self.env.cr.execute('SELECT id FROM auction_lots WHERE auction_id in %s '\
           'AND state=%s AND obj_price > 0', (tuple(ids), 'draft',))
auction_lots_ids = [x[0] for x in self.env.cr.fetchall()]

# better
auction_lots_ids = self.search([('auction_id','in',ids), ('state','=','draft'), ('obj_price','>',0)])

SQL injections

在使用手动SQL查询时,必须注意不要引入SQL注入漏洞。当用户输入被错误地过滤或引用不当,允许攻击者在SQL查询中引入不受欢迎的子句(例如绕过过滤器或执行UPDATE或DELETE命令)时,就会出现该漏洞。

确保安全的最佳方法是永远不要使用Python字符串连接(+)或字符串参数插值(%)将变量传递给SQL查询字符串。

第二个几乎同样重要的原因是,决定如何格式化查询参数是数据库抽象层(psycopg2)的工作,而不是您的工作!例如,psycopg2知道,当您传递一个值列表时,它需要将它们格式化为逗号分隔的列表,并将其括在括号中!

# the following is very bad:
#   - it's a SQL injection vulnerability
#   - it's unreadable
#   - it's not your job to format the list of ids
self.env.cr.execute('SELECT distinct child_id FROM account_account_consol_rel ' +
           'WHERE parent_id IN ('+','.join(map(str, ids))+')')

# better
self.env.cr.execute('SELECT DISTINCT child_id '\
           'FROM account_account_consol_rel '\
           'WHERE parent_id IN %s',
           (tuple(ids),))

这一点非常重要,所以在重构时也要小心,最重要的是不要复制这些模式!
这里有一个令人难忘的例子,可以帮助您记住问题是关于什么的(但不要复制代码)。在继续之前,请务必阅读pyscopg2的在线文档,以了解如何正确使用它:

  • 查询参数的问题
  • 如何使用psycopg2传递参数
  • 高级参数类型
  • Psycopg文档

Unescaped field content(未转义字段内容)

在使用JavaScript和XML呈现内容时,可能会尝试使用t-raw来显示富文本内容。应该避免将其作为频繁使用的XSS向量。

要控制从计算到最终集成到浏览器DOM中的数据的完整性是非常困难的。在引入时正确转义的t-raw在下一次错误修复或重构时可能不再安全

QWeb.render('insecure_template', {
    info_message: "You have an <strong>important</strong> notification",
})
<div t-name="insecure_template">
    <div id="information-bar"><t t-raw="info_message" /></div>
</div>

由于消息内容受到控制,上述代码可能感觉安全,但这是一种不好的做法,一旦该代码在将来发展,可能会导致意外的安全漏洞

// XSS possible with unescaped user provided content !
QWeb.render('insecure_template', {
    info_message: "You have an <strong>important</strong> notification on " \
        + "the product <strong>" + product.name + "</strong>",
})

而以不同的方式格式化模板可以防止此类漏洞。

QWeb.render('secure_template', {
    message: "You have an important notification on the product:",
    subject: product.name
})
<div t-name="secure_template">
    <div id="information-bar">
        <div class="info"><t t-esc="message" /></div>
        <div class="subject"><t t-esc="subject" /></div>
    </div>
</div>
.subject {
    font-weight: bold;
}

Escaping vs Sanitizing(逃跑vs消毒)

重要的
当您混合数据和代码时,无论数据有多安全,转义总是100%强制性的

转义将文本转换为代码。每次将DATA/TEXT与CODE混合时都必须这样做(例如,生成要在safe_eval中求值的HTML或python代码),因为CODE总是需要对TEXT进行编码。这对安全性至关重要,但它也是一个正确性问题。即使没有安全风险(因为文本是100%保证安全或可信的),它仍然是必需的(例如,避免破坏生成的HTML的布局)。

转义永远不会破坏任何特性,只要开发人员确定哪个变量包含TEXT,哪个包含CO

from odoo.tools import html_escape, html_sanitize
data = "<R&D>" # `data` is some TEXT coming from somewhere

# Escaping turns it into CODE, good!
code = html_escape(data)
code
'&lt;R&amp;D&gt;'

# Now you can mix it with other code...
self.message_post(body="<strong>%s</strong>" % code)

消毒将代码转换为更安全的代码(但不是必要的安全代码)。它不适用于TEXT。只有在CODE不受信任时才需要进行清理,因为它全部或部分来自某些用户提供的数据。如果用户提供的数据是TEXT形式的(例如,由用户填写的表单的内容),并且如果该数据在放入CODE之前被正确转义,那么清理是无用的(但仍然可以完成)。但是,如果用户提供的数据没有转义,那么清理将无法按预期工作

# Sanitizing without escaping is BROKEN: data is corrupted!
>>> html_sanitize(data)
''

# Sanitizing *after* escaping is OK!
>>> html_sanitize(code)
'<p>&lt;R&amp;D&gt;</p>'

清理可能会破坏特性,这取决于代码是否包含不安全的模式。这就是为什么fields.Html和tools.html_sanitize()有选项来微调样式等的清理级别。必须根据数据的来源和所需的功能仔细考虑这些选项。卫生处理的安全性与卫生处理的破坏是平衡的:卫生处理越安全,破坏东西的可能性越大

>>code = "<p class='text-warning'>Important Information</p>"
# this will remove the style, which may break features
# but is necessary if the source is untrusted
>> html_sanitize(code, strip_classes=True)
'<p>Important Information</p>'

Evaluating content(评价内容)

有些人可能希望通过eval来解析用户提供的内容。无论如何都应该避免使用eval。可以使用更安全的沙盒方法safe_eval,但它仍然为运行它的用户提供了巨大的功能,并且必须仅为受信任的特权用户保留,因为它打破了代码和数据之间的障碍

# very bad
domain = eval(self.filter_domain)
return self.search(domain)

# better but still not recommended
from odoo.tools import safe_eval
domain = safe_eval(self.filter_domain)
return self.search(domain)

# good
from ast import literal_eval
domain = literal_eval(self.filter_domain)
return self.search(domain)

解析内容不需要eval
在这里插入图片描述

Accessing object attributes(访问对象属性)

如果需要动态检索或修改记录的值,则可能需要使用getattr和setattr方法

# unsafe retrieval of a field value
def _get_state_value(self, res_id, state_field):
    record = self.sudo().browse(res_id)
    return getattr(record, state_field, False)

然而,这段代码并不安全,因为它允许访问记录的任何属性,包括私有属性或方法。

已经定义了记录集的__getitem__,并且可以轻松安全地访问动态字段值:

# better retrieval of a field value
def _get_state_value(self, res_id, state_field):
    record = self.sudo().browse(res_id)
    return record[state_field]

上面的方法显然仍然过于乐观,必须对记录id和字段值进行额外的验证

猜你喜欢

转载自blog.csdn.net/weixin_44141284/article/details/130921643
今日推荐