fix: Disallow batched queries in GraphQL endpoint (#10050)

* Disallow multiple queries in GraphQL endpoint

* Fix mix credo

* Add Plug.Parsers to each pipeline

* Process review comments

* Process review comments
mf-only-health-webapp
Victor Baranov 6 months ago committed by GitHub
parent def8a1aed0
commit 5bbf68e756
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 9
      apps/block_scout_web/lib/block_scout_web/admin_router.ex
  2. 9
      apps/block_scout_web/lib/block_scout_web/api_key_v2_router.ex
  3. 46
      apps/block_scout_web/lib/block_scout_web/api_router.ex
  4. 9
      apps/block_scout_web/lib/block_scout_web/endpoint.ex
  5. 35
      apps/block_scout_web/lib/block_scout_web/graphql/body_reader.ex
  6. 27
      apps/block_scout_web/lib/block_scout_web/router.ex
  7. 9
      apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex
  8. 9
      apps/block_scout_web/lib/block_scout_web/utils_api_v2_router.ex
  9. 20
      apps/block_scout_web/lib/block_scout_web/web_router.ex
  10. 3
      config/runtime.exs

@ -9,6 +9,15 @@ defmodule BlockScoutWeb.AdminRouter do
alias BlockScoutWeb.Plug.Admin.{CheckOwnerRegistered, RequireAdminRole}
pipeline :browser do
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
length: 10_000,
query_string_length: 5_000,
pass: ["*/*"],
json_decoder: Poison
)
plug(:accepts, ["html"])
plug(:fetch_session)
plug(:fetch_flash)

@ -6,6 +6,15 @@ defmodule BlockScoutWeb.APIKeyV2Router do
alias BlockScoutWeb.Plug.{CheckApiV2, Logger}
pipeline :api_v2 do
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
length: 10_000,
query_string_length: 5_000,
pass: ["*/*"],
json_decoder: Poison
)
plug(Logger, application: :api_v2)
plug(:accepts, ["json"])
plug(CheckApiV2)

@ -16,22 +16,52 @@ defmodule BlockScoutWeb.ApiRouter do
alias BlockScoutWeb.{AddressTransactionController, APIKeyV2Router, SmartContractsApiV2Router, UtilsApiV2Router}
alias BlockScoutWeb.Plug.{CheckAccountAPI, CheckApiV2, RateLimit}
@max_query_string_length 5_000
forward("/v2/smart-contracts", SmartContractsApiV2Router)
forward("/v2/key", APIKeyV2Router)
forward("/v2/utils", UtilsApiV2Router)
pipeline :api do
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
length: 20_000_000,
query_string_length: @max_query_string_length,
pass: ["*/*"],
json_decoder: Poison
)
plug(BlockScoutWeb.Plug.Logger, application: :api)
plug(:accepts, ["json"])
end
pipeline :account_api do
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
length: 100_000,
query_string_length: @max_query_string_length,
pass: ["*/*"],
json_decoder: Poison
)
plug(BlockScoutWeb.Plug.Logger, application: :api)
plug(:accepts, ["json"])
plug(:fetch_session)
plug(:protect_from_forgery)
plug(CheckAccountAPI)
end
pipeline :api_v2 do
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
query_string_length: @max_query_string_length,
pass: ["*/*"],
json_decoder: Poison
)
plug(BlockScoutWeb.Plug.Logger, application: :api_v2)
plug(:accepts, ["json"])
plug(CheckApiV2)
@ -41,6 +71,14 @@ defmodule BlockScoutWeb.ApiRouter do
end
pipeline :api_v2_no_session do
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
query_string_length: @max_query_string_length,
pass: ["*/*"],
json_decoder: Poison
)
plug(BlockScoutWeb.Plug.Logger, application: :api_v2)
plug(:accepts, ["json"])
plug(CheckApiV2)
@ -48,6 +86,13 @@ defmodule BlockScoutWeb.ApiRouter do
end
pipeline :api_v1_graphql do
plug(
Plug.Parsers,
parsers: [:json, Absinthe.Plug.Parser],
json_decoder: Poison,
body_reader: {BlockScoutWeb.GraphQL.BodyReader, :read_body, []}
)
plug(BlockScoutWeb.Plug.Logger, application: :api)
plug(:accepts, ["json"])
plug(RateLimit, graphql?: true)
@ -57,7 +102,6 @@ defmodule BlockScoutWeb.ApiRouter do
alias BlockScoutWeb.API.V2
scope "/account/v2", as: :account_v2 do
pipe_through(:api)
pipe_through(:account_api)
get("/authenticate", AuthenticateController, :authenticate_get)

@ -43,15 +43,6 @@ defmodule BlockScoutWeb.Endpoint do
plug(Plug.RequestId)
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
length: 20_000_000,
query_string_length: 1_000_000,
pass: ["*/*"],
json_decoder: Poison
)
plug(Plug.MethodOverride)
plug(Plug.Head)

