Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Security: Got data ransom for Revolt's Mongo DB #393

Open
TyraVex opened this issue Jan 9, 2025 · 2 comments
Open

Security: Got data ransom for Revolt's Mongo DB #393

TyraVex opened this issue Jan 9, 2025 · 2 comments

Comments

@TyraVex
Copy link

TyraVex commented Jan 9, 2025

What happened?

Hello,

I have been self-hosting revolt for a while now, always only exposing the website, api, january and autumn to the internet. Other sevices like mongo were hosted behind a docker virtual network with no ports linked to localhost or internet.

Updating revolt today, after not touching it for a few months, revealed that my revolt DB was gone. Inspecting the container with mongosh shows the following:

READ__ME_TO_RECOVER_YOUR_DATA> db.README.find()
[
  {
    _id: ObjectId('677dd540b44cd9b314b4cc0f'),
    content: 'All your data is backed up. You must pay 0.0041 BTC to bc1q8***********************em In 48 hours, your data will be publicly disclosed and deleted. (more information: go to http://********** paying send mail to us: *********@onionmail.org and we will provide a link for you to download your data. Your DBCODE is: ********'
  }
]

I censored most of the information in case the data is publicity available, that could include accounts, messages, and password hashes, I guess?

Anyway, I am not very mad, the data was mildly sensitive and backups helped me get back right before the incident. This is more of a prevention than a rant, do whatever you want with this information.

Note: it seems that the VPS revolt was run on did not suffer any other damage than the mongodb container itself.

@github-project-automation github-project-automation bot moved this to 🆕 Untriaged in Revolt Project Jan 9, 2025
@insertish
Copy link
Member

insertish commented Feb 10, 2025

How was your database deployed?
We provide reasonable defaults that do not allow the database to be accessed externally, see https://github.com/revoltchat/self-hosted/blob/master/compose.yml#L5; if you are using Docker / our self-hosted repo, does your configuration differ from this? If not, we'd have reason to be concerned and can look into this further.

Also relevant: https://www.mongodb.com/docs/manual/security/

@TyraVex
Copy link
Author

TyraVex commented Feb 12, 2025

Hello,

Thanks for the response. I don't exactly know when that happened, but at the time, my config looked something like so, with a few differences regarding minio when I was trying to make the old autumn attachments work:

compose.yml:

name: revolt

services:
  database:
    image: mongo
    volumes:
      - ./data/db:/data/db
    networks:
      - backend-network
    restart: always

  redis:
    image: eqalpha/keydb
    networks:
      - backend-network
    restart: always

  rabbit:
    image: rabbitmq:4
    environment:
      RABBITMQ_DEFAULT_USER: rabbituser
      RABBITMQ_DEFAULT_PASS: rabbitpass
    volumes:
      - ./data/rabbit:/var/lib/rabbitmq
    healthcheck:
      test: rabbitmq-diagnostics -q ping
      interval: 2s
      timeout: 2s
      retries: 30
      start_period: 10s
    networks:
      - backend-network
    restart: always

  minio:
    image: minio/minio
    command: server /data
    volumes:
      - ./data/minio:/data
    healthcheck:
      test: ["CMD", "curl", "-f", "localhost:9000/minio/health/live"]
      interval: 1s
      timeout: 2s
      retries: 10
    environment:
      MINIO_ROOT_USER: minioautumn
      MINIO_ROOT_PASSWORD: minioautumn
      MINIO_DOMAIN: minio
    networks:
      backend-network:
        aliases:
          - revolt-uploads.minio
          # legacy support:
          - attachments.minio
          - avatars.minio
          - backgrounds.minio
          - icons.minio
          - banners.minio
          - emojis.minio
    restart: always

  createbuckets:
    image: minio/mc
    depends_on:
      minio:
        condition: service_healthy
    entrypoint: >
      /bin/sh -c "
        /usr/bin/mc config host add minio http://minio:9000 minioautumn minioautumn &&
        /usr/bin/mc mb minio/revolt-uploads
      "
    networks:
      - backend-network

  revolt-backend:
    build:
      context: .
      dockerfile: Dockerfile.useCurrentArch
    depends_on:
      rabbit:
        condition: service_healthy
    ports:
      - "127.0.0.1:3010:14702"  # API
      - "127.0.0.1:3011:14703"  # Bonfire
      - "127.0.0.1:3012:14704"  # Autumn
      - "127.0.0.1:3013:14705"  # January
    volumes:
      - type: bind
        source: ./Revolt.overrides.toml
        target: /Revolt.toml
    command: >
      /bin/sh -c "
        ./target/release/revolt-delta &
        ./target/release/revolt-bonfire &
        ./target/release/revolt-january &
        ./target/release/revolt-autumn &
        ./target/release/revolt-pushd &
        wait
      "
    networks:
      - backend-network
    restart: always

networks:
  backend-network:

Revolt.overrides.toml:

[database]
# MongoDB connection URL
# Defaults to the container name specified in self-hosted
mongodb = "mongodb://database:27017"
# Redis connection URL
# Defaults to the container name specified in self-hosted
redis = "redis://redis:6379"

[hosts]
# Web locations of various services
# Defaults assume all services are reverse-proxied
# See https://github.com/revoltchat/self-hosted/blob/master/Caddyfile
#
# Remember to change these to https/wss where appropriate in production!
app = "[removed for privacy]"
api = "[removed for privacy]"
events = "[removed for privacy]"
autumn = "[removed for privacy]"
january = "[removed for privacy]"
voso_legacy = ""
voso_legacy_ws = ""

