PHP代码审计11—逻辑漏洞

文章目录

    • 一、常见的逻辑漏洞
      • 1、身份验证漏洞
      • 2、支付逻辑漏洞
      • 3、越权访问
    • 二、DedeCMS 任意用户密码重置分析
      • 1、漏洞分析
      • 2、漏洞复现
    • 三、CmsEasy7.6.3.2 订单金额修改漏洞分析
      • 1、漏洞复现
      • 2、漏洞分析
    • 四、参考资料

一、常见的逻辑漏洞

1、身份验证漏洞

1)暴力破解漏洞

  • 综述:

    暴力破解漏洞,是常见的web安全问题,往往有基于表单的暴力破解、针对验证码的暴力破解等。造成这种安全隐患的原因在于系统没有对用户的数据提交次数做限制,导致了攻击者能够无限制利用工具自动化提交数据。
    对于攻击者来说,只要在时间和计算力足够的情况下,大多数系统都是可以通过暴力破解来攻破的。
    
  • 防御方法:

    对于暴力破解漏洞的防御,主要是设置安全的认证策略:
    1.是否要求用户设置复杂的密码;
    2.是否每次认证都使用安全的验证码(想想你买火车票时输的验证码~)或者手机otp;
    3.是否对尝试登录的行为进行判断和限制(如:连续5次错误登录,进行账号锁定或IP地址锁定等);
    4.是否采用了双因素认证;
    

2)Session攻击

  • 综述:

    常见的Session攻击方式有Session会话劫持和Session会话固定攻击。攻击者在进行攻击的时候,往往胡通过预测、劫持、固定会话的方式来获取一个有效的Session来发起攻击。获取session的方式主要如下:
        1、 暴力破解:尝试各种Session ID,直到破解为止;
        2、 预测:如果Session ID使用非随机的方式产生,那么就有可能计算出来;
        3、 窃取:使用网络嗅探,XSS攻击等方法获得。
    
  • 防御:

    为了防止攻击者获取用户Session,就需要针对攻击者可能的攻击方式进行防御,比如:
    1、设置复杂的、不可逆向破解的Session,比如通过时间戳进行hash,防止Session暴力破解和猜解
    2、设置HttpOnly,防止攻击者通过XSS等方法劫持COOKIe
    3、校验http头信息,比如host,user-agent等
    4、防止SessionId长时间不变,让用户每次登录时的SessionId都发生改变或者固定时间重置SessionID。
    

3)短信轰炸

  • 综述:

    短信轰炸的原理和暴力破解漏洞类似,都是由于对操作没有做次数限制导致的,只不过这里的操作点在短信发送上。短信轰炸的影响是两个方面的,一是对用户造成干扰,二是消耗平台短信资源。
    
  • 防御:

    防御方法和暴力破解的防御方法类似,只要做好次数限制就可以很好的防御。
    

2、支付逻辑漏洞

  • 常见的支付逻辑漏洞

    • 修改支付价格
    • 修改订单数量
    • 修改优惠券、积分
    • 无限制使用等
  • 防御方法

    1、对支付流程的每个环节进行校验,并且防止跳过某一个环节。
    2、用户确认购买后,立即在后端验证商品价格(商品单价、商品数量、折扣优惠)、订单价格和到账金额。
    3、对一些优惠券、折扣券的使用方式进行测试。
    

3、越权访问

对于越权访问,可以分为两个类别,那就是水平越权和锤子越权。水平越权,简单理解就是操作同等权限的其他用户内容,垂直越权就是低权限用户操作管理员用户内容。

1)水平越权

该漏洞就是用户在权限相同级别下的组,可以进行越权访问、修改、删除数据。
形成的原因是由于服务区端在接受到请求数据进行操作时,没有判断数据的所属人而导致的越权数据访问漏洞。

2)垂直越权

由于系统没有做权限控制,仅仅在菜单和按钮上做了权限控制,导致恶意用户只要猜测其他管理页面的URL或者敏感的参数信息,就可以访问或控制其他角色用户的数据和页面,也就是意味着普通用户可以执行只有超级用户才拥有的操作。

3)未授权访问

