Post

Word Mess - Bugforge Weekly Challenge

A WordPress mass assignment bug chained with a custom template SSTI from BugForge Labs. Reading the official WordPress documentation turned out to be the best attack tool here.

Word Mess - Bugforge Weekly Challenge

This one’s a BugForge weekly challenge called Word Mess - a WordPress-based lab that chains two separate bugs together. Neither one alone gets you the flag. Here’s the full breakdown.

Platform: BugForge Labs   Category: Web Application Exploitation   Vulnerability: WordPress Mass Assignment + Custom Template SSTI

The Big Picture

1
2
3
4
5
Register account → Find wp-admin dashboard → Update profile, spot wm_ meta fields
→ Inject WordPress capabilities via mass assignment → Unlock Users and Comments sections
→ Find options-discussion.php → Set default_role to administrator
→ Register new user → Gets admin by default → Find Theme Editor with custom template tags
→ Test <?wm ?> syntax → Confirm SSTI → Execute system command → Get the flag

Step 1 - Opening the Lab

The lab opens with a WordPress-looking homepage showing three posts: “Welcome to WordPress”, “Announcing the REST API”, and “Getting Started with Plugins”.

Home Page

I tried the comment feature on the first post but it didn’t lead anywhere useful. However, I noticed a Dashboard link sitting in the footer.

Dashboard Link in Footer


Step 2 - Registering and Logging In

Clicking Dashboard redirected me to /wp-admin which showed a login.php page. I could register a new account from there.

Login Page with wp-admin Redirect

Credentials used:

1
2
3
username : test
email    : test@gmail.com
password : 123456

After registering and logging in, I landed on a basic user dashboard with Dashboard, Posts, and Profile columns.

After Login Dashboard


Step 3 - Exploring the Profile Update

Inside the Profile section, I found updateable fields: Display Name, Email, Biographical Info, and Social URL.

One thing I noticed - every new request triggered a human verification check: “WordMess is protected by Forgeflare.” Good security practice, but not the vulnerability here.

When I updated my profile, it sent a POST request to wp-admin/profile.php:

1
2
3
4
5
_wpnonce=836b50f453
&display_name=test
&email=test%40gmail.com
&meta%5Bwm_bio%5D=this+is+a+biographical+data
&meta%5Bwm_social%5D=https%3A%2F%2Fwww.google.com

The page also showed a note: “Profile preferences are saved under your wm_ user meta.”

After Profile Update

The meta[wm_*] naming pattern was interesting. These values were being saved directly as WordPress user meta. If the server accepts arbitrary meta keys without validating them, I could potentially inject WordPress capability flags into my own user record - without the app ever intending to grant them.


Step 4 - Injecting Capabilities via Mass Assignment

I read through the WordPress Roles and Capabilities documentation to understand what capability keys exist.

WordPress stores user capabilities in the database under wp_capabilities. If the profile endpoint saves any meta[wm_*] key I send without checking, I can write capability flags like list_users directly into my account.

I added this to the profile update request:

1
&meta[wm_capabilities][list_users]=1

The server returned a 302 redirect - it accepted the payload.

List Users Payload Works

I refreshed the dashboard and a new Users button appeared in the sidebar - confirming the capability was now applied to my account.

Users Button Now Visible


Step 5 - Adding More Capabilities

I kept experimenting with other capabilities from the docs: edit_users, edit_files, moderate_comments, install_plugins. Adding moderate_comments unlocked the Discussion and Comments sections:

1
2
&meta[wm_capabilities][list_users]=1
&meta[wm_capabilities][moderate_comments]=1

moderate_comments Works

Inside the Discussion settings page (wp-admin/options-discussion.php), the update request body looked like:

1
2
3
_wpnonce=75f5259b80
&options[comment_moderation]=1
&options[default_comment_status]=open

Step 6 - Escalating to Administrator via Default Role

Still reading the WordPress documentation, I found that options-discussion.php also accepts a default_role parameter - it sets the role assigned to every newly registered user.

I added it to the request:

1
&options[default_role]=administrator

The server accepted it.

Default Role Set to Administrator


Step 7 - Registering a New Admin Account

I logged out and registered a fresh account with the username hacker. After logging in, this account had full administrator privileges - because the default role was now set to administrator.

Administrator Privileges Granted

New sections appeared in the sidebar: Media (API), Theme Editor, and more.


Step 8 - Finding the Custom Template Tag in Theme Editor

Inside Edit Themes, I found a file called footer.wm with this content:

1
Proudly powered by  &middot; &copy; <?wm year ?> &middot; <a href="/wp-json">REST API</a>

Website Footer

This matched exactly what appeared at the bottom of the homepage. The <?wm year ?> tag was rendering the current year (2026) server-side. That’s a custom template tag being evaluated - a potential SSTI point worth probing.


Step 9 - Testing for SSTI

I tested different payloads inside the footer template:

PayloadOutputWhat it means
{{7*7}}{{7*7}}Wrong syntax for this engine
<?wm year ?>2026Correct syntax, evaluated
<?wm 7*7 ?>(empty)Something is happening
<?wm '7*7' ?>7*7String passed through
<?wm ['id']\|filter('system') ?>command outputRCE confirmed

The <?wm ?> tags were the key. The double curly brace syntax used by Jinja2 didn’t work here because this app uses its own custom wm template engine. Once I matched the right delimiter, the filter('system') call executed the shell command.


Step 10 - Getting the Flag

Using the payload:

1
<?wm ['id']|filter('system') ?>

The footer rendered the output of the id command - and along with it, the flag.

Flag Retrieved

No reverse shell needed. The SSTI itself was enough to expose the flag directly in the page output.


How Could This Be Fixed?

IssueFix
Profile endpoint accepts arbitrary meta[wm_*] keysWhitelist exactly which meta keys are allowed to be updated - never pass raw user input directly into update_user_meta()
options-discussion.php accepts default_role from user inputRestrict which option keys can be updated via this endpoint - default_role should never be changeable this way
Custom <?wm ?> template engine evaluates user-controlled inputNever pass user-controlled content through a template renderer - treat all user input as plain text only

Key Takeaways

  • WordPress user meta is a mass assignment risk. If the server accepts arbitrary meta[key] fields from the client without a whitelist, an attacker can write anything into their own user record - including capabilities the app never meant to expose.
  • Read the official documentation when you are stuck. The WordPress capabilities list and the default_role option both came straight from the official docs. The app follows WordPress conventions, so the docs became the attack map.
  • Template syntax varies between engines. {{7*7}} failing does not mean SSTI is impossible - it just means the engine uses different delimiters. The <?wm year ?> tag in the footer was the app giving away its own syntax.
  • You do not always need a reverse shell to get the flag. SSTI with a system() filter was enough to read the flag directly from the page output.
  • The most useful clues are often already on the page. The footer template tag <?wm year ?> was sitting there the whole time - it just needed a closer look.

Happy Hacking!

Writeup by: Rabin Gaire

Challenge: BugForge Weekly - Word Mess | Aug 29th, 2026

This post is licensed under CC BY 4.0 by the author.