[api]

[api.smtp]
# Email server configuration for verification
# Defaults to no email verification (host field is empty)
host = ""
username = ""
password = ""
from_address = ""
reply_to = ""
port = 0
use_tls = false

[api.registration]
# Whether an invite should be required for registration
# See https://github.com/revoltchat/self-hosted#making-your-instance-invite-only
invite_only = true

[api.vapid]
# Generate your own keys:
# 1. Run `openssl ecparam -name prime256v1 -genkey -noout -out vapid_private.pem`
# 2. Find `private_key` using `base64 vapid_private.pem`
# 3. Find `public_key` using `openssl ec -in vapid_private.pem -outform DER|tail -c 65|base64|tr '/+' '_-'|tr -d '\n'`
private_key = "[removed for privacy]"

[api.fcm]
# Google Firebase Cloud Messaging Service Account Key
# Obtained from the cloud messaging console
key_type = "service_account"
project_id = "[removed for privacy]"
private_key_id = "[removed for privacy]"
private_key = "[removed for privacy]"
client_email = "[removed for privacy]"
client_id = "[removed for privacy]"
auth_uri = "[removed for privacy]"
token_uri = "[removed for privacy]"
auth_provider_x509_cert_url = "[removed for privacy]"
client_x509_cert_url = "[removed for privacy]"

[api.security]
# Authifier Shield API key
authifier_shield_key = ""
# Legacy voice server management token
voso_legacy_token = ""
# Whether services are behind the Cloudflare network
trust_cloudflare = false

[api.workers]
# Maximum concurrent connections (to proxy server)
max_concurrent_connections = 100

[files]
# Encryption key for stored files
# Generate your own key using `openssl rand -base64 32`
encryption_key = "[removed for privacy]"
webp_quality = 70.0
# Mime types that cannot be uploaded or served
# Example for Windows executables and Android installation files:
# ["application/vnd.microsoft.portable-executable", "application/vnd.android.package-archive"]
blocked_mime_types = []

[files.s3]
# S3 protocol endpoint
endpoint = "http://minio:9000"
# S3 region name
region = "minio"
# S3 protocol key ID
access_key_id = "minioautumn"
# S3 protocol access key
secret_access_key = "minioautumn"
# Bucket to upload to by default
default_bucket = "revolt-uploads"

[files.limit]
min_resolution = [1, 1]
max_mega_pixels = 100
max_pixel_side = 16_000

[files.preview]
attachments = [1280, 1280]
backgrounds = [1280, 1280]
banners = [1280, 1280]
avatars = [128, 128]
icons = [128, 128]
emojis = [128, 128]

[features]
webhooks_enabled = false

[features.limits]

[features.limits.global]
message_embeds = 100
message_replies = 100
message_reactions = 100
server_emoji = 10000
server_roles = 1000
server_channels = 1000
body_limit_size = 20_000_000_000
group_size = 1000
new_user_days = 3

[features.limits.new_user]
bots = 100
servers = 1000
message_length = 100000
message_attachments = 100
outgoing_friend_requests = 100
attachment_size = 20_000_000_000
background_size = 4_000_000
banner_size = 4_000_000
avatar_size = 500_000
icon_size = 500_000
emoji_size = 500_000

[features.limits.default]
bots = 100
servers = 1000
message_length = 100000
message_attachments = 100
outgoing_friend_requests = 100
attachment_size = 20_000_000_000
background_size = 4_000_000
banner_size = 4_000_000
avatar_size = 500_000
icon_size = 500_000
emoji_size = 500_000

External nginx (no docker here):

    server {
        server_name [removed for privacy];

        root /home/[removed for privacy]/dist;
        index index.html;

        location / {
            try_files $uri $uri/ /index.html;
        }

        listen 443 ssl;
        listen [::]:443 ssl;
    }

    server {
        server_name [removed for privacy];

        location / {
            proxy_pass http://127.0.0.1:3010;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";
            proxy_set_header Host $host;
        }

        listen 443 ssl;
        listen [::]:443 ssl;
    }

    server {
        server_name [removed for privacy];

        location / {
            proxy_pass http://127.0.0.1:3011;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";
            proxy_set_header Host $host;
        }

        listen 443 ssl;
        listen [::]:443 ssl;
    }

    server {
        server_name [removed for privacy];

        location / {
            proxy_pass http://127.0.0.1:3012;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";
            proxy_set_header Host $host;
        }

        listen 443 ssl;
        listen [::]:443 ssl;
    }

    server {
        server_name [removed for privacy];

        location / {
            proxy_pass http://127.0.0.1:3013;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";
            proxy_set_header Host $host;
        }

        listen 443 ssl;
        listen [::]:443 ssl;
    }

    // one for each domain
    server {
        server_name [removed for privacy];
        if ($host = [removed for privacy]) {
            return 301 https://$host$request_uri;
        }
        listen 80;
        listen [::]:80;
        return 404;
    }

I believe I did not use any firewall at the time. But I am pretty sure mongo was unreachable with or without. Only the domains are exposed today.

If any of this is still sensitive information, please let me know

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 🆕 Untriaged
Development

No branches or pull requests

2 participants