DVWA 通关指南:File Upload(文件上传)
File Upload(文件上传)
Uploaded files represent a significant risk to web applications. The first step in many attacks is to get some code to the system to be attacked. Then the attacker only needs to find a way to get the code executed. Using a file upload helps the attacker accomplish the first step.
上传的文件对 web 应用程序来说是一个巨大的风险,许多攻击的第一步是上传攻击代码到被攻击的系统上,然后攻击者只需要找到方法来执行代码即可完成攻击。也就是是说,文件上传是攻击者需要完成的第一步。
The consequences of unrestricted file upload can vary, including complete system takeover, an overloaded file system, forwarding attacks to backend systems, and simple defacement. It depends on what the application does with the uploaded file, including where it is stored.
不受限制的文件上载的后果可能不同,包括完全接管系统、文件系统过载、将攻击转发到后端系统以及简单的破坏。这取决于应用程序对上载的文件做了什么,和文件的存储位置。
Execute any PHP function of your choosing on the target system (such as phpinfo() or system()) thanks to this file upload vulnerability.
利用文件上传漏洞,在目标系统(例如 phpinfo() 或 system())上执行您选择的任何 PHP 函数。
Low Level
Low level will not check the contents of the file being uploaded in any way. It relies only on trust.
Low level 不会以任何方式检查正在上载的文件的内容,它信任虽有上传的文件。
源码审计
源码如下,basename(path,suffix) 函数返回路径中的文件名部分,如果可选参数 suffix 为空则返回的文件名包含后缀名,反之不包含后缀名。move_uploaded_file() 函数将上传的文件移动到新位置。若成功则返回 true,否则返回 false。由此可见源码对上传文件直接移动,而文件的类型、内容没有做任何的检查、过滤。
<?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
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
?>
攻击方式
由于没有任何的过滤,因此我们可以直接上传一个一句话木马。
<?php @eval($_POST['attack']) ?>
直接上传,网页没有进行过滤,直接返回了上传成功的信息。
打开蚁剑,使用上传的一句话木马进行连接,直接 Webshell。此时可以随意访问服务器上的任意文件,进行任意操作。
Medium Level
When using the medium level, it will check the reported file type from the client when its being uploaded.
在中等级的页面下,将在上传时检查客户端报告的文件类型。
源码审计
源码如下,FILES 是一个已经弃用的 HTTP 文件上传变量,它是一个通过 HTTP POST 方式上传到当前脚本的项目的数组。由此可见源码会获取文件的文件名、文件类型和文件大小,它要求文件类型必须是 jpeg 或者 png,同时限制文件大小不能超过 100000B(约为97.6KB)。
<?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
echo '<pre>Your image was not uploaded.</pre>';
}
else{
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else{
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
?>
此时我们再直接传输上面的一句话木马,会发现上传失败。
攻击方式
我们还是先做个一句话木马,然后用 brup 抓包,看到上传的 PHP 文件类型会被显示在包中。
修改文件类型为 “image/png”,然后放包。
可以看到虽然我们传的还是一句话木马,但是通过修改 http 报文可以通过网页的白名单检测,再次蚁剑连接即可。
High Level
Once the file has been received from the client, the server will try to resize any image that was included in the request.
当服务器从客户端接收到文件,它将尝试调整请求中包含的任何图像的大小。
源码审计
源码如下,strrpos(string,find,start) 函数返回字符串 find 在另一字符串 string 中最后一次出现的位置,如果没有找到字符串则返回 false,可选参数 start 规定在何处开始搜索。getimagesize(string filename) 函数会通过读取文件头,返回图片的长、宽等信息,如果没有相关的图片文件头则报错。源码通过字符串匹配来确定文件后缀名,并且查看文件的相关参数,提高了过滤的强度。
<?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
echo '<pre>Your image was not uploaded.</pre>';
}
else{
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else{
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
?>
攻击方式
由于源码会去检查文件头,现在我们不能再传 php 文件了,应该把一句话木马包在一张图片里面。
首先我们要准备一张图片和一句话木马,然后使用 copy 命令把两个文件合成为一个文件。
copy 1.jpg/b + 1.php/a 2.jpg
然后直接上传,网页提示上传成功。但是此时是不能用蚁剑连接的,因为蚁剑的原理是向上传文件发送包含参数的 post 请求,通过控制参数来执行不同的命令。这里服务器将木马文件解析成了图片文件,因此向其发送 post 请求时,服务器并不会执行相应命令。
因此我们要把这张图片当做 php 来执行才行,这时我们想到了 File Inclusion(文件包含) 漏洞,构造 payload。
http://localhost/dvwa-master/vulnerabilities/fi/?page=file:///D:\DVWA-master\hackable\uploads\2.jpg
访问下看看,可以看到这个 url 使得一句话木马被解析,也就是说这个时候就可以使用蚁剑连接。
Impossible Level
This will check everything from all the levels so far, as well then to re-encode the image. This will make a new image, therefor stripping any "non-image" code (including metadata).
到目前为止,源码将检查所有级别的所有内容。
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
//$target_file = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';
$target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
$temp_file = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
$temp_file .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
// Is it an image?
if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) &&
( $uploaded_size < 100000 ) &&
( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&
getimagesize( $uploaded_tmp ) ) {
// Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD)
if( $uploaded_type == 'image/jpeg' ) {
$img = imagecreatefromjpeg( $uploaded_tmp );
imagejpeg( $img, $temp_file, 100);
}
else {
$img = imagecreatefrompng( $uploaded_tmp );
imagepng( $img, $temp_file, 9);
}
imagedestroy( $img );
// Can we move the file to the web root from the temp folder?
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
// Yes!
echo "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
}
else {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
// Delete any temp files
if( file_exists( $temp_file ) )
unlink( $temp_file );
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
源码如下,Impossible 级别的代码对上传文件进行了重命名,并加入 Anti-CSRF token 防护 CSRF 攻击,同时使用上诉所有机制对文件的内容,导致攻击者无法上传木马文件。
总结与防御
在向网页上传文件时,如果服务器端代码未对客户端上传的文件进行严格的验证和过滤,就容易被上传上来的脚本文件等木马攻击。这类脚本称之为 WebShell,用户可以利用这种恶意脚本查看服务器目录、修改服务器文件和执行系统命令等。
为了防御这种攻击,可以使用白名单判断文件类型和后缀是否合法,同时对上传后的文件进行重命名防止被攻击者利用。