前言 网上找了一些CMS,听说bluecms对新手挺友好的,复现一下。
初探 emmm,这个cms和上一个cms相比有点不太一样,个人感觉上一个CMS的逻辑更好找,直接给 m,c,f 传参,然后去调用相应的类,函数。这个稍微显得有点杂乱。
目录结构大致长这样:
入口还是index.php 文件
漏洞复现 sql注入 前台 这次借助了seay 这个漏扫工具,我们扫一下:
可以看到其实可能漏洞存在,关于seay 这个工具,在一些线下比赛也用过,虽然很多时候都是比较鸡肋的。但是有些时候它能给你一些惊喜。一般我们会通过黑盒找功能点,然后看输入是否可控,然后抓包去看一些参数,然后白盒结合着看源代码。但是有些时候你去黑合测试找功能点,是找不到的,不会很明显的给我们展示出来,就像之前hxb的一次线下,那个任意文件读取,基本上你去黑盒测,很难找到。这里的ad_js.php 也是一样,去黑盒基本上找不到,此时seay的作用就体现出来了。我们看一下ad_js.php 这个文件:
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 <?php define ('IN_BLUE' , true );require_once dirname (__FILE__ ) . '/include/common.inc.php' ;$ad_id = !empty ($_GET ['ad_id' ]) ? trim ($_GET ['ad_id' ]) : '' ;if (empty ($ad_id )){ echo 'Error!' ; exit (); } $ad = $db ->getone ("SELECT * FROM " .table ('ad' )." WHERE ad_id =" .$ad_id );if ($ad ['time_set' ] == 0 ){ $ad_content = $ad ['content' ]; } else { if ($ad ['end_time' ] < time ()) { $ad_content = $ad ['exp_content' ]; } else { $ad_content = $ad ['content' ]; } } $ad_content = str_replace ('"' , '\"' ,$ad_content );$ad_content = str_replace ("\r" , "\\r" ,$ad_content );$ad_content = str_replace ("\n" , "\\n" ,$ad_content );echo "<!--\r\ndocument.write(\"" .$ad_content ."\");\r\n-->\r\n" ;?>
重点在$ad = $db->getone("SELECT * FROM ".table('ad')." WHERE ad_id =".$ad_id); 这条查询语句上,我们跟进一下 getone() 函数:
1 2 3 4 5 6 function getone ($sql , $type =MYSQL_ASSOC ) { $query = $this ->query ($sql ,$this ->linkid); $row = mysql_fetch_array ($query , $type ); return $row ; }
执行了查询操作,然后返回查询结果,这里可以发现没有过滤,所以这里我们可以SQL注入,一开始我大意了,以为是字符型,还想办法闭合啊注释啥的,其实这里是数字型的,我们不需要闭合注释啥的。所以这里存在SQL注入,然后又看到下面的语句echo "<!--\r\ndocument.write(\"".$ad_content."\");\r\n-->\r\n"; 会把$ad_content内容带出来,有回显我们可以用联合注入。然后这里用时间盲注也可以。用联合注入更快,按照联合注入的流程,我们查列数从1开始,?ad_id=1 order by 8发现 order by 8的时候报错,说明只有7,然后我们去找回显位 ?ad_id=-1 union select 1,2,3,4,5,6,7 发现在7的地方有回显:
然后依次就是爆库,爆表,爆字段名了:?ad_id=-1 union select 1,2,3,4,5,6,group_concat(table_name)from information_schema.tables where table_schema=database()
可以看到这里可以注入的。
同样也是seay爆的可能漏洞,这里getip() 值是可控的,通过伪造Client-ip 或 X-Forwarded-For 我们全局搜素一下getip 看哪里用到了这个:
我们跟进一下comment.php 代码,关键部分:
1 2 3 $sql = "INSERT INTO " .table ('comment' )." (com_id, post_id, user_id, type, mood, content, pub_date, ip, is_check) VALUES ('', '$id ', '$user_id ', '$type ', '$mood ', '$content ', '$timestamp ', '" .getip ()."', '$is_check ')" ;$db ->query ($sql );
这里的 getip() 值可控,很明显又存在着注入。
我们尝试一下,个人资料 -> 发布新闻 -> 评论
其中评论那里的内容是我们可控的:
insert 语句是可以一次性插入多条语句的。这里我们构造:1','2'),('','1','0','1','6',(select database()),'1','99
我们抓个包测试一下:
然后发现注入成功:
这里对回显字符长度有限制,不过没关系我们可以用 limit 来截取
后台 127.0.0.1/admin/login.php 处存在管理员登陆宽字节注入,我们可以用万能密码登入进去,我们结合代码看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 elseif ($act == 'do_login' ){ $admin_name = isset ($_POST ['admin_name' ]) ? trim ($_POST ['admin_name' ]) : '' ; $admin_pwd = isset ($_POST ['admin_pwd' ]) ? trim ($_POST ['admin_pwd' ]) : '' ; $remember = isset ($_POST ) ? intval ($_POST ['rememberme' ]) : 0 ; if ($admin_name == '' ){ showmsg ('�û�������Ϊ��' ); } if ($admin_pwd == '' ){ showmsg ('�û����벻��Ϊ��' ); } if (check_admin ($admin_name , $admin_pwd )){ update_admin_info ($admin_name ); if ($remember == 1 ){ setcookie ('Blue[admin_id]' , $_SESSION ['admin_id' ], time ()+86400 ); setcookie ('Blue[admin_name]' , $admin_name , time ()+86400 ); setcookie ('Blue[admin_pwd]' , md5 (md5 ($admin_pwd ).$_CFG ['cookie_hash' ]), time ()+86400 ); } }else { showmsg ('��������û�������������' ); } showmsg ('��ӭ�� ' .$admin_name .' ���������ڽ�ת���������...' , 'index.php' ); }
因为这里用的是gbk2312编码,所以到这里成了乱码。也正因为这样,我们可以尝试一下宽字节注入,在前面有一个安全函数:
1 2 3 4 5 6 7 if (!get_magic_quotes_gpc ()){ $_POST = deep_addslashes ($_POST ); $_GET = deep_addslashes ($_GET ); $_COOKIES = deep_addslashes ($_COOKIES ); $_REQUEST = deep_addslashes ($_REQUEST ); }
这里对传入的参数做了过滤,我们跟进一下deep_addslashes() 函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function deep_addslashes ($str ) { if (is_array ($str )) { foreach ($str as $key =>$val ) { $str [$key ] = deep_addslashes ($val ); } } else { $str = addslashes ($str ); } return $str ; }
也就是所有传过来的参数经过了 addslashes() 函数 。所以是很可能存在宽字节注入的
我们再回头来看那个login.php的代码,关键在于:check_admin($admin_name, $admin_pwd) ,这个函数,我们跟进一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 function check_admin ($name , $pwd ) { global $db ; $row = $db ->getone ("SELECT COUNT(*) AS num FROM " .table ('admin' )." WHERE admin_name='$name ' and pwd = md5('$pwd ')" ); if ($row ['num' ] > 0 ) { return true ; } else { return false ; } }
这里的name出了之前的addslashes() 没有其他过滤,很显然可以存在宽字节。
发现成功宽字节注入
文件包含 这个文件包含发生在: user.php 里面,我们直接定位到关键代码:
1 2 3 4 5 6 7 8 9 10 11 elseif ($act == 'pay' ){ include 'data/pay.cache.php' ; $price = $_POST ['price' ]; $id = $_POST ['id' ]; $name = $_POST ['name' ]; if (empty ($_POST ['pay' ])) { showmsg ('�Բ�����û��ѡ��֧����ʽ' ); } include 'include/payment/' .$_POST ['pay' ]."/index.php" ; }
pay参数过滤不完整,我们可以用 ../ 跳转到上一层,然后后面的我们可以用%00来截断,不过这个对版本有要求
得保证php版本低于5.3.4 且 php .ini 里面的 magic_quotes_gpc=off
这个地方也可以配合上传点来制造图片🐎
一开始用%00截断死活复现不成功,后面feng师傅提醒我这里的 deep_addslashes 会对 %00 转义,所以%00这里行不通,但是用.来实现长度限制绕过应该是可以的。这里也要求低版本的PHP,php <5.2.8版本网上也有师傅复现成功了。
任意文件删除 先说第一处任意文件删除漏洞,我本地再D盘新建了一个 flag.txt文件,然后seay漏扫了一下发现:
/admin/link.php 里面存在任意文件删除漏洞,我们跟进代码看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 elseif ($act == 'do_edit' ){ $link_name = !empty ($_POST ['link_name' ]) ? trim ($_POST ['link_name' ]) : '' ; $link_site = !empty ($_POST ['link_site' ]) ? trim ($_POST ['link_site' ]) : '' ; $show_order = !empty ($_POST ['show_order' ]) ? intval ($_POST ['show_order' ]) : 0 ; if (!empty ($_POST ['link_logo' ])){ if (strpos ($_POST ['link_logo' ], 'http://' ) != false && strpos ($_POST ['link_logo' ], 'https://' ) != false ){ showmsg ('ֻ֧�ֱ�վ���·����ַ' ); } else { $link_logo = trim ($_POST ['link_logo' ]); } }else { if (file_exists (BLUE_ROOT.$_POST ['link_logo2' ])){ @unlink (BLUE_ROOT.$_POST ['link_logo2' ]); } }
发生漏洞的代码主要在 @unlink(BLUE_ROOT.$_POST['link_logo2']); 这里调用了 unlike函数,但是 link_logo2参数是我们可控的,并且除了那个转义函数外,没有任何安全过滤,所以导致了漏洞的发生。我们本地验证一下:
然后发现D盘的 hello.txt 文件被删除了。
任意文件读取 还是seay扫到了 /admin/tpl_manage.php 可能存在任意文件读取,我们跟进看一下:
1 2 3 4 5 6 7 8 9 10 11 12 elseif ($act == 'edit' ){$file = $_GET ['tpl_name' ];if (!$handle = @fopen (BLUE_ROOT.'templates/default/' .$file , 'rb' )){ showmsg ('��Ŀ��ģ���ļ�ʧ��' ); } $tpl ['content' ] = fread ($handle , filesize (BLUE_ROOT.'templates/default/' .$file ));$tpl ['content' ] = htmlentities ($tpl ['content' ], ENT_QUOTES, GB2312);fclose ($handle );$tpl ['name' ] = $file ;template_assign (array ('current_act' , 'tpl' ), array ('�༭ģ��' , $tpl ));$smarty ->display ('tpl_info.htm' );}
这里的 $file 变量可控,然后又只有一个 deep_addslashes 函数 ,我们也可以通过 ../ 跳转目录来读取文件。
本地测试一下:
发现可行,此处存在任意文件读取漏洞。
写shell 还是tpl_manage.php 中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 elseif ($act == 'do_edit' ){ $tpl_name = !empty ($_POST ['tpl_name' ]) ? trim ($_POST ['tpl_name' ]) : '' ; $tpl_content = !empty ($_POST ['tpl_content' ]) ? deep_stripslashes ($_POST ['tpl_content' ]) : '' ; if (empty ($tpl_name )){ return false ; } $tpl = BLUE_ROOT.'templates/default/' .$tpl_name ; if (!$handle = @fopen ($tpl , 'wb' )){ showmsg ("��Ŀ��ģ���ļ� $tpl ʧ��" ); } if (fwrite ($handle , $tpl_content ) === false ){ showmsg ('д��Ŀ�� $tpl ʧ��' ); } fclose ($handle ); showmsg ('�༭ģ��ɹ�' , 'tpl_manage.php' ); }
这里$tpl_name 和 $tpl_content 都是可控的,我们直接写🐎
然后尝试访问一下
成功拿到shell
小结 漏洞不止这些,基本上出问题的原因一样,参数过滤不完善,参数可控。
找用户输入的地方,找功能点,黑盒白盒结合着审计。
一些自动化审计软件比如seay有时候能给一个大致的审计方向。