为什么想要审计这套源码呐?之前看到某大佬在做反钓鱼网站的时候,发现钓鱼网站的后台用的就是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

  1. <?php
  2. class MessageAction extends CommAction {
  3. public function index(){
  4.  
  5. if($this->ispost()){
  6.  
  7. if(session('code') != md5(htmlspecialchars(addslashes($_POST['code']),ENT_QUOTES))){
  8. $this->error('验证码错误');
  9. }
  10. if($_POST['title']==false ){
  11. $this->error('标题不能为空');
  12. }
  13. if($_POST['username']==false ){
  14. $this->error('姓名不能为空');
  15. }
  16. if($_POST['mail']==false ){
  17. $this->error('邮箱不能为空');
  18. }
  19. if($_POST['content']==false ){
  20. $this->error('内容不能为空');
  21. }
  22. $data=$_POST;
  23. $data['time']=time();
  24. if(M('message')->data($data)->add()){
  25. $this->success('留言成功');
  26. }else{
  27. $this->error('留言失败');
  28. }
  29.  
  30. }else{
  31. $this->display();
  32. }
  33. }
  34. }
  35.  
  36. //data() 函数
  37. public function data($data=''){
  38. if('' === $data && !empty($this->data)) {
  39. return $this->data;
  40. }
  41. if(is_object($data)){
  42. $data = get_object_vars($data);
  43. }elseif(is_string($data)){
  44. parse_str($data,$data);
  45. }elseif(!is_array($data)){
  46. throw_exception(L('_DATA_TYPE_INVALID_'));
  47. }
  48. $this->data = $data;
  49. return $this;
  50. }

data() 函数只是将字符串格式化,add() 函数写入数据库…

不只是XSS,还有可能存在SQL注入呐~

0x03 SQL注入:

在 Common\common.php 中看到过滤函数

  1. function inject_check($sql_str) {
  2. return eregi ( 'select|inert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|UNION|into|load_file|outfile', $sql_str );
  3. }

有上面这个函数调用的话,就不存在什么注入了!

直接看登陆是否存在SQL注入,这样就可以绕过了!

登录验证在\Lib\Action\Admin\LoginAction.class.php 中

是做了过滤的。

继续看看留言板!这是用户和后台交互的地方!

MYSQL数据库监控得到如下结果:

这里做了转义,再看看代码!

data() 函数:

  1. /**
  2. * 设置数据对象值
  3. * @access public
  4. * @param mixed $data 数据
  5. * @return Model
  6. */
  7. public function data($data=''){
  8. if('' === $data && !empty($this->data)) {
  9. return $this->data;
  10. }
  11. if(is_object($data)){
  12. $data = get_object_vars($data);
  13. }elseif(is_string($data)){
  14. parse_str($data,$data); //在GPC开启下会调用addslashes() 转义函数
  15. }elseif(!is_array($data)){
  16. throw_exception(L('_DATA_TYPE_INVALID_'));
  17. }
  18. $this->data = $data;
  19. return $this;
  20. }

再看 add() 函数:

  1. * 新增数据
  2. * @access public
  3. * @param mixed $data 数据
  4. * @param array $options 表达式
  5. * @param boolean $replace 是否replace
  6. * @return mixed
  7. */
  8. public function add($data='',$options=array(),$replace=false) {
  9. if(empty($data)) {
  10. // 没有传递数据,获取当前数据对象的值
  11. if(!empty($this->data)) {
  12. $data = $this->data;
  13. // 重置数据
  14. $this->data = array();
  15. }else{
  16. $this->error = L('_DATA_TYPE_INVALID_');
  17. return false;
  18. }
  19. }
  20. // 分析表达式
  21. $options = $this->_parseOptions($options);
  22. // 数据处理
  23. $data = $this->_facade($data);
  24. if(false === $this->_before_insert($data,$options)) {
  25. return false;
  26. }
  27. // 写入数据到数据库
  28. $result = $this->db->insert($data,$options,$replace);
  29. if(false !== $result ) {
  30. $insertId = $this->getLastInsID();
  31. if($insertId) {
  32. // 自增主键返回插入ID
  33. $data[$this->getPk()] = $insertId;
  34. $this->_after_insert($data,$options);
  35. return $insertId;
  36. }
  37. $this->_after_insert($data,$options);
  38. }
  39. return $result;
  40. }

