Initial commit
This commit is contained in:
250
tests/Feature/SearchXssProtectionTest.php
Normal file
250
tests/Feature/SearchXssProtectionTest.php
Normal file
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Http\Livewire\MainSearchBar;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Livewire;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SearchXssProtectionTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
/**
|
||||
* Test that the sanitizeHighlights method properly escapes XSS attempts.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_search_highlights_escape_script_tags()
|
||||
{
|
||||
// Create a test user
|
||||
$user = User::factory()->create([
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@example.com',
|
||||
]);
|
||||
|
||||
// Authenticate as the user
|
||||
$this->actingAs($user);
|
||||
|
||||
// Create MainSearchBar component instance
|
||||
$component = new MainSearchBar();
|
||||
|
||||
// Use reflection to access the private sanitizeHighlights method
|
||||
$reflection = new \ReflectionClass($component);
|
||||
$method = $reflection->getMethod('sanitizeHighlights');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Test 1: Simple script tag injection
|
||||
$maliciousHighlights = [
|
||||
'about_short_en' => [
|
||||
'I am a developer <script>alert("XSS")</script> looking for work'
|
||||
]
|
||||
];
|
||||
|
||||
$sanitized = $method->invoke($component, $maliciousHighlights);
|
||||
|
||||
$this->assertStringNotContainsString('<script>', $sanitized['about_short_en'][0]);
|
||||
$this->assertStringContainsString('<script>', $sanitized['about_short_en'][0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that img tag with onerror handler is escaped.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_search_highlights_escape_img_onerror()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$component = new MainSearchBar();
|
||||
$reflection = new \ReflectionClass($component);
|
||||
$method = $reflection->getMethod('sanitizeHighlights');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$maliciousHighlights = [
|
||||
'about_en' => [
|
||||
'Contact me <img src=x onerror=alert(document.cookie)> for projects'
|
||||
]
|
||||
];
|
||||
|
||||
$sanitized = $method->invoke($component, $maliciousHighlights);
|
||||
|
||||
// The img tag should be escaped
|
||||
$this->assertStringNotContainsString('<img', $sanitized['about_en'][0]);
|
||||
$this->assertStringContainsString('<img', $sanitized['about_en'][0]);
|
||||
// The equals sign in the attribute should be escaped, making it safe
|
||||
$this->assertStringContainsString('src=x', $sanitized['about_en'][0]); // Text 'onerror' may remain but is harmless when HTML is escaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that iframe injection is escaped.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_search_highlights_escape_iframe()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$component = new MainSearchBar();
|
||||
$reflection = new \ReflectionClass($component);
|
||||
$method = $reflection->getMethod('sanitizeHighlights');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$maliciousHighlights = [
|
||||
'motivation_en' => [
|
||||
'My motivation <iframe src="https://evil.com"></iframe> is strong'
|
||||
]
|
||||
];
|
||||
|
||||
$sanitized = $method->invoke($component, $maliciousHighlights);
|
||||
|
||||
$this->assertStringNotContainsString('<iframe', $sanitized['motivation_en'][0]);
|
||||
$this->assertStringContainsString('<iframe', $sanitized['motivation_en'][0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Elasticsearch highlight tags are preserved.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_search_highlights_preserve_elasticsearch_tags()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$component = new MainSearchBar();
|
||||
$reflection = new \ReflectionClass($component);
|
||||
$method = $reflection->getMethod('sanitizeHighlights');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Simulate Elasticsearch highlight with configured tags
|
||||
$validHighlights = [
|
||||
'name' => [
|
||||
'John <span class="font-semibold text-white leading-tight">Smith</span>'
|
||||
]
|
||||
];
|
||||
|
||||
$sanitized = $method->invoke($component, $validHighlights);
|
||||
|
||||
// Elasticsearch highlight tags should be preserved
|
||||
$this->assertStringContainsString('<span class="font-semibold text-white leading-tight">Smith</span>', $sanitized['name'][0]);
|
||||
$this->assertStringContainsString('John', $sanitized['name'][0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that user content with special characters is escaped but highlight tags preserved.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_search_highlights_escape_user_content_preserve_highlight_tags()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$component = new MainSearchBar();
|
||||
$reflection = new \ReflectionClass($component);
|
||||
$method = $reflection->getMethod('sanitizeHighlights');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// User content with XSS attempt + Elasticsearch highlight tags
|
||||
$mixedHighlights = [
|
||||
'about_short_en' => [
|
||||
'I love <span class="font-semibold text-white leading-tight">coding</span> <script>alert("XSS")</script>'
|
||||
]
|
||||
];
|
||||
|
||||
$sanitized = $method->invoke($component, $mixedHighlights);
|
||||
|
||||
// Elasticsearch tags preserved
|
||||
$this->assertStringContainsString('<span class="font-semibold text-white leading-tight">coding</span>', $sanitized['about_short_en'][0]);
|
||||
|
||||
// User XSS attempt escaped
|
||||
$this->assertStringNotContainsString('<script>alert("XSS")</script>', $sanitized['about_short_en'][0]);
|
||||
$this->assertStringContainsString('<script>', $sanitized['about_short_en'][0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that empty highlights array is handled correctly.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_search_highlights_handle_empty_array()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$component = new MainSearchBar();
|
||||
$reflection = new \ReflectionClass($component);
|
||||
$method = $reflection->getMethod('sanitizeHighlights');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$emptyHighlights = [];
|
||||
$sanitized = $method->invoke($component, $emptyHighlights);
|
||||
|
||||
$this->assertEmpty($sanitized);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that event handler attributes are escaped.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_search_highlights_escape_event_handlers()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$component = new MainSearchBar();
|
||||
$reflection = new \ReflectionClass($component);
|
||||
$method = $reflection->getMethod('sanitizeHighlights');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$maliciousHighlights = [
|
||||
'about_en' => [
|
||||
'Click <a href="#" onclick="alert(1)">here</a> for more info'
|
||||
]
|
||||
];
|
||||
|
||||
$sanitized = $method->invoke($component, $maliciousHighlights);
|
||||
|
||||
// The anchor tag and attributes should be escaped
|
||||
$this->assertStringNotContainsString('<a href', $sanitized['about_en'][0]);
|
||||
$this->assertStringContainsString('<a href="#"', $sanitized['about_en'][0]);
|
||||
// Verify the dangerous onclick is escaped (quotes are converted to ")
|
||||
$this->assertStringContainsString('onclick="', $sanitized['about_en'][0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that data URIs with JavaScript are escaped.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_search_highlights_escape_data_uris()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$component = new MainSearchBar();
|
||||
$reflection = new \ReflectionClass($component);
|
||||
$method = $reflection->getMethod('sanitizeHighlights');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$maliciousHighlights = [
|
||||
'about_en' => [
|
||||
'Visit <a href="data:text/html,<script>alert(1)</script>">link</a>'
|
||||
]
|
||||
];
|
||||
|
||||
$sanitized = $method->invoke($component, $maliciousHighlights);
|
||||
|
||||
// The anchor tag should be escaped, making the data URI harmless
|
||||
$this->assertStringNotContainsString('<a href="data:text/html', $sanitized['about_en'][0]);
|
||||
$this->assertStringContainsString('<a', $sanitized['about_en'][0]);
|
||||
// Verify the script tag within the data URI is also escaped
|
||||
$this->assertStringContainsString('<script>', $sanitized['about_en'][0]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user