freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

API测试(一):PortSwigger靶场笔记
2024-07-14 01:24:43

写在前面

这篇文章是关于作者在学习PortSwigger的API Test类型漏洞时的记录和学习笔记
使用到的工具为Burp Suite Pro

漏洞简介

什么是api

API全称为Application Interface,是应用程序对外提供功能的接口,现在主要有三种api风格,分别是JSON风格的api,RESTful风格的api以及Graphic风格的api

JSON风格

请求获取用户信息

POST /api/getUser HTTP/1.1
Host: example.com
Content-Type: application/json

{
  "userId": 1
}

RESTful风格

请求获取用户信息

GET /api/users/1 HTTP/1.1
Host: example.com

请求修改用户信息

POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json

{
  "name": "Jane Doe",
  "email": "jane.doe@example.com"
}

Graphic风格

请求获取用户信息

POST /graphql HTTP/1.1
Host: example.com
Content-Type: application/json

{
  "query": "{ user(id: 1) { id, name, email } }"
}

API测试是什么

API测试这种漏洞主要包含了对开放api期望运行逻辑的破坏以及对未开放的api的检测和调用,导致的危害可能有敏感信息泄露,应用行为被改变等
这篇文章主要讨论JSON和RESTful APIs
当你在测试一个api的时候,关注下面这些点:

  • api接收并处理的数据,包括必需的和可选的

  • api接受的请求类型,包括请求方法和media formats

  • 速率限制和认证机制

和api交互

使用不同的请求方法

HTTP1.0定义了三种请求方法: GET、POST、HEAD

  • GET方法请求一个指定资源的表示形式,使用 GET的请求应该只被用于获取数据。

  • HEAD方法请求一个与 GET请求的响应相同的响应,但没有响应体。

  • POST方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用。
    HTTP1.1新增了六种请求方法:OPTIONS、PUT、DELETE、TRACE 、CONNECT、PATCH

  • OPTIONS方法用于描述目标资源的通信选项。

  • PUT方法用有效载荷请求替换目标资源的所有当前表示。

  • DELETE方法删除指定的资源。

  • TRACE方法沿着到目标资源的路径执行一个消息环回测试。

  • CONNECT方法建立一个到由目标资源标识的服务器的隧道。

  • PATCH方法用于对资源应用部分修改。

使用不同的Content-Type

以下是最常使用的10种Content-Type
application/json

  • 用途:用于 JSON 格式的数据传输,常用于 RESTful API。
    text/html

  • 用途:用于 HTML 格式的数据传输,常用于 Web 页面响应。
    application/x-www-form-urlencoded

  • 用途:用于 HTML 表单提交的编码方式,将表单数据编码为键值对。
    multipart/form-data

  • 用途:用于文件上传表单的编码方式,允许同时上传文件和其他数据。
    text/plain

  • 用途:用于纯文本数据传输,没有特殊的格式化要求。
    application/xml

  • 用途:用于 XML 格式的数据传输。
    application/javascript

  • 用途:用于传输 JavaScript 代码。
    application/octet-stream

  • 用途:用于传输二进制数据,不指定特定的文件格式,通常用于文件下载。
    image/jpeg

  • 用途:用于传输 JPEG 格式的图像文件。
    image/png

  • 用途:用于传输 PNG 格式的图像文件。

Labs

lab1

lab地址:Exploiting an API endpoint using documentation
这个lab是最基础的,通关条件是删除用户carlos的账户
登陆网站,尝试发现的所有功能,然后观察burpsuite的HTTP History,发现一个方法为PATCH的请求,api端点为/api/user/wiener,直接尝试修改http请求方法为delete,对用户carlos进行请求
l1-s1.png
l1-s2.png

lab2

lab地址:Exploiting server-side parameter pollution in a query string
这一个lab关注的点是请求字符串中的参数污染,通关条件是登陆administrator的账户,删除carlos账户
惯例先进行所有的操作,观察burpsuite的http history
将/fogot-password的post请求发送到repeater研究
l2-s1.png
l2-s2.png
l2-s3.png
将请求发送到intruder模块对field参数的值进行爆破
l2-s4.png
爆破显示正确的结果
l2-s5.png
将field的值设置为email发送原post请求,发现响应和没有设置field一样,再去http history查看有没有其他可能的值
发现GET /static/js/forgotPassword.js,js文件内容如下

let forgotPwdReady = (callback) => {
    if (document.readyState !== "loading") callback();
    else document.addEventListener("DOMContentLoaded", callback);
}

function urlencodeFormData(fd){
    let s = '';
    function encode(s){ return encodeURIComponent(s).replace(/%20/g,'+'); }
    for(let pair of fd.entries()){
        if(typeof pair[1]=='string'){
            s += (s?'&':'') + encode(pair[0])+'='+encode(pair[1]);
        }
    }
    return s;
}

const validateInputsAndCreateMsg = () => {
    try {
        const forgotPasswordError = document.getElementById("forgot-password-error");
        forgotPasswordError.textContent = "";
        const forgotPasswordForm = document.getElementById("forgot-password-form");
        const usernameInput = document.getElementsByName("username").item(0);
        if (usernameInput && !usernameInput.checkValidity()) {
            usernameInput.reportValidity();
            return;
        }
        const formData = new FormData(forgotPasswordForm);
        const config = {
            method: "POST",
            headers: {
                "Content-Type": "x-www-form-urlencoded",
            },
            body: urlencodeFormData(formData)
        };
        fetch(window.location.pathname, config)
            .then(response => response.json())
            .then(jsonResponse => {
                if (!jsonResponse.hasOwnProperty("result"))
                {
                    forgotPasswordError.textContent = "Invalid username";
                }
                else
                {
                    forgotPasswordError.textContent = `Please check your email: "${jsonResponse.result}"`;
                    forgotPasswordForm.className = "";
                    forgotPasswordForm.style.display = "none";
                }
            })
            .catch(err => {
                forgotPasswordError.textContent = "Invalid username";
            });
    } catch (error) {
        console.error("Unexpected Error:", error);
    }
}

const displayMsg = (e) => {
    e.preventDefault();
    validateInputsAndCreateMsg(e);
};

forgotPwdReady(() => {
    const queryString = window.location.search;
    const urlParams = new URLSearchParams(queryString);
    const resetToken = urlParams.get('reset-token');
    if (resetToken)
    {
        window.location.href = `/forgot-password?reset_token=${resetToken}`;
    }
    else
    {
        const forgotPasswordBtn = document.getElementById("forgot-password-btn");
        forgotPasswordBtn.addEventListener("click", displayMsg);
    }
});

最关键的一段代码是:

forgotPwdReady(() => {
    const queryString = window.location.search;
    const urlParams = new URLSearchParams(queryString);
    const resetToken = urlParams.get('reset-token');
    if (resetToken)
    {
        window.location.href = `/forgot-password?reset_token=${resetToken}`;
    }
    else
    {
        const forgotPasswordBtn = document.getElementById("forgot-password-btn");
        forgotPasswordBtn.addEventListener("click", displayMsg);
    }
});

代码中涉及一个参数reset_token,尝试将field这只为reset_token,发现响应变化了
l2-s6.png
根据/forgot-password?reset_token=${resetToken},重新构建get请求
l2-s7.png
然后就是设置新密码,删除carlos账户

lab3

lab地址:Finding and exploiting an unused API endpoint
这个lab关注的点是发现和测试未使用的api端点,通关条件是购买一件商品
进去先尝试所有功能,发现无法充值,余额为0,我的思路是将商品价格修改或绕过付款流程
观察brupsuite的http history,发现:GET /api/products/1/price HTTP/2,发送到repeater研究
先修改http方法为OPTION,发现允许get和patch方法
l3-s1.png
构建patch方法的请求
l3-s2.png
构造指定格式的数据,数据是猜测的,但是比较好猜
l3-s3.png
之后就可以零元购,通关了

lab4

lab地址:Exploiting a mass assignment vulnerability
这个lab关注批量赋值漏洞,通关条件是购买一件商品,还是之前的思路
按惯例先尝试所有功能,然后查看burpsuite的http history
发现两个有意思的请求:POST /api/checkout HTTP/2GET /api/checkout HTTP/2
点击付款按钮时会先发送POST请求,数据为购买的商品的列表,包括商品id和数量

{"chosen_products":[{"product_id":"1","quantity":1}]}

GET请求则会返回该次交易的相关信息,如下图:
l4-s1.png
第一个尝试还是先修改商品的价格,发现没有成功
l4-s2.png
然后修改代表折扣的字段,发送请求后就直接成功购买了
l4-s3.png

lab5

lab地址:Exploiting server-side parameter pollution in a REST URL
这个lab和lab2的思路是一样的,关注的重点在改变服务端请求的行为,但是多了许多防御措施,需要更多技巧结合,通关条件和lab2一样
先使用lab2用过的测试技巧,测试后推测服务端请求可能把username内容放在url的路径部分
l5-e1.png
l5-e2.png
l5-e3.png
构造username为:./administrator
l5-s1.png
l5-s2.png
读取配置文件,发现url路径:/api/api/internal/v1/users/{username}/field/{field}
l5-s3.png
将username参数改为administrator/field/x%23,试图重写field
l5-s4.png
同lab2一样,查看GET /static/js/forgotPassword.js
l5-i1.png
构造username为:administrator/field/passwordResetToken%23
l5-s5.png
通过路径遍历进行指定版本
l5-s6.png
构造get请求发送
l5-s8.png
然后修改密码,删除carlos账户,通关

如何防御API导致的漏洞

首先,最好从开始设计系统时就考虑安全因素
其次,最好遵守下列准则

  • 如果不想你的api被公开地访问,保管好你的文档

  • 确保你的文档足够全面,允许合法测试人员收集全面的攻击面

  • 设置允许接受的http方法

  • 验证收到的请求或响应是期望的格式

  • 使用普遍的报错信息以避免泄露有价值的信息

  • 对所有版本的api设置防御措施,而不仅仅是当前主要版本

  • 将用户可以修改的属性列入白名单,用户不能修改的敏感属性列入黑名单

# 网络安全 # web安全 # 网络安全技术 # PortSwigger # API安全
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录