从seacms12.9教你学会代码审计
真爱和自由 发表于 四川 漏洞分析 686浏览 · 2024-08-20 09:16

前言

去搜了一圈网上也没有审计seacms12.9的集合,这里就来学习php审计一下

环境搭建

这个不说了,下载源码phpstduy就ok了,我使用的是12.9版本

漏洞审计

基础审计

include/webscan/webscan.php

$getfilter = "\\<.+javascript:window\\[.{1}\\\\x|<.*=(&#\\d+?;?)+?>|<.*(data|src)=data:text\\/html.*>|\\b(alert\\(|confirm\\(|expression\\(|prompt\\(|benchmark\s*?\(.*\)|sleep\s*?\(.*\)|\\b(group_)?concat[\\s\\/\\*]*?\\([^\\)]+?\\)|\bcase[\s\/\*]*?when[\s\/\*]*?\([^\)]+?\)|load_file\s*?\\()|<[a-z]+?\\b[^>]*?\\bon([a-z]{4,})\s*?=|^\\+\\/v(8|9)|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)@{0,2}(\\(.+\\)|\\s+?.+?\\s+?|(`|'|\").*?(`|'|\"))FROM(\\(.+\\)|\\s+?.+?|(`|'|\").*?(`|'|\"))|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
//post拦截规则
$postfilter = "<.*=(&#\\d+?;?)+?>|<.*data=data:text\\/html.*>|\\b(alert\\(|confirm\\(|expression\\(|prompt\\(|benchmark\s*?\(.*\)|sleep\s*?\(.*\)|\\b(group_)?concat[\\s\\/\\*]*?\\([^\\)]+?\\)|\bcase[\s\/\*]*?when[\s\/\*]*?\([^\)]+?\)|load_file\s*?\\()|<[^>]*?\\b(onerror|onmousemove|onload|onclick|onmouseover)\\b|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)(\\(.+\\)|\\s+?.+?\\s+?|(`|'|\").*?(`|'|\"))FROM(\\(.+\\)|\\s+?.+?|(`|'|\").*?(`|'|\"))|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
//cookie拦截规则
$cookiefilter = "benchmark\s*?\(.*\)|sleep\s*?\(.*\)|load_file\s*?\\(|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)@{0,2}(\\(.+\\)|\\s+?.+?\\s+?|(`|'|\").*?(`|'|\"))FROM(\\(.+\\)|\\s+?.+?|(`|'|\").*?(`|'|\"))|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";

可以看到对我们的sql和xss做了非常严格的限制

在include/common.php中对用户输入也进行了转义的处理,实现xss或者sql注入是非常困难了

function _RunMagicQuotes(&$svar)
{
   if(!get_magic_quotes_gpc())
   {
      if( is_array($svar) )
      {
         foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v);
      }
      else
      {
         $svar = addslashes($svar);
      }
   }
   return $svar;
}


foreach(Array('_GET','_POST','_COOKIE','_SERVER') as $_request)
{
   foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}

checksql方法

function CheckSql($db_string,$querytype='select')
{
    global $cfg_cookie_encode;
    $clean = '';
    $error='';
    $old_pos = 0;
    $pos = -1;
    $log_file = sea_INC.'/../data/'.md5($cfg_cookie_encode).'_safe.txt';
    $userIP = GetIP();
    $getUrl = GetCurUrl();  


    //如果是普通查询语句,直接过滤一些特殊语法
    if($querytype=='select')
    {
        $notallow1 = "[^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}";
        //$notallow2 = "--|/\*";
        if(m_eregi($notallow1,$db_string)){exit('SQL check');}
    }

    //完整的SQL检查
    while (true)
    {
        $pos = stripos($db_string, '\'', $pos + 1);
        if ($pos === false)
        {
            break;
        }
        $clean .= substr($db_string, $old_pos, $pos - $old_pos);
        while (true)
        {
            $pos1 = stripos($db_string, '\'', $pos + 1);
            $pos2 = stripos($db_string, '\\', $pos + 1);
            if ($pos1 === false)
            {
                break;
            }
            elseif ($pos2 == false || $pos2 > $pos1)
            {
                $pos = $pos1;
                break;
            }
            $pos = $pos2 + 1;
        }
        $clean .= '$s$';
        $old_pos = $pos + 1;
    }
    $clean .= substr($db_string, $old_pos);
    $clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));

    if (stripos($clean, '@') !== FALSE  OR stripos($clean,'char(')!== FALSE  OR stripos($clean,'script>')!== FALSE   OR stripos($clean,'<script')!== FALSE  OR stripos($clean,'"')!== FALSE OR stripos($clean,'$s$$s$')!== FALSE)
        {
            $fail = TRUE;
            if(preg_match("#^create table#i",$clean)) $fail = FALSE;
            $error="unusual character";
        }
    //老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它
    if (stripos($clean, 'union') !== false && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0)
    {
        $fail = true;
        $error="union detect";
    }

    //发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们
    elseif (stripos($clean, '/*') > 2 || stripos($clean, '--') !== false || stripos($clean, '#') !== false)
    {
        $fail = true;
        $error="comment detect";
    }

    //这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库
    elseif (stripos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0)
    {
        $fail = true;
        $error="sleep detect";
    }
    elseif (stripos($clean, 'updatexml') !== false && preg_match('~(^|[^a-z])updatexml($|[^[a-z])~s', $clean) != 0)
    {
        $fail = true;
        $error="updatexml  detect";
    }
    elseif (stripos($clean, 'extractvalue') !== false && preg_match('~(^|[^a-z])extractvalue($|[^[a-z])~s', $clean) != 0)
    {
        $fail = true;
        $error="extractvalue  detect";
    }
    elseif (stripos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
    {
        $fail = true;
        $error="benchmark detect";
    }
    elseif (stripos($clean, 'load_file') !== false && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0)
    {
        $fail = true;
        $error="file fun detect";
    }
    elseif (stripos($clean, 'into outfile') !== false && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~s', $clean) != 0)
    {
        $fail = true;
        $error="file fun detect";
    }

    //老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息
    elseif (preg_match('~\([^)]*?select~s', $clean) != 0)
    {
        $fail = true;
        $error="sub select detect";
    }
    if (!empty($fail))
    {
        fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||$error\r\n");
        exit("<font size='5' color='red'>Safe Alert: Request Error step 2!</font>");
    }
    else
    {

        return $db_string;
    }
}

/js/player/dmplayer/player/index.php反射型xss

我们看到代码处

#loading-box {
    background: #<?php echo ($_GET['color']); ?> !important;
}

