在 Python 中声明文件权限时避免竞争条件
应用程序想要解析并“执行”文件,并且出于安全原因想要断言该文件是可执行的。
想了一下,您意识到这个初始代码有一个竞争条件,使安全方案无效:
import os
class ExecutionError (Exception):
pass
def execute_file(filepath):
"""Execute serialized command inside @filepath
The file must be executable (comparable to a shell script)
>>> execute_file(__file__) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
ExecutionError: ... (not executable)
"""
if not os.path.exists(filepath):
raise IOError('"%s" does not exist' % (filepath, ))
if not os.access(filepath, os.X_OK):
raise ExecutionError('No permission to run "%s" (not executable)' %
filepath)
data = open(filepath).read()
print '"Dummy execute"'
print data
竞争条件存在于 和 之间
os.access(filepath, os.X_OK)
,
data = open(filepath).read()
因为在这两个系统调用之间,文件有可能被不同内容的不可执行文件覆盖。
我的第一个解决方案是更改关键调用的顺序(并跳过现在冗余的存在性检查):
fobj = open(filepath, "rb")
if not os.access(filepath, os.X_OK):
raise ExecutionError('No permission to run "%s" (not executable)' %
filepath)
data = fobj.read()
这是否解决了竞争条件?我怎样才能正确解决呢?
安全方案的基本原理,简要地(我认为)
该文件将能够在其内部执行任意命令 环境,因此它类似于 shell 脚本。
免费桌面上存在一个安全漏洞,其 .desktop 文件定义了 应用程序:该文件可以指定任何带有参数的可执行文件,并且 它可以选择自己的图标和名称。所以随机下载的文件可能会隐藏 在任何名称或图标后面并执行任何操作。那很糟糕。
通过要求 .desktop 文件具有可执行位解决了这个问题 设置,否则它们将不会以名称/图标和免费桌面呈现 在开始之前会询问用户是否要启动该程序。
与 Mac OS X 的非常好的设计相比:“这个程序是从网上下载的, 你确定要打开它吗?”。
所以在寓言中,你必须 chmod +x shell 您下载的脚本,我考虑了上面问题中的设计。
结束语
也许作为结论,也许我们应该保持简单:如果文件必须是可执行的,则使其可执行并让内核在用户调用时执行它。将任务委派给它所属的地方。
An application wants to parse and "execute" a file, and wants to assert the file is executable for security reasons.
A moments thought and you realize this initial code has a race condition that makes the security scheme ineffective:
import os
class ExecutionError (Exception):
pass
def execute_file(filepath):
"""Execute serialized command inside @filepath
The file must be executable (comparable to a shell script)
>>> execute_file(__file__) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
ExecutionError: ... (not executable)
"""
if not os.path.exists(filepath):
raise IOError('"%s" does not exist' % (filepath, ))
if not os.access(filepath, os.X_OK):
raise ExecutionError('No permission to run "%s" (not executable)' %
filepath)
data = open(filepath).read()
print '"Dummy execute"'
print data
The race condition exists between
os.access(filepath, os.X_OK)
and
data = open(filepath).read()
Since there is a possibility of the file being overwritten with a non-executable file of different content between these two system calls.
The first solution I have is to change the order of the critical calls (and skip the now-redundant existance check):
fobj = open(filepath, "rb")
if not os.access(filepath, os.X_OK):
raise ExecutionError('No permission to run "%s" (not executable)' %
filepath)
data = fobj.read()
Does this solve the race condition? How can I solve it properly?
Security scheme rationale, briefly (I thought)
The file will be able to carry out arbitrary commands inside its
environment, so it is comparable to a shell script.
There was a security hole on free desktops with .desktop files that define
applications: The file may specify any executable with arguments, and
it may choose its own icon and name. So a randomly downloaded file could hide
behind any name or icon and do anything. That was bad.
This was solved by requiring that .desktop files have the executable bit
set, otherwise they will not be rendered with name/icon, and the free desktop
will ask the user if it wants to start the program before commencing.
Compare this to Mac OS X's very good design: "This program has been downloaded from the web,
are you sure you want to open it?".
So in allegory with this, and the fact that you have to chmod +x
shell
scripts that you download, I thought about the design in the question above.
Closing words
Maybe in conclusion, maybe we should keep it simple: If the file must be executable, make it executable and let the kernel execute it when invoked by the user. Delegation of the task to where it belongs.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
可执行性附加到您打开的文件中,没有什么可以阻止多个文件指向包含您希望读取的数据的索引节点。换句话说,可以从同一文件系统中其他地方的不可执行文件读取相同的数据。此外,即使打开文件后,您也无法阻止同一文件的可执行性发生更改,甚至可能会取消链接。
我认为您可以使用的“最大努力”是使用 os.fstat 对打开的文件进行检查,并检查前后的保护模式和修改时间,但这最多只会减少读取文件时未检测到更改的可能性。
再考虑一下,如果您是此文件中数据的原始创建者,您可以考虑编写一个从未链接到文件系统的索引节点,这是通过文件共享内存的常见技术。或者,如果所包含的数据最终必须向其他用户公开,您可以使用文件锁定,然后逐步将保护位扩展到需要它的用户。
最终,您必须确保恶意用户根本没有对该文件的写访问权限。
The executability is attached to the file you open, there is nothing stopping several files from pointing to the inode containing the data you wish to read. In other words, the same data may be readable from a non-executable file elsewhere in the same filesystem. Furthermore, even after opening the file, you can't prevent the executability of that same file from changing, it could even be unlinked.
The "best effort" available to you as I see it would be do checks using
os.fstat
on the opened file, and check protection mode and modification time before and after, but at best this will only reduce the possibility that changes go undetected while you read the file.On second thoughts, if you're the original creator of the data in this file, you could consider writing an inode that's never linked to the filesystem in the first place, this a common technique in memory sharing via files. Alternatively if the data contained must eventually made public to other users, you could use file locking, and then progressively extend the protection bits to those users that require it.
Ultimately you must ensure malicious users simply don't have write access to the file.
您无法完全解决此竞争条件 - 例如,在您首先打开然后检查权限的版本中,可能权限在您打开文件之后和之前就发生了更改。已经更改了权限。
如果您可以自动地将文件移动到潜在坏人无法到达的目录,那么您可以放心,在处理该文件时,该文件的任何内容都不会在您眼皮子底下被更改。如果潜在的坏人可以到达任何地方,或者您无法将文件移动到他们无法到达的地方,那么就没有防御能力。
顺便说一句,我不清楚这个方案(即使它可以发挥作用)实际上会增加任何安全性 - 当然,如果坏人可以将有毒内容放入文件中,那么他们就不会超出
chmod +x
也是这样吗?You cannot entirely solve this race condition -- e.g., in the version where you first open, then check permissions, it's possible that the permissions get changed just after you've opened the file and just before you've changed the permissions.
If you can atomically move the file to a directory where the potential bad guys can't reach, then you can rest assured that nothing about the file will be changed from under your nose while you're dealing with it. If the potential bad guys can reach everywhere, or you can't move the file to where they can't reach, there's no defense.
BTW, it's not clear to me how this scheme, even if it could be made to work, would actually add any security -- surely if the bad guys can put poisoned content in the file it's not beyond them to
chmod +x
it as well?您能做的最好的事情就是:
当然,它也有缺点,但如果您的用例像您所说的那么简单,它就可以解决问题。
The best you can do is :
Of course, there are drawbacks, but if your use case is as simple as you seem to say, it could do the trick.
您应该更改文件所有权,以便攻击者无法访问它“chown root:root file_name”。执行“chmod 700 file_name”,以便其他帐户无法读取/写入/执行该文件。这完全避免了 TOCTOU 的问题,这就是人们如何防止在您的系统上拥有用户帐户的攻击者修改文件。
You should change the files ownership such that an attacker cannot access it "chown root:root file_name". Do a "chmod 700 file_name" so that no other accounts can read/write/execute the file. This avoids the problem of a TOCTOU all together and this is how people prevent files from being modified by an attacker who has a user account on your system.
另一种方法是将文件名更改为意想不到的名称,或者甚至将整个文件(如果不是太大)复制到临时目录(必要时加密),让您进行检查,然后重命名/将文件复制回来。
当然这是一个非常繁重的过程。
但你最终会得到这样的结果,因为系统从一开始就没有设置安全性。安全程序会对他想要保证安全的数据进行签名或加密。在你的情况下,这是不可能的。
除非您确实进行了严格的加密,否则无法在您无法控制的机器上确保 100 安全。
Another way to do it is to change the file name to something unexpected, or even copy the entire file - if it's not too big - to a temp dir (encrypted if necessary), make you checks, then rename / copy the file back.
Of course it's a very heavy process.
But you end up with that because the system has not been set for safety from the beginning. A safe program would sign or encrypt data he wants to keep safe. In your case, it's not possible.
Unless you realy on heavy encrypting, there is not way to ensure 100 safety on a machine you don't control.