一、前言
现在,越来越多的系统偏向于使用API来调用系统功能,通过API,开发人员可以更加灵活方便的构建应用程序,渗透测试人员也应进一步学习API的渗透测试,关注API的安全性,我将API渗透测试的步骤简单划分为:API信息搜集、API交互测试、API漏洞总结三个步骤,下面通过burpsuite的API渗透测试实验室,了解一下API渗透测试的具体内容。
二、API信息搜集
要进行API的渗透测试,首先需要尽可能的搜集有关API的信息,首先应尽量去了解API的端点功能与交互参数要求,以便于构造有效的HTTP请求与API端点进行交互。应注重获取以下信息:
- API的功能信息。
- API处理的输入数据,包括强制参数和可选参数。
- API接受的HTTP方法类型与文件类型。
- API的身份验证机制。
通过api文档,开发人员更容易读懂与使用API端点,在信息搜集过程中,可以去寻找API的描述文档,例如swagger的api-docs文件。
当然还有更多其他相似路径,如/api、/swagger/index.html、/openapi.json,如果发现api的功能具体端点如/api/swagger/v1/users/123,可以依次尝试访问/api/swagger/v1、/api/swagger、/api看能否获取到更多有效信息。也可接住buspsuite插件来自动化的发现api信息,如APIKit:GitHub - ishkawa/APIKit: Type-safe networking abstraction layer that associates request type with response type.。
访问某页面时,程序可能调取了大量的API端点,通过burpsuite的抓取历史,可能会看到更多的API端点有关信息。同时在js文件中也可能存在API端点信息,可以使用JS Link Finder(burpsuite插件)或者FindSomething(浏览器插件)来发现更多的端点信息,往往能看到意想不到的端点。
三、API交互测试
在获取到API端点信息后,便可以尝试与API端点进行交互来发现可利用的漏洞,如信息泄露、文件上传、注入漏洞等。
1.尝试交互识别API支持的HTTP方法
API可能支持不同的HTTP方法,并且不同的HTTP方法可能带来不同功能,例如:
GET 获取资源检索数据
POST 传入新的数据
PATCH 更改资源内容
DELETE 删除某资源
OPTIONS 检索可使用的HTTP方法
burpsuite Intruder 内置的HTTP verbs 字典自动的尝试不同的http方法,观察不同http方法访问API端口返回数据的不同。(注意,真实渗透测试时要注意避免使用DELETE等可能导致系统变化的方法,以免对系统造成影响)
2.尝试交互识别API支持的内容类型
API端点通常支持特定的文件格式,接受不同文件内容时可能产生不同效果,如:
- 触发信息泄露漏洞
- 绕过一些限制
- 有针对性的使用某些漏洞,例如,传入JSON数据时是安全接收处理的,但传入xml数据时可能引起XXE漏洞。
更改文件内容类型一般去修改,Content-Type HTTP头,然后重新格式化正文,在burpsuite中支持使用Content type converter插件来进行json与xml的格式转换。
3.尝试对API端点进行模糊测试
确定了API的初始端点后可以尝试根据实际情况进行猜测其他可能的API端口,如PUT /api/user/update端点可能用于更新用户信息,GET /api/user/list可能用来查看用户列表等。
4.寻找隐藏参数
当您进行 API 侦察时,您可能会发现 API 支持的未记录参数。您可以尝试使用它们来更改应用程序的行为。Burp 包含许多可以帮助您识别隐藏参数的工具:
- Burp Intruder 能够模糊猜测隐藏参数,使用常用参数名称的单词列表来替换现有参数或添加新参数。
- Param miner 插件能够自动猜测每个请求最多 65,536 个参数名称。
- 内容发现工具能够发现未与您可以浏览到的可见内容链接的内容,包括参数。
四、API参数自动绑定漏洞
当软件款框架自动将请求参数绑定到内部对象上字段时,可能会发生自动绑定漏洞,可能会导致应用程序支持开发人员未打算进行处理的参数,我们同意通过识别的隐藏参数来利用自动绑定漏洞,例如:
通过GET /api/users/123接口我们获取到user对象具有id、name、email、isAdmin参数。
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"isAdmin": "false"
}
当我们发现系统中具有一个
PATCH /api/users/接口可以更新用户名与邮件,向服务器发送一下JSON数据。
{
"username": "wiener",
"email": "wiener@example.com",
}
那么可能isAdmin和id参数也可能会自动绑定到user对象上。
要测试是否可以修改枚举isAdmin参数值,将其添加到PATCH请求中:
{
"username": "wiener",
"email": "wiener@example.com",
"isAdmin": false,
}
另外,发送一个isAdmin参数值无效的PATCH请求。
{
"username": "wiener",
"email": "wiener@example.com",
"isAdmin": dadsas,
}
如果应用返回的行为不同,可能表示新增的isAdmin参数通过赋值可能对程序产生了影响,可以利用这点来让当前用户获得admin权限。
尝试构造:
{
"username": "wiener",
"email": "wiener@example.com",
"isAdmin": true,
}
五、服务器端参数污染漏洞
某些系统包含无法直接从 Internet 访问的内部 API。当网站将用户输入嵌入到对内部 API 的服务器端请求中而没有进行适当的编码时,就会发生服务器端参数污染。这意味着攻击者可能能够操纵或注入参数,这可能使他们能够执行以下操作:
- 覆盖现有参数。
- 修改应用程序行为。
- 访问未经授权的数据。
用户输入的任何类型的参数都可能存在参数污染漏洞。例如,查询参数,表单字段,标头和路径等。
要测试查询字符串中的服务器端参数污染,请将#、 &、 和=等查询语法字符放入输入中,并观察应用程序如何响应。
一定要向浏览器传入URL编码后的字符,特殊字符到从浏览器到服务器会经过URL解码。
#(URL编码%23)使用#截断
例如:一个程序具有一个通过用户名查询用户信息的功能,当用户从浏览器输入用户名时,浏览器发出以下请求:
GET /userSearch?name=peter&back=/home
服务器接受到用户发送的请求,会使用以下请求查询内部API,查询用户的可公开数据
GET /users/search?name=peter&publicProfile=true
可以尝试使用#来尝试截断服务器请求,将#后面的自费注释为无效字符
GET /userSearch?name=peter%23foo&back=/home
服务器尝试访问以下接口:
GET /users/search?name=peter#foo&publicProfile=true
如果返回用户peter的信息则服务器请求可能被成功截断,如何返回Invalid name,则可能未成功截断,%23foo被当做用户名的一部分,如果可以成功截断,则可以注释掉后面的publicProfile=true默认自带参数,可能会返回更多用户信息。
&(URL编码%26)使用&添加参数
例如:可以将查询字符串修改为一下字符
GET /userSearch?name=peter%26foo=xyz&back=/home
服务器向内部API发送以下服务器端请求
GET /users/search?name=peter&foo=xyz&publicProfile=true
根据响应可判断是否参数被成功插入,例如响应未发生改变,可能参数被成功插入但因不是有效参数被服务器忽略,响应发生改变则可能被当做是name参数字符串的一部分,导致查询失效。
当我们已经获取到对象有效参数时,可通过此方式插入有效参数,例如知道当前user对象具有email参数,可以将其添加到查询字符串中,如下:
GET /userSearch?name=peter%26email=foo&back=/home
这会导致服务器向内部API发送以下请求:
GET /users/search?name=peter&email=foo&publicProfile=true
查看响应获取更多信息。
也可通过添加同名参数尝试覆盖现在的参数,如下。
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
到请求中。这可能使您能够以管理员用户身份登录或者获取管理员账号的一定信息或者权限。
测试 REST 路径中的服务器端参数污染
RESTful API 可以将参数名称和值放置在 URL 路径中,而不是查询字符串中。例如,考虑以下路径:
/api/users/123
URL 路径可能会细分如下:
/api是根 API 端点。
/users代表一种资源,在本例中为users。
/123代表一个参数,这里是特定用户的标识符。
考虑一个允许您根据用户名编辑用户配置文件的应用程序。请求被发送到以下端点:
GET /edit_profile.php?name=peter
这会产生以下服务器端请求:
GET /api/private/users/peter
攻击者可能能够操纵服务器端 URL 路径参数来利用 API。要测试此漏洞,请添加路径遍历序列以修改参数并观察应用程序如何响应。
您可以提交 URL 编码peter/../admin作为参数的值name:
GET /edit_profile.php?name=peter%2f..%2fadmin
这可能会导致以下服务器端请求:
GET /api/private/users/peter/../admin
如果服务器端客户端或后端 API 规范此路径,则可能会解析为/api/private/users/admin 从而获取到管理员信息。
结构化数据格式中服务器端参数污染的测试
攻击者可能能够操纵参数来利用服务器处理其他结构化数据格式(例如 JSON 或 XML)时的漏洞。要对此进行测试,请将额外的结构化数据注入到用户输入中,并查看服务器如何响应。
考虑一个应用程序,该应用程序允许用户编辑其个人资料,然后通过对服务器端 API 的请求应用其更改。当编辑姓名时,浏览器会发出以下请求:
POST /myaccountname=peter
这会产生以下服务器端请求:
PATCH /users/7312/update
{"name":"peter"}
您可以尝试将access_level参数添加到请求中,如下所示:
POST /myaccountname=peter","access_level":"administrator
如果在没有充分验证或清理的情况下将用户输入添加到服务器端 JSON 数据,则会导致以下服务器端请求:
PATCH /users/7312/update
{name="peter","access_level":"administrator"}
这可能会导致用户peter被授予管理员访问权限。
考虑一个类似的示例,但客户端用户输入采用 JSON 数据。当您编辑姓名时,您的浏览器会发出以下请求:
POST /myaccount
{"name": "peter"}
这会产生以下服务器端请求:
PATCH /users/7312/update
{"name":"peter"}
您可以尝试将access_level参数添加到请求中,如下所示:
POST /myaccount
{"name": "peter\",\"access_level\":\"administrator"}
如果用户输入被解码,然后添加到服务器端 JSON 数据而没有进行适当的编码,则会产生以下服务器端请求:
PATCH /users/7312/update
{"name":"peter","access_level":"administrator"}
同样,这可能会导致用户peter被授予管理员访问权限。
结构化格式注入也可以发生在响应中。例如,如果用户输入安全地存储在数据库中,然后嵌入到后端 API 的 JSON 响应中而没有进行适当的编码,则可能会发生这种情况。通常可以像在请求中那样检测和利用响应中的结构化格式注入。
Burp 包含自动化工具,可以帮助检测服务器端参数污染漏洞。
可以使用Backslash Powered Scanner 来帮助检测当前API是否存在参数污染漏洞。
防止服务器端参数污染
为了防止服务器端参数污染,请使用允许列表来定义不需要编码的字符,并确保所有其他用户输入在包含在服务器端请求中之前都经过编码。您还应该确保所有输入都遵循预期的格式和结构。