未经身份认证的用户,访问到了系统中需要认证的用户才能访问的内容,这久属于未授权访问的范畴,这类漏洞往往是由于系统在设计了某些接口的时候,需要登录后的用户从某个入口进行调用,但是未考虑到攻击者在未经登录的情况下,直接调用该接口,从而导致了漏洞的产生。

二、DedeCMS 任意用户密码重置分析

漏洞情报:

PHP代码审计11—逻辑漏洞_第1张图片

1、漏洞分析

首先可以看到,漏洞点在 resetpassword.php 中,所以我么进入文件中看看打字的逻辑:

if(empty($dopost)) $dopost = "";
$id = isset($id)? intval($id) : 0;

if($dopost == ""){
    include(dirname(__FILE__)."/templets/resetpassword.htm");
}elseif($dopost == "getpwd"){
	//验证验证码、邮箱是否正确
   if($type == 1){
        //以安全问题取回密码;
    } else if ($type == 2){
     //以安全问题取回密码,
     if($member['safequestion'] == 0){
            showmsg('对不起您尚未设置安全密码,请通过邮件方式重设密码', 'login.php');
            exit;
        }
        require_once(dirname(__FILE__)."/templets/resetpassword3.htm");
   }
......
}else if($dopost == "safequestion"){
  //如果采用的安全问题验证,在这里验证安全问题是否正确
   if ($row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer) {
     //如果正确,执行sn函数   
     sn($mid, $row['userid'], $row['email'], 'N');
        exit();
    }
......
}else if($dopost == "getpasswd"){
  	//验证通过,重置密码
  	if(empty($setp)){
    	//判断临时修改码是否过期
  	} elseif($setp == 2){
        //判断临时修改码是否正确,并检验两次输入的密码是否一致,一致则使用udate语句重置密码
    }
}

可以看到,大致的逻辑就是通过$dopost参数来标识要执行哪一个步骤。

当我们的dopost为getpwd时,验证用户邮箱是否正确,然后通过用户选择的密码找回方式进行执行后续步骤。这里type=1标识使用邮箱找回,就会发送邮件。当type等于2时就会检测是否设置了安全密码,在设置了的情况下,就会包含“templets/resetpassword3.htm”,让用户输入安全问题。

当用户选择安全问题验证,并输入内容后,就会传入$dopost='dafequestion',然后验证我们的安全问题。在源文件的第86行,验证安全问题的方式是这样的:

if ($row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer) {
        sn($mid, $row['userid'], $row['email'], 'N');
        exit();
    }

在这里,要求同时满足两个条件,就是安全问题正确、并且安全答案正确。当我们的用户没有设置安全问题和答案的时候,默认情况下安全问题的值为0,答案为NULL。即数据库中的值$row['safequestion']="0"$row['safeanswer']=null

所以当我们传入的安全问题的值和答案都为空时,if表达式是这样的:

if('0' == '' && null == '') 

等价于:

if(false && true) 

所以我们只需要让$row['safequestion'] == $safequestion 的结果为true即可绕过检测。由于这里使用的== 而不是使用的===,所以我们就可以利用php弱类型的方法来进行绕过,比如我们的0、0.0、0e1都可以绕过。当我们传入safequestion=0.0&&$safeanswer='的时候,就会导致我们的if语句执行结果为if(true && true),从而绕过安全问题验证。然后执行sn()函数。

//file /inc/inc_pwd_functions.php
function sn($mid,$userid,$mailto, $send = 'Y')
{
    global $db;
    $tptim= (60*10);
    $dtime = time();
    $sql = "SELECT * FROM #@__pwd_tmp WHERE mid = '$mid'";
    $row = $db->GetOne($sql);
    if(!is_array($row)){
        //发送新邮件;
        newmail($mid,$userid,$mailto,'INSERT',$send);
    }elseif($dtime - $tptim > $row['mailtime']){
      	//10分钟后可以再次发送新验证码;
        newmail($mid,$userid,$mailto,'UPDATE',$send);
    }else{
        return ShowMsg('对不起,请10分钟后再重新申请', 'login.php');
    }
}

可以看到,在这里,主要调用了newmail()函数:

