mirror of https://github.com/pixelfed/pixelfed
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
75 lines
2.2 KiB
PHP
75 lines
2.2 KiB
PHP
<?php
|
|
|
|
use App\Jobs\StoryPipeline\StoryFetch;
|
|
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
uses(LazilyRefreshDatabase::class);
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| StoryFetch SSRF hardening
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Both outbound fetches (bearcap story JSON and media download) route through
|
|
| SecureMediaFetchService, which refuses to follow redirects to private /
|
|
| reserved addresses. These tests exercise the payload-fetch sink directly.
|
|
|
|
|
*/
|
|
|
|
function seedPublicIpForStory(string $host): void
|
|
{
|
|
Cache::put('helpers:url:public-ips:'.hash('xxh128', $host), ['203.0.113.60'], 3600);
|
|
}
|
|
|
|
/**
|
|
* Invoke the private fetchStoryPayload() via reflection.
|
|
*/
|
|
function callFetchStoryPayload(string $url, string $token)
|
|
{
|
|
$job = new StoryFetch([]);
|
|
$ref = new ReflectionMethod($job, 'fetchStoryPayload');
|
|
$ref->setAccessible(true);
|
|
|
|
return $ref->invoke($job, $url, $token);
|
|
}
|
|
|
|
beforeEach(function () {
|
|
Cache::flush();
|
|
});
|
|
|
|
it('fetches the story payload over the hardened path', function () {
|
|
seedPublicIpForStory('pixelfed.org');
|
|
|
|
Http::fake([
|
|
'https://pixelfed.org/story' => Http::response('{"id":"https://pixelfed.org/s/1"}', 200, [
|
|
'Content-Type' => 'application/json',
|
|
]),
|
|
]);
|
|
|
|
$payload = callFetchStoryPayload('https://pixelfed.org/story', 'bearcap-token-1234567890');
|
|
|
|
expect($payload)->toBeArray()
|
|
->and($payload['id'])->toBe('https://pixelfed.org/s/1');
|
|
});
|
|
|
|
it('refuses a payload fetch that redirects to a private address', function () {
|
|
seedPublicIpForStory('pixelfed.org');
|
|
|
|
Http::fake([
|
|
'https://pixelfed.org/story' => Http::response('', 302, [
|
|
'Location' => 'http://169.254.169.254/latest/meta-data/',
|
|
]),
|
|
'169.254.169.254/*' => Http::response('SECRET', 200),
|
|
]);
|
|
|
|
$payload = callFetchStoryPayload('https://pixelfed.org/story', 'bearcap-token-1234567890');
|
|
|
|
expect($payload)->toBeNull();
|
|
|
|
Http::assertNotSent(function ($request) {
|
|
return str_contains($request->url(), '169.254.169.254');
|
|
});
|
|
});
|