本文主要内容

  • 简单文件头检测、文件幻数检测
  • 完整文件结构检测

0x01 简单文件头检测

  • 文件幻数检测

    JPG : FF D8 FF E0 00 10 4A 46 49 46

    GIF : 47 49 46 38 39 61 (GIF89a)

    PNG: 89 50 4E 47

  • 如下图所示代码,是通过文件头的起始部分进行匹配,比较简单的一种文件类型检测方法。

简单文件头检测测试示例

  • 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<?php
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 = "uploads/".rand(10, 99).".".$file_type;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
?>

<div id="upload_panel">
<ol>
<li>
<h3>上传区</h3>
<form enctype="multipart/form-data" method="post">
<p>请选择要上传的图片:<p>
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>
<div id="msg">
<?php
if($msg != null){
echo "提示:".$msg;
}
?>
</div>
<div id="img">
<?php
if($is_upload){
echo '<img src="'.$img_path.'" width="250px" />';
}
?>
</div>
</li>

</ol>
</div>
  • 如果仅在含phpinfo()的文件(phpinfo.php)将其修改为phpinfo.gif是不行的,需在文件内容开头添加gif的文件幻数GIF8a

    1
    GIF89a<?php phpinfo();?>
  • 测试:利用burpsuite截包,repeater重放功能

  • 利用:利用本地文件包含,访问已上传的图片文件(因为图片无法直接被解析)

0x02 完整文件结构检测

  • 通过调用图像函数(如:getimagesize/imagecreatefromgif/imagecreatefrompng),进行检测文件是否为图像,需要文件内容保持相对完整,所以无法通过上追加头部起始字节的方法进行绕过。
  • 但是对于getimagesize,如果在文件头追加GIF89a,也是可以成功上传的,但是imagecreatefromgif等无法绕过

2.1 绕过方法

  • 将图片文件与欲上传的文件进行合并绕过检测
  • copy命令进行文件合并
  • 合并后的文件只要未经过清洗或缩放等操作即可通过检测,并保存欲上传文件的完整性
  • 上传文件的图片部分在解析为PHP时会以乱码显示,建议与尽量小的文件进行合并,否则会有大量乱码

2.2图片一句话木马制作

  • 创建一个文件夹,包含a.bat,tp.gif,yjh.php
  • a.bat内容为cmd;yjh.php内容为<?php phpinfo(): ?>
  • 点击bat文件进入DOS命令,写入”copy tp.gif/b+yjh.php tpyjh.gif” 回车

2.3实战示例

  • 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<?php
function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);
$ext = image_type_to_extension($info[2]);
if(stripos($types,$ext)){
return $ext;
}else{
return false;
}
}else{
return false;
}
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_type = isImage($temp_file);
if(@imagecreatefromgif($temp_file)){

if($file_type == 'unknown'){
$msg = "文件未知,上传失败!";
}else{
$img_path = "uploads/".rand(10, 99).$file_type;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}else{
$msg="文件不是gif图片";
}
}
?>

<div id="upload_panel">
<ol>
<li>
<h3>上传区</h3>
<form enctype="multipart/form-data" method="post">
<p>请选择要上传的图片:<p>
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>
<div id="msg">
<?php
if($msg != null){
echo "提示:".$msg;
}
?>
</div>
<div id="img">
<?php
if($is_upload){
echo '<img src="'.$img_path.'" width="250px" />';
}
?>
</div>
</li>

</ol>
</div>
  • 利用burpsuite重放,获取生成的随机图片文件

  • 利用本地文件包含访问图片

0x03 恶意文件内容检测