Persona - Personalise Your Forum Identity

Persona - Personalise Your Forum Identity 1.3.3

No permission to download

Brad P

Well-known member
Licensed customer
Brad P submitted a new resource:

Persona - Personalise Your Forum Identity - Let your members customise their usernames, avatars, titles, posts, and profiles. Fully built on Xen

Persona gives every member on your forum the tools to make their presence their own. Username colours, fonts, effects, avatars, badges, post themes, profile backgrounds -- it's all in one place, with a live preview so they can see exactly what they're getting before they save.

I built this because I wanted something that actually works across every page, not just posts. Persona renders on threads, profiles, popup cards, the forums list, the visitor dropdown -- everywhere a username shows...

Read more about this resource...
 
Brad P updated Persona - Personalise Your Forum Identity with a new update entry:

Version 1.0.1

Version 1.0.1

Fixed an issue where usernames and custom titles in the message sidebar could lose their center alignment when Persona features were active. This affected users with nameplates, badges, mood pills, or username effects (wave, bounce, glitch, typewriter) -- the added styling would shift names and titles off-center.

Changes:

Added centering fix for message sidebar user details when per-user Persona CSS is applied
Badge wrapper now uses centered flex layout
Mood pill indicator...

Read the rest of this update entry...
 
Brad P updated Persona - Personalise Your Forum Identity with a new update entry:

Persona v1.1.3 — Element Conflict Fix + Auto-Rebuild on Upgrade

What's Fixed

Icon and Mood Badge rendering conflict resolved. When a user configured both a username icon (fire, crown, etc.) and a mood badge ("Fierce", "Chill", etc.), only one would display. The root cause was that both features were writing CSS to the same ::after pseudo-element, so whichever rule came last would overwrite the other.

The CSS generator now tracks which pseudo-element the icon occupies and automatically assigns the mood badge to the opposite one. For example, if a user...

Read the rest of this update entry...
 
1782594901348.webp
Upgrading from 1.0.1 to 1.1.3 gets me here.
Updated simply via the Admin button 'Install/Upgrade' and uploading the .zip file. @Brad P

I did not check the checkbox (something with only overwrite modified files)
 
Brad P updated Persona - Personalise Your Forum Identity with a new update entry:

Persona v1.1.5

Fixed -- Persona v1.1.5

The bug: regenerateUserCss() in Repository/Persona.php required two arguments ($userId and array $style), but Setup.php was calling it with only $userId during the post-upgrade rebuild step. This caused an ArgumentCountError any time someone upgraded from an older version (like 1.0.1 to 1.1.3 or 1.1.4).

The fix: Made $style an optional parameter (array $style = null). When it's not passed, the method now fetches the user's style data from the UserStyle entity...

Read the rest of this update entry...
 
That fixed it. Thank you.

1782600823345.webp

I still have this issue though - it was not fixed with 1.0.1
Tried also Private/Incognito mode to rule out any caching.
 
Brad, on Shortie's forum, i have a background & border around my name but it doesn't show in the member online sidebar, but it does on my tooltip see here:
smokey_name_nobgca.webp

As you can see it works in the membertooltip:
smokey_nobgca_tooltip.webp

Live preview of what it's supposed to look like:
Smokey_Livepreview.webp


See here.

I checked the default XF theme and it has the same issue.
 
Brad P updated Persona - Personalise Your Forum Identity with a new update entry:

Persona v1.2.0 — Sidebar & Context Rendering Fix

What's Fixed

Username background and border not rendering in sidebar widgets.
When a user configured a background colour and/or border on their username, it displayed correctly in tooltips, the live preview, and post areas — but not in the "Members online" sidebar or similar inline-list contexts. The root cause was an override rule that explicitly stripped background, border, and padding from usernames inside...

Read the rest of this update entry...
 
Brad, on Shortie's forum, i have a background & border around my name but it doesn't show in the member online sidebar, but it does on my tooltip see here:
View attachment 340784

As you can see it works in the membertooltip:
View attachment 340785

Live preview of what it's supposed to look like:
View attachment 340786


See here.

I checked the default XF theme and it has the same issue.

This is fixed in v1.2.0 the sidebar was explicitly stripping background and border from usernames to avoid layout issues in inline lists, but that meant any background pill or border disappeared in "Members online" and similar widgets. The new version keeps them intact everywhere using display: inline-block with vertical-align: baseline instead, which works across all themes.
 
