☠️
EXFILTRATING
DATA...
01010111 10110010 00101001 11000110 01101100 10011011 00010001 11100101 01000010 10101111
[ <-- BACK_TO_SEARCH ]
Decidim logo [ WEBSITE ] [ VENDOR STATS ]
CVSS: 5.7 🐛 CWE-672
👁️
Common Weakness Enumeration - Identifies the specific category of software weakness or flaw.

CVE-2023-48220

CVE-2023-48220 Unauthorized Access in Decidim
📅 PUBLISHED: 2024-02-20 17:24 UTC 🔄 UPDATED: 2026-09-23 20:29 UTC
[ OFFICIAL PATCH DEPLOYED ↓ ] FIX: LIKELY — 2 independent sources confirm fix
[ DEFENSE PATCH ↓ ]
💣 PoC: None
👁️
Proof of Concept - Indicates if functional exploit code is publicly available, drastically increasing the risk of immediate attacks.
🎯 EPSS: 0.79%
👁️
0.79% probability
19.8× above the median CVE
Tier: ELEVATED
30-day exploitation window · FIRST.org · updated daily
Country: Spain License: Open Source
Patch available — upgrade to latest release
CVSS SEVERITY SCALE
9.0–10.0 CRITICAL
7.0–8.9 HIGH
4.0–6.9 MEDIUM
0.1–3.9 LOW
0.0 NONE
5.7
THREAT LEVEL
■ tracking this CVE ✓ independent patch confirmation ⧗ same-origin mirror (GHSA) ⚠ active exploitation verification policy →

Executive Summary

CVE-2023-48220 is a vulnerability in the devise_invitable gem, a dependency of the Decidim participatory democracy framework. The flaw allows an invited user to accept an invitation indefinitely, bypassing the configured invite_for expiry period (set to 2 weeks by default in Decidim). This occurs because the password reset functionality in devise_invitable does not check whether the invitation is still valid before accepting it. An attacker with a valid invitation link can exploit this to gain unauthorized access to a Decidim instance even after the invitation has expired.

The vulnerability affects Decidim versions from 0.0.1.alpha3 up to (but not including) 0.26.9, 0.27.5, and 0.28.0. The root cause lies in the devise_invitable gem, which must be upgraded to version 2.0.9 or later. The CVSS score is 5.7 (Medium), with a low EPSS score of 0.0%, indicating low exploitation activity in the wild. No public exploit code or Nuclei templates are available.

⚡ AUTOMATE YOUR CVE INTELLIGENCE

This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.

›Patch confidence — UNVERIFIED to CONFIRMED, cross-referenced NVD + RHSA + Debian
›Exploit & PoC — poc_status, poc_url, has_exploit. Not in NVD.
›Shodan dorks — ready-made queries to find exposed vulnerable systems
›Detection rules — YARA · Wazuh · Elastic (all plans) · Sigma (Pro)
›pending_fields — know exactly which of 7 fields NVD hasn't processed yet
›Free backfill — data incomplete when you paid? Re-check within 30 days = 0 credits
PAYG €0.08/lookup Standard €19/mo (was €29) · 5k credits Pro €65/mo (was €99) · 20k credits FOUNDING PRICE · LOCKED FOR LIFE · ENDS 31 DEC 2026
[ FROM €19/MO ]
Free sandbox key available
No signup required

Supply Chain & Package Security

This vulnerability is a supply chain issue, as it originates from a third-party Ruby gem (devise_invitable). Decidim, being a Ruby on Rails application, relies on this gem for invitation management. The fix requires updating the gem dependency in the Gemfile and running bundle update devise_invitable. Organizations using Decidim should verify their dependency tree and ensure no other gems are affected by similar issues.

CVSS Breakdown & Threat Level

  • CVSS Score: 5.7 (Medium)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L
  • Attack Vector: Network (AV:N)
  • Attack Complexity: Low (AC:L)
  • Privileges Required: Low (PR:L) – attacker needs a valid invitation link
  • User Interaction: None (UI:N)
  • Scope: Unchanged (S:U)
  • Confidentiality Impact: Low (C:L)
  • Integrity Impact: Low (I:L)
  • Availability Impact: Low (A:L)

Threat Level: Medium. The vulnerability requires an invitation link, which limits the attack surface. However, once exploited, an attacker can gain persistent access to a Decidim instance, potentially leading to data manipulation or privilege escalation.

Deep Dive & Mechanics

The devise_invitable gem extends Devise to allow users to be invited to a system. When an invitation is sent, a token is generated and stored in the database. The invitation has an expiry period defined by the invite_for configuration (e.g., 2 weeks). After this period, the invitation should be invalid.

The vulnerability lies in the accept_invitation method, which is called when a user clicks the invitation link. The method checks if the user has been invited but does not verify the invite_for expiry. This means that even if the invitation has expired, the user can still accept it by using the password reset functionality. The password reset flow in Devise calls accept_invitation internally, bypassing the expiry check.

Technical Flow:

  1. An admin invites a user (e.g., user@example.com).
  2. The invitation link is sent with a token.
  3. The invitation expires after 2 weeks.
  4. The attacker obtains the invitation link (e.g., via email interception or database access).
  5. The attacker visits the invitation link and is prompted to set a password.
  6. Instead of setting a password, the attacker uses the "Forgot your password?" link.
  7. The password reset flow calls accept_invitation, which does not check the invite_for expiry.
  8. The attacker sets a new password and gains access to the account.

Vulnerable vs. Patched Code

Vulnerable Code (devise_invitable < 2.0.9):

