upload-labs靶场通关详解:第14关
一、分析源代码
这一关的任务说明已经相当于给出了答案,就是让我们上传一个图片木马,可以理解为图片中包含了一段木马代码。
function getReailFileType($filename){$file = fopen($filename, "rb");$bin = fread($file, 2); //只读2字节fclose($file);$strInfo = @unpack("C2chars", $bin); $typeCode = intval($strInfo['chars1'].$strInfo['chars2']); $fileType = ''; switch($typeCode){ case 255216: $fileType = 'jpg';break;case 13780: $fileType = 'png';break; case 7173: $fileType = 'gif';break;default: $fileType = 'unknown';} return $fileType;
}$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){$temp_file = $_FILES['upload_file']['tmp_name'];$file_type = getReailFileType($temp_file);if($file_type == 'unknown'){$msg = "文件未知,上传失败!";}else{$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;if(move_uploaded_file($temp_file,$img_path)){$is_upload = true;} else {$msg = "上传出错!";}}
}
现在我们分析代码,它这里通过读取文件头的二进制数据判断文件真实类型,而非依赖文件扩展名。简单来说,就是它会通过读取文件头的前两个字节来判断是否是合法的JPG文件。
每个文件类型都有特定的头部字节序列(称为 “魔法数” 或 “签名”),用于标识文件的真实类型。
比如,JPG 文件的头部起始字节为 FF D8,这两个字节是 JPG 文件的必要标识,任何合法的 JPG 文件都必须以FF D8开头。
FF和D8是十六进制数,通常以0xFF、0xD8的形式书写(前缀0x表示十六进制),但在文件头描述中常省略前缀,直接写为FF D8。
FF的十六进制对应二进制为 11111111(8 位,即 1 字节),十进制值为 255。
D8的十六进制对应二进制为 11011000,十进制值为 216。
因此,FF D8本质上是两个连续的字节,其十进制值又分别为 255 和 216。
那为什么用十六进制描述文件头?是为了简洁。
二进制需要 8 位表示 1 字节(如11111111),而十六进制仅需 2 位(FF),更便于阅读和记录。
这关的代码中,通过以下步骤验证 JPG 文件:
1.读取文件前 2 字节,得到二进制数据 FF D8。
2.将两个字节转换为十进制数:255(FF)和216(D8)。
3.拼接为整数 255216,与switch中的case 255216匹配,从而判定为 JPG 文件。
之前我们可以将666.php后缀改成jpg来绕过,但是文件的内容仍然是恶意代码,这里显然无法通过文件头的检测。这关我们将尝试用图片马的方式来绕过。
二、解题思路
图片马的制作有很多种方法,这里我们讲解一种简单的方法:利用cmd命令中的copy。
copy /b 1.jpg + 2.jpg 3.jpg
这段命令是将1.jpg文件和2.jpg文件合并为3.jpg文件,参数/b的意思是以二进制模式复制文件,是为了保证图片数据的完整性。
我们可以利用这条命令将图片和木马合成为图片木马,进行上传,此时图片木马(假设JPG格式)的头部仍然为FF D8,但是中间或者末尾包含了恶意代码,但是这关的PHP代码只检测文件头部的前两个字节,对后面的内容没有检查,所以就产生了绕过。
然后再利用文件包含漏洞(这里按步骤做即可,不另外解释,日后会学习)去验证图片马是否被解析。
三、解题步骤
1.准备一句话木马666.php和一张正常的图片777.jpg。
2.将其合成为图片马。
3.上传图片马。
4.利用文件包含漏洞验证图片马是否能被解析。点击文件包含漏洞,跳转到另一个页面。
在URL中构造文件包含漏洞的语句,即在include.php后面拼接“?file=图片路径”。
隐藏在图片中的木马被成功解析。