This is fixed in v1.2.0 the sidebar was explicitly stripping background and border from usernames to avoid layout issues in inline lists, but that meant any background pill or border disappeared in "Members online" and similar widgets. The new version keeps them intact everywhere using display: inline-block with vertical-align: baseline instead, which works across all themes.
One last thing.....the tooltip, when a background color is active, it looks weird (super wide)

see:
Persona_tooltip_bgwide.webp
 
Hi Brad, thanks for building this - the feature set is ambitious and the live preview is genuinely nice.

I reviewed the current 1.2.0 and confirmed the issues below with working proof-of-concepts on a throwaway local install (benign markers only). I am posting publicly rather than only via PM because the addon is already installed on some boards and those admins should know so they can protect their members while you patch. This is not a pile-on - the two critical ones share a single small root cause and are quick to fix. I have deliberately written the payloads out in words rather than pasting literal exploit strings; happy to share exact repro steps privately.

For fairness up front: 1.2.0 already added URL-scheme validation for link fields and fixed the version_id, so you are clearly hardening this. Everything below I verified by actually exploiting it on a local install, not just by reading code - including confirming it affects logged-out guests and appears in the raw server response with no cookies (so it is genuinely server-side, not a browser-only trick).

Proven (working PoC on a local 1.2.0 install)

Root cause: the chosen font names are never validated against the real font list. The username-font and title-font values arrive from the save request as free text with no allowlist (Pub/Controller/Persona.php around lines 135 and 151) and are stored raw - neither field is in the colour/url/bool sanitising lists. The customizer offers them as a dropdown of your bundled fonts, but the server never checks the submitted value is one of them, so a member can send any string by editing the request. Each value then reaches two output sinks unescaped.

1. Stored XSS via the username font (critical)
On render the value is concatenated into the Google-Fonts stylesheet link in the page head (Listener.php ~80-85), and the whole head blob is emitted as pre-escaped / trusted markup (Listener.php ~264), so quotes and angle brackets are not escaped. A font value that contains a double-quote followed by an HTML tag closes the link's URL attribute and injects markup (including script) into the head.
  • Verified: as an ordinary unprivileged member I saved such a value; my (harmless) marker script then executed on a normal forum page, and it fired for a logged-out guest too. I also fetched the page over a plain request with no cookies and the injected markup was present in the raw server HTML - so it is stored server-side and served to every visitor, not a client-side trick.
  • Impact: the listener runs on every public page (hint public PAGE_CONTAINER) and emits font links for every styled user, so one member's stored value runs script in every visitor's browser site-wide = session/cookie theft, actions taken as staff/admins.

2. Stored XSS via the user-title font (critical)
Same issue, second field: the title-font value feeds the same head stylesheet link (Listener.php ~67-70 into ~80-85 and ~264). Verified the same way. A fix must cover both font fields - allowlisting only the username font leaves this one open.

3. Stored CSS injection via the font fields (high)
The same font value is also written into the generated stylesheet inside the font-family declaration (Repository/Persona.php ~972 and ~1553). A single quote closes that declaration and lets arbitrary style rules into a stylesheet that is served to everyone who sees the member's content. Verified: from a font value alone I injected a page-wide rule and watched it apply across the whole page, including for a guest. This is style-level (defacement, clickable overlays, loading remote resources) rather than script execution, hence high not critical.

4. Stored CSS injection via the gradient-direction field (medium)
The username gradient-direction value is written raw into the CSS gradient declaration (Repository/Persona.php ~921). Verified: a short value (this column is only 20 chars, but that is enough) closed the declaration and injected a global rule that restyled the whole page for a guest. The same unvalidated pattern applies to the nameplate border-style (~1368) and post border-style (~1191) fields. The colour fields, by contrast, are only 7 chars and cannot break out, so those are fine - it is specifically the style-token fields.

5. Premium-tier / paywall bypass via Import (high, if you gate cosmetics by tier)
The tier check runs only in the normal save action (Pub/Controller ~166-201). The import, copy-from-user and revert-to-history paths all call saveUserStyle directly with no tier check, and the render path checks only whether an asset is active, never its tier. Verified: as a plain Registered member I used the import feature with a style code equipping a Pro-tier avatar frame (min_tier = pro); it saved, and the generated stylesheet then rendered that Pro frame's border on my avatar site-wide - a paid cosmetic on a free account.

