【问题标题】:How do I prevent execution of arbitrary commands from a Django app making system calls?如何防止从 Django 应用程序执行系统调用的任意命令?
【发布时间】:2009-04-20 15:03:41
【问题描述】:

我正在开发一个 Django 应用程序,它必须对服务器上的外部程序进行系统调用。在为系统调用创建命令时,应用程序从表单中获取值并将它们用作调用的参数。我想这意味着人们基本上可以使用虚假参数并编写任意命令供 shell 执行(例如,只需放置一个分号,然后 rm -rf *)。

这很糟糕。虽然大多数用户不是恶意的,但这是一个潜在的安全问题。如何处理这些潜在的利用点?

编辑(澄清):用户将看到一个表单,该表单由每个参数和选项的各个字段分开。但是,某些字段将作为开放文本字段提供。所有这些字段都被合并并提供给subprocess.check_call()。但是,从技术上讲,这与仅向用户提供命令提示符并没有太大的区别。这一定是相当普遍的,所以其他开发人员会做些什么来清理输入,以免他们收到Bobby Tables

【问题讨论】:

    标签: python django security


    【解决方案1】:

    根据我对问题的理解,我假设您没有让用户指定要在 shell 上运行的命令,而只是这些命令的参数。在这种情况下,您可以通过使用subprocess 模块和 使用shell 来避免shell injection 攻击(即在subprocess.Popen 构造函数中指定使用默认的shell=False 参数。

    哦,并且从不对包含来自用户的任何输入的任何字符串使用os.system()

    【讨论】:

    【解决方案2】:

    从不信任用户。来自网络浏览器的任何数据都应被视为已污染。并且绝对不要尝试通过 JS 或限制可以在 FORM 字段中输入的内容来验证数据。在将其传递给外部应用程序之前,您需要在服务器上进行测试。

    编辑后更新:无论您如何在前端向用户展示表单,后端都应将其视为来自一组文本框,周围有大的闪烁文本说“在这里插入你想要的任何东西!”

    【讨论】:

    • 好点。我不认为客户会负责和验证;在运行命令之前,我将在服务器端进行验证。不过,为了他人的利益,值得您说明。
    【解决方案3】:

    为此,您必须执行以下操作。如果您不知道“选项”和“参数”是什么,请阅读optparse background

    每个“命令”或“请求”实际上都是模型的一个实例。使用某人可能提供的所有参数定义您的请求模型。

    1. 对于简单的选项,您必须提供一个包含特定 CHOICES 列表的字段。对于“开”或“关”的选项(命令行中的-x),您应该提供一个 CHOICE 列表,其中包含两个人类可理解的值(“Do X”和“Do not do X”。)

    2. 对于具有值的选项,您必须提供一个接受选项值的字段。您必须编写一个带有此字段验证的表单。稍后我们将返回选项值验证。

    3. 对于参数,您有第二个模型(第一个模型带有 FK)。这可能与单个 FilePath 字段一样简单,也可能更复杂。同样,您可能还必须提供一个表单来验证此模型的实例。

    选项验证因选项类型而异。您必须将可接受的值缩小为尽可能窄的字符集,并编写一个绝对确定只传递有效字符的解析器。

    您的选项将属于与 optparse 中的选项类型相同的类别——string、int、long、choice、float 和 complex。请注意,int、long、float 和 complex 已经由 Django 的模型和表单定义了验证规则。 Choice 是一种特殊的字符串,已经被 Django 的模型和表单支持。

    剩下的是“字符串”。定义允许的字符串。为这些字符串编写一个正则表达式。使用正则表达式进行验证。大多数时候,您永远不能接受任何形式的引号("' 或 `)。

    最后一步。您的模型有一个方法可以将命令作为字符串序列发出,所有这些都准备好用于subprocess.Popen


    编辑

    这是我们应用程序的支柱。这很常见,我们有一个带有多个表单的模型,每个表单都用于运行一个特殊的批处理命令。该模型非常通用。表单是构建模型对象的非常具体的方法。这就是 Django 设计的工作方式,它有助于适应 Django 深思熟虑的设计模式。

    任何“可用作开放文本字段”的字段都是错误的。每个“打开”的字段都必须有一个正则表达式来指定允许的内容。如果你不能正式化一个正则表达式,你必须重新考虑你在做什么。

    不能用正则表达式约束的字段绝对不能是命令行参数。时期。在使用之前,它必须存储到文件到数据库列中。


    编辑

    像这样。

    class MySubprocessCommandClass( models.Model ):
        myOption_1 = models.CharField( choice = OPTION_1_CHOICES, max_length=2 )
        myOption_2 = models.CharField( max_length=20 )
        etc.
        def theCommand( self ):
            return [ "theCommand", "-p", self.myOption_1, "-r", self.myOption_2, etc. ]
    

    您的表单是该模型的 ModelForm。

    您不必save() 模型的实例。我们保存它们,以便我们可以创建一个准确记录运行内容的日志。

    【讨论】:

    • 感谢您的回答。 Rick Copeland 的回答表明 subprocess 模块中包含安全性,只要 shell=False,将执行一个且只有一个命令;初始命令之后的所有其他部分都被视为命令的参数。由于我一开始就控制一个命令,我真的需要为参数设计正则表达式吗?
    • 另外,我很好奇,为什么需要模型?正如你所说,我很难把所有东西放在一起。我们的表单已经表示为 forms.Form 的子类;我们目前正在使用它进行验证。这如何与模型联系在一起。您所指的命令是来自 django.core.management.base 的实际 (Base)Command 类吗?
    • 与 Management.Command 层次结构无关。不,这个命令是完全独立的。
    • 非常感谢您通过示例代码进行的澄清!这非常有帮助。
    【解决方案4】:

    答案是,不要让用户输入 shell 命令!没有任何借口允许执行任意 shell 命令。

    另外,如果您确实必须允许用户为外部命令提供参数,请不要使用 shell。在 C 语言中,您可以使用 execvp() 直接为命令提供参数,但是对于 django,我不确定您将如何执行此操作(但我确信有办法)。当然,你仍然应该做一些参数清理,特别是如果命令有可能造成任何伤害。

    【讨论】:

      【解决方案5】:

      根据您的命令范围,您可以自定义表单,以便在单独的表单字段中输入参数。您可以更轻松地解析拟合值的那些。 此外,请注意反引号和其他特定于 shell 的内容。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-09-10
        • 2012-10-23
        • 1970-01-01
        • 2020-09-26
        相关资源
        最近更新 更多