file put contents漏洞
一个19岁的小伙子,在某个开源CMS里发现了一行代码: 这就是PHP里最容易被低估的函数之一。它不显眼,不像 我见过一个后台导出功能,用户提交文件名,代码拼成 更隐蔽的是内容本身。有些场景里文件名固定,但写入的内容来自用户。比如缓存系统把序列化后的数据存进 PHP的流包装器让这件事更有意思。 说个真事。去年帮朋友看一个WordPress插件,功能是“保存自定义CSS”。代码把用户输入的CSS写进 防御这件事,思路比技巧重要。路径参数必须白名单,别用黑名单过滤 还有个小细节: 说到底,漏洞不在函数本身。开发者脑子里“写文件”和“执行代码”是两件事,但在Web目录下,这两件事之间只隔着一个文件扩展名。file_put_contents($path, $content)。他没多想,把$content换成了自己构造的字符串,服务器就多了一个php文件。三天后,这个CMS的官方仓库多了个CVE编号。eval那样自带危险标签,也不像system那样被WAF重点盯防。file_put_contents做的事情很朴素——把一段数据写进文件。第三个参数还能追加,写日志、缓存、导出数据,日常开发里到处都是它。问题恰恰出在这份朴素上。/tmp/export_{ $name}.csv然后写进去。看起来没问题?如果$name是../../var/www/html/shell.php呢。路径穿越加上文件写入,等于任意文件上传,还绕过了大多数上传检测。那个项目的开发者后来跟我说,他当时觉得“只是写个文件而已”。.php文件,头部加一句exit;防直接访问。如果攻击者能在序列化数据里塞进<?php system($_GET['c']); ?>,而缓存文件的扩展名恰好是.php,这行代码就会被解析。文件名不可控?没关系,内容可控就够了。php://filter、phar://、compress.zlib://这些协议在file_put_contents里同样有效。有人用php://filter/write=convert.base64-decode/resource=shell.php来绕过内容检测——写入的base64字符串经过过滤器解码后变成webshell。WAF看到的是普通字符,落到磁盘上就是可执行代码。wp-content/uploads/custom.css。文件名固定,内容经过strip_tags过滤。看起来无懈可击。但strip_tags不处理PHP短标签,而那个服务器恰好开了short_open_tag。攻击者提交<?=system($_GET[0]);?>,过滤后原样写入。访问这个css文件,服务器当成PHP执行了。朋友看完沉默了很久。../,编码变体太多。写入目录要限制权限,open_basedir该开就开。内容如果来自用户,扩展名别用.php,.html都比它强。最彻底的办法是不让用户控制写入路径和内容中的任何一项,但现实里很难做到,所以退而求其次——把写入目录设在Web根目录之外。file_put_contents的第二个参数可以是数组。有人觉得数组就安全了,其实PHP会把它拼接成字符串再写入,该有的风险一个不少。这个函数的返回值是写入字节数,失败返回false。很多代码不检查返回值,写入失败静默继续,日志里什么都看不到。排查问题时这种沉默比报错更折磨人。file_put_contents只是把这个距离缩短到了几行代码。每次看到它出现在用户输入附近,我都会多看两眼。不是不信任这个函数,是不信任那种“只是写个文件”的松懈。