网站上传漏洞的前提是了解文件上传这个功能吗?
2021-10-17
PHP代码审计:(一)文件上传0x00概览
在网站运行过程中,不可避免地会更新网站的某些页面或内容,这时就需要使用网站上的文件上传功能。如果对上传的文件没有限制,或者绕过了限制,则可能会使用该功能将可执行文件和脚本上传到服务器,从而进一步导致服务器崩溃。
可见php文件上传代码,了解上传漏洞的前提是了解文件上传的功能及其原理。如果只知道有文件上传,而且可能有漏洞,那就和不知道一样。
具体来说,用户上传的一些文件仍然是PHP脚本。用户可以通过服务器直接访问这些上传到服务器上的PHP脚本,并且会执行其中包含的一些命令。文件上传功能如此强大,如果您的网站在文件上传方面没有很好的控制,它就会崩溃。
文件上传漏洞的原因有很多,主要包括:
其中开源编辑器漏洞和文件上传漏洞原理相同,只是多了一个编辑器。上传时,我们的脚本仍然会被上传。
松散过滤非常常见,我们将在以下示例中看到。比如大小写问题,网站只验证是否是小写,我们可以把后缀名改成大写。
然后是文件解析漏洞。比如系统会涉及到这种情况:文件名为1.php;.jpg,IIS 6.0 可能认为是jpg文件,但是当它执行被执行。我们可以利用这个解析漏洞进行上传。再比如php文件上传代码,有一些未知的后缀,比如a.php.xxx。由于后缀名无法识别,可能会被释放。如果攻击者再次执行该文件,则该网站可能被控制。
最后是路径截断,就是在上传的文件中使用一些特殊的符号,使文件在上传时被截断。比如a.php.jpg,在网站上验证时,后缀会被认为是jpg,但保存到硬盘时,会被截断为a.php,这是一个直接的php文件。
通常用于截断路径的字符有:
这些是可能导致截断的字符。需要注意的是,在实战中,由于网站的编解码规则不同,需要灵活应用。例如,\0 失败可以替换,或者你可以尝试各种编码,例如,或者,多试几次。
0x01 代码
文件上传首先需要一个表单,如下,我们称之为a.html:
这里有几个要素:
接下来是PHP脚本中的东西。在 PHP 中,通过 $ 对象读取文件,并使用以下属性:
t.php 中的代码是这样写的:
可以看到aaa是文件输入框中的name属性。
我们把这两个文件放在服务器的目录下,或者直接在目录下启动PHP自带的服务器。打开a.html,上传文件后,会得到这样的结果。这里我直接上传了a.html:
array(5) {
["name"]=> string(6) "a.html"
["type"]=> string(9) "text/html"
["tmp_name"]=> string(44) "C:\Users\asus\AppData\Local\Temp\php43A1.tmp"
["error"]=> int(0)
["size"]=> int(133)
}
需要说明的是,在处理文件上传的时候,不要相信文件类型的类型,因为浏览器生成后类型是可以改变的。您甚至可以手动构建类型与实际内容不匹配的数据包。
同时,不应信任文件名名称。相反,文件名和扩展名应该分开,并且扩展名应该被列入白名单。文件名根据需要丢弃并重新生成,或过滤并重新使用。
过度相信这些东西会产生一些隐患,我们将在下面看到。
0x02 实战
实战部分,我会用DVWA中的例子来演示。DVWA是一套用PHP+编写的WEB漏洞测试程序,用于常规WEB漏洞教学和检测。包含SQL注入、XSS、盲注等常见安全漏洞。项目主页在这里,源码也在这里。
下载部署后,我们打开///,这里是上传漏洞部分的源码,可以看到难度分为低、中、高三个级别。
先看低级难度low.php:
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
$html .= 'Your image was not uploaded.
';}else {// 是的!$html .=”
{$target_path} succesfully uploaded!
";}}
可以看到没有过滤,可以直接上传任何文件。
然后是.php:
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
// Is it an image?
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
( $uploaded_size < 100000 ) ) {
// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
$html .= 'Your image was not uploaded.
';}else {// 是的!$html .=”
{$target_path} succesfully uploaded!
";}}else {// file$html .= '
Your image was not uploaded. We can only accept JPEG or PNG images.
';}}
注意第10行和第11行,验证类型必须是jpg或png,大小必须小于某个值。后者可以忽略。刚才我说类型不可信,我们可以抓包,改类型,然后提交。
因为我想演示如何突破上传限制,而不是如何使用上传的脚本,所以我直接创建了一个新的PHP文件,并在其中写入了一些内容。打开抓包,我们会在请求体中看到类似这样的内容:
------WebKitFormBoundaryh4zhLV52OKhf6aJg
Content-Disposition: form-data; name="uploaded"; filename="a.php"
Content-Type: application/octet-stream
右键单击“发送到”并将该 /- 更改为 /jpeg。点击“前往”发送。
最后,高级high.php:
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// Is it an image?
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
( $uploaded_size < 100000 ) &&
getimagesize( $uploaded_tmp ) ) {
// Can we move the file to the upload folder?
if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
// No
$html .= 'Your image was not uploaded.
';}else {// 是的!$html .=”
{$target_path} succesfully uploaded!
";}}else {// file$html .= '
Your image was not uploaded. We can only accept JPEG or PNG images.
';}}
观察同样的位置,这次改用后缀名来判断。这时候我们可以在后缀前插入\0,即a.php\0.jpg。请注意,它不是斜线加零,而是空字符。这样判断的时候,后缀是.jpg,写入磁盘时会被截断为a.php。它可以被上传或执行。
我们还捕获包裹并交付。先把a.php改成a.php.jpg,然后切换到十六进制编辑模式插入空字符。
鼠标拖动的范围是a.php.jpg,在第二个2e网格上右击,点击“byte”,会自动插入一个\0。然后点击“开始”,你就完成了。
注意这里插入的话,会上传成功,但是访问的时候会直接当作图片处理,里面的内容不会被执行。
0x03 解决方案
同目录下还有一个.php,里面有正确的做法。大家可以看看。里面的代码使用了$=md5(().$).'.'。$; 生成独立的文件名,使其不受原文件名中各种截断字符的干扰。
0x04 注意