Rate Limit bypass by mimicking frontend requests
Summary
The API rate limiting for authenticated and unauthenticated users is skipped when
it "looks like" a frontend request. This is defined in lib/gitlab/rack_attack/request.rb
as follows:
def throttle_unauthenticated_api?
api_request? &&
!should_be_skipped? &&
!frontend_request? &&
!throttle_unauthenticated_packages_api? &&
!throttle_unauthenticated_files_api? &&
!throttle_unauthenticated_deprecated_api? &&
Gitlab::Throttle.settings.throttle_unauthenticated_api_enabled &&
unauthenticated?
end
...
def throttle_authenticated_api?
api_request? &&
!frontend_request? &&
!throttle_authenticated_packages_api? &&
!throttle_authenticated_files_api? &&
!throttle_authenticated_deprecated_api? &&
Gitlab::Throttle.settings.throttle_authenticated_api_enabled
end
...
def frontend_request?
strong_memoize(:frontend_request) do
next false unless env.include?('HTTP_X_CSRF_TOKEN') && session.include?(:_csrf_token)
# CSRF tokens are not verified for GET/HEAD requests, so we pretend that we always have a POST request.
Gitlab::RequestForgeryProtection.verified?(env.merge('REQUEST_METHOD' => 'POST'))
end
end
We can pass the frontend_request?
check on API requests by simply supplying a valid _gitlab_session
cookie and the
corresponding CSRF token in the X-CSRF-Token
header. This allows us to bypass an active rate limit on the API.
Steps to reproduce
- Trigger an unauthenticated rate limit hit, e.g. with
ruby -ne "0.upto 10_000 do puts 'curl https://gitlab.com/api/v4/user/joernchen/keys -v 2>&1|grep rate';\$stdout.flush end " | parallel --jobs 40
- Go to
https://gitlab.com/users/sign_in
in an incognito window (logged out) and take note of the CSRF token and the_gitlab_session
cookie with the help of e.g. the dev tools - To confirm the rate limit is in place do an unauthenticated API request, e.g.
curl https://gitlab.com/api/v4/users/joernchen/keys
- To bypass the rate limit do an unauthenticated API request with the anonymous session observed in 2., e.g.
curl https://gitlab.com/api/v4/users/joernchen/keys -H "X-CSRF-Token: l-u9Bmj-BbeRiXSZiOnlypk3Ey6zL4bkKn5XMmb_zO9nefCA7-zUg3rZgXWNI-w5sHbLd05cXKYP_RNGWlIDKw" -b _gitlab_session=ce03f8f2ed4f9b61ea88ff126de85cfd
What is the current bug behavior?
Rate limit can be bypassed by supplying a valid (authenticated or anonymous) session and its respective CSRF token.
What is the expected correct behavior?
The rate limit should be enforced.
Possible fixes
diff --git a/lib/gitlab/rack_attack/request.rb b/lib/gitlab/rack_attack/request.rb
index e45782b8be0b..f21c3c076dc9 100644
--- a/lib/gitlab/rack_attack/request.rb
+++ b/lib/gitlab/rack_attack/request.rb
@@ -89,7 +89,6 @@ def throttle?(throttle, authenticated:)
def throttle_unauthenticated_api?
api_request? &&
!should_be_skipped? &&
- !frontend_request? &&
!throttle_unauthenticated_packages_api? &&
!throttle_unauthenticated_files_api? &&
!throttle_unauthenticated_deprecated_api? &&
@@ -107,7 +106,6 @@ def throttle_unauthenticated_web?
def throttle_authenticated_api?
api_request? &&
- !frontend_request? &&
!throttle_authenticated_packages_api? &&
!throttle_authenticated_files_api? &&
!throttle_authenticated_deprecated_api? &&