6. Moderation (blocked-user) bypass (medium)
The blocked-user flag is checked in the normal save action (~118) but not in the import / reset / copy / revert paths - the same paths that skip the tier gate in #5 - and blocking never regenerates or removes the already-served stylesheet. So a blocked user can re-apply their style (including an abusive one) and the block is effectively a no-op.

Additional (confirmed in code, not separately load-tested)
  • Availability / DoS: every public page load runs unbounded full-table scans of the user-styles table (twice) plus a join to the users table and a full badge scan, into PHP memory (getAllUserConfigs, getAllUserBadges) with no caching - it runs for guests too and will degrade as the styled-user count grows.

Minor / cosmetic
  • Custom-font upload is broken: it throws a fatal (an undefined-method error on the upload move call, Admin/Controller ~197) for any file, so nothing is ever written - a functional bug, not the security hole it looks like. When you wire it up, add validity + extension/size checks and use the framework file helpers.
  • Admin dashboard layout: the ACP Overview stat values render as vertical one-character-per-line columns (the stat row is not laid out), and the Tools / User Configurations button rows are misaligned.
  • Counters / history: the style-views and total-saves counters are non-atomic (lost updates under load); the style-history timeline snapshots the new style rather than the previous one, so revert restores the wrong state.

Fix
Validate both font fields (and the gradient / border style-token fields) against hard allowlists before use, and stop emitting user-derived strings as pre-escaped / trusted markup (let the templater escape them). Move the tier and blocked-user checks down into saveUserStyle so the import / copy / revert paths are covered, not just the normal save. That closes 1-6.

If you have Persona installed right now: I would disable it, or restrict who can set styles, until a fixed build is out - because of issues 1 and 2 any single member account can run script in every visitor's browser.
 
Brad P updated Persona - Personalise Your Forum Identity with a new update entry:

Persona v1.3.0

This update addresses a number of input validation issues that were reported by a community member (thanks qubn for the thorough write-up).

What's fixed:

  • Font fields (username and title) are now validated against the known font list before saving. Previously these were free text, which meant a crafted value could inject into the Google Fonts link tag or into generated CSS. Both are now blocked.
  • Gradient direction, border styles, text transforms, icon names, nameplate...

Read the rest of this update entry...
 
Hi Brad, thanks for building this - the feature set is ambitious and the live preview is genuinely nice.

I reviewed the current 1.2.0 and confirmed the issues below with working proof-of-concepts on a throwaway local install (benign markers only). I am posting publicly rather than only via PM because the addon is already installed on some boards and those admins should know so they can protect their members while you patch. This is not a pile-on - the two critical ones share a single small root cause and are quick to fix. I have deliberately written the payloads out in words rather than pasting literal exploit strings; happy to share exact repro steps privately.

For fairness up front: 1.2.0 already added URL-scheme validation for link fields and fixed the version_id, so you are clearly hardening this. Everything below I verified by actually exploiting it on a local install, not just by reading code - including confirming it affects logged-out guests and appears in the raw server response with no cookies (so it is genuinely server-side, not a browser-only trick).

Proven (working PoC on a local 1.2.0 install)

Root cause: the chosen font names are never validated against the real font list. The username-font and title-font values arrive from the save request as free text with no allowlist (Pub/Controller/Persona.php around lines 135 and 151) and are stored raw - neither field is in the colour/url/bool sanitising lists. The customizer offers them as a dropdown of your bundled fonts, but the server never checks the submitted value is one of them, so a member can send any string by editing the request. Each value then reaches two output sinks unescaped.

1. Stored XSS via the username font (critical)
On render the value is concatenated into the Google-Fonts stylesheet link in the page head (Listener.php ~80-85), and the whole head blob is emitted as pre-escaped / trusted markup (Listener.php ~264), so quotes and angle brackets are not escaped. A font value that contains a double-quote followed by an HTML tag closes the link's URL attribute and injects markup (including script) into the head.
  • Verified: as an ordinary unprivileged member I saved such a value; my (harmless) marker script then executed on a normal forum page, and it fired for a logged-out guest too. I also fetched the page over a plain request with no cookies and the injected markup was present in the raw server HTML - so it is stored server-side and served to every visitor, not a client-side trick.
  • Impact: the listener runs on every public page (hint public PAGE_CONTAINER) and emits font links for every styled user, so one member's stored value runs script in every visitor's browser site-wide = session/cookie theft, actions taken as staff/admins.

