WebVulnerabilities

GraphQLに対する攻撃について

概要

GraphQLは、クエリ言語として広く利用されるようになってきたAPI通信のプロトコルで、クライアントが必要とするデータを柔軟に取得することができます。しかし、この柔軟性が攻撃者に悪用される可能性もあり、さまざまな攻撃が可能です。代表的な攻撃には、情報漏洩、DoS攻撃、スキーマの解析、認証・認可の不備を悪用した攻撃などがあります。


脆弱性を持つコードの例と攻撃手法

1. 情報漏洩攻撃

脆弱なコード例(Node.js):

const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
  type User {
    id: ID!
    username: String!
    password: String!
    email: String!
  }

  type Query {
    users: [User]
  }
`;

const resolvers = {
  Query: {
    users: () => [
      { id: 1, username: 'admin', password: 'secret', email: 'admin@example.com' },
      { id: 2, username: 'user', password: 'password', email: 'user@example.com' },
    ],
  },
};

const server = new ApolloServer({ typeDefs, resolvers });

server.listen().then(({ url }) => {
  console.log(`Server ready at ${url}`);
});

攻撃手法:

2. バッチリクエストによるDoS攻撃

脆弱なコード例(Python Flask with Graphene):

from flask import Flask
from flask_graphql import GraphQLView
import graphene

class User(graphene.ObjectType):
    id = graphene.ID()
    username = graphene.String()

class Query(graphene.ObjectType):
    users = graphene.List(User)

    def resolve_users(self, info):
        return [
            User(id="1", username="admin"),
            User(id="2", username="user")
        ]

schema = graphene.Schema(query=Query)

app = Flask(__name__)
app.add_url_rule(
    '/graphql',
    view_func=GraphQLView.as_view('graphql', schema=schema, graphiql=True)
)

if __name__ == '__main__':
    app.run()

攻撃手法:

3. 認証・認可の不備を悪用した攻撃

脆弱なコード例(Java with Spring Boot and GraphQL):

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;

import java.util.List;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

@Controller
class UserController {
    @QueryMapping
    public List<User> users() {
        return List.of(
            new User(1, "admin", "secret", "admin@example.com"),
            new User(2, "user", "password", "user@example.com")
        );
    }
}

攻撃手法:

4. データの肥大化によるDoS攻撃

脆弱なコード例(PHP with GraphQL-PHP):

use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Schema;
use GraphQL\GraphQL;

require_once __DIR__ . '/vendor/autoload.php';

$queryType = new ObjectType([
    'name' => 'Query',
    'fields' => [
        'users' => [
            'type' => Type::listOf(new ObjectType([
                'name' => 'User',
                'fields' => [
                    'id' => Type::nonNull(Type::string()),
                    'username' => Type::nonNull(Type::string()),
                    'bio' => Type::string(),
                ]
            ])),
            'resolve' => function() {
                return [
                    ['id' => '1', 'username' => 'admin', 'bio' => str_repeat('a', 1000000)],
                    ['id' => '2', 'username' => 'user', 'bio' => str_repeat('b', 1000000)],
                ];
            }
        ]
    ]
]);

$schema = new Schema(['query' => $queryType]);

$rawInput = file_get_contents('php://input');
$input = json_decode($rawInput, true);
$query = $input['query'];

$result = GraphQL::executeQuery($schema, $query);
$output = $result->toArray();
echo json_encode($output);

攻撃手法:


防止策

  1. 適切な認証と認可:
    • クエリごとに適切な認証・認可を設定し、アクセス制限を適用します。機密情報を含むフィールドには特に厳格な認証を設定します。

    Node.jsの例:

    const { AuthenticationError } = require('apollo-server');
    const resolvers = {
        Query: {
            users: (parent, args, context) => {
                if (!context.user.isAdmin) {
                    throw new AuthenticationError('Unauthorized');
                }
                return context.db.getUsers();
            }
        }
    };
    
  2. クエリの複雑さ制限:
    • クエリの深さや複雑さを制限し、リソースを過剰に消費するクエリを防ぎます。

    Pythonの例:

    from graphql.execution.executors.asyncio import AsyncioExecutor
    from graphql.validation.rules import NoComplexity
    
    schema = graphene.Schema(query=Query, mutation=Mutation)
    app.add_url_rule(
        '/graphql',
        view_func=GraphQLView.as_view('graphql', schema=schema, graphiql=True, executor=AsyncioExecutor())
    )
    app.config['GRAPHQL'] = {'MAX_COMPLEXITY': 100}
    
  3. バッチリクエストの制限:
    • 1つのリクエストで実行できるクエリの数を制限し、バッチリクエストによるDoS攻撃を防ぎます。
  4. 入力データの検証:
    • ユーザーが提供する入力データを検証し、異常に大きなデータや不正なデータが処理されないようにします。

    Javaの例:

    public class ValidationDirective extends SchemaDirectiveWiring {
        @Override
        public GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition> env) {
            GraphQLFieldDefinition field = env.getElement();
            return field.transform(builder -> builder.argument(GraphQLArgument.newArgument()
                    .name("limit")
                    .type(Scalars.GraphQLInt)
                    .defaultValue(10)
            ));
        }
    }
    
  5. デフォルトのフィールド選択と最大制限の設定:
    • デフォルトのフィールド選択を最小限に設定し、必要以上のデータを返さないようにする。

まとめ: GraphQLは柔軟性が高いため、攻撃者によって悪用される可能性があります。認証と認可の適切な設定、

クエリの複雑さの制限、入力データの検証などの防止策を講じることで、GraphQLに対する攻撃を効果的に防ぐことができます。これにより、アプリケーションのセキュリティと信頼性を向上させることが可能です。