API 安全测试(偏渗透测试)

文章目录

关注我们,咱们在安全的路上,扬帆起航

在这里插入图片描述

第一部分 API

1. 什么是API

​ API,全称为Application Programming Interface(应用程序编程接口),是一套预先定义的函数、协议和工具的集合,用于构建软件应用。API定义了软件组件之间如何相互通信,它允许不同的软件系统之间进行交互,而无需开发者了解这些系统的具体实现细节。随着数字化转型的深入,API产品的价值日益增高,特别是与微服务、DevOps等技术的融合,使得API成为企业战略发展加速的利器。

​ 我们以 家庭宽带异常 作为例子更加形象的向大家介绍一下 API 。假如说我们当前家庭网络宽带服务出现异常,那么我们可以通过呼叫运营商服务来解决这个问题,那么「运营商服务」就是一个接口,他需要我们向他传递服务类型「维修」和服务地址「家庭住址」等内容,经过一段时间的处理响应,我们能够得到「网络已恢复正常的结果」的结果。这里也可以用伪代码表示

def 运营商服务(服务类型,服务地址):
	运维人员约定上门服务时间
	
	维修网络:
        运维人员上门维修网络
        运维人员调试网络
	if 网络通畅 then
		return "网络已恢复正常的结果"
	else
		goto 维修网络

2. API 的作用

  • 模块化设计:API允许开发者将大型复杂的系统分解成更小、更易于管理的模块
  • 复用性:通过API,开发者可以重用现有的代码和功能
  • 集成:API使得不同的软件系统能够相互通信和集成,实现数据和功能的共享
  • 扩展性:API允许开发者轻松扩展应用程序的功能,通过添加新的API调用来实现
  • 维护性:API的标准化使得维护和更新软件系统更加容易

3. API 按照协议类型分类

3.1 RESTful API

​ 基于HTTP协议,使用标准的HTTP方法(GET, POST, PUT, DELETE等)

​ 示例:

​ 获取所有文章列表 GET https://example.com/articles
​ 创建新文章 POST https://example.com/articles data:文章内容
​ 获取特定文章 GET https://example.com/articles/[: 文章ID]

3.2 SOAP API

​ 使用XML格式进行通信,支持复杂的事务处理

​ 示例:

​ 获取用户信息

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUserInfo xmlns="http://tempuri.org/">
      <userId>[: 用户id]</userId>
    </GetUserInfo>
  </soap:Body>
</soap:Envelope>

3.3 GraphQL API

​ 允许客户端指定需要哪些数据,减少数据传输量

​ 示例:

​ 获取文章信息

query {
  article(id: [: 文章id]) {
    title
    content
    author {
      name
      email
    }
  }
}

3.4 gRPC

​ 使用Protocol Buffers作为接口描述语言,支持高效的双向流通信

第二部分 API测试

1. 什么是API测试

​ API测试是一种软件测试方法,专注于应用程序编程接口(API)的功能、可靠性、性能和安全性。API测试通常在后端服务和前端应用之间或服务与服务之间进行,以确保API按照预期工作,并满足业务需求。

2. API测试流程

  1. 确定测试目的和范围

    ​ 首先需要阅读产品设计文档和接口文档,明确要测试的接口的功能和特性,并确定测试的范围,例如测试的是哪些接口、请求和响应的数据格式、参数、返回值等

  2. 设计测试用例

    ​ 根据该接口参数,构造不同的用例,测试接口在参数合法及非法情况下能否达到预期效果

  3. 使用 API 接口测试工具发送请求并验证响应

3. API测试的作用

  1. 功能验证:确保API能够正确执行其功能,返回预期的结果
  2. 异常处理:测试API在遇到错误输入或异常条件时的行为。
  3. 性能评估:评估API的响应时间和处理能力,确保在高负载下仍能正常工作。
  4. 安全性检查:确保API能够抵御常见的安全威胁,如SQL注入、跨站脚本(XSS)等。
  5. 兼容性测试:确保API在不同的平台和环境中表现一致。
  6. 版本控制:测试API的新版本是否与旧版本兼容,以及是否正确地引入了新特性或修改。
  7. 文档验证:确保API文档的准确性,以便开发者能够正确地使用API。

4. API测试类别

​ API测试可以根据不同的标准进行分类,以下是一些常见的分类:

  1. 单元测试:针对API的单个功能或方法进行测试,确保它们按预期工作
  2. 集成测试:测试多个API组件或服务之间的交互,确保它们能够协同工作
  3. 功能测试:测试API的业务逻辑和功能,确保它们满足业务需求
  4. 性能测试:评估API在不同负载下的性能,包括响应时间、吞吐量和资源消耗
  5. 安全性测试:检查API的安全性,包括认证、授权、数据加密和防止常见安全漏洞
  6. 兼容性测试:确保API在不同的平台、设备和浏览器上都能正常工作
  7. 回归测试:在API更新或修改后进行测试,以确保新代码没有破坏现有功能
  8. 端到端测试:模拟真实用户场景,从前端到后端完整地测试整个应用程序