@ -0,0 +1,35 @@
defmodule BlockScoutWeb.GraphQL.BodyReader do
@moduledoc """
This module is responsible for reading the body of a graphql request and counting the number of queries in the body.
"""
alias Plug.Conn
@max_number_of_queries 1
def read_body(conn, opts) do
{:ok, body, conn} = Conn.read_body(conn, opts)
updated_conn = update_in(conn.assigns[:raw_body], &[body | &1 || []])
json_body = Jason.decode!(body)
json_body_length =
if is_list(json_body) do
Enum.count(json_body)
else
1
end
error = %{errors: [%{message: "Max batch size is 1"}]}
if json_body_length > @max_number_of_queries do
{:ok, "",
updated_conn
|> Conn.put_resp_content_type("application/json")
|> Conn.resp(400, Jason.encode!(error))
|> Conn.halt()}
else
{:ok, body, updated_conn}
end
end
end

@ -4,11 +4,22 @@ defmodule BlockScoutWeb.Router do
alias BlockScoutWeb.Plug.{GraphQL, RateLimit}
alias BlockScoutWeb.{ApiRouter, WebRouter}
@max_query_string_length 5_000
if Application.compile_env(:block_scout_web, :admin_panel_enabled) do
forward("/admin", BlockScoutWeb.AdminRouter)
end
pipeline :browser do
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
length: 100_000,
query_string_length: @max_query_string_length,
pass: ["*/*"],
json_decoder: Poison
)
plug(BlockScoutWeb.Plug.Logger, application: :block_scout_web)
plug(:accepts, ["html"])
plug(:fetch_session)
@ -18,11 +29,27 @@ defmodule BlockScoutWeb.Router do
end
pipeline :api do
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
length: 20_000_000,
query_string_length: @max_query_string_length,
pass: ["*/*"],
json_decoder: Poison
)
plug(BlockScoutWeb.Plug.Logger, application: :api)
plug(:accepts, ["json"])
end
pipeline :api_v1_graphql do
plug(
Plug.Parsers,
parsers: [:json, Absinthe.Plug.Parser],
json_decoder: Poison,
body_reader: {BlockScoutWeb.GraphQL.BodyReader, :read_body, []}
)
plug(BlockScoutWeb.Plug.Logger, application: :api)
plug(:accepts, ["json"])
plug(RateLimit, graphql?: true)

@ -7,6 +7,15 @@ defmodule BlockScoutWeb.SmartContractsApiV2Router do
alias BlockScoutWeb.Plug.{CheckApiV2, RateLimit}
pipeline :api_v2_no_forgery_protect do
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
length: 20_000_000,
query_string_length: 5_000,
pass: ["*/*"],
json_decoder: Poison
)
plug(BlockScoutWeb.Plug.Logger, application: :api_v2)
plug(:accepts, ["json"])
plug(CheckApiV2)

@ -7,6 +7,15 @@ defmodule BlockScoutWeb.UtilsApiV2Router do
alias BlockScoutWeb.Plug.{CheckApiV2, RateLimit}
pipeline :api_v2_no_forgery_protect do
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
length: 100_000,
query_string_length: 5_000,
pass: ["*/*"],
json_decoder: Poison
)
plug(BlockScoutWeb.Plug.Logger, application: :api_v2)
plug(:accepts, ["json"])
plug(CheckApiV2)

@ -7,7 +7,18 @@ defmodule BlockScoutWeb.WebRouter do
alias BlockScoutWeb.Plug.CheckAccountWeb
@max_query_string_length 5_000
pipeline :browser do
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
length: 20_000_000,
query_string_length: @max_query_string_length,
pass: ["*/*"],
json_decoder: Poison
)
plug(BlockScoutWeb.Plug.Logger, application: :block_scout_web)
plug(:accepts, ["html"])
plug(:fetch_session)
@ -18,6 +29,15 @@ defmodule BlockScoutWeb.WebRouter do
end
pipeline :account do
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
length: 100_000,
query_string_length: @max_query_string_length,
pass: ["*/*"],
json_decoder: Poison
)
plug(BlockScoutWeb.Plug.Logger, application: :block_scout_web)
plug(:accepts, ["html"])
plug(:fetch_session)

@ -117,9 +117,6 @@ config :block_scout_web, Api.GraphQL,
"0x69e3923eef50eada197c3336d546936d0c994211492c9f947a24c02827568f9f"
),
enabled: ConfigHelper.parse_bool_env_var("API_GRAPHQL_ENABLED", "true"),
token_limit: ConfigHelper.parse_integer_env_var("API_GRAPHQL_TOKEN_LIMIT", 1000),
# Needs to be 215 to support the schema introspection for graphiql
max_complexity: ConfigHelper.parse_integer_env_var("API_GRAPHQL_MAX_COMPLEXITY", 215),
rate_limit_disabled?: ConfigHelper.parse_bool_env_var("API_GRAPHQL_RATE_LIMIT_DISABLED"),
global_limit: ConfigHelper.parse_integer_env_var("API_GRAPHQL_RATE_LIMIT", default_graphql_rate_limit),
limit_by_key: ConfigHelper.parse_integer_env_var("API_GRAPHQL_RATE_LIMIT_BY_KEY", default_graphql_rate_limit),

Loading…
Cancel
Save