如何防止Django应用通过系统调用执行任意命令?
我正在开发一个Django应用程序,它需要在服务器上调用外部程序。在创建这个系统调用的命令时,应用程序会从一个表单中获取值,并将这些值作为参数传递给调用。我想这意味着,用户实际上可以使用一些无效的参数,随意写命令让系统执行(比如,只要加个分号,然后 rm -rf *
)。
这可不太好。虽然大多数用户并没有恶意,但这确实是一个潜在的安全问题。那我们该如何处理这些可能被利用的地方呢?
补充说明(为了更清楚):用户会看到一个表单,里面有多个字段用于输入各种参数和选项。不过,有些字段会是开放的文本框。所有这些字段的内容会被组合起来,然后传递给 subprocess.check_call()
。从技术上讲,这其实和直接给用户一个命令提示符没什么区别。这种情况应该很常见,那么其他开发者是怎么处理输入的,以确保他们不会遇到 Bobby Tables 的问题呢?
5 个回答
要做到这一点,你需要按照以下步骤进行。如果你不知道“选项”和“参数”是什么,可以先看看optparse 的背景介绍。
每个“命令”或“请求”其实都是一个模型的实例。你需要定义一个请求模型,包含用户可能提供的所有参数。
对于简单的选项,你必须提供一个包含特定选择的字段。对于那些“开”或“关”的选项(比如命令行中的
-x
),你应该提供一个包含两个易于理解的值的选择列表(“做 X”和“不要做 X”)。对于有值的选项,你必须提供一个字段来接收这个选项的值。你需要写一个表单来验证这个字段的输入。稍后我们会再讨论选项值的验证。
对于参数,你需要一个第二个模型(与第一个模型有外键关系)。这可以是一个简单的文件路径字段,也可以更复杂。同样,你可能需要提供一个表单来验证这个模型的实例。
选项的验证方式取决于选项的类型。你必须将可接受的值限制在最小的字符集合内,并编写一个解析器,确保只通过有效的字符。
你的选项会分为与 optparse 中的选项类型相同的类别——字符串、整数、长整型、选择、浮点数和复数。注意,整数、长整型、浮点数和复数的验证规则已经由 Django 的模型和表单定义好了。选择是一种特殊的字符串,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()
。我们保存它们是为了能够准确记录运行了什么。
永远不要信任用户。来自网页浏览器的数据都应该被认为是不可靠的。绝对不要试图通过JavaScript或限制表单字段的输入来验证数据。在将数据传递给外部应用程序之前,你需要在服务器上进行测试。
更新在你编辑后:无论你在前端如何展示表单,后端都应该把它当作是来自一组文本框的数据,旁边还闪烁着大字提示“随便输入你想要的内容!”
根据我对这个问题的理解,我假设你并不是让用户直接输入要在命令行上执行的命令,而只是让他们输入这些命令的参数。在这种情况下,你可以通过使用 subprocess
模块来避免 命令注入 攻击,并且不要使用命令行(也就是说,在 subprocess.Popen
的构造函数中设置默认的 shell=False
参数)。
哦,还有,绝对不要对任何包含用户输入的字符串使用 os.system()
。