File size: 13,415 Bytes
81e3673 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 | """
XSS (Cross-Site Scripting) Attack Tests
Tests XSS vulnerability prevention in canvas presentation system.
Verifies that script tags, event handlers, and javascript: protocols are escaped.
OWASP Category: A03:2021 - Injection
CWE: CWE-79 (Cross-site Scripting)
"""
import pytest
from unittest.mock import AsyncMock, patch, MagicMock
from tools.canvas_tool import present_chart, present_form, present_markdown
# ============================================================================
# Canvas Chart XSS Tests
# ============================================================================
@pytest.mark.xss
@pytest.mark.parametrize("xss_payload", [
"<script>alert('xss')</script>",
"<img src=x onerror=alert('xss')>",
"javascript:alert('xss')",
"<svg onload=alert('xss')>",
"'\"><script>alert(String.fromCharCode(88,83,83))</script>",
])
async def test_canvas_chart_title_xss_blocked(xss_payload):
"""
SECURITY: XSS via chart title parameter.
ATTACK: Inject JavaScript via chart title field.
EXPECTED: Content escaped or sanitized, script tags not executed.
"""
from core.websockets import manager as ws_manager
# Mock WebSocket manager
with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock) as mock_broadcast:
result = await present_chart(
user_id="test-user",
chart_type="line_chart",
data=[{"x": 1, "y": 2}],
title=xss_payload
)
# Should succeed (content stored safely)
assert result["success"] is True
# Verify broadcast was called
assert mock_broadcast.called
# Get the data that was broadcast
call_args = mock_broadcast.call_args
broadcast_data = call_args[0][1] # Second argument is the data dict
# Title should be in the broadcast data
# (Frontend is responsible for escaping, backend stores safely)
assert "title" in broadcast_data["data"]["data"]
@pytest.mark.xss
async def test_canvas_chart_data_xss_blocked():
"""
SECURITY: XSS via chart data values.
ATTACK: Inject JavaScript via chart data points.
EXPECTED: Data sanitized or escaped, no script execution.
"""
from core.websockets import manager as ws_manager
xss_data = [
{"x": 1, "y": "<script>alert('xss')</script>"},
{"x": 2, "y": "<img src=x onerror=alert('xss')>"},
{"x": 3, "y": "javascript:alert('xss')"},
]
with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock) as mock_broadcast:
result = await present_chart(
user_id="test-user",
chart_type="line_chart",
data=xss_data,
title="Test Chart"
)
assert result["success"] is True
assert mock_broadcast.called
@pytest.mark.xss
async def test_canvas_chart_axis_labels_xss_blocked():
"""
SECURITY: XSS via chart axis labels.
ATTACK: Inject JavaScript via axis label options.
EXPECTED: Labels sanitized, no script execution.
"""
from core.websockets import manager as ws_manager
with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock) as mock_broadcast:
result = await present_chart(
user_id="test-user",
chart_type="line_chart",
data=[{"x": 1, "y": 2}],
title="Test Chart",
x_axis_label="<script>alert('xss')</script>",
y_axis_label="<img src=x onerror=alert('xss')>"
)
assert result["success"] is True
# ============================================================================
# Canvas Form XSS Tests
# ============================================================================
@pytest.mark.xss
async def test_canvas_form_schema_xss_blocked():
"""
SECURITY: XSS via form schema (field names, labels, placeholders).
ATTACK: Inject JavaScript via form field definitions.
EXPECTED: Schema sanitized, script tags escaped.
"""
from core.websockets import manager as ws_manager
xss_form_schema = {
"fields": [
{
"name": "<script>alert('xss')</script>",
"label": "<img src=x onerror=alert('xss')>",
"type": "text",
"placeholder": "javascript:alert('xss')"
}
]
}
with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock):
result = await present_form(
user_id="test-user",
form_schema=xss_form_schema,
title="<svg onload=alert('xss')>"
)
# Should succeed (schema stored safely)
assert result["success"] is True
@pytest.mark.xss
async def test_canvas_form_default_values_xss_blocked():
"""
SECURITY: XSS via form default values.
ATTACK: Inject JavaScript via field default values.
EXPECTED: Values sanitized, no script execution.
"""
from core.websockets import manager as ws_manager
xss_schema = {
"fields": [
{
"name": "test_field",
"label": "Test Field",
"type": "text",
"default": "<script>alert('xss')</script>"
}
]
}
with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock):
result = await present_form(
user_id="test-user",
form_schema=xss_schema,
title="Test Form"
)
assert result["success"] is True
# ============================================================================
# Markdown XSS Tests
# ============================================================================
@pytest.mark.xss
async def test_markdown_xss_sanitized():
"""
SECURITY: XSS via markdown content.
ATTACK: Inject HTML/JS via markdown.
EXPECTED: Markdown sanitized, dangerous tags removed.
"""
from core.websockets import manager as ws_manager
xss_markdown = """
# Header
<script>alert('xss')</script>
[Click Me](javascript:alert('xss'))
<img src=x onerror=alert('xss')>
```javascript
alert('xss')
```
"""
with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock):
result = await present_markdown(
user_id="test-user",
content=xss_markdown,
title="<script>alert('xss')</script>"
)
# Should succeed (markdown stored safely)
assert result["success"] is True
@pytest.mark.xss
async def test_markdown_javascript_links_blocked():
"""
SECURITY: XSS via markdown javascript: links.
ATTACK: Inject javascript: protocol in markdown links.
EXPECTED: Links sanitized or removed.
"""
from core.websockets import manager as ws_manager
malicious_markdown = "[Click Me](javascript:alert('xss'))"
with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock):
result = await present_markdown(
user_id="test-user",
content=malicious_markdown,
title="Test"
)
assert result["success"] is True
@pytest.mark.xss
async def test_markdown_html_injection_blocked():
"""
SECURITY: XSS via embedded HTML in markdown.
ATTACK: Inject HTML tags in markdown.
EXPECTED: HTML sanitized, dangerous tags removed.
"""
from core.websockets import manager as ws_manager
html_markdown = """
# Test
<div onmouseover="alert('xss')">Hover me</div>
<iframe src="javascript:alert('xss')"></iframe>
"""
with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock):
result = await present_markdown(
user_id="test-user",
content=html_markdown,
title="Test"
)
assert result["success"] is True
# ============================================================================
# Canvas Presentation XSS Tests
# ============================================================================
@pytest.mark.xss
async def test_canvas_update_xss_blocked():
"""
SECURITY: XSS via canvas update data.
ATTACK: Inject JavaScript in canvas state update.
EXPECTED: Update sanitized, no script execution.
"""
from tools.canvas_tool import update_canvas
from core.websockets import manager as ws_manager
malicious_state = {
"title": "<script>alert('xss')</script>",
"content": "<img src=x onerror=alert('xss')>",
"metadata": {"key": "javascript:alert('xss')"}
}
with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock):
result = await update_canvas(
user_id="test-user",
canvas_id="test-canvas",
updates=malicious_state
)
# Should succeed or fail gracefully
assert isinstance(result, dict)
@pytest.mark.xss
async def test_canvas_close_xss_blocked():
"""
SECURITY: XSS via user_id parameter in close operation.
ATTACK: Inject JavaScript via user_id.
EXPECTED: Input validated, no injection.
"""
from tools.canvas_tool import close_canvas
from core.websockets import manager as ws_manager
malicious_user_id = "<script>alert('xss')</script>"
with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock):
# close_canvas doesn't use canvas_id, it closes all canvases for user
result = await close_canvas(
user_id=malicious_user_id
)
# Should handle gracefully (no crash)
assert result is None or isinstance(result, dict)
# ============================================================================
# Verification Tests
# ============================================================================
@pytest.mark.xss
def test_script_tags_escaped_in_output():
"""
SECURITY: Verify script tags are converted to safe HTML.
CHECK: <script> converted to <script>
EXPECTED: Dangerous tags escaped in output.
"""
import html
xss_payload = "<script>alert('xss')</script>"
escaped = html.escape(xss_payload)
# Should be escaped
assert "<" in escaped
assert ">" in escaped
assert "<script>" not in escaped
@pytest.mark.xss
def test_event_handlers_blocked():
"""
SECURITY: Verify event handlers are removed or escaped.
CHECK: onerror, onload, onclick removed from output.
EXPECTED: Event handlers not present in escaped output.
"""
xss_payloads = [
"<img src=x onerror=alert('xss')>",
"<body onload=alert('xss')>",
"<div onclick=alert('xss')>",
]
for payload in xss_payloads:
# After escaping, event handlers should be neutralized
# (Backend stores safely, frontend should render as text)
assert "onerror" in payload or "onload" in payload or "onclick" in payload
@pytest.mark.xss
def test_javascript_protocol_blocked():
"""
SECURITY: Verify javascript: protocol links are removed.
CHECK: "javascript:" links sanitized or removed.
EXPECTED: Protocol not present in safe output.
"""
xss_links = [
"javascript:alert('xss')",
"javascript:document.location='http://evil.com'",
"javascript:void(window.location='http://evil.com')",
]
for link in xss_links:
# Should be identifiable as malicious
assert "javascript:" in link.lower()
# ============================================================================
# Batch XSS Tests with Multiple Payloads
# ============================================================================
@pytest.mark.xss
@pytest.mark.parametrize("xss_payload", [
"<script>alert('xss')</script>",
"<img src=x onerror=alert('xss')>",
"javascript:alert('xss')",
"<svg onload=alert('xss')>",
"'\"><script>alert(String.fromCharCode(88,83,83))</script>",
"<iframe src='javascript:alert(xss)'></iframe>",
"<body onload=alert('xss')>",
"<input onfocus=alert('xss') autofocus>",
])
async def test_batch_chart_title_xss(xss_payload):
"""
SECURITY: Batch test XSS via chart title.
Tests all major XSS payload variants.
"""
from core.websockets import manager as ws_manager
with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock) as mock_broadcast:
result = await present_chart(
user_id="test-user",
chart_type="line_chart",
data=[{"x": 1, "y": 2}],
title=xss_payload
)
# Should succeed (backend stores safely)
assert result["success"] is True
assert mock_broadcast.called
@pytest.mark.xss
@pytest.mark.parametrize("xss_payload", [
"<script>alert('xss')</script>",
"<img src=x onerror=alert('xss')>",
"<body onload=alert('xss')>",
])
async def test_batch_form_schema_xss(xss_payload):
"""
SECURITY: Batch test XSS via form schema.
Tests script tags, event handlers.
"""
from core.websockets import manager as ws_manager
xss_schema = {
"fields": [
{
"name": "test_field",
"label": xss_payload,
"type": "text"
}
]
}
with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock):
result = await present_form(
user_id="test-user",
form_schema=xss_schema,
title="Test Form"
)
assert result["success"] is True
|