# In lib/devise_invitable/models.rb
def accept_invitation!
  self.invitation_accepted_at = Time.current
  self.invitation_token = nil
  save(validate: false)
end

This method does not check invitation_due_at or invite_for.

Patched Code (devise_invitable >= 2.0.9):

# In lib/devise_invitable/models.rb
def accept_invitation!
  if invitation_due_at && invitation_due_at < Time.current
    errors.add(:base, :invitation_expired)
    return false
  end
  self.invitation_accepted_at = Time.current
  self.invitation_token = nil
  save(validate: false)
end

The patched version checks invitation_due_at against the current time and rejects expired invitations.

Exploitation & Lab Setup

Red Team (Exploitation)

Prerequisites:

  • A valid invitation link for a Decidim instance (e.g., https://decidim.example.com/users/invitation/accept?invitation_token=abc123).
  • The invitation must have been sent but may be expired.

Steps:

  1. Obtain the invitation link (e.g., via phishing, email access, or database dump).
  2. Visit the link in a browser. You will be prompted to set a password.
  3. Instead of setting a password, navigate to the password reset page (e.g., https://decidim.example.com/users/password/new).
  4. Enter the invited user's email address.
  5. Check the email inbox for the password reset link (if you have access) or use a timing attack to guess the token.
  6. Click the password reset link and set a new password.
  7. Log in with the new password. The account is now active.

Proof of Concept (PoC):

# Assuming you have the invitation token
curl -c cookies.txt -b cookies.txt -L "https://decidim.example.com/users/invitation/accept?invitation_token=abc123"
# Then trigger password reset
curl -c cookies.txt -b cookies.txt -X POST "https://decidim.example.com/users/password" \
  -d "user[email]=attacker@example.com"
# Follow the password reset link from email
curl -c cookies.txt -b cookies.txt -L "https://decidim.example.com/users/password/edit?reset_password_token=xyz789"
# Set new password
curl -c cookies.txt -b cookies.txt -X PUT "https://decidim.example.com/users/password" \
  -d "user[password]=NewP@ss123&user[password_confirmation]=NewP@ss123"

Blue Team (Detection & Prevention)

Detection:

  • Monitor for multiple password reset requests for the same user within a short time.
  • Alert on invitations accepted after the configured invite_for period.
  • Log all invitation acceptance events and correlate with password reset events.

Prevention:

  • Upgrade devise_invitable to version 2.0.9 or later.
  • As a workaround, manually cancel expired invitations in the database:
    UPDATE users SET invitation_token = NULL, invitation_accepted_at = NULL WHERE invitation_due_at < NOW();
    
  • Implement additional monitoring on the users table for unexpected changes to invitation_accepted_at.

Indicators of Compromise (IOCs)

  • Network: Unusual password reset requests for invited users.
  • Logs: Multiple Devise::PasswordsController#create actions followed by Devise::InvitationsController#edit for the same user.
  • Database: Rows in users table where invitation_accepted_at is set but invitation_due_at is in the past.
  • File: Modified Gemfile.lock showing devise_invitable version < 2.0.9.

Detection Engineering

YARA Rule

rule Detect_Devise_Invitable_Vulnerability {
    meta:
        description = "Detects vulnerable devise_invitable gem versions in Gemfile.lock"
        author = "Valters IT"
        date = "2025-04-10"
        cve = "CVE-2023-48220"
    strings:
        $gem = "devise_invitable"
        $version_low = /devise_invitable\s*\(([0-1]\.\d+\.\d+|2\.0\.[0-8])\)/
    condition:
        $gem and $version_low
}

Sigma Rule

title: CVE-2023-48220 - Invitation Expiry Bypass via Password Reset
id: 7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d
status: experimental
description: Detects password reset requests for invited users that may indicate exploitation of CVE-2023-48220
author: Valters IT
date: 2025-04-10
logsource:
    category: application
    product: decidim
detection:
    selection:
        event: "password_reset_request"
        user_invited: true
    condition: selection
falsepositives:
    - Legitimate password resets for invited users
level: medium

Suricata Rule

alert http any any -> any any (msg:"CVE-2023-48220 - Invitation Bypass via Password Reset"; flow:to_server; content:"/users/password"; http_uri; content:"user[email]"; http_client_body; classtype:attempted-user; sid:1000001; rev:1;)

Remediation & Mitigation

Immediate Actions:

  1. Upgrade devise_invitable gem to version 2.0.9 or later in the Gemfile:
    gem 'devise_invitable', '~> 2.0.9'
    
  2. Run bundle update devise_invitable and restart the application.
  3. If upgrade is not possible, manually cancel all expired invitations in the database:
    UPDATE users SET invitation_token = NULL, invitation_accepted_at = NULL WHERE invitation_due_at < NOW();
    

Long-term Measures:

  • Implement automated dependency scanning (e.g., Dependabot, Snyk) to detect vulnerable gems.
  • Enforce invitation expiry checks at the application level as a defense-in-depth measure.
  • Monitor for unusual invitation acceptance patterns.

Workaround:

  • Cancel invitations directly from the database as described above. This is a temporary measure until the gem is upgraded.

Valters IT
Senior DevSecOps & Cyber Threat Intelligence Expert

◈ SOURCE Analysis & enrichment: valtersit.com/cve/CVE-2023-48220/ · CVE data: NVD/MITRE (public domain) · © 2026 ValtersIT Hub
>_ SYSTEM_CORE: INTEL_ACTIVE
90% TEAM-CURATED // 10% AUTOMATED INGESTION.
ALL PATCH REFERENCES AND VERSION DATA SOURCED FROM NVD + OSV OFFICIAL REGISTRIES.