创建、使用和安全地删除临时文件¶
我们经常需要创建临时文件来保存无法保存在内存中的数据,或者传递给必须从文件读取的外部程序。显而易见的方法是在常见的系统临时目录(例如 /tmp)中生成一个唯一的文件名,但正确地执行起来比想象的要困难得多。安全地创建临时文件或目录意味着遵循许多规则(有关更多详细信息,请参阅参考资料)。我们绝不应该自己执行此操作,而应该使用正确的现有库函数。我们还必须小心清理我们的临时文件,即使在发生错误的情况下也是如此。
如果我们不采取所有这些预防措施,我们就会面临许多危险的安全问题。能够预测文件名并写入包含临时文件的目录的恶意用户可以通过在程序创建文件本身之前使用临时文件的名称创建一个符号链接,从而有效地劫持临时文件。这允许恶意用户提供恶意数据或导致程序对攻击者选择的文件产生影响。参考资料中有更详细的潜在危险描述。
大多数编程语言都提供了创建临时文件的函数。但是,其中一些函数是不安全的,不应使用。我们需要小心使用安全的函数。
尽管有更安全的临时文件创建 API,但我们仍然必须注意我们在哪里创建临时文件。通常,临时文件应始终在本地文件系统上创建。许多远程文件系统(例如,NFSv2)不支持安全创建临时文件所需的打开标志。
Python¶
使用 |
避免 |
|---|---|
tempfile.TemporaryFile |
tempfile.mktemp |
tempfile.NamedTemporaryFile |
open |
tempfile.SpoolTemporaryFile |
|
tempfile.mkstemp |
|
tempfile.mkdtemp |
应尽可能使用 tempfile.TemporaryFile。除了安全地创建临时文件外,它还会隐藏文件并自动清理文件。
错误¶
使用可预测路径创建临时文件会使其容易受到检查时机与使用时机攻击 (TOCTOU) 的影响。给定以下代码片段,攻击者可能会预先在指定位置放置一个文件。
import os
import tempfile
# This will most certainly put you at risk
tmp = os.path.join(tempfile.gettempdir(), filename)
if not os.path.exists(tmp):
with open(tmp, "w") file:
file.write("defaults")
Python 标准库中还有一种不安全的方法,无法以安全的方式用于创建临时文件。
import os
import tempfile
open(tempfile.mktemp(), "w")
最后,我们有很多方法可以尝试创建一个安全的文件名,但这些文件名并不安全且很容易预测。
filename = "{}/{}.tmp".format(tempfile.gettempdir(), os.getpid())
open(filename, "w")
正确¶
Python 标准库提供了许多安全的方法来创建临时文件和目录。以下是一些使用它们的方法示例。
创建文件
import os
import tempfile
# Use the TemporaryFile context manager for easy clean-up
with tempfile.TemporaryFile() as tmp:
# Do stuff with tmp
tmp.write('stuff')
# Clean up a NamedTemporaryFile on your own
# delete=True means the file will be deleted on close
tmp = tempfile.NamedTemporaryFile(delete=True)
try:
# do stuff with temp
tmp.write('stuff')
finally:
tmp.close() # deletes the file
# Handle opening the file yourself. This makes clean-up
# more complex as you must watch out for exceptions
fd, path = tempfile.mkstemp()
try:
with os.fdopen(fd, 'w') as tmp:
# do stuff with temp file
tmp.write('stuff')
finally:
os.remove(path)
我们还可以安全地创建一个临时目录并在其中创建临时文件。我们需要在创建文件之前设置 umask,以确保文件上的权限仅允许创建者具有读写访问权限。
import os
import tempfile
tmpdir = tempfile.mkdtemp()
predictable_filename = 'myfile'
# Ensure the file is read/write by the creator only
saved_umask = os.umask(0077)
path = os.path.join(tmpdir, predictable_filename)
print path
try:
with open(path, "w") as tmp:
tmp.write("secrets!")
except IOError as e:
print 'IOError'
else:
os.remove(path)
finally:
os.umask(saved_umask)
os.rmdir(tmpdir)
后果¶
程序可能会被欺骗为对错误的文件执行文件操作,或者使用恶意文件代替预期的临时文件