//file /inc/inc_pwd_functions.php
function newmail($mid, $userid, $mailto, $type, $send)
{
    global $db,$cfg_adminemail,$cfg_webname,$cfg_basehost,$cfg_memberurl;
    $mailtime = time();
    $randval = random(8);
    $mailtitle = $cfg_webname.":密码修改";
    $mailto = $mailto;
    $headers = "From: ".$cfg_adminemail."\r\nReply-To: $cfg_adminemail";
    $mailbody = "亲爱的".$userid.":\r\n您好!感谢您使用".$cfg_webname."网。\r\n".$cfg_webname."应您的要求,重新设置密码:(注:如果您没有提出申请,请检查您的信息是否泄漏。)\r\n本次临时登陆密码为:".$randval." 请于三天内登陆下面网址确认修改。\r\n".$cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&id=".$mid;
    if($type == 'INSERT'){
    	.....
    } elseif($type == 'UPDATE')
    {
        $key = md5($randval);
        $sql = "UPDATE `#@__pwd_tmp` SET `pwd` = '$key',mailtime = '$mailtime'  WHERE `mid` ='$mid';";
        if($db->ExecuteNoneQuery($sql)){
            if($send == 'Y') {
               .....
            } elseif($send == 'N'){
                return ShowMsg('稍后跳转到修改页', $cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&id=".$mid."&key=".$randval);
            }
        } else{
            ShowMsg('对不起修改失败,请与管理员联系', 'login.php');
        }
    }
}

由于我们传入的$type=update,$send="N",所以我们只关注

return ShowMsg('稍后跳转到修改页', $cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&id=".$mid."&key=".$randval);

这里返回了我们的密码修改链接,即/resetpassword.php?dopost=getpasswd&id=".$mid."&key=".$randval,其中,$randval`为我们的临时修改码。到这里就是我们修改密码的地方。整个漏洞利用也就结束了。

所以综合来分析一下,可以发现,我们可以直接绕过验证码验证步骤,直接请求$dopost=dafequestion来构造safequestion=0.0&&$safeanswer=''绕过安全问题检测,从而通过修改id(管理员id为1)来修改未设置密保问题的任意用户密码。

2、漏洞复现

构造payload,通过id获取用户密码修改链接:

/member/resetpassword.php?dopost=safequestion&safequestion=0.0&safeanswer=&id=1

PHP代码审计11—逻辑漏洞_第2张图片

这里设置的id=1,即修改admin用户的密码,然后可见,返回了admin用户的密码修改链接,我们访问:

PHP代码审计11—逻辑漏洞_第3张图片

成功的进入了admin用户的密码修改页面,然后只需要重新设置密码即可。

三、CmsEasy7.6.3.2 订单金额修改漏洞分析

1、漏洞复现

搭建好环境之后,先注册会员,然后选择商品添加到购物车:

PHP代码审计11—逻辑漏洞_第4张图片

在加入购物车这里抓包,然后修改商品数量为-10:

PHP代码审计11—逻辑漏洞_第5张图片

发包,重放后,进入购物车结算,可见金额已经变为了负数:

PHP代码审计11—逻辑漏洞_第6张图片

然后选择支付,就能够发现,钱包余额增加了。

PHP代码审计11—逻辑漏洞_第7张图片

2、漏洞分析

这个漏洞的原理和逻辑也非常的简单,就是我们在添加购物车的时候,没有添加数量做验证和处理,直接保存到了数据库中,而结算金额的时候,依赖数据库中的商品数量进行计算的时候,也咩有对金额和数量做处理,导致了”越买越多“的情况出现。具体的代码逻辑很简单,没分析的必要,就不浪费时间了。。。。。

四、参考资料

  • 狼组安全wiki—常见的逻辑漏洞
  • 逻辑漏洞梳理与总结
  • 暴力破解漏洞详解
  • Session攻击手段分析
  • 支付逻辑漏洞详解
  • 知乎—Web安全之越权漏洞
  • DedeCMS V5.7 任意用户密码重置漏洞复现
  • CmsEasy7.6.3.2 订单金额修改漏洞复现

你可能感兴趣的:(PHP代码审计,php,安全,web安全,逻辑漏洞,失效的访问控制)