应用严格的文件权限

应使用严格的文件权限创建文件,以防止信息泄露和代码执行等漏洞。特别是,任何可能包含机密信息的文件的访问权限应设置为仅允许所有者/服务和组访问(即,不允许世界/其他用户访问)。

在授予诸如配置文件之类的文件的写入访问权限时应谨慎,以防止包括拒绝服务和远程代码执行在内的漏洞。

错误

考虑一个名为“secureserv”的服务,其配置文件“secureserv.conf”存储包括密码的配置。

ls -l secureserv.conf
-rw-rw-rw-   1 secureserv  secureserv   6710 Feb 17 22:00 secureserv.conf

在此,文件权限设置为 666(所有者、组和其他用户具有读写访问权限)。这将允许系统上的所有用户访问配置文件中包含的敏感信息。

在使用 Python 在 *NIX 操作系统上写入文件时,文件输出将使用应用程序设置的 umask 进行写入。根据您的操作系统配置,在将敏感数据写入文件时,这可能会过于宽松。

给定这个程序

with open('testfile.txt', 'w') as fout:
    fout.write("secrets!")

文件权限将默认设置为环境设置。在此,umask 允许系统上的其他用户读取文件内容。

ls -l testfile.txt
-rw-r--r--  1 user  staff  4 Feb 19 10:59 testfile.txt

正确

最好为包含敏感信息的文件设置严格的权限。要修复上面的示例,您需要设置权限,使该文件仅由创建它的用户可读和可写。

chmod 0600 securesev.conf
ls -l secureserv.conf
-rw-------   1 secureserv  secureserv   6710 Feb 17 22:00 secureserv.conf

以下是如何在 Python 中安全地创建仅由文件所有者可读和可写的文件的示例。

import os

flags = os.O_WRONLY | os.O_CREAT | os.EXLC
with os.fdopen(os.open('testfile.txt', flags, 0o600), 'w') as fout:
    fout.write("secrets!")

请注意,验证文件的所有者和组也很重要。尤其重要的是要注意哪些其他用户是您授予访问权限的组的成员。最佳实践是,如果不需要组访问权限,则不要授予它。这就是最小权限原则。

测试指南

应该能够测试任何执行文件创建的操作,以确保权限设置正确。以下示例检查文件是否以预期的权限创建,如果文件已存在则引发异常。

import stat
import os
import unittest


def do_something(path, mode):
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
    return os.fdopen(os.open(path, flags, 0o600), mode)

class TestFileCreation(unittest.TestCase):

    def test_correct_permissions(self):
        """
        Make sure that a file can be created with specific permissions
        """
        test_file = "demo.txt"
        with do_something(test_file, "w") as fout:
            fout.write("blah blah blah")

        finfo = os.stat(test_file)
        assert(finfo.st_mode & stat.S_IRUSR)
        assert(finfo.st_mode & stat.S_IWUSR)
        assert(not finfo.st_mode & stat.S_IXUSR)
        assert(not finfo.st_mode & stat.S_IRGRP)
        assert(not finfo.st_mode & stat.S_IWGRP)
        assert(not finfo.st_mode & stat.S_IXGRP)
        assert(not finfo.st_mode & stat.S_IROTH)
        assert(not finfo.st_mode & stat.S_IWOTH)
        assert(not finfo.st_mode & stat.S_IXOTH)

        os.remove(test_file)

    def test_file_exists(self):
        """
        If the file already exists an OSError should be raised.
        """
        test_file = "demo2.txt"

        # simulate file already placed at location by attacker
        with open(test_file, "w") as fout:
            fout.write("nasty attacker stuff")

        # ensure that we can't open the file
        with self.assertRaises(OSError):
            with do_something(test_file, "w") as fout:
                fout.write("this should never happen..")

        os.remove(test_file)

您还可以使用 mock 来确保对 os.open 的任何调用都使用严格的权限进行。

import os
import mock
import unittest

def do_something(dummy_file):
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
    with os.fdopen(os.open(dummy_file, flags, 0o600), "w") as fout:
        fout.write("blah blah")

    print("doing something")


class TestUsingMock(unittest.TestCase):

    def test_do_something(self):
        """
        Make sure that any file created is done with sufficiently secure permissions.
        """
        with mock.patch('os.open') as os_open, mock.patch('os.fdopen') as os_fdopen:

            # setup test call
            dummy_file = "dummy.txt"
            os_open.return_value = 123
            do_something(dummy_file)

            # ensure the file is being created with specific flags
            # and permission set
            flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
            os_open.assert_called_with(dummy_file, flags, 0o600)
            os_fdopen.assert_called_with(123, "w")

后果

  • 从配置文件中读取密码 - 恶意用户可以从文件中读取敏感信息(例如密码)。

  • 设置新密码 - 恶意用户可以将新密码写入文件,从而可能授予访问权限。

  • 代码执行 - 如果配置文件存储命令或参数,恶意用户可以篡改配置文件以实现代码执行。

  • 拒绝服务 - 攻击者可以删除文件内容以阻止服务正常运行。

参考

  • 应根据最小权限原则实施文件系统控制。

  • 有关在 Linux 中设置安全文件系统权限的更多信息,请参见此处