为什么想要审计这套源码呐?之前看到某大佬在做反钓鱼网站的时候,发现钓鱼网站的后台用的就是PCWAP,所以我觉得有必要审计一下,顺便记录,打击网络犯罪!
0x00 PCAWAP:
PCWAP手机网站建站平台是一套可以实现PC和WAP手机版网站同一后台管理的PHP免费开源手机建站CMS系统。我简单看了一下,和ThinkPHP差不多的结构。
PCWAP
官网下载地址:http://www.pcwap.cn/1.html
下面我简单根据漏洞类型来审计…
小东在审计的过程中,发现有很多的漏洞,不好文字描述,所以下面的东西,复杂的,就直接给出 EXP
…
0X01 敏感信息泄漏:
1.安装完成后,虽然不存在重装漏洞,但是并未删除Install/pcwap.sql
文件,导致数据库字段信息泄漏
2.默认后台路径:http://www.test.com/index.php?s=/Admin
3.默认数据库备份文件地址:http://www.test.com/Data/pcwap.sql
http://www.test.com/index.php?Data/pcwap_admin.sql
如果没对这个目录做限制,有这两个东西,还不是…
4.默认是开启了Debug模式,当访问不存在的模块时,会爆出绝对路径。
EXP: http://www.test.com/index.php?s=/9%27
EXP: http://www.test.com/Lib/Action/EmptyAction.class.php
0x02 XSS:
首先来到留言板,黑盒留言测试
火狐浏览器作为管理员已登录,来到后台查看评论就弹窗:
这样一个存储性XSS
就确定存在了,可以打到管理员的cookie
看 Tpl\Admin\Message\index.html
其中的留言等各个参数都是直接以变量输出,未做过滤,那么再去看看存入数据库的呐?
在 Lib\Action\Home\MessageAction.class.php
<?php class MessageAction extends CommAction { public function index(){ if($this->ispost()){ if(session('code') != md5(htmlspecialchars(addslashes($_POST['code']),ENT_QUOTES))){ $this->error('验证码错误'); } if($_POST['title']==false ){ $this->error('标题不能为空'); } if($_POST['username']==false ){ $this->error('姓名不能为空'); } if($_POST['mail']==false ){ $this->error('邮箱不能为空'); } if($_POST['content']==false ){ $this->error('内容不能为空'); } $data=$_POST; $data['time']=time(); if(M('message')->data($data)->add()){ $this->success('留言成功'); }else{ $this->error('留言失败'); } }else{ $this->display(); } } } //data() 函数 public function data($data=''){ if('' === $data && !empty($this->data)) { return $this->data; } if(is_object($data)){ $data = get_object_vars($data); }elseif(is_string($data)){ parse_str($data,$data); }elseif(!is_array($data)){ throw_exception(L('_DATA_TYPE_INVALID_')); } $this->data = $data; return $this; }
data()
函数只是将字符串格式化,add()
函数写入数据库…
不只是XSS,还有可能存在SQL注入呐~
0x03 SQL注入:
在 Common\common.php
中看到过滤函数
function inject_check($sql_str) { return eregi ( 'select|inert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|UNION|into|load_file|outfile', $sql_str ); }
有上面这个函数调用的话,就不存在什么注入了!
直接看登陆是否存在SQL注入,这样就可以绕过了!
登录验证在\Lib\Action\Admin\LoginAction.class.php
中
是做了过滤的。
继续看看留言板!这是用户和后台交互的地方!
MYSQL数据库监控得到如下结果:
这里做了转义,再看看代码!
data()
函数:
/** * 设置数据对象值 * @access public * @param mixed $data 数据 * @return Model */ public function data($data=''){ if('' === $data && !empty($this->data)) { return $this->data; } if(is_object($data)){ $data = get_object_vars($data); }elseif(is_string($data)){ parse_str($data,$data); //在GPC开启下会调用addslashes() 转义函数 }elseif(!is_array($data)){ throw_exception(L('_DATA_TYPE_INVALID_')); } $this->data = $data; return $this; }
再看 add()
函数:
* 新增数据 * @access public * @param mixed $data 数据 * @param array $options 表达式 * @param boolean $replace 是否replace * @return mixed */ public function add($data='',$options=array(),$replace=false) { if(empty($data)) { // 没有传递数据,获取当前数据对象的值 if(!empty($this->data)) { $data = $this->data; // 重置数据 $this->data = array(); }else{ $this->error = L('_DATA_TYPE_INVALID_'); return false; } } // 分析表达式 $options = $this->_parseOptions($options); // 数据处理 $data = $this->_facade($data); if(false === $this->_before_insert($data,$options)) { return false; } // 写入数据到数据库 $result = $this->db->insert($data,$options,$replace); if(false !== $result ) { $insertId = $this->getLastInsID(); if($insertId) { // 自增主键返回插入ID $data[$this->getPk()] = $insertId; $this->_after_insert($data,$options); return $insertId; } $this->_after_insert($data,$options); } return $result; }
再追溯 insert()
函数:
* 插入记录 * @access public * @param mixed $data 数据 * @param array $options 参数表达式 * @param boolean $replace 是否replace * @return false | integer */ public function insert($data,$options=array(),$replace=false) { $values = $fields = array(); $this->model = $options['model']; foreach ($data as $key=>$val){ if(is_array($val) && 'exp' == $val[0]){ $fields[] = $this->parseKey($key); $values[] = $val[1]; }elseif(is_scalar($val) || is_null(($val))) { // 过滤非标量数据 $fields[] = $this->parseKey($key); if(C('DB_BIND_PARAM') && 0 !== strpos($val,':')){ $name = md5($key); $values[] = ':'.$name; $this->bindParam($name,$val); }else{ $values[] = $this->parseValue($val); } } } $sql = ($replace?'REPLACE':'INSERT').' INTO '.$this->parseTable($options['table']).' ('.implode(',', $fields).') VALUES ('.implode(',', $values).')'; $sql .= $this->parseLock(isset($options['lock'])?$options['lock']:false); $sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:''); return $this->execute($sql,$this->parseBind(!empty($options['bind'])?$options['bind']:array())); }
进一步看 parserKey()
函数:
/** * 字段和表名处理添加` * @access protected * @param string $key * @return string */ protected function parseKey(&$key) { $key = trim($key); if(!preg_match('/[,\'\"\*\(\)`.\s]/',$key)) { $key = '`'.$key.'`'; } return $key; }
原来是在 before_insert()
函数,做了转义:
* 对保存到数据库的数据进行处理 * @access protected * @param mixed $data 要操作的数据 * @return boolean */ protected function _facade($data) { // 检查非数据字段 if(!empty($this->fields)) { foreach ($data as $key=>$val){ if(!in_array($key,$this->fields,true)){ unset($data[$key]); }elseif(is_scalar($val)) { // 字段类型检查 $this->_parseType($data,$key); } } } // 安全过滤 if(!empty($this->options['filter'])) { $data = array_map($this->options['filter'],$data); unset($this->options['filter']); } $this->_before_write($data); return $data; }
OJBK,此处没有注入!
0x04 任意文件下载:
function downloadBak() { $file_name = $_GET['file']; $file_dir = $this->config['path']; if (!file_exists($file_dir . "/" . $file_name)) { //检查文件是否存在 return false; exit; } else { $file = fopen($file_dir . "/" . $file_name, "r"); // 打开文件 // 输入文件标签 header('Content-Encoding: none'); header("Content-type: application/octet-stream"); header("Accept-Ranges: bytes"); header("Accept-Length: " . filesize($file_dir . "/" . $file_name)); header('Content-Transfer-Encoding: binary'); header("Content-Disposition: attachment; filename=" . $file_name); //以真实文件名提供给浏览器下载 header('Pragma: no-cache'); header('Expires: 0'); //输出文件内容 echo fread($file, filesize($file_dir . "/" . $file_name)); fclose($file); exit; } }
EXP:http://www.test.com/index.php?s=/Admin/Sqlback/downloadBak/file/..\index.php
这样就可以下载任意文件,但是需要管理员权限~
来看看权限验证吧!
public function _initialize(){ if(session('adminuser')!=C('webuser')){ $this->error('你没有权限',U('/Admin/Index/home')); } }
验证的是session,没办法绕过!这个漏洞只有在后台可利用!
0x05 任意文件删除:
//删除数据备份 function deletebak() { if (unlink($this->config['path'] . $this->dir_sep . $_GET['file'])) { $this->success('删除备份成功!'); } else { $this->error('删除备份失败!'); } }
EXP: http://www.test.com/index.php?s=/Admin/Sqlback/deletebak/file/..\index.php
同样需要管理员的权限!
再看了一下命令注入,也没法儿利用…
0x06 总结:
虽然此次审计没发现什么特别致命的东西,如果想要 getshell
有这样的方法!
1.首先就是需要管理员权限,(弱口令第一考虑,其次就是 XSS
打管理员 Cookie
)
2.通过任意文件下载网站配置信息:http://www.test.com/index.php?s=/Admin/Sqlback/downloadBak/file/..\Conf\pcwap.php
,可以得到网站配置信息(数据库连接信息),这里可通过 Mysql
写文件拿到shell,(网站的物理路径可通过报错信息得到)
3.通过任意文件删除漏洞,删除文件配置文件可重装:http://www.test.com/index.php?s=/Admin/Sqlback/deletebak/file/..\Conf\pcwap.php
,即可重装,然后安装到自己的远程数据库,MYSQL
写 Shell 即可。
就这样吧~
版权声明:《 [代码审计]PCWAP 》为DYBOY原创文章,转载请注明出处!
最后编辑:2018-7-14 12:07:55
2019-04-30 23:50