将PHP(/ PHP-FPM / Apache)的临时上传文件存储在RAM中而不是文件系统(或仅encryption)?
原来的问题
所以我正在进行的这个项目对于file upload是致命的。
在这个问题的范围内,我没有用这个术语来表示有效载荷。 我在讲保密 。
程序总是会崩溃,并使临时文件在文件系统中徘徊。 这很正常。 稍微保密的偏执可以写一个cronjob,每隔几分钟打一个临时文件夹,并在cronjob调用之前删除几秒钟以前的任何东西(不是所有的东西 ,只是因为否则它可能捕获正在上传的文件)。
…不幸的是,我们把这个偏执狂进一步说了一遍:
理想情况下,我们很乐意永远不会从file upload到临时文件,而是在与进程相关的RAM中。
有没有办法教PHP寻找临时文件在内存中而不是在文件系统中的斑点? 我们使用PHP-FPM作为CGI处理程序,使用Apache作为我们的networking服务器,以防万一。 (另请注意:'Filesystem'是这里的关键字,而不是'disc',因为当然有方法将文件系统映射到RAM,但是这不能修复可访问性和自动的崩溃后清理问题。 )
或者,这些临时文件是否可以在写入光盘时立即进行encryption ,从而不会在没有encryption的情况下保存在文件系统中?
线程概述
不幸的是,我只能接受一个答案 – 但是对于任何读这个答案的人来说,整个线索是非常有价值的,并且包含了许多人的集体见解。 根据你希望达到的目标,被接受的答案可能对你并不感兴趣 。 如果您是通过search引擎来到这里的, 请花一点时间阅读整篇文章 。
下面是我看到的用于快速参考的用例汇编:
Re:PHP的临时文件
-
RAM而不是光盘(例如由于I / O问题)→ RAMdisk /可比( 质粒87 , Joe Hopfgartner )
-
立即(每文件系统用户)encryption→encryption( ADW )(根据Sander Marechal + +)
-
安全的文件许可→ 限制的本地Linux权限(可select每个虚拟主机 ) ( Gilles )或SELinux(请参阅各种评论)
-
过程连接的内存,而不是文件系统(所以进程崩溃删除文件) (本来是由问题的意图)
-
不要让文件数据直接到达PHP→ 反向代理 ( Cal )
-
禁用PHP写入文件系统→ 请参阅此答案中的PHP错误链接 ( Stephan B )或在CGI模式下运行PHP ( Phil Lello )
-
只写文件→
/dev/null
文件系统 ( Phil Lello )(如果你可以另外访问数据stream,这是很有用的,但不能closures并行运行的文件写入function; PHP是否允许这样做是不明确的)
-
Re:你的文件,上传后
- 存储在数据库而不是光盘→数据库中的文件encryptionHowTo ( Rook )
你有没有考虑过在用户和Web服务器之间放置一个图层? 在web服务器前面使用类似perlbal的一些自定义代码将允许你在上传的文件被写入任何地方之前拦截它们,encryption它们,把它们写到本地的ramdisk,然后在web服务器上正确地代理请求文件名和解密密钥)。
如果PHP进程崩溃,则encryption的文件将被保留但不能被解密。 没有未encryption的数据被写入(ram)磁盘。
CGI来拯救!
如果你创build了一个cgi-bin目录,并进行了适当的configuration,你将会通过stdin得到这个消息(据我所知,文件不会写入磁盘)。
所以,在你的apacheconfiguration里添加
ScriptAlias /cgi-bin/ /var/www/<site-dir>/cgi-bin/ <Directory "/var/www/<site-dir>/cgi-bin"> AllowOverride None Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Order allow,deny Allow from all </Directory>
然后编写一个CGI模式的PHP脚本来parsing发布的数据。 从我的(有限的)testing中,似乎没有创build本地文件。 示例将从stdin中读取的内容以及环境variables转储给您,以了解可以使用的内容。
示例脚本安装为/ var / www // cgi-bin / test
#!/usr/bin/php Content-type: text/html <html><body> <form enctype="multipart/form-data" action="/cgi-bin/test" method="POST"> <!-- MAX_FILE_SIZE must precede the file input field --> <input type="hidden" name="MAX_FILE_SIZE" value="30000" /> <!-- Name of input element determines name in $_FILES array --> Send this file: <input name="userfile" type="file" /> <input type="submit" value="Send File" /> </form> <pre> <? echo "\nRequest body\n\n"; $handle = fopen ("php://stdin","r"); while (($line = fgets($handle))) echo "$line"; fclose($handle); echo "\n\n"; phpinfo(INFO_ENVIRONMENT); echo "\n\n"; ?> </pre> </body></html>
示例输出这是我上传纯文本文件时的输出(源):
<html><body> <form enctype="multipart/form-data" action="/cgi-bin/test" method="POST"> <!-- MAX_FILE_SIZE must precede the file input field --> <input type="hidden" name="MAX_FILE_SIZE" value="30000" /> <!-- Name of input element determines name in $_FILES array --> Send this file: <input name="userfile" type="file" /> <input type="submit" value="Send File" /> </form> <pre> Request body -----------------------------19908123511077915841334811274 Content-Disposition: form-data; name="MAX_FILE_SIZE" 30000 -----------------------------19908123511077915841334811274 Content-Disposition: form-data; name="userfile"; filename="uploadtest.txt" Content-Type: text/plain This is some sample text -----------------------------19908123511077915841334811274-- phpinfo() Environment Variable => Value HTTP_HOST => dev.squello.com HTTP_USER_AGENT => Mozilla/5.0 (X11; U; Linux x86_64; en-GB; rv:1.9.2.16) Gecko/20110323 Ubuntu/10.04 (lucid) Firefox/3.6.16 HTTP_ACCEPT => text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 HTTP_ACCEPT_LANGUAGE => en-gb,en;q=0.5 HTTP_ACCEPT_ENCODING => gzip,deflate HTTP_ACCEPT_CHARSET => ISO-8859-1,utf-8;q=0.7,*;q=0.7 HTTP_KEEP_ALIVE => 115 HTTP_CONNECTION => keep-alive HTTP_REFERER => http://dev.squello.com/cgi-bin/test CONTENT_TYPE => multipart/form-data; boundary=---------------------------19908123511077915841334811274 CONTENT_LENGTH => 376 PATH => /usr/local/bin:/usr/bin:/bin SERVER_SIGNATURE => <address>Apache/2.2.14 (Ubuntu) Server at dev.squello.com Port 80</address> SERVER_SOFTWARE => Apache/2.2.14 (Ubuntu) SERVER_NAME => dev.squello.com SERVER_ADDR => 127.0.0.1 SERVER_PORT => 80 REMOTE_ADDR => 127.0.0.1 DOCUMENT_ROOT => /var/www/dev.squello.com/www SERVER_ADMIN => webmaster@localhost SCRIPT_FILENAME => /var/www/dev.squello.com/cgi-bin/test REMOTE_PORT => 58012 GATEWAY_INTERFACE => CGI/1.1 SERVER_PROTOCOL => HTTP/1.1 REQUEST_METHOD => POST QUERY_STRING => REQUEST_URI => /cgi-bin/test SCRIPT_NAME => /cgi-bin/test </pre> </body></html>
我对此有了一个灵感:黑洞文件系统。
本质上,这是一个假的文件系统,数据永远不会被写入,但是所有的文件都存在,并且没有内容。
有关unix.se的讨论关于这些, 一个答案涉及FUSE实现这个(引用在这里):
对于我所知道的任何unix,这不是支持的,但是您可以使用FUSE做任何事情。 至less有一个nullfs¹的实现 ,一个文件系统,每个文件都存在,其行为如同
/dev/null
(这不是我见过的唯一实现)。¹ 不要与* BSD nullfs混淆,这与bindfs类似。
我没有机会testing这个, 但是如果将upload_tmp_dir设置为黑洞位置,上传(应该)永远不会写入磁盘,但仍然可以在$ HTTP_RAW_POST_DATA (或php://input)。 如果它工作,它比修补PHP更好
我不熟悉PHP,所以我的答案不会直接映射到一个how-to,但是我认为你在某种误解下工作,这些误解是关于各种系统function提供的保护,这导致你拒绝有效的解决scheme的解决scheme具有完全相同的安全属性。 从你的意见,我收集你运行Linux; 我的答案大部分适用于其他unices,但不适用于其他系统,如Windows。
据我所知,你担心三种攻击情况:
- 攻击者获得对机器的物理访问权限,将其closures,将磁盘取出并将其内容读取给她。 (如果攻击者可以读取你的RAM,你已经丢失了。)
- 攻击者可以在机器上以用户身份运行代码。
- CGI脚本中的错误允许进程读取由其他进程创build的临时文件。
第一种types的攻击者可以读取磁盘上未encryption的所有内容,而没有使用没有encryption的密钥的内容 。
第二种攻击者可以做什么取决于她是否可以像运行CGI脚本的用户那样运行代码。
如果她只能像其他用户一样运行代码,那么保护文件的工具就是权限 。 你应该有一个模式为700(= drwx------
)的目录,即只能由用户访问,并由运行CGI脚本的用户拥有。 其他用户将无法访问该目录下的文件。 您不需要任何额外的encryption或其他保护。
如果她可以作为CGI用户运行代码(当然,它包含以root身份运行的代码),那么你已经失败了。 如果您以相同的用户身份运行代码,则可以看到另一个进程的内存 – debugging程序始终都在执行该代码! 在Linux下,您可以通过浏览/proc/$pid/mem
轻松地看到它。 与阅读一个文件相比,阅读一个进程的内存在技术上有一定的挑战性,但是在安全性方面,没有什么区别。
因此,将文件中的数据本身并不是一个安全问题 。
现在我们来看看第三个问题。 担心的是,CGI中的一个错误允许攻击者窥探文件,而不是运行任意代码 。 这与可靠性问题有关 – 如果CGI进程死亡,可能会留下临时文件。 但是更一般的是:文件可能被一个并发运行的脚本读取。
防止这种情况的最好方法的确是避免将数据存储在文件中。 这应该在PHP或其库的层面上完成,我不能帮助。 如果这是不可能的,那么Phil Lellobuild议的 nullfs是一个合理的解决方法:PHP进程将认为它正在将数据写入文件,但文件将永远不会包含任何数据。
还有另外一个常见的unix技巧可能在这里有用:一旦你创build了一个文件,你可以取消连接 (删除)并继续使用它。 一旦它被取消链接,文件就不能被其以前的名称访问,但只要该文件在至less一个进程中打开,数据就会保留在文件系统中。 但是,这对于可靠性来说是非常有用的,当操作系统因任何原因死亡时,操作系统将删除数据。 攻击者可以使用进程的权限打开任意文件,可以通过/proc/$pid/fd/$fd
访问数据。 而且随时可以打开文件的攻击者在创build文件和取消关联之间有一个小窗口:如果她可以打开文件,那么她可以随后看到添加到其中的数据。 这可能是有用的保护,因为它将攻击转变为时间敏感的攻击,并且可能需要许多并发连接,所以可以通过连接速率限制器来应对或至less变得更加困难。
您是否使用FUSE创build了一个只能由特定用户访问的encryption目录?
内存将不会与特定的进程相关联,但文件只能被特定用户访问(您的Web服务器运行的同一个用户!),这可能就足够了?
PHP会将上传的文件存储到文件系统中,在脚本有机会截获数据的情况下,php://input和$ HTTP_RAW_POST_DATA在这种情况下将是空的(甚至当您设置file_uploads = Off
)。
对于小文件,你可以尝试设置<form enctype="application/x-www-form-urlencoded" ...
但我没有成功使用这个。 我build议你重新编译php,并注释掉在这个Bug报告中处理file upload的部分(Comment by pollita@php.net)。
临:没有file upload,数据在PHP:/ /inputCon:重新编译,没有供应商支持
你的顾虑是有效的。 这个问题有几个解决scheme。 一个是将文件存储在数据库中。 像MongoDB或CouchDB这样的NoSQL数据库可以有效地存储文件。 MySQL是另一种select,比NoSQL有优势。 像MySQL这样的关系型数据库使得植入访问控制变得非常容易,因为你可以通过主键来关联files
和users
表。
在MySQL中,您可以使用longblob
数据types保存2 ^ 32位或约500MB。 您可以使用MEMORY引擎创build一个驻留内存的CREATE TABLE files ENGINE=MEMORY ...
: CREATE TABLE files ENGINE=MEMORY ...
还有更多。 MySQL以aes_encrypt()
和des_encrypt()
的forms进行encryption,但它们都使用ECB模式,这是垃圾 。
$sensi_file=file_get_contents($_FILES['sensitive']['tmp_name']); unlink($_FILES['sensitive']['tmp_name']);//delete the sensitive file. $sensi_file=mysql_real_escape_string($sensi_file);//Parametrized quires will also use this function so that should also be binary safe. mysql_query("insert into files (file)values('$sensi_file')");
只需select文件,就像$sensi_file
一样使用它。 请记住,您正在逃避input,以获取他的字符文字,从而存储原始的二进制文件。
你可以创build一个tmpfs
,并用适当的umask来挂载它。 这样,唯一可以读取文件的进程就是创build它的用户。 而且,因为这是一个tmpfs
,所以没有任何东西存储在磁盘上。
我会build议ADW的encfs
解决scheme。 Encfs不encryption卷,但它逐个文件地encryption文件,仍然留下大量的元数据。
最明显的做法是:
- build立一个公羊磁盘 – http://www.vanemery.com/Linux/Ramdisk/ramdisk.html
- 相应地configurationupload_tmp_dir
我没有看到任何问题。 只要确保你分配足够的空间艰难。
您可以使用LUKS或truecrypt实时encryption
编辑:
您的评论后,我想我现在明白你的问题
Apache / PHP不支持这一点。
你可以编写你自己的deamon,打开一个套接字连接来监听并以你想要的方式处理传入的数据。 基本上写你自己的networking服务器在PHP。 不应该工作太多。 也有一些很好的类可用。 zend有一些服务器库,它支持http处理。
但是你可以在perl中做到这一点。 您可以通过大块文件发送数据块来处理相关的内存。 PHP只是有一个不同的工作stream程。
你有没有考虑在Linux下创build一个RAMdisk?
http://www.vanemery.com/Linux/Ramdisk/ramdisk.html
由于这将显示为本地文件系统位置,因此只需在此位置指向您的PHP实例(假设它具有正确的权限)即可。
我不确定如果磁盘发生故障会造成什么后果,我想这个位置会变成不可写或不存在的。 围绕性能和大文件可能会有更多的分歧,但由于这不是我的专业领域,我不能告诉你很多。
希望这个对你有帮助。