|
|
|
@@ -1,212 +0,0 @@ |
|
|
|
/** |
|
|
|
* XSS Fix Verification Test |
|
|
|
* |
|
|
|
* This test verifies that the XSS vulnerability in check-code pages has been |
|
|
|
* properly fixed by replacing dangerouslySetInnerHTML with safe React rendering. |
|
|
|
*/ |
|
|
|
|
|
|
|
import React from 'react' |
|
|
|
import { cleanup, render } from '@testing-library/react' |
|
|
|
import '@testing-library/jest-dom' |
|
|
|
|
|
|
|
// Mock i18next with the new safe translation structure |
|
|
|
jest.mock('react-i18next', () => ({ |
|
|
|
useTranslation: () => ({ |
|
|
|
t: (key: string) => { |
|
|
|
if (key === 'login.checkCode.tipsPrefix') |
|
|
|
return 'We send a verification code to ' |
|
|
|
|
|
|
|
return key |
|
|
|
}, |
|
|
|
}), |
|
|
|
})) |
|
|
|
|
|
|
|
// Mock Next.js useSearchParams |
|
|
|
jest.mock('next/navigation', () => ({ |
|
|
|
useSearchParams: () => ({ |
|
|
|
get: (key: string) => { |
|
|
|
if (key === 'email') |
|
|
|
return 'test@example.com<script>alert("XSS")</script>' |
|
|
|
return null |
|
|
|
}, |
|
|
|
}), |
|
|
|
})) |
|
|
|
|
|
|
|
// Fixed CheckCode component implementation (current secure version) |
|
|
|
const SecureCheckCodeComponent = ({ email }: { email: string }) => { |
|
|
|
const { t } = require('react-i18next').useTranslation() |
|
|
|
|
|
|
|
return ( |
|
|
|
<div> |
|
|
|
<h1>Check Code</h1> |
|
|
|
<p> |
|
|
|
<span> |
|
|
|
{t('login.checkCode.tipsPrefix')} |
|
|
|
<strong>{email}</strong> |
|
|
|
</span> |
|
|
|
</p> |
|
|
|
</div> |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
// Vulnerable implementation for comparison (what we fixed) |
|
|
|
const VulnerableCheckCodeComponent = ({ email }: { email: string }) => { |
|
|
|
const mockTranslation = (key: string, params?: any) => { |
|
|
|
if (key === 'login.checkCode.tips' && params?.email) |
|
|
|
return `We send a verification code to <strong>${params.email}</strong>` |
|
|
|
|
|
|
|
return key |
|
|
|
} |
|
|
|
|
|
|
|
return ( |
|
|
|
<div> |
|
|
|
<h1>Check Code</h1> |
|
|
|
<p> |
|
|
|
<span dangerouslySetInnerHTML={{ __html: mockTranslation('login.checkCode.tips', { email }) }}></span> |
|
|
|
</p> |
|
|
|
</div> |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
describe('XSS Fix Verification - Check Code Pages Security', () => { |
|
|
|
afterEach(() => { |
|
|
|
cleanup() |
|
|
|
}) |
|
|
|
|
|
|
|
const maliciousEmail = 'test@example.com<script>alert("XSS")</script>' |
|
|
|
|
|
|
|
it('should securely render email with HTML characters as text (FIXED VERSION)', () => { |
|
|
|
console.log('\n🔒 Security Fix Verification Report') |
|
|
|
console.log('===================================') |
|
|
|
|
|
|
|
const { container } = render(<SecureCheckCodeComponent email={maliciousEmail} />) |
|
|
|
|
|
|
|
const spanElement = container.querySelector('span') |
|
|
|
const strongElement = container.querySelector('strong') |
|
|
|
const scriptElements = container.querySelectorAll('script') |
|
|
|
|
|
|
|
console.log('\n✅ Fixed Implementation Results:') |
|
|
|
console.log('- Email rendered in strong tag:', strongElement?.textContent) |
|
|
|
console.log('- HTML tags visible as text:', strongElement?.textContent?.includes('<script>')) |
|
|
|
console.log('- Script elements created:', scriptElements.length) |
|
|
|
console.log('- Full text content:', spanElement?.textContent) |
|
|
|
|
|
|
|
// Verify secure behavior |
|
|
|
expect(strongElement?.textContent).toBe(maliciousEmail) // Email rendered as text |
|
|
|
expect(strongElement?.textContent).toContain('<script>') // HTML visible as text |
|
|
|
expect(scriptElements).toHaveLength(0) // No script elements created |
|
|
|
expect(spanElement?.textContent).toBe(`We send a verification code to ${maliciousEmail}`) |
|
|
|
|
|
|
|
console.log('\n🎯 Security Status: SECURE - HTML automatically escaped by React') |
|
|
|
}) |
|
|
|
|
|
|
|
it('should demonstrate the vulnerability that was fixed (VULNERABLE VERSION)', () => { |
|
|
|
const { container } = render(<VulnerableCheckCodeComponent email={maliciousEmail} />) |
|
|
|
|
|
|
|
const spanElement = container.querySelector('span') |
|
|
|
const strongElement = container.querySelector('strong') |
|
|
|
const scriptElements = container.querySelectorAll('script') |
|
|
|
|
|
|
|
console.log('\n⚠️ Previous Vulnerable Implementation:') |
|
|
|
console.log('- HTML content:', spanElement?.innerHTML) |
|
|
|
console.log('- Strong element text:', strongElement?.textContent) |
|
|
|
console.log('- Script elements created:', scriptElements.length) |
|
|
|
console.log('- Script content:', scriptElements[0]?.textContent) |
|
|
|
|
|
|
|
// Verify vulnerability exists in old implementation |
|
|
|
expect(scriptElements).toHaveLength(1) // Script element was created |
|
|
|
expect(scriptElements[0]?.textContent).toBe('alert("XSS")') // Contains malicious code |
|
|
|
expect(spanElement?.innerHTML).toContain('<script>') // Raw HTML in DOM |
|
|
|
|
|
|
|
console.log('\n❌ Security Status: VULNERABLE - dangerouslySetInnerHTML creates script elements') |
|
|
|
}) |
|
|
|
|
|
|
|
it('should verify all affected components use the secure pattern', () => { |
|
|
|
console.log('\n📋 Component Security Audit') |
|
|
|
console.log('============================') |
|
|
|
|
|
|
|
// Test multiple malicious inputs |
|
|
|
const testCases = [ |
|
|
|
'user@test.com<img src=x onerror=alert(1)>', |
|
|
|
'test@evil.com<div onclick="alert(2)">click</div>', |
|
|
|
'admin@site.com<script>document.cookie="stolen"</script>', |
|
|
|
'normal@email.com', |
|
|
|
] |
|
|
|
|
|
|
|
testCases.forEach((testEmail, index) => { |
|
|
|
const { container } = render(<SecureCheckCodeComponent email={testEmail} />) |
|
|
|
|
|
|
|
const strongElement = container.querySelector('strong') |
|
|
|
const scriptElements = container.querySelectorAll('script') |
|
|
|
const imgElements = container.querySelectorAll('img') |
|
|
|
const divElements = container.querySelectorAll('div:not([data-testid])') |
|
|
|
|
|
|
|
console.log(`\n📧 Test Case ${index + 1}: ${testEmail.substring(0, 20)}...`) |
|
|
|
console.log(` - Script elements: ${scriptElements.length}`) |
|
|
|
console.log(` - Img elements: ${imgElements.length}`) |
|
|
|
console.log(` - Malicious divs: ${divElements.length - 1}`) // -1 for container div |
|
|
|
console.log(` - Text content: ${strongElement?.textContent === testEmail ? 'SAFE' : 'ISSUE'}`) |
|
|
|
|
|
|
|
// All should be safe |
|
|
|
expect(scriptElements).toHaveLength(0) |
|
|
|
expect(imgElements).toHaveLength(0) |
|
|
|
expect(strongElement?.textContent).toBe(testEmail) |
|
|
|
}) |
|
|
|
|
|
|
|
console.log('\n✅ All test cases passed - secure rendering confirmed') |
|
|
|
}) |
|
|
|
|
|
|
|
it('should validate the translation structure is secure', () => { |
|
|
|
console.log('\n🔍 Translation Security Analysis') |
|
|
|
console.log('=================================') |
|
|
|
|
|
|
|
const { t } = require('react-i18next').useTranslation() |
|
|
|
const prefix = t('login.checkCode.tipsPrefix') |
|
|
|
|
|
|
|
console.log('- Translation key used: login.checkCode.tipsPrefix') |
|
|
|
console.log('- Translation value:', prefix) |
|
|
|
console.log('- Contains HTML tags:', prefix.includes('<')) |
|
|
|
console.log('- Pure text content:', !prefix.includes('<') && !prefix.includes('>')) |
|
|
|
|
|
|
|
// Verify translation is plain text |
|
|
|
expect(prefix).toBe('We send a verification code to ') |
|
|
|
expect(prefix).not.toContain('<') |
|
|
|
expect(prefix).not.toContain('>') |
|
|
|
expect(typeof prefix).toBe('string') |
|
|
|
|
|
|
|
console.log('\n✅ Translation structure is secure - no HTML content') |
|
|
|
}) |
|
|
|
|
|
|
|
it('should confirm React automatic escaping works correctly', () => { |
|
|
|
console.log('\n⚡ React Security Mechanism Test') |
|
|
|
console.log('=================================') |
|
|
|
|
|
|
|
// Test React's automatic escaping with various inputs |
|
|
|
const dangerousInputs = [ |
|
|
|
'<script>alert("xss")</script>', |
|
|
|
'<img src="x" onerror="alert(1)">', |
|
|
|
'"><script>alert(2)</script>', |
|
|
|
'\'>alert(3)</script>', |
|
|
|
'<div onclick="alert(4)">click</div>', |
|
|
|
] |
|
|
|
|
|
|
|
dangerousInputs.forEach((input, index) => { |
|
|
|
const TestComponent = () => <strong>{input}</strong> |
|
|
|
const { container } = render(<TestComponent />) |
|
|
|
|
|
|
|
const strongElement = container.querySelector('strong') |
|
|
|
const scriptElements = container.querySelectorAll('script') |
|
|
|
|
|
|
|
console.log(`\n🧪 Input ${index + 1}: ${input.substring(0, 30)}...`) |
|
|
|
console.log(` - Rendered as text: ${strongElement?.textContent === input}`) |
|
|
|
console.log(` - No script execution: ${scriptElements.length === 0}`) |
|
|
|
|
|
|
|
expect(strongElement?.textContent).toBe(input) |
|
|
|
expect(scriptElements).toHaveLength(0) |
|
|
|
}) |
|
|
|
|
|
|
|
console.log('\n🛡️ React automatic escaping is working perfectly') |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
export {} |