2. Stored XSS via the user-title font (critical)
Same issue, second field: the title-font value feeds the same head stylesheet link (Listener.php ~67-70 into ~80-85 and ~264). Verified the same way. A fix must cover both font fields - allowlisting only the username font leaves this one open.

3. Stored CSS injection via the font fields (high)
The same font value is also written into the generated stylesheet inside the font-family declaration (Repository/Persona.php ~972 and ~1553). A single quote closes that declaration and lets arbitrary style rules into a stylesheet that is served to everyone who sees the member's content. Verified: from a font value alone I injected a page-wide rule and watched it apply across the whole page, including for a guest. This is style-level (defacement, clickable overlays, loading remote resources) rather than script execution, hence high not critical.

4. Stored CSS injection via the gradient-direction field (medium)
The username gradient-direction value is written raw into the CSS gradient declaration (Repository/Persona.php ~921). Verified: a short value (this column is only 20 chars, but that is enough) closed the declaration and injected a global rule that restyled the whole page for a guest. The same unvalidated pattern applies to the nameplate border-style (~1368) and post border-style (~1191) fields. The colour fields, by contrast, are only 7 chars and cannot break out, so those are fine - it is specifically the style-token fields.

5. Premium-tier / paywall bypass via Import (high, if you gate cosmetics by tier)
The tier check runs only in the normal save action (Pub/Controller ~166-201). The import, copy-from-user and revert-to-history paths all call saveUserStyle directly with no tier check, and the render path checks only whether an asset is active, never its tier. Verified: as a plain Registered member I used the import feature with a style code equipping a Pro-tier avatar frame (min_tier = pro); it saved, and the generated stylesheet then rendered that Pro frame's border on my avatar site-wide - a paid cosmetic on a free account.

6. Moderation (blocked-user) bypass (medium)
The blocked-user flag is checked in the normal save action (~118) but not in the import / reset / copy / revert paths - the same paths that skip the tier gate in #5 - and blocking never regenerates or removes the already-served stylesheet. So a blocked user can re-apply their style (including an abusive one) and the block is effectively a no-op.

Additional (confirmed in code, not separately load-tested)
  • Availability / DoS: every public page load runs unbounded full-table scans of the user-styles table (twice) plus a join to the users table and a full badge scan, into PHP memory (getAllUserConfigs, getAllUserBadges) with no caching - it runs for guests too and will degrade as the styled-user count grows.

Minor / cosmetic
  • Custom-font upload is broken: it throws a fatal (an undefined-method error on the upload move call, Admin/Controller ~197) for any file, so nothing is ever written - a functional bug, not the security hole it looks like. When you wire it up, add validity + extension/size checks and use the framework file helpers.
  • Admin dashboard layout: the ACP Overview stat values render as vertical one-character-per-line columns (the stat row is not laid out), and the Tools / User Configurations button rows are misaligned.
  • Counters / history: the style-views and total-saves counters are non-atomic (lost updates under load); the style-history timeline snapshots the new style rather than the previous one, so revert restores the wrong state.

Fix
Validate both font fields (and the gradient / border style-token fields) against hard allowlists before use, and stop emitting user-derived strings as pre-escaped / trusted markup (let the templater escape them). Move the tier and blocked-user checks down into saveUserStyle so the import / copy / revert paths are covered, not just the normal save. That closes 1-6.

If you have Persona installed right now: I would disable it, or restrict who can set styles, until a fixed build is out - because of issues 1 and 2 any single member account can run script in every visitor's browser.
Hey, appreciate the detailed write-up. You were right on all counts.

v1.3.0 is up now and covers everything you flagged:

  • Font fields are validated against the known font list (Google Fonts + any custom fonts added by the admin). Arbitrary text no longer gets through.
  • Gradient direction, border styles, text transforms, icons, and shapes all go through strict allowlists now. Invalid values get silently replaced with defaults.
  • Colour fields are regex-validated (hex, rgb/rgba, hsl/hsla only).
  • The block check and tier enforcement are both in the core save method now, so import, copy, revert, and reset all respect them. No more bypasses through those routes.
  • Font names get sanitized before they hit the link tag in the listener as well, so even if something slipped past the save validation it wouldn't render as HTML.
Good catch on the tier bypass through import/copy -- that one was easy to miss since the UI enforces it visually but the backend didn't on those paths. Fixed now.

Thank you 😊
 
Back
Top Bottom