user->create(['role' => 'administrator']); wp_set_current_user($admin); } /** * Adversarial: internal / loopback / link-local source URLs are refused by the * safe-fetch path before any request leaves the box — cloud-metadata exfil, * loopback port-probe, RFC1918 reach all fail. */ public function test_internal_target_urls_are_rejected_by_the_safe_fetch_path() { $internal = [ 'http://169.254.169.254/latest/meta-data/x.png', // cloud metadata 'http://127.0.0.1/x.png', // loopback 'http://localhost/x.png', 'http://10.0.0.1/x.png', // RFC1918 'http://192.168.1.1/x.png', ]; foreach ($internal as $url) { $this->assertWPError( download_url($url), "Expected the safe-fetch path to refuse internal target {$url}" ); } } /** * The import fetch goes through the SSRF-safe path: the outbound request for the * source URL carries reject_unsafe_urls=true, which only wp_safe_remote_get() sets * (download_url() uses it). A regression to a raw wp_remote_get($source) would drop * the flag and reopen SSRF. * * Mutation-verified: replacing media_sideload_image() in uploadMedia() with a direct * wp_remote_get($source)+wp_upload_bits() drops reject_unsafe_urls and turns this red. */ public function test_upload_fetches_source_through_the_unsafe_url_rejecting_path() { $captured = []; add_filter('http_request_args', function ($args, $url) use (&$captured) { $captured[$url] = $args; return $args; }, 10, 2); // Short-circuit the network: write valid PNG bytes to download_url()'s stream // target so the sideload completes without a real socket, then report 200. $png = base64_decode( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' ); add_filter('pre_http_request', function ($pre, $args, $url) use ($png) { if (!empty($args['filename'])) { file_put_contents($args['filename'], $png); } return [ 'headers' => [], 'body' => '', 'response' => ['code' => 200, 'message' => 'OK'], 'cookies' => [], 'filename' => $args['filename'] ?? null, ]; }, 10, 3); $src = 'https://images.unsplash.com/photo-ssrf-pin.png'; $req = new \WP_REST_Request('POST', '/extendify/v1/draft/upload-image'); $req->set_param('source', $src); $res = ImageController::uploadMedia($req); $id = $res->get_data()['id']; $this->assertIsInt($id); $this->assertGreaterThan(0, $id, 'media_sideload_image() should create an attachment'); $this->assertArrayHasKey($src, $captured, 'the client source URL was fetched'); $this->assertTrue( (bool) $captured[$src]['reject_unsafe_urls'], 'the source fetch must use the SSRF-safe (reject_unsafe_urls) path' ); } }