可以看到这里没有做任何的过滤,直接输出了我们的代码,而且这个文件也没有引用任何的waf,前面审计的对这个文件是不影响的

导致直接触发了漏洞

admin_ping.php存在代码执行

我们先复现一波

确认后

我们看代码

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

可以看到我们的输入又是没有任何的过滤就被写入了我们的/data/admin/ping.php

造成了我们的任意代码执行漏洞

admin/admin_safe.php任意文件下载

POC

http://seacms:8181/.../admin_safe.php?action=download&file=D:\phpstudy_pro\WWW\seacms-12_9\nginx.htaccess(这个填自己需要的文件位置)

我们看到代码位置

if($action=="download" && isset($_GET['file']) && trim($_GET['file'])!="")
 {
  $file = $_GET['file'];
  ob_clean();
  if (@file_exists($file)) {
   header("Content-type: application/octet-stream");
      header("Content-Disposition: filename=\"".basename($file)."\"");
   echo file_get_contents($file);
  }
  exit();
 }

没有对我们的file做任何的限制,导致了任意文件读取

admin_datarelate.php存在sql注入漏洞

可以看到是查询出了数据

我们看到代码部分

elseif($action=="result"){
    include(sea_ADMIN.'/templets/admin_datarelate_result.htm');
    exit();
}

我们来到

/templets/admin_datarelate_result.htm文件

$sql = stripslashes($sql);
    if(strtolower(substr(trim($sql),0,6))!='select')
    {
        $sqlArr = explode(';',rtrim(trim($sql),';'));
        //var_dump($sqlArr);
        foreach($sqlArr as $sql)
        {
            $num=$dsql->ExecuteNoneQuery2($sql);
            if($num==-1)
            {
                echo '<div style="color:#F00;text-align:center">'.$dsql->GetError().'<br />SQL执行失败请检查SQL语句是否正确<a href="//www.seacms.net">[?]</a></div></tr></table>';
                viewFoot();
                exit();
            }
        }
        echo '<div style="color:#F00;text-align:center">SQL执行成功,'.$num.'条数据受影响</div></tr></table>';
        viewFoot();
        exit();
    }

可以看到是没有对我们的sql做任何过滤的,其实正常来说我们的代码有个逻辑,就是那个checksql

使用Execute方法或者getOne方法都有检测的逻辑

function Execute($id="me", $sql='')
    {
        global $dsql;
        self::$i++;
        if($dsql->isClose)
        {
            $this->Open(false);
            $dsql->isClose = false;
        }
        if(!empty($sql))
        {
            $this->SetQuery($sql);
        }

        //SQL语句安全检查
        if($this->safeCheck)
        {
            CheckSql($this->queryString);
        }

但是这里执行sql的是我们的ExecuteNoneQuery2方法

function ExecuteNoneQuery2($sql='')
    {
        global $dsql;
        self::$i++;
        if($dsql->isClose)
        {
            $this->Open(false);
            $dsql->isClose = false;
        }

        if(!empty($sql))
        {
            $this->SetQuery($sql);
        }
        if(is_array($this->parameters))
        {
            foreach($this->parameters as $key=>$value)
            {
                $this->queryString = str_replace("@".$key,"'$value'",$this->queryString);
            }
        }
        mysqli_query($this->linkID,$this->queryString);
        return mysqli_affected_rows($this->linkID);
    }

是没有进行sql检测的,其实也很好理解,那个界面本来的作用就是给你来查数据的,没有必要过滤

/js/player/dmplayer/dmku/index.php 未授权sql注入

这个相比于上一个来说是危害更大,因为不需要登录admin用户

确实是sleep了,说明漏洞存在,我们看到代码

if ($_GET['ac'] == "edit") {
    $cid = $_POST['cid'] ?: showmessage(-1, null);
    $data = $d->编辑弹幕($cid) ?:  succeedmsg(0, '完成');
    exit;
}

我们跟进编辑弹幕方法

一路来到

public static function 编辑_弹幕($cid)
    {
        try {
            global $_config;
            $text = $_POST['text'];
            $color = $_POST['color'];
            $conn = @new mysqli($_config['数据库']['地址'], $_config['数据库']['用户名'], $_config['数据库']['密码'], $_config['数据库']['名称'], $_config['数据库']['端口']);

            $sql = "UPDATE sea_danmaku_list SET text='$text',color='$color' WHERE cid=$cid";
            $result = "UPDATE sea_danmaku_report SET text='$text',color='$color' WHERE cid=$cid";
            $conn->query($sql);
            $conn->query($result);
        } catch (PDOException $e) {
            showmessage(-1, '数据库错误:' . $e->getMessage());
        }
    }

这里我们可以看到查询又是使用的原生的query方法,所以并没有过滤

所以导致sql注入

参考https://github.com/seacms-net/CMS/issues

0 条评论
某人
表情
可输入 255