再追溯 insert() 函数:

  1. * 插入记录
  2. * @access public
  3. * @param mixed $data 数据
  4. * @param array $options 参数表达式
  5. * @param boolean $replace 是否replace
  6. * @return false | integer
  7. */
  8. public function insert($data,$options=array(),$replace=false) {
  9. $values = $fields = array();
  10. $this->model = $options['model'];
  11. foreach ($data as $key=>$val){
  12. if(is_array($val) && 'exp' == $val[0]){
  13. $fields[] = $this->parseKey($key);
  14. $values[] = $val[1];
  15. }elseif(is_scalar($val) || is_null(($val))) { // 过滤非标量数据
  16. $fields[] = $this->parseKey($key);
  17. if(C('DB_BIND_PARAM') && 0 !== strpos($val,':')){
  18. $name = md5($key);
  19. $values[] = ':'.$name;
  20. $this->bindParam($name,$val);
  21. }else{
  22. $values[] = $this->parseValue($val);
  23. }
  24. }
  25. }
  26. $sql = ($replace?'REPLACE':'INSERT').' INTO '.$this->parseTable($options['table']).' ('.implode(',', $fields).') VALUES ('.implode(',', $values).')';
  27. $sql .= $this->parseLock(isset($options['lock'])?$options['lock']:false);
  28. $sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:'');
  29. return $this->execute($sql,$this->parseBind(!empty($options['bind'])?$options['bind']:array()));
  30. }

进一步看 parserKey() 函数:

  1. /**
  2. * 字段和表名处理添加`
  3. * @access protected
  4. * @param string $key
  5. * @return string
  6. */
  7. protected function parseKey(&$key) {
  8. $key = trim($key);
  9. if(!preg_match('/[,\'\"\*\(\)`.\s]/',$key)) {
  10. $key = '`'.$key.'`';
  11. }
  12. return $key;
  13. }

原来是在 before_insert() 函数,做了转义:

  1. * 对保存到数据库的数据进行处理
  2. * @access protected
  3. * @param mixed $data 要操作的数据
  4. * @return boolean
  5. */
  6. protected function _facade($data) {
  7. // 检查非数据字段
  8. if(!empty($this->fields)) {
  9. foreach ($data as $key=>$val){
  10. if(!in_array($key,$this->fields,true)){
  11. unset($data[$key]);
  12. }elseif(is_scalar($val)) {
  13. // 字段类型检查
  14. $this->_parseType($data,$key);
  15. }
  16. }
  17. }
  18. // 安全过滤
  19. if(!empty($this->options['filter'])) {
  20. $data = array_map($this->options['filter'],$data);
  21. unset($this->options['filter']);
  22. }
  23. $this->_before_write($data);
  24. return $data;
  25. }

OJBK,此处没有注入!

0x04 任意文件下载:

  1. function downloadBak() {
  2. $file_name = $_GET['file'];
  3. $file_dir = $this->config['path'];
  4. if (!file_exists($file_dir . "/" . $file_name)) { //检查文件是否存在
  5. return false;
  6. exit;
  7. } else {
  8. $file = fopen($file_dir . "/" . $file_name, "r"); // 打开文件
  9. // 输入文件标签
  10. header('Content-Encoding: none');
  11. header("Content-type: application/octet-stream");
  12. header("Accept-Ranges: bytes");
  13. header("Accept-Length: " . filesize($file_dir . "/" . $file_name));
  14. header('Content-Transfer-Encoding: binary');
  15. header("Content-Disposition: attachment; filename=" . $file_name); //以真实文件名提供给浏览器下载
  16. header('Pragma: no-cache');
  17. header('Expires: 0');
  18. //输出文件内容
  19. echo fread($file, filesize($file_dir . "/" . $file_name));
  20. fclose($file);
  21. exit;
  22. }
  23. }

EXP:http://www.test.com/index.php?s=/Admin/Sqlback/downloadBak/file/..\index.php

这样就可以下载任意文件,但是需要管理员权限~

来看看权限验证吧!

  1. public function _initialize(){
  2. if(session('adminuser')!=C('webuser')){
  3. $this->error('你没有权限',U('/Admin/Index/home'));
  4. }
  5. }

验证的是session,没办法绕过!这个漏洞只有在后台可利用!

0x05 任意文件删除:

  1. //删除数据备份
  2. function deletebak() {
  3. if (unlink($this->config['path'] . $this->dir_sep . $_GET['file'])) {
  4. $this->success('删除备份成功!');
  5. } else {
  6. $this->error('删除备份失败!');
  7. }
  8. }

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的更多相关文章

  1. PHP代码审计中你不知道的牛叉技术点

    一.前言 php代码审计如字面意思,对php源代码进行审查,理解代码的逻辑,发现其中的安全漏洞.如审计代码中是否存在sql注入,则检查代码中sql语句到数据库的传输 和调用过程. 入门php代码审计实 ...

  2. 技术专题-PHP代码审计

    作者:坏蛋链接:https://zhuanlan.zhihu.com/p/24472674来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 一.前言 php代码审计如字面 ...

  3. 关于PHP代码审计和漏洞挖掘的一点思考

    这里对PHP的代码审计和漏洞挖掘的思路做一下总结,都是个人观点,有不对的地方请多多指出. PHP的漏洞有很大一部分是来自于程序员本身的经验不足,当然和服务器的配置有关,但那属于系统安全范畴了,我不太懂 ...

  4. Kindeditor 代码审计

    <?php /** * KindEditor PHP * * 本PHP程序是演示程序,建议不要直接在实际项目中使用. * 如果您确定直接使用本程序,使用之前请仔细确认相关安全设置. * */ r ...

  5. 一个CMS案例实战讲解PHP代码审计入门

    前言 php代码审计介绍:顾名思义就是检查php源代码中的缺点和错误信息,分析并找到这些问题引发的安全漏洞. 1.环境搭建: 工欲善其事必先利其器,先介绍代码审计必要的环境搭建 审计环境 window ...

  6. php代码审计基础笔记

    出处: 九零SEC连接:http://forum.90sec.org/forum.php?mod=viewthread&tid=8059 --------------------------- ...

  7. 【PHP代码审计】 那些年我们一起挖掘SQL注入 - 3.全局防护Bypass之Base64Decode

    0x01 背景 现在的WEB程序基本都有对SQL注入的全局过滤,像PHP开启了GPC或者在全局文件common.php上使用addslashes()函数对接收的参数进行过滤,尤其是单引号.同上一篇,我 ...

  8. 【PHP代码审计】 那些年我们一起挖掘SQL注入 - 2.全局防护Bypass之UrlDecode

    0x01 背景 现在的WEB程序基本都有对SQL注入的全局过滤,像PHP开启了GPC或者在全局文件common.php上使用addslashes()函数对接收的参数进行过滤,尤其是单引号.遇到这种情况 ...

  9. PHP代码审计】 那些年我们一起挖掘SQL注入 - 1.什么都没过滤的入门情况

    0x01 背景 首先恭喜Seay法师的力作<代码审计:企业级web代码安全架构>,读了两天后深有感触.想了想自己也做审计有2年了,决定写个PHP代码审计实例教程的系列,希望能够帮助到新人更 ...

随机推荐

  1. 八十二、SAP中的ALV创建之一,新建一个程序

    一.创建一个ALV的程序 二.填写程序属性 三.保存到本地对象 四.来到代码区,这样一个新工程就创建好了,我们后续来写相关的创建代码

  2. 035-PHP简单定义一个闭包函数

    <?php /* + 什么是闭包函数?即一个函数内部,包含了1-N个匿名函数, + 用处是可以做局部数据缓存与实现封装(有点类似class) */ # 函数内部,定义一个匿名函数,即可称为闭包函 ...

  3. 142-PHP trait的定义和使用

    <?php trait info{ //定义trait static function getinfo(){ return '这是一个'.__CLASS__.'类.<br />'; ...

  4. mock的使用及取消,node模仿本地请求:为了解决前后端分离,用户后台没写完接口的情况下

    借鉴:https://www.jianshu.com/p/dd23a6547114 1.说到这里还有一种是配置node模拟本地请求 (1)node模拟本地请求: 补充一下 [1]首先在根目录下建一个d ...

  5. LeetCode#3 - 无重复字符的最长字串(滑动窗口)

    题目: 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度. 示例: abcabcbb 输出的结果应该是3,最长的无重复的字串是'abc' 果然无论做什么都要静下心来啊!昨晚上卡了一个多小 ...

  6. ZOJ - 3961 Let's Chat(区间相交)

    题意:给定一个长度为n的序列,A和B两人分别给定一些按递增顺序排列的区间,区间个数分别为x和y,问被A和B同时给定的区间中长度为m的子区间个数. 分析: 1.1 ≤ n ≤ 109,而1 ≤x, y  ...

  7. 解决RecyclerView瀑布流效果结合Glide使用时图片变形的问题

    问题描述:使用Glide加载RecyclerView的Item中的图片,RecyclerView使用了瀑布流展示图片,但是滚动时图片会不断的加载,并且大小位置都会改变,造成显示错乱. 解决方法:使用瀑 ...

  8. 20 - CommonJS - 规范的具体内容

  9. Python pip换源

    前言 哈喽呀,小伙伴们,晚上好呀,今天要给大家带来点什么呐,我们就来说说python的pip换源吧,这个换源,相对来说,还是比较重要的,能少生好几次气的,哈哈哈 为什么要换源 我们搞python的,肯 ...

  10. C# 遇到的报错:1、试图加载格式不正确、2、线程间操作无效

    一. 调用第三方控件出现“试图加载格式不正确的程序”原因与解决办法 二. 线程间操作无效: 从不是创建控件"Form1"的线程访问它. 1) C#中Invoke的用法