###6.3 会话认证漏洞

概述

会话认证是一个非常大的话题,涉及各种认证协议和框架,如 cookie、 session、sso、 oauth、 openid 等,出现问题比较多的在 cookie 上面,cookie 是 Web 服务器返回给客户端的一段常用来标识用户身份或者认证情况的字符串,保存在客户端,浏览器下次请求时会自动带上这个标识,由于这个标识字符串可以被用户修改,所以存在安全风险,一般这块的认证安全问题都出在服务端直接取用 cookie 的数据而没有校验,其次是 cookie 加密数据存在可预测的情况。另外是 session 是保存在服务器端的信息,如果没有代码操作,客户端不能直接修改 session,相对比较安全。sso、 oauth、 openid 与 cookie、 session 相比不是一个维度的东西,由于这块在应用代码审计没有什么合适的案例,暂时先不介绍。

实例

认证漏洞在代码审计的时候能遇到比较多的是出现在 cookie 验证上面,通常是没有使用 session 来认证,而是直接将用户信息保存在 cookie 中,程序使用的时候直接调用。一般这个过程都有一个统一的函数去取数据调用,容易导致SQL注人和越权等漏洞。在挖掘登录认证漏洞的时候,可以先看一下程序的登录功能代码,看看整个登录过程的业务逻辑有没有可以控制 session 值或者直接绕过密码验证的漏洞;另外需要关注程序验证是否为登录的代码,通俗的说是验证 cookie 的代码,是不是直接去取 cookie 里面的值,然后怎么去判断这个值来验证是否登录。以前见过相当粗糙的验证是直接判断 cookie 里面的 username 参数是否为空,还有就是以 cookie 里面的用户名来作为当前用户,这种情况直接把用户名改成 admin 等管理员用户名就直接是管理员权限了。

cookie认证安全

cookie 可以保存任何字符串,各个浏览器保存 cookie 字节数大小不一样,一般都不超过4096 个字节,通常 cookie 用来保存登录账号的标识信息,比如用户名或者sessionid 等,浏览器每次请求的时候都会再次带上对应这个域名的 cookie 信息,服务器应用程序可以对 cookie 进行读取修改或者删除等任意操作。cookie 出现问题比较多的是 cookie 的 SQL 注人等常见漏洞,以及 Web 应用程序在服务端直接读取 cookie 的用户名或者 ID 值来操作当前这个用户的数据,这里存在很大的一个问题是 cookie 可以伪造,从而就导致了伪造用户身份登录的漏洞。
通常一个 cookie 验证的代码大概如下:

<?php
session start();

function login()
{
    if(账号密码正确)
    {
        setcookie('username', 'admin');
        $_SESSION['username']= 'admin':
    }
}
//判断cookie里面的用户名是否和session里的用户名一致

if($_COOKIE['username']===$_SESSION['username'])
{
    //操作$_SESSION['username']用户的数据
}else{
    1ogin();
}

这样的写法一般情况不会出现验证上面的安全问题,下面我们通过案例来看看有问题的写法。

任意用户登录分析

乌云漏洞编号为 WooYun-2015-90324 的 “ESPCMS 所有版本任意用户登录” 漏洞来做一个简单的分析。

function in center(){
if($this->CON['mem_isucenter']){
    include_once admin_ROOT . " public/uc_client/client.php";
}
parent::start_pagetemplate();
parent::member_purview();
$lng=(admin_LNG == "big5")?$this->CON['is_lancode']:admin_LNG;
$db_where="userid=$this->ec_member_username_id AND username='$this->ec_member_username'";

$db_table1 = db_prefix .'member AS a';
$db_table2 = db_prefix ."member value As b":
$db_sql = "SELECT * FROM $db_table1 LEFT JOIN $db_table2 ON a.userid= b.userid WHERE a.userid = $this->ec_member_username_id";
$rsMember = $this->db->fetch_first($db_sql);

$rsMember['rankname'] = $this->get_member_purview($rsMember['mcid'],"rankname");
$userid = $this->ec_member_username_id;
//获取userid
if (empty ($userid)){
    exit( "user err!");
}
$db_table = db_prefix."order";

$db_where = "WHERE userid=$userid";

在代码中 $userid = $this->ec_member_username_id;这行代码设置当前用户 ID,随后根据这个 $userid 变量去直接操作这个id的用户数据,而这个 $this->ec_member_username_id 变量的值又是从哪来的呢?注意代码最开始的地方有调用 parent::member_purview() 函数,我们跟过去看看,member_purview() 函数,代码如下:

function member_purview ($userrank = false, $url = nul1, $upur1=false){
$this->ec_member_username = $this->fun->eccode($this->fun->accept ('ecisp_member_username','C'),'DECODE',db_pscode);
$user_info =explode('|', $this->fun->eccode($this->fun->accept('ecisp_member_info','c'),'DECODE',db_pscode));
1ist($this->ec_member_username_id, $this->ec_member_alias,
$this->ec_member_integral, $this->ec_member_mcid, $this->ec_memberemail,$this->ec_member_lastip, $this->ec_member_ipadd, $this->ec_member_useragent, $this->ec_member_adminclassurl)=$user_info;

可以看到 list() 函数中使用 $user_info 数组为 $this->ec_member_username_id 变量进行赋值,而 Suser_info 数组是从 cookie 中解密出来的,关于这个算法的加密代码eccode() 函数,代码如下:

function eccode ($string,$operation='DECODE',
$key='IQLFK24s224%0safS3s号1f%',$mcrype=true){
$result=null;
if ($operation = 'ENCODE'){
    for ($i = 0; $i < strlen($string); $i++){
        $char=substr($string, $i, 1);
        $keychar=substr($key,($i % strlen($key))-1, 1);
        $char = chr(ord($char) + ord($keychar));
        $result.=$char;
    }
    $result=base64_encode($result);
    $result=str_replace(array('+','/',"="),array('-','_',''),$result);
}elseif ($operation == 'DECODE'){
    $data = str_replace(array('-','_'),array('+','/'),$string);
    $mod4 = strlen($data) % 4;
    if ($mod4){
        $data .= substr('====', Smod4);
    }
    $string = base64_decode($data);
    for($i=0; $i< strlen($string);$i++){
        $char=substr($string, $i, 1);
        $keychar = substr($key,($i % strlen($key))-1, 1);
        $char = chr(ord($char)-ord($keychar));
        $result.=$char;
    }
}
return $result;
}

这是一个很明显的可逆算法,这里就不再重点分析这个算法。

漏洞防范

所有用户输人的值都是不完全可信的,所以在防御认证漏洞之前,我们应该先了解认证的业务逻辑,严格限制输人的异常字符以及避免使用客户端提交上来的内容去直接进行操作。应该把 cookie 和 session 结合起来使用,不能从 cookie 中获取参数值然后进行操作。另外在设置 session 时,需要保证客户端不能操作敏感 session 参数。特别需要注意的是敏感数据不要放到 cookie 中,目前还有不少应用会把账号和密码都直接放入到 cookie 中,cookie 在浏览器端以及传输过程中都存在被窃取的可能性,如果程序限制了一个用户只能同时在一个TP 上面登录,这时候即使别人拿到了你不带密码的 cookie 也会使用不了,但是如果 cookie 里面保存了用户名和密码,这时候攻击者就可以尝试用密码直接登录了。

作者:天下兵马大都督  创建时间:2022-05-19 09:36
 更新时间:2023-04-12 16:17