基于pikachu靶场的水平越权详解

1. pikachu靶场搭建

如果你在之前已经使用过phpstudy了,参考pikachu 靶场环境搭建
如果没有,参考pikachu 靶场搭建
如果在靶场搭建中遇到一些问题,参考皮卡丘靶场搭建遇到的问题大全

2. 水平越权简介

水平越权是指攻击者通过获取与自己拥有相同权限级别的其他用户的访问权限,从而访问或操作这些用户的资源。通常发生在权限控制不足的场景中,例如,攻击者在登录系统后,通过修改请求参数或URL,绕过身份验证机制,访问他人账户的数据。

3. pikachu靶场水平越权黑盒思路

以下为pikachu靶场水平越权的界面,点一下提示,可以看到三个人的用户名与密码。
不妨假设这么一个情境:我的名字叫kobe,我拥有一个该网站的账号,我希望利用该网站存在的水平越权漏洞来查看lucy和lili的信息。


先登陆我(kobe)的账号,点击查看个人信息,可以看到我(kobe)自己的信息。

再点一下查看个人信息,并使用burpsuite抓包。可以看到,该网站通过get方式向后端查询了username=kobe的资料。

GET /pikachu-master/vul/overpermission/op1/op1_mem.php?username=kobe&submit=%E7%82%B9%E5%87%BB%E6%9F%A5%E7%9C%8B%E4%B8%AA%E4%BA%BA%E4%BF%A1%E6%81%AF HTTP/1.1
Host: pikachu
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Referer: http://pikachu/pikachu-master/vul/overpermission/op1/op1_mem.php?username=kobe&submit=%E7%82%B9%E5%87%BB%E6%9F%A5%E7%9C%8B%E4%B8%AA%E4%BA%BA%E4%BF%A1%E6%81%AF
Cookie: PHPSESSID=os55u46tgc0km87ksvmp3iisal
Upgrade-Insecure-Requests: 1
Priority: u=0, i

修改包为username=lucy并发包,尝试探测网站是否存在水平越权。此时,lucy的信息被我(robe)给越权查看了。

4. pikachu靶场水平越权白盒原理

在后端代码中,未对用户身份进行校验,而是直接接收了username并构造sql查询语句,使得可以通过篡改数据包中的username参数来水平越权查看其他人的数据。

if(isset($_GET['submit']) && $_GET['username']!=null){
    $username=escape($link, $_GET['username']);
    $query="select * from member where username='$username'";
    $result=execute($link, $query);
    if(mysqli_num_rows($result)==1){
        $data=mysqli_fetch_assoc($result);
        $uname=$data['username'];
        $sex=$data['sex'];
        $phonenum=$data['phonenum'];
        $add=$data['address'];
        $email=$data['email'];
  
        $html.=<<<A
<div id="per_info">
   <h1 class="per_title">hello,{$uname},你的具体信息如下:</h1>
   <p class="per_name">姓名:{$uname}</p>
   <p class="per_sex">性别:{$sex}</p>
   <p class="per_phone">手机:{$phonenum}</p>    
   <p class="per_add">住址:{$add}</p>
   <p class="per_email">邮箱:{$email}</p>
</div>
A;
    }
}

5. 防护建议

要解决这个问题,可以使用session会话进行身份验证,并确保只能获取当前登录用户的个人信息,而不是依赖于传入的用户名参数。

// 启用会话
session_start();

// 检查用户是否已登录,并使用会话中的用户名,而不是通过GET参数传递
if(isset($_SESSION['username'])){
    // 获取当前登录用户的用户名
    $username = $_SESSION['username'];
    
    // 进行数据库查询,确保只能获取当前登录用户的信息
    $username = escape($link, $username);
    $query = "SELECT * FROM member WHERE username='$username'";
    $result = execute($link, $query);
    
    if(mysqli_num_rows($result) == 1){
        $data = mysqli_fetch_assoc($result);
        $uname = $data['username'];
        $sex = $data['sex'];
        $phonenum = $data['phonenum'];
        $add = $data['address'];
        $email = $data['email'];
        
        $html .= <<<A
<div id="per_info">
   <h1 class="per_title">Hello, {$uname}, 你的具体信息如下:</h1>
   <p class="per_name">姓名: {$uname}</p>
   <p class="per_sex">性别: {$sex}</p>
   <p class="per_phone">手机: {$phonenum}</p>    
   <p class="per_add">住址: {$add}</p> 
   <p class="per_email">邮箱: {$email}</p> 
</div>
A;
    }
} else {
    // 如果没有登录,则重定向到登录页面
    header("Location: login.php");
    exit();
}