Skip to content

ReDoS due to device-detector parsing user agents

HackerOne report #1772063 by afewgoats on 2022-11-13, assigned to Ottilia Westerlund:

Report | How To Reproduce

Report

Summary

The device-detector ruby gem is used by Gitlab for parsing the User-Agent header into a device_name after logging in.
The gem uses a large list of regular expressions taken from the upstream matomo-org/device-detector to process the header, several of which are vulnerable to Regular Expression Denial of Service (ReDoS) with cubic worst-case complexity.

One such regular expression containing the vulnerable fragment (\d+[\.\d]+).* is Chrome/(\d+[\.\d]+).*MRCHROME.

The \d+, [\.\d]+ and .* groups overlap and all match digits 0-9. As long as the malicious header starts with Chrome/ and does not end in MRCHROME there will be cubic-complexity backtracking and ReDoS with a very long string of digits e.g. Chrome/000000000001230....

The CPU runs at 100% processing the regex until killed by the 60s timeout. Multiple simultaneous requests would overwhelm the server.

Steps to reproduce
  1. Set user agent to "Chrome/" + "0" * 3456 using dev tools or Burp

  2. Sign in to Gitlab (must successfully sign in). The response to POST /users/sign_in will add cookie known_sign_in.

  3. Any further requests to Gitlab will cause CPU exhaustion and then timeout after 60s with a 500 error when trying to match the known_sign_in and _gitlab_session cookies.

Doubling the number of digits in the malicious string makes the processing take about 8 times as long (ignoring the timeout).

Impact

Denial of Service - one request causes high CPU usage for 60 seconds. Attackers must have valid credentials.

Examples

To try just the vulnerable module in gitlab-rails console:

> dd = DeviceDetector.new ("Chrome/" + "0" * 3456)  
> dd.name  
...  
What is the current bug behavior?

Certain logged-in requests timeout after 60 seconds of high CPU due to ReDoS.

What is the expected correct behavior?

Malicious User-Agent strings should not cause DoS.

Relevant logs and/or screenshots

User-agent to copy paste:

Chrome/000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000  

Stack trace:

Rack::Timeout::RequestTimeoutException (Request ran for longer than 60000ms ):

app/models/active_session.rb:76:in `block in set'  
lib/gitlab/redis/wrapper.rb:23:in `block in with'  
lib/gitlab/redis/wrapper.rb:23:in `with'  
app/models/active_session.rb:68:in `set'  
config/initializers/warden.rb:28:in `block (2 levels) in <top (required)>'  
lib/gitlab/database/load_balancing/rack_middleware.rb:68:in `sticking_namespaces'  
lib/gitlab/database/load_balancing/rack_middleware.rb:39:in `unstick_or_continue_sticking'  
lib/gitlab/database/load_balancing/rack_middleware.rb:21:in `call'  
lib/gitlab/middleware/rails_queue_duration.rb:33:in `call'  
lib/gitlab/metrics/rack_middleware.rb:16:in `block in call'  
lib/gitlab/metrics/web_transaction.rb:46:in `run'  
lib/gitlab/metrics/rack_middleware.rb:16:in `call'  
lib/gitlab/jira/middleware.rb:19:in `call'  
lib/gitlab/middleware/go.rb:20:in `call'  
lib/gitlab/etag_caching/middleware.rb:21:in `call'  
lib/gitlab/middleware/query_analyzer.rb:11:in `block in call'  
lib/gitlab/database/query_analyzer.rb:37:in `within'  
lib/gitlab/middleware/query_analyzer.rb:11:in `call'  
lib/gitlab/middleware/multipart.rb:173:in `call'  
lib/gitlab/middleware/read_only/controller.rb:50:in `call'  
lib/gitlab/middleware/read_only.rb:18:in `call'  
lib/gitlab/middleware/same_site_cookies.rb:27:in `call'  
lib/gitlab/middleware/handle_malformed_strings.rb:21:in `call'  
lib/gitlab/middleware/basic_health_check.rb:25:in `call'  
lib/gitlab/middleware/handle_ip_spoof_attack_error.rb:25:in `call'  
lib/gitlab/middleware/request_context.rb:21:in `call'  
lib/gitlab/middleware/webhook_recursion_detection.rb:15:in `call'  
config/initializers/fix_local_cache_middleware.rb:11:in `call'  
lib/gitlab/middleware/compressed_json.rb:26:in `call'  
lib/gitlab/middleware/rack_multipart_tempfile_factory.rb:19:in `call'  
lib/gitlab/middleware/sidekiq_web_static.rb:20:in `call'  
lib/gitlab/metrics/requests_rack_middleware.rb:77:in `call'  
lib/gitlab/middleware/release_env.rb:13:in `call'  
Results of GitLab environment info
System information  
System:  
Proxy:          no  
Current User:   git  
Using RVM:      no  
Ruby Version:   2.7.5p203  
Gem Version:    3.1.6  
Bundler Version:2.3.15  
Rake Version:   13.0.6  
Redis Version:  6.2.7  
Sidekiq Version:6.4.2  
Go Version:     unknown

GitLab information  
Version:        15.5.3-ee  
Revision:       48f51d8b0c3  
Directory:      /opt/gitlab/embedded/service/gitlab-rails  
DB Adapter:     PostgreSQL  
DB Version:     13.6  
URL:            https://gitlab.example.com  
HTTP Clone URL: https://gitlab.example.com/some-group/some-project.git  
SSH Clone URL:  git@gitlab.example.com:some-group/some-project.git  
Elasticsearch:  no  
Geo:            no  
Using LDAP:     no  
Using Omniauth: yes  
Omniauth Providers: 

GitLab Shell  
Version:        14.12.0  
Repository storage paths:  
- default:      /var/opt/gitlab/git-data/repositories  
GitLab Shell path:              /opt/gitlab/embedded/service/gitlab-shell  

Impact

Denial of Service - one request causes high CPU usage for 60 seconds. Attackers must have valid credentials.

Not the worst issue, but I see in hacktivity you have recently awarded DoS reports with the same impact so I'm hoping this report is accepted as well.

How To Reproduce

Please add reproducibility information to this section: