目录
前言:
仅记录我收集知识的历程
考点:
PNG 文件上传
finfo_file() 函数原理。
getimagesize() 函数原理与绕过
代码审计
解题:
进入环境:
首先登录,进入上传页面。
提示 上传小于256kb ,小于256px*256px的PNG图片
上传 . php 然后抓个包看看回显。
返回了 cookie , 以点号分割,应该是 jwt 格式,复制这个cookie到jwt网页上:JSON 网络令牌 - jwt.io
提示我们上传的 不是PNG 格式,bp 改一下后缀,改成PNG,相应的,Content-Type: 也改成 image/png
依旧返回了 cookie ,再去解码看看返回的什么。 解码后,依旧是 不为 PNG文件。
思路到这,还是不知道他过滤了什么,可能是文件头格式,但现在 上传一个正常的png文件试试,说不定还能遇到 任意文件读取,或者目录穿越。。
成功上传了,并且改了主页的头像。
但是到这就没了 后续,几番测试也没有结果。 看了下别人的文章,原来题目是给了 源码的。我没发现, 因为 是上传题,直接看upload,php 了。
<?php
error_reporting(0);
require_once('config.php');
require_once('lib/util.php');
require_once('lib/session.php');
$session = new SecureClientSession(CLIENT_SESSION_ID, SECRET_KEY);
// check whether file is uploaded
if (!file_exists($_FILES['file']['tmp_name']) || !is_uploaded_file($_FILES['file']['tmp_name'])) {
error('No file was uploaded.');
}
// check file size
if ($_FILES['file']['size'] > 256000) {
error('Uploaded file is too large.');
}
// check file type
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);
if (!in_array($type, ['image/png'])) {
error('Uploaded file is not PNG format.');
}
// check file width/height
$size = getimagesize($_FILES['file']['tmp_name']);
if ($size[0] > 256 || $size[1] > 256) {
error('Uploaded image is too large.');
}
if ($size[2] !== IMAGETYPE_PNG) {
// I hope this never happens...
error('What happened...? OK, the flag for part 1 is: <code>' . getenv('FLAG1') . '</code>');
}
// ok
$filename = bin2hex(random_bytes(4)) . '.png';
move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_DIR . '/' . $filename);
$session->set('avatar', $filename);
flash('info', 'Your avatar has been successfully updated!');
redirect('/');
还是少不了代码审计啊。
关键点:
// check file type
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);
if (!in_array($type, ['image/png'])) {
error('Uploaded file is not PNG format.');
finfo_file() 函数检测文件类型是否为 image/png 格式。
if ($size[2] !== IMAGETYPE_PNG) {
// I hope this never happens...
error('What happened...? OK, the flag for part 1 is: <code>' . getenv('FLAG1') . '</code>');
}
在检查文件长宽的时候,getimagesize() 函数用于获取图像大小及相关信息,成功将返回一个数组。 如果索引2 不是PNG 则输出flag。、
对于 getimagesize() 函数,返回的数组。测试一下此函数。
可见他返回了 如下信息:
array(6) {
[0]=>
int(73)
[1]=>
int(100)
[2]=>
int(3)
[3]=>
string(23) "width="73" height="100""
["bits"]=>
int(8)
["mime"]=>
string(9) "image/png"
}
解释结果:
- 索引 [0] 给出的是图片宽度的像素值
- 索引 [1] 给出的是图片高度的像素值
- 索引 [2] 给出的是图像的类型 ,返回的是数字,其中 1 = GIF,2 = JPG,3 = PNG,4 = SWF,5 = PSD,6 = BMP,7 = TIFF(intel byte order),8 = TIFF(motorola byte order),9 = JPC,10 = JP2,11 = JPX,12 = JB2,13 = SWC,14 = IFF,15 = WBMP,16 = XBM
- 索引 [3] 给出的是一个宽度和高度的字符串,可直接用于HTML的<image>标签。
- 索引 [bits] 给出的是图像的每种颜色的位数,二进制格式。
- 索引 [channels] 给出的是图像的通道值,RGB图像默认是3
- 索引 [mime] 给出的是 图像的MIME 信息,此信息可以用来在HTTP Content-Type 头信息中发送正确的信息 如: header("Content-type: image/jpeg");
进入正题:
函数 finfo_ file() 主要是识别PNG 文件 十六进制下的第一行信息,若保留文件头信息,破坏掉 文件宽高等 其余信息,也就是说 破坏getimagesize() 函数无法正确的检验就可以了
只要把我这里选中的文件头格式留下就可以破坏 getimagesize 的检查了,因为文件头他还是会检查是否为png 的。我们可以使用misc 常用的 winhex 来修改他的值:
使用 winhex 只留其文件头,将后面的图片内容全部删除。这样他就检查不到了。
成功得到 flag 。
总结:
对于 getimagesize() 和 finfo_file ()还是熟悉了挺多,还有索引表达的意思。
其次,还有很有意思的代码审计。