前言
在某次攻防中,遇到了cms站一个上传点,上传正常图片会回显路径,上传其他文件提示上传失败,且没有过多信息回显,一度以为没有漏洞。
通过找源码代码审计后getshell。
测试
在某次测试过程中发现,通过泄漏的md5进入后台,在后台配置中有处“上传logo”的功能。
通过一顿测试,上传文件为图片后缀但内容非图片(文件头),会提示“上传的不是一张图片”。
如果上传图片文件, 后缀改为php等 会提示“上传失败”。只有正常图片+图片后缀 会返回图片路径。
一时陷入僵局。
一顿百度找到了源代码,那就从代码审计入手吧。
进行代码审计
首先找到上传点的入口文件。
第一行 导入命名空间“phpWeChat” 内的 Upload 类,下面是引用包含一些文件。
再往下,$action是用于下面 switch函数内做索引匹配而调用不同功能的。
$action的值是通过 GET方式获取 “action”参数的值。如果空,就用预设的imageupload
当url为 :get:url/index.php?action=imageupload 进入到“imageupload”代码块
switch($action)
{
case 'imageupload':
$originalname=preg_replace('/[^a-z0-9_]/i','',$originalname);
//过滤文件名特殊字符作用,i:不区分大小写模式
$image=Upload::imageUpload($upload_file,$originalname);
//图片落地
if(is_image($image)) { 省略} //is_image() 获取文件后缀
其中核心的代码是$image 这一行。
通过使用命名空间中Upload类的imageUpload()方法获取并处理图片后落地,将结果返回。
返回的结果为在下一行if函数作为条件进行判断 。而具体做了什么先看imageUpload()函数做了什么,返回什么结果。
先看Upload类代码如下:
这里使用了命名空间,并在刚才的index.php文件内导入,来进行使用。
Upload类下,定义了几个静态方法,主要是图片、视频、压缩包上传,图片放大等方法
主要看下被调用的imageUpload()方法
在if中的表达式,通过文件上传变量 $_FILES 获取图片用此方法文件上传后,会被存储在服务器临时目录内,
上传成功后将满足if条件,进入if内代码。前四行依次获取上传的图片信息:文件名、临时路径、文件后缀、文件大小。
再经过两个if判断,判断是否post传入图片、判断文件是否超过限制大小。
到了209行这个关键点,会对文件进行检查。
此处的if判断内使用了getimagesize()函数,并检查结果是否为数组,结果并取反
getimagesize()函数会文件头进行检查,来判断文件是否为有效的图片类型。并返回一个包含图片信息的数组。如果上传了非图片内容,getimagesize()返回 false
所以上传非图片内容时,getimagesize()返回 false 并取反 此处会执行exit并弹框”上传的不是一张图片”
对该函数需要进行一个绕过。
此时就需要一个图片马进行绕过。或是利用gif文件的头。
下面是一个正常的gif文件头。
GIF89a图形文件就是一个根据图形交换格式(GIF)89a版(1989年7 月发行)进行格式化之后的图形。在GIF89a之前还有87a版(1987年5月发行)
可构造内容,进行绕过。
GIF89a phpinfo();
条件满足后,代码往下执行。
前两个变量分别赋值了,将要储存到的 文件名和 目录名
文件名取客户端提交时间+随机值,目录名取当前年月日,然后make_dir创建文件夹。
到了218行,if内表达式 使用move_uploaded_file将文件从tmp临时目录移动到网站上传目录内,此时文件已经写入硬盘目录。move_uploaded_file执行成功后会返回 真值,进入if内进行图片文件尺寸放大等操作。
成功后,这时将返回一个文件路径结果
再回到index入口文件。$image结果是imageUpload()方法返回的文件路径
$image在if判断的条件内,被is_image()处理。
is_image()通过获得后缀,然后在多个图片后缀数组内进行匹配,匹配到返回真值,否则 假。
如果不为’gif’,’jpg’,’jpeg’,’png’,’bmp’内后缀,结果为假的,前台将返回上传失败提示。
但其实在服务器端,文件已经落地,返回任何信息也只是掩耳盗铃:
总结
1.文件写入目录前,文件的后缀直接使用用户提交时的文件后缀,且未对后缀或文件类型进行检查。
2.文件落地后才进行后缀检查,这时的后缀检查完全是马后炮。
3.虽然对文件有有效性进行验证,但getimagesize()函数是可通过文件头(gif文件头)绕过。
4.move_uploaded_file()函数在php5部分版本中 可以使用 “%00”截断路径名,绕过后缀。
5.文件名前缀,是取客户端提交时间戳+四位随机数,文件名可以被枚举。
上述问题造成了任意文件上传漏洞。
往期回顾
商务咨询:
0571-87031601
商务邮箱:
原文始发于微信公众号(雷石安全实验室):无回显的任意文件上传