(这里我们仅讨论安全性测试)

第三部分 API测试(安全性测试)

​ (这里我们将会以 burpsuite 官网提供的靶场环境进行讲解)

​ API(应用程序编程接口)使得软件系统和应用程序能够通信和共享数据。随着数字化转型的深入,随之而来的安全问题也不容忽视。动态网站的数据交互功能大多数是由 API 完成的,就比如常见的 SQL 注入、任意文件上传、任意文件下载漏洞,就是通过 API 与后端进行交互而产生的。所以说API 中存在的安全漏洞可能会破坏网站的机密性、完整性和可用性的核心方面。其次,API规范性带来的一个问题就是API很容易被发现,比如在URL中出现的/v1/login,参数中出现的"function": "login"等,而且参数过多就会导致信息泄露以及便于攻击者执行频率分析攻击,比如"role": "user"容易让攻击者联想到"role": "admin"等。另一方面,传输过多的数据、返回过多的数据、参数值暴露敏感信息等都是数据过多导致的安全问题。

1 API 安全测试方法

​ 要想全面解决API的安全问题,就要在每次API研发完成之后进行全面的安全测试,为了防止测试过程中出现的遗漏,我们需要整理归纳,为相应的检测设计一个检查列表,对列表内的内容反复检查,确认没有遗漏项(这里我们摘录了一个网上的测试条目 [开发安全的API 所需要核对清单][https://github.com/shieldfy/API-Security-Checklist/blob/master/README-zh.md])。

1.1 身份认证

  • 不要使用 Basic Auth ,请使用标准的认证协议(如 JWT,OAuth)
  • 不要重新实现 Authenticationtoken generatingpassword storage,请使用标准库
  • 限制密码错误尝试次数,并且增加账号冻结功能
  • 密码或账号登录失败时返回模糊的提示信息,防止暴力破解攻击
  • 加密所有的敏感数据
  • 不要将API Key,云组件Key等硬编码到前端页面或APP中
  • 使用开源框架时禁止使用默认Key,比如Shiro

1.2 JWT(JSON Web Token)

  • 使用随机复杂的密钥(JWT Secret)以增加暴力破解的难度
  • 不要在请求体中直接提取数据,要对数据进行加密(HS256RS256
  • 使 token 的过期时间尽量的短(TTLRTTL
  • 不要在 JWT 的请求体中存放敏感数据,因为它是可解码的
  • 避免存储过多的数据。 JWT 通常在标头中共享,并且它们有大小限制

1.3 访问限制

  • 限制流量来防止 DDoS 攻击和暴力攻击
  • 对API接口访问进行速率限制防止业务数据被批量爬取
  • 在服务端使用 HTTPS 协议来防止 MITM (中间人攻击)
  • 使用 HSTS 协议防止 SSL Strip 攻击
  • 关闭目录列表
  • 禁止公开存储文件列表可未授权访问
  • 对于私有 API,仅允许从列入白名单的 IP/主机进行访问
  • 禁止将内部组件接口、登录管理接口暴露于公网中
  • 禁止将SourceMap文件暴露到公网中
  • 禁止将API接口描述文档暴露到公网中

1.4 Authorization

1.4.1 OAuth 授权或认证协议
  • 始终在后台验证 redirect_uri,只允许白名单的 URL
  • 始终在授权时使用有效期较短的授权码(code)而不是令牌(access_token)(不允许 response_type=token
  • 使用随机哈希数的 state 参数来防止跨站请求伪造(CSRF)
  • 对不同的应用分别定义默认的作用域和各自有效的作用域参数

1.5 HTTP Request

  • 使用与操作相符的 HTTP 操作函数,GET(读取)POST(创建)PUT(替换/更新) 以及 DELETE(删除记录),如果请求的方法不适用于请求的资源则返回 405 Method Not Allowed
  • 在请求头中的 content-type 字段使用内容验证来只允许支持的格式(如 application/xmlapplication/json 等等)并在不满足条件的时候返回 406 Not Acceptable
  • 验证 content-type 中申明的编码和你收到正文编码一致(如 application/x-www-form-urlencodedmultipart/form-dataapplication/json 等等)
  • 验证用户输入来避免一些普通的易受攻击缺陷(如 XSSSQL-注入远程代码执行 等等)
  • 不要在 URL 中使用任何敏感的数据(credentialsPasswordssecurity tokens,or API keys),而是使用标准的认证请求头。
  • 仅使用服务器端加密
  • 使用一个 API Gateway 服务来启用缓存、限制访问速率(如 QuotaSpike ArrestConcurrent Rate Limit)以及动态地部署 APIs resources

1.6 接口处理

  • 检查是否所有的接口都包含必要都身份认证,以避免被破坏了的认证体系
  • 避免使用特有的资源 id。使用 /me/orders 替代 /user/654321/orders
  • 使用 UUID 代替自增长的 id
  • 对于访问资源进行权限检查,防止横向越权
  • 如果需要解析 XML 文件,确保实体解析(entity parsing)是关闭的以避免 XXE 攻击
  • 如果需要解析 XML 文件,确保实体扩展(entity expansion)是关闭的以避免通过指数实体扩展攻击实现的 Billion Laughs/XML bomb
  • 在文件上传中使用 CDN
  • 如果数据处理量很大,尽可能使用队列或者 Workers 在后台处理来避免阻塞请求,从而快速响应客户端
  • 不要忘了把 DEBUG 模式关掉
  • 可用时使用不可执行的堆栈
  • 禁止使用类似于PHP extract函数将接口输入参数转换为变量

1.7 HTTP Response

  • 增加请求返回头 X-Content-Type-Options: nosniff
  • 增加请求返回头 X-Frame-Options: deny
  • 增加请求返回头 Content-Security-Policy: default-src 'none'
  • 删除请求返回中的指纹头 - X-Powered-ByServerX-AspNet-Version 等等
  • 在响应中遵循请求的 content-type,如果你的请求类型是 application/json 那么你返回的 content-type 就是 application/json
  • 不要返回敏感的数据,如 credentialsPasswordssecurity tokens
  • 给请求返回使用合理的 HTTP 响应代码。(如 200 OK400 Bad Request401 Unauthorized405 Method Not Allowed 等等)
  • 返回统一的错误页面,误将调用堆栈等信息在错误页面中展示
  • 仅返回前端需要的业务数据,禁止返回过多类型敏感数据
  • 前端对敏感业务数据使用时应结合业务需求对敏感数据进行脱敏
  • 禁止在前端对数据进行脱敏,数据返回时在后端进行脱敏

1.8 持续集成和持续部署

  • 使用单元测试以及集成测试的覆盖率来保障你的设计和实现
  • 引入代码审查流程,禁止私自合并代码
  • 在推送到生产环境之前确保服务的所有组件都用杀毒软件静态地扫描过,包括第三方库和其它依赖
  • 对您的代码持续运行安全测试(静态/动态分析)
  • 检查您的依赖项(软件和操作系统)是否存在已知漏洞
  • 为部署设计一个回滚方案

1.9 监控

  • 对所有服务和组件使用集中式登录
  • 使用代理来监控所有流量、错误、请求和响应
  • 使用短信,Slack,电子邮件,电报,Kibana, Cloudwatch等提醒
  • 确保你没有记录任何敏感数据,如信用卡、密码、pin等
  • 使用IDS和/或IPS系统监视您的API请求和实例
  • 使用API检测设备进行API资产梳理、日志审计

2. API 信息探测

​ 开始 API 测试之前,首先需要尽可能多地查找 API 相关的信息,例如 API调用的路径,API 接受的请求类型(包括支持的 HTTP 方法和媒体格式)API的各个参数的含义(包括强制参数和可选参数),API的含义,速率限制和身份验证机制等,就如同渗透测试一样,尽可能多的信息就能够显示出更多的暴露面,越多的暴露面也就意味着能够有更多的测试手段

2.1 API 接口探测

2.1.1 对于已暴露的接口

​ 在很多情况下,一个动态网站的加载会调用很多的API接口,例如当我们浏览一个图书馆网站页面其中的一本图书时的时候,可能会发现如下网络调用过程

GET /api/books/ HTTP/1.1
GET /api/books/flipbooks HTTP/1.1
GET /api/book/flipbooks/1234 HTTP/1.1
GET /api/book/flipbooks/1234/book_status HTTP/1.1

​ 根据这些命名我们可以很轻松的推断出,我们首先通过 /api/books/ 获取了所有图书的基本信息, 然后我们通过 /api/books/flipbooks 查询了动画书类别的图书, /api/book/flipbooks/1234 说明了我们选中了其中一本图书, /api/book/flipbooks/1234/book_status 可能是在校对这本图书是否有库存或者是否已经借出,方便后续页面渲染或者借/还书本流程的继续。

2.1.2 对于未暴露的接口

​ 对于未暴露的接口,一种方法是我们可以通过模糊测试的方法获取接口参数、数据类型;也可以通过查询接口文档获取相应信息

​ 例如 GET /api/book/flipbooks/1234 HTTP/1.1 是我们已知的获取某个参数值为1234的动画书书籍详细信息的接口,那么我们不妨可以猜测 DELETE /api/book/flipbooks/1234 HTTP/1.1 是一个能够删除某个参数值为1234的动画书书籍的接口。我们也可以通过类似爆破工具 ffuf 等对接口进行fuzz测试,这在真实环境中或许是个不错的选择。我们也可以通过对前端代码的审计阅读,获取相关接口信息,例如Vue.js会在代码打包时生成一个route文件,我们可以尝试访问这个文件获取部分接口调用信息。

2.1.3 API 文档

​ API文档是一套详细的技术文档,用于描述应用程序编程接口(API)的工作原理、使用方法和实现细节。它为开发者提供了必要的信息,以便他们能够理解、集成和使用API。API 文档通常是公开的,主要是为了方便供外部开发人员使用时。

2.1.3.1 API 文档包含的内容
  • 接口概述:介绍API的基本概念、目的和使用场景
  • 终端点(Endpoints)列表:列出API提供的所有HTTP请求URL或服务端点
  • 请求方法:说明每个端点支持的HTTP方法(如GET、POST、PUT、DELETE等)
  • 请求参数:详细描述每个请求所需的参数,包括参数名称、类型、是否必须、默认值等
  • 请求示例:提供实际的请求示例,帮助开发者理解如何构造请求
  • 响应结构:描述API响应的数据结构,包括状态码、响应头和响应体
  • 响应示例:提供实际的响应示例,帮助开发者理解预期的响应格式
  • 认证和授权:说明如何安全地访问API,包括所需的认证机制和令牌。
  • 错误代码:列出可能遇到的错误代码及其含义,帮助开发者处理异常情况。
2.1.3.2 API 文档的重要性
  • 易于理解和使用 清晰的API文档使开发者能够快速理解API的功能和使用方法
  • 促进开发:详细的文档可以加速开发过程,因为开发者可以避免猜测API的行为
  • 维护和支持:文档提供了API维护和故障排除的参考,有助于解决问题
  • 标准化:文档定义了API的标准用法,确保所有开发者遵循相同的模式
  • 协作:在团队中,文档作为共享知识的基础,有助于团队成员之间的协作
  • 降低错误率:通过提供明确的指导,API文档可以减少开发中的错误和误解

(此处可以跳转至第一个实验)

2.2 API 接口分析

​ 这部分通常是对调用 API 的请求数据包进行分析,主要包括请求行分析、请求头分析、请求数据分析等。

2.2.1 HTTP请求行分析

​ 请求行指的是 HTTP Request 请求报文的第一行,一般包括 GET /user/user.php?action=show&uid=1#news HTTP/1.1

2.2.1.1 HTTP 请求方式

​ 通过 HTTP 协议访问指定资源执行特定操作需要使用对应的请求方式。例如:

  • GET - 通常是对资源的访问、查询
  • POST - 通常是新增资源或修改资源信息
  • DELETE - 通常用于资源的删除
  • PATCH - 通常是修改资源信息
  • OPTIONS - 检索有关可在资源上使用的请求方法类型的信息
2.2.1.2 HTTP 请求资源分析

​ 请求资源的路径也可以为我们带来很多信息,如上述示例中 /user/user.php 是展示用户界面的,那么我们可以进行合理猜测, /admin/admin.php/admin/user.php 等是展示管理员相关界面的。又比如说是 Login.php 我们很容易想到,这是一个登陆界面,那么相对的会不会存在 Register.php 这个注册页面就需要我们进行测试了。或者说针对 /api/v1/info?book_id=1 这是一个 v1 版本的 API接口,用于查询书籍ID编号为1的图书的内容,那么针对 v2、v3或者是v1.1、v1.2,会不会存在相应或者类似的接口呢?

2.2.1.3 HTTP 请求参数分析

​ 请求参数的构建是为了完成某个特定的操作而设计的,例如在上述示例中, action=show 就可以猜测当前功能点尝试展示用户信息,而 uid=1 可以认为是指定了需要展示的用户的ID编号。这两个参数组合在一起就完成了对特定用户的某部分数据的展示。

2.2.2 HTTP请求头分析
2.2.2.1 HTTP 请求数据传输格式分析

​ API 接口通常接受特定格式的数据。但是有时它们的行为可能会有所不同,具体取决于请求中提供的数据的内容类型。更改内容类型可以使您:

  1. 触发泄露有用信息的错误

  2. 绕过有缺陷的防御

  3. 利用处理逻辑的差异

​ 例如存在这样一款API,它能够根据数据传输格式的不同做出不同的处理方式,它能够支持 JSON 数据格式和 XML 数据格式的传输。服务器在处理 JSON 数据时可能是安全的,但在处理 XML 时容易受到注入攻击。

​ 要更改内容类型,通常我们需要修改 HTTP 请求头中的 Content-Type 字段,然后相应地重新格式化请求正文。您可以使用内容类型转换器自动在 XML 和 JSON 之间转换请求中提交的数据( application/jsonapplication/x-www-form-urlencoded 等)。

(此处可以跳转至第二个实验)

2.3 隐藏参数查询

​ 因为API的请求参数可能是在请求行中的查询字符串也可能是在请求体中的数据,所以统一归纳在这个部分。

2.3.1 命名规范查询

​ 一般情况下,API 参数的命名方式和变量的命名方式一致,都会象征性的指向对应的事物,例如一想到人相关的参数,我们肯定会在第一时间想到姓名、性别、身高体重等。那么我们可以通过常见的API命名约定和行业术语的单词列表,经过驼峰编码等方式形成字典,批量爆破参数的存在性。

2.3.2 关联性查询

​ 针对一个抽象化对象的结构,通常他的属性和方法会具有该结构对象的特性,例如对物体长度的测量,对于人类我们常说的是身高,对于鳄鱼等我们经常将其描述为体长。结构属性之间也会存在着或多或少的关联性,例如体检测试报告,很多时候在报告上记录了身高,难免也会记录体重。对于这些关联参数,在日常API测试环境中我们可以逐个添加到接口处进行测试。

​ 例如,我们可以通过 GET /api/users/123 获取用户 123 的具体信息,请求返回以下 JSON:

{
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com",
    "isAdmin": "false"
}

​ 在 个人中心 面板上存在一个修改用户信息的功能,他会发起 PATCH /api/users/ 请求发送如下数据包

{
    "username": "wiener",
    "email": "wiener@example.com",
}

​ 通过 GET 请求 我们可以知道 nameemailisAdminid 是建立了相应联系的对象,但是当我们尝试修改用户信息时,仅仅是提交了 nameemail 的数据,那么我们是不是可以尝试使用 PATCH 方法访问 /api/users/123 ,并尝试带上 isAdmin 参数已修改当前用户权限

{
    "username": "wiener",
    "email": "wiener@example.com",
    "isAdmin": true,
}

​ 如果应用程序的行为不同,这可能表明无效值会影响查询逻辑,但有效值不会影响查询逻辑。这可以表明该参数可以被用户成功更新。如果请求中的 isAdmin 值在没有充分验证和清理的情况下绑定到用户对象,则用户 wiener 可能会被错误地授予管理权限。要确定是否属于这种情况,请以 wiener 身份浏览应用程序,看看是否可以访问管理功能。

2.4 批量参数分配

​ 上面的测试方法也被叫做 Mass assignment vulnerabilities , 在数据传参的过程中,有意或无意地创建隐藏参数。当软件框架自动将请求参数绑定到内部对象上的字段时,就会导致原本调用处理逻辑进入预料之外的分支进行

(此处可以跳转至第三个实验)

3. 服务端参数污染

​ 某些系统包含无法通过互联网直接访问的内部 API。当网站将用户输入嵌入到对内部 API 的服务器端请求中而未进行充分编码时,就会发生服务器端参数污染。这意味着攻击者可能能够操纵或注入参数,从而使他们能够实现以下目的:

  • 覆盖现有参数。
  • 修改应用程序行为。
  • 访问未经授权的数据。

​ 您可以测试任何用户可控的输入点是否存在任何类型的参数污染。例如,查询参数、表单字段、标头和 URL 路径参数,这些都可能存在漏洞。

3.1 查询字符串中的服务器端参数污染环境描述

​ 要测试查询字符串中的服务器端参数污染,请在输入中 放置查询语法字符(如 # 锚点、 = 赋值、 & 拼接 ) ,然后观察应用程序如何响应。那么我们现在假设有一个易受攻击的应用程序,它允许您根据用户名搜索其他用户。当您搜索用户时,您的浏览器会发出以下请求:

GET /userSearch?name=peter&back=/home

​ 为了检索用户信息,服务器在接收到浏览的请求后使用以下请求查询内部 API:

GET /users/search?name=peter&publicProfile=true

将前端传递过来的name参数的值传递给内部API,作为调用内部API的参数的一部分

3.2 截断查询字符串

​ 在 URL 的定义中 # 字符常用来设置 html 页面的锚点,后端程序在解析URL资源路径和请求参数时,会忽略 # 后面的内容。那么这个时候我们可以在前端浏览器发送带有 URL 编码的 # (因为浏览器发送的时候,如果 # 不是编码形式,那么 # 之后的内容将会被当作页面锚点而不被传输给服务器,也就无法转发送给内部API)字符的请求资源地址,来尝试截断服务器端请求

​ 例如可以将查询字符串修改为以下内容:

GET /userSearch?name=peter%23foo&back=/home

​ 前端会发送以上请求给服务器,服务器将会尝试以下面的形式访问内部API:

GET /users/search?name=peter#foo&publicProfile=true

​ 我们可以通过查看响应数据包内容以查找有关查询是否被截断的线索。例如,如果响应返回 peter 的信息,则服务器端查询可能已被截断,因为内部数据确实存在 peter 用户, 对于内部API的请求,我们传递的 # 并没有被当作 name 的一部分,而是被当作锚点忽略了。如果返回错误消息 Invalid name ,则应用程序可能已将 #foo 视为用户名 peter 的一部分。这表明服务器端请求可能未被截断。

3.3 注入参数

3.3.1 注入无效参数

​ 在 URL 的定义中 & 字符常用来设置 URL 资源请求的查询参数,那么我们可以使用 URL 编码&字符尝试向服务器端请求添加第二个参数。例如,我们可以将查询字符串修改为以下内容:

GET /userSearch?name=peter%26foo=xyz&back=/home

​ 这会导致服务器端向内部 API 发出以下请求:

GET /users/search?name=peter&foo=xyz&publicProfile=true

​ 查看响应以获取有关如何解析附加参数的线索。例如,如果响应没有变化,这可能表明该参数已成功注入但被应用程序忽略。

​ 为了获得更加精确的结果,我们最好是再多做几组实验

3.3.2 注入有效参数

​ 如果您能够修改查询字符串,则可以尝试向服务器端请求添加第二个有效参数。例如,如果已经确定了 API 调用过程中可以存在 email 参数,我们请求用户信息通过 /api/user?id=1 我们可以获取到 id=1 这个用户的部分信息,但是就是不包括 email 地址,只有到查询字符串中存在 email=true 是才能查看到email信息

GET /api/user?id=1
HOST: *.*.*.*

HTTP/1.1 200 Ok
...

{username: 'Mike'}
-----------------------------------------------
GET /api/user?id=1&email=true
HOST: *.*.*.*

HTTP/1.1 200 Ok
...

{username: 'Mike', 'email': 'test@gmail.com'}

​ 则可以将其添加到查询字符串中,如下所示:

GET /userSearch?name=peter%26email=true&back=/home

​ 会导致服务器端向内部 API 发出以下请求:

GET /users/search?name=peter&email=true&publicProfile=true

如果页面成功显示id=1的用户和他的邮箱,那么参数注入成功,如果无法成功显示id=1的用户的信息,那么参数被应用程序忽略

3.4 覆盖现有参数

​ 要确认应用程序是否容易受到服务器端参数污染,也可以尝试覆盖原始参数。通过注入具有相同名称的第二个参数来执行此操作。

​ 例如,您可以将查询字符串修改为以下内容:

GET /userSearch?name=peter%26name=carlos&back=/home

​ 这会导致服务器端向内部 API 发出以下请求:

GET /users/search?name=peter&name=carlos&publicProfile=true

​ 内部 API 解释两个name参数。其影响取决于应用程序(中间件或者后端程序)如何处理第二个参数。这在不同的网络技术中有所不同。例如:

  • PHP 仅解析最后一个参数。这将导致用户搜索carlos
  • ASP.NET 结合了这两个参数。这将导致用户搜索peter,carlos,从而可能导致Invalid username错误消息。
  • Node.js / express 仅解析第一个参数。这会导致用户搜索peter,结果不变。

​ 如果您能够覆盖原始参数,则可能能够进行攻击。例如,您可以添加 name=administrator到请求中。这可能使您能够以管理员用户身份登录。

​ 这种参数污染的方法常被用来绕过安全设备防护策略。

(此处可以跳转至第四个实验)

3.5 Restful API 的服务器端参数污染

​ Restful API接口类型通常会将参数名称和值放在 URL 路径中,而不是查询字符串中。例如:

/api/users/123

​ 这个URL 路径可以解析成如下几个部分:

  • /api是根 API 接口
  • /users代表一种资源,这里表示请求users相关接口
  • /123表示一个参数,这里是特定用户的标识符

​ 假设当前页面存在一个edit_profile.php,它允许您根据用户名编辑用户个人资料。我们可以通过发送如下请求编辑name=peter的用户的信息

GET /edit_profile.php?name=peter

​ edit_profile.php则是通过接受查询字符串并且凭借到以下路径从而对内部API进行访问:

GET /api/private/users/peter

​ 那么此处根据相对路径和绝对路径访问文件的原理,因为name参数可控,那么我们就可以尝试提交经过 URL 编码后的数: peter/../admin 作为参数的值name

GET /edit_profile.php?name=peter%2f..%2fadmin

​ 这可能会导致edit_profile.php发起如下请求访问内部API:

GET /api/private/users/peter/../admin

​ 如果服务器端客户端或后端 API 规范化该路径,则可能会解析为/api/private/users/admin

3.6 结构化数据格式中的服务器端参数污染

​ 如果在数据传递过程中采用的是结构化数据格式(例如 JSON 或 XML),那么攻击者可能通过构造对应的payload拼接到格式化字符串中,从而达到意料之外的结果

3.6.1 POST Data -> json

​ 假设有一款应用,它允许用户编辑个人资料,然后通过向服务器端 API 发出请求来应用更改,当您编辑姓名时,浏览器会发出以下请求:

POST /myaccount 
...

name=peter 

​ 这会导致服务器端发送请求:

PATCH /users/7312/update 
...

{"name":"peter", "access_level":"user"} 

​ 如果没有外部干扰,那么此时当前用户名已经被修改成为 peter,我们假设access_level参数是用来控制用户类型信息的(分为普通用户 user 和管理员用户administrator),那么我们就可以尝试按如下方式将access_level参数添加到请求中:

POST /myaccount 
...

name=peter","access_level":"administrator

​ 如果用户输入在没有经过充分验证或清理的情况下被添加到服务器端 JSON 数据中,则会导致以下服务器端请求:

PATCH /users/7312/update 
...

{name="peter","access_level":"administrator", "access_level":"user"} 

​ 根据后端处理json数据格式的不同,这可能会导致用户peter被授予管理员访问权限。

3.6.2 POST Json -> json

​ 上述的例子是将原始的POST请求数据包解析后拼接到json内容中去,这回客户端用户输入的是 JSON 数据。当你编辑姓名时,浏览器会发出以下请求:

POST /myaccount 
...

{"name": "peter"} 

​ 这会导致服务器端发送以下请求:

PATCH /users/7312/update 
...

{"name":"peter", "access_level":"user"} 

​ 同样的,我们在客户端构造一个结构化的数据包给服务器

POST /myaccount 
...

{"name": "peter\",\"access_level\":\"administrator"}

​ 如果对用户输入进行解码,然后在没有进行充分编码的情况下将其添加到服务器端 JSON 数据中,则会导致服务器端发送以下请求:

PATCH /users/7312/update 
...

{"name":"peter","access_level":"administrator", "access_level":"user"}

​ 再次,这可能会导致用户peter被授予管理员访问权限。

4. 降低 API 可能存在的风险

  • 对于API 文档应采用白名单的方式进行访问限制
  • 验证每个请求或响应的内容类型是否是预期的
  • 使用通用错误消息以避免泄露可能对攻击者有用的信息(报错注入)

4.1 防止服务器端参数污染

​ 为了防止服务器端参数污染,请使用允许列表定义不需要编码的字符,并确保所有其他用户输入在包含在服务器端请求中之前都经过编码。您还应确保所有输入都符合预期的格式和结构。

第四部分 lab

1. Lab: Exploiting an API endpoint using documentation

1.1 实验要求

​ 请找到公开的API文档并删除"carlos"

1.2 提示

​ 以使用以下凭据 wiener:peter 登录到自己的帐户

1.3 题解

  1. 使用凭据“wiener:peter”登录应用程序并更新您的电子邮件地址

在这里插入图片描述

  1. 将登录后的身份信息查询数据包放入 Bp 自带的 Repeat 模块

在这里插入图片描述

​ 点击重放,依旧能够成功获取到 wiener 的用户名和邮箱地址

  1. API 路径拼接猜测

​ 首先删除 /api/user/wiener 路径中的呢 wiener 资源,访问对应的API路径,发现身份认证报错

在这里插入图片描述

​ 继续删除 /api/user/ 路径中的呢 user 资源,访问对应的API路径,发现成功访问到当前站点的 API 文档

在这里插入图片描述

​ 根据 API 文档可知,我们可以通过使用 DELETE 方法访问 /user/[: username] 来删除对应 username 的用户,那么我们只要把这里的 username 替换为 carlos 就可以完成实验目标了

在这里插入图片描述

2. Lab: Finding and exploiting an unused API endpoint

2.1 实验要求

​ 利用隐藏的API端点购买轻型l33t皮夹克

2.2 提示

​ 以使用以下凭据 wiener:peter 登录到自己的帐户

2.3 题解

  1. 打开站点首页,点击任意单品查看详细信息

    ​ 在 Bp 的访问记录里可以看到,通过访问 /api/products/1/price 我们就可以查看我们刚刚看的首页第一件商品的部分信息

在这里插入图片描述

  1. 查看当前接口允许的HTTP请求方式

​ 可以通过使用 OPTIONS 请求获取当前页面允许的访问方式,在相应包中我们可以很清楚的看到,当前页面允许通过 GETPATCH 这两个方法进行访问

在这里插入图片描述

  1. 尝试使用 PATCH 方法修改商品内容

    ​ PATCH 方法允许我们对某些资源的信息进行修改,但是当我们使用 PATCH 方法访问 /api/products/1/price 这个接口时,可以看到在返回包中写出身份未校验。

在这里插入图片描述

​ 所以我们使用上述题干中的身份凭着登陆用户后在对这个接口进行访问,可以看到在返回包中显示,她希望我们通过 Content-Type: applocation/json 的方式对资源文件进行访问

在这里插入图片描述

​ 将对应的 Content-Type 修改正确后在 data 的 body 中传递一个空的 json数据,发送后响应包中显示缺少 price 参数

在这里插入图片描述

​ 我们在json数据中加入price参数,并将其设置为0以达到免费购买的目的,数据包成功发送

在这里插入图片描述

  1. 查看首页验证结果

    返回商品首页,发现 l33t 的夹克衫的价格已经被修改成了 0

在这里插入图片描述

在这里插入图片描述

3. Lab: Exploiting a mass assignment vulnerability

3.1 实验要求

​ 找到并利用批量分配漏洞购买轻型l33t皮夹克

3.2 提示

​ 以使用以下凭据 wiener:peter 登录到自己的帐户

3.3 题解

  1. 使用登陆凭证登录系统
  2. 在网站的首页找到商品皮夹克,并查看详细内容

​ 可以看到它使用GET调用了 /api/checkout 接口,获取了当前商品的信息

{
	"chosen_discount":{
		"percentage":0
	},
	"chosen_products":[{
		"product_id":"1",
		"name":"Lightweight \"l33t\" Leather Jacket",
		"quantity":4,
		"item_price":133700
	}]
}

在这里插入图片描述

  1. 点击下单,并抓取对应地数据包

​ 可以看到它使用POST调用了 /api/checkout 接口,发送了当前用户选中的商品的部分信息

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

在这里插入图片描述

​ 那么此处便可以大胆的猜测,两处调用的结构体是一致的,也就是说其实在POST请求时,我们也可以添加如下的内容:

"chosen_discount":{
	"percentage":0
},

来修改原本商品的优惠价格

在这里插入图片描述

​ 可以看到成功修改了原本商品的价格,所以只需要把参数 percentage 修改成100 就可以将商品"零元购"

4. Laboratory: Using server-side parameter contamination in query strings

4.1 实验要求

​ 以 administrator 身份登录并删除 carlos 账号

4.2 提示

4.3 题解

  1. 尝试找回密码

​ 题目要求我们用 administrator 的身份去删除某个账号,但是题干中并没有给出对应的管理员账号密码或其他用户密码,那么我们只能尝试重置 administrator 的密码

在这里插入图片描述

  1. JS 代码审计

​ 在重置密码页面点击 submit 后会发送好几个数据包,我们主要关注以下一个重要的数据包。

​ 这里尝试以 POST 的方式请求 /forget-password 节点,POST 的数据内容是一个 csrf token 和需要重置密码的用户的用户名

在这里插入图片描述

​ 我们先尝试进行账号探测,修改 username 的内容为其他可能存在的账号名称(这里我输入了 administratorx)

在这里插入图片描述

​ 可以看到,后台程序报错了,错误原因是不合理的用户名,那么说明此处的username确确实实进入了后端程序中,且能够进行用户存活性探测,那么此处我们尝试拼接一个多余的参数上去,这里尝试拼接 &x=y

在这里插入图片描述

​ 可以看到后端对输入的参数进行了校验,因为如果 &x=y 不被解析并拼接进入内部API调用,那么这里应该报错 Invalid username 而不是 Parameter is not supported, 所以这里是存在参数污染的。既然拼接参数不行,那么我们就尝试注释请求字符串,也就是使用字符 #

在这里插入图片描述

​ 这里的错误又发生了变化 Filed not specified, 也就是说某个字段不存在,这里的字段很简单,就是 Field ,那么我们尝试拼接一个 Field 字段上去

在这里插入图片描述

​ 这回参数名称和个数是对了,但是参数类型值发生了错误,Invaild field 说明了当前参数不合法,首先排除参数名称错误,因为如果参数名称错误应该会爆出和上面一样的错误 Filed not specified ,所以这里应该是参数的类型错误或者是参数的值错误,这里就只能使用爆破或者fuzz模糊测试了

​ 最终发现这个 field 参数对应的是要显示的内容,也就是那个字段需要显示就写那个字段,经过测试在我们为数不多的字典中只有 usernameemail 两个字段能够成功访问,但是这两个参数并没有实际的利用价值

在这里插入图片描述

在这里插入图片描述

​ 这时我们就需要先放下这个数据包,因为在找回密码的功能被执行时,还有一个数据包很值得注意 /static/js/forgetPassword.js ,这个数据包的JS内容似乎与忘记密码、重置密码有关

在这里插入图片描述

​ 经过代码审计发现,里面的 forgotPwdReady() 函数是用来重置密码的,他会向后台的 /forgot-password 接口发送数据并重置密码,他所需要一个参数 reset-token 参数便能够重置密码

​ 那么我么就可以通过 /forget-password 节点存在的参数污染获取到这个参数的数值从而重置密码,成功登录administrator账号

在这里插入图片描述

参考文献

[1] https://mp.weixin.qq.com/s/LlKvLlVI1L3ikiHf0pnLBA

[2] https://mp.weixin.qq.com/s/FSChkTsLVdxzUs6H0vY4GQ

[3] https://portswigger.net/web-security/api-testing

[4] https://github.com/shieldfy/API-Security-Checklist/blob/master/README-zh.md

[5] https://mp.weixin.qq.com/s/I3u3yccRVsuDoFGUAv-oTQ

  • 55
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值