mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-04 23:45:07 +00:00
fix: sanitize output for email (#8373)
# Which Problems Are Solved
ZITADEL uses HTML for emails and renders certain information such as
usernames dynamically. That information can be entered by users or
administrators. Due to a missing output sanitization, these emails could
include malicious code.
This may potentially lead to a threat where an attacker, without
privileges, could send out altered notifications that are part of the
registration processes. An attacker could create a malicious link, where
the injected code would be rendered as part of the email.
During investigation of this issue a related issue was found and
mitigated, where on the user's detail page the username was not
sanitized and would also render HTML, giving an attacker the same
vulnerability.
While it was possible to inject HTML including javascript, the execution
of such scripts would be prevented by most email clients and the Content
Security Policy in Console UI.
# How the Problems Are Solved
- All arguments used for email are sanitized (`html.EscapeString`)
- The email text no longer `html.UnescapeString` (HTML in custom text is
still possible)
- Console no longer uses `[innerHtml]` to render the username
# Additional Changes
None
# Additional Context
- raised via email
---------
Co-authored-by: peintnermax <max@caos.ch>
(cherry picked from commit 189505c80f
)
This commit is contained in:
parent
e6d7f4af47
commit
4b59cac67b
@ -16,7 +16,7 @@
|
||||
></div>
|
||||
</div>
|
||||
<div class="cnsl-doc-row">
|
||||
<span class="cnsl-type" [innerHtml]="sub"></span>
|
||||
<span class="cnsl-type">{{ sub }}</span>
|
||||
<a *ngIf="docLink" mat-icon-button [href]="docLink" rel="noreferrer" target="_blank">
|
||||
<mat-icon class="icon">info_outline</mat-icon>
|
||||
</a>
|
||||
|
@ -82,7 +82,7 @@
|
||||
|
||||
.cnsl-type {
|
||||
font-size: 14px;
|
||||
margin-top: 2rem;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,7 @@
|
||||
title="{{ project?.projectName }}"
|
||||
[hasActions]="false"
|
||||
docLink="https://zitadel.com/docs/guides/manage/console/projects#what-is-a-granted-project"
|
||||
sub="{{ 'PROJECT.PAGES.TYPE.GRANTED_SINGULAR' | translate }} {{ 'ACTIONS.OF' | translate }} <strong>{{
|
||||
project?.projectOwnerName
|
||||
}}</strong>"
|
||||
sub="{{ 'PROJECT.PAGES.TYPE.GRANTED_SINGULAR' | translate: { name: project?.projectOwnerName } }}"
|
||||
[isActive]="project?.state === ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE"
|
||||
[isInactive]="project?.state === ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE"
|
||||
stateTooltip="{{ 'ORG.STATE.' + project?.state | translate }}"
|
||||
|
@ -1765,7 +1765,7 @@
|
||||
"TYPE": {
|
||||
"OWNED": "Притежавани проекти",
|
||||
"OWNED_SINGULAR": "Собствен проект",
|
||||
"GRANTED_SINGULAR": "Приет проект"
|
||||
"GRANTED_SINGULAR": "Отпуснат проект на {{name}}"
|
||||
},
|
||||
"PRIVATELABEL": {
|
||||
"0": {
|
||||
|
@ -1773,7 +1773,7 @@
|
||||
"TYPE": {
|
||||
"OWNED": "Vlastní projekty",
|
||||
"OWNED_SINGULAR": "Vlastní projekt",
|
||||
"GRANTED_SINGULAR": "Přidělený projekt"
|
||||
"GRANTED_SINGULAR": "Projekt přidělený {{name}}"
|
||||
},
|
||||
"PRIVATELABEL": {
|
||||
"TITLE": "Nastavení brandingu",
|
||||
|
@ -1771,7 +1771,7 @@
|
||||
"TYPE": {
|
||||
"OWNED": "Eigene Projekte",
|
||||
"OWNED_SINGULAR": "Eigenes Projekt",
|
||||
"GRANTED_SINGULAR": "Berechtigtes Projekt"
|
||||
"GRANTED_SINGULAR": "Berechtigtes Projekt von {{name}}"
|
||||
},
|
||||
"PRIVATELABEL": {
|
||||
"TITLE": "Branding Verhalten",
|
||||
|
@ -1772,7 +1772,7 @@
|
||||
"TYPE": {
|
||||
"OWNED": "Owned Projects",
|
||||
"OWNED_SINGULAR": "Owned Project",
|
||||
"GRANTED_SINGULAR": "Granted Project"
|
||||
"GRANTED_SINGULAR": "Granted Project of {{name}}"
|
||||
},
|
||||
"PRIVATELABEL": {
|
||||
"TITLE": "Branding Setting",
|
||||
|
@ -1773,7 +1773,7 @@
|
||||
"TYPE": {
|
||||
"OWNED": "Proyectos propios",
|
||||
"OWNED_SINGULAR": "Proyecto propio",
|
||||
"GRANTED_SINGULAR": "Proyecto concedido"
|
||||
"GRANTED_SINGULAR": "Proyecto asignado {{name}}"
|
||||
},
|
||||
"PRIVATELABEL": {
|
||||
"TITLE": "Ajustes de imagen de marca",
|
||||
|
@ -1770,8 +1770,8 @@
|
||||
"ZITADELPROJECT": "Ceci appartient au projet ZITADEL. Attention",
|
||||
"TYPE": {
|
||||
"OWNED": "Projets possédés",
|
||||
"OWNED_SINGULAR": "Projet propre",
|
||||
"GRANTED_SINGULAR": "Projet concédé"
|
||||
"OWNED_SINGULAR": "Projet possédé",
|
||||
"GRANTED_SINGULAR": "Projet attribué à {{name}}"
|
||||
},
|
||||
"PRIVATELABEL": {
|
||||
"TITLE": "Image de marque",
|
||||
|
@ -1771,7 +1771,7 @@
|
||||
"TYPE": {
|
||||
"OWNED": "Progetti proprietari",
|
||||
"OWNED_SINGULAR": "Progetto proprietario",
|
||||
"GRANTED_SINGULAR": "Progetto delegato"
|
||||
"GRANTED_SINGULAR": "Progetto concesso di {{name}}"
|
||||
},
|
||||
"PRIVATELABEL": {
|
||||
"TITLE": "Impostazione branding",
|
||||
|
@ -1767,7 +1767,7 @@
|
||||
"TYPE": {
|
||||
"OWNED": "所有プロジェクト",
|
||||
"OWNED_SINGULAR": "所有プロジェクト",
|
||||
"GRANTED_SINGULAR": "グラントされたプロジェクト"
|
||||
"GRANTED_SINGULAR": "{{name}}に付与されたプロジェクト"
|
||||
},
|
||||
"PRIVATELABEL": {
|
||||
"TITLE": "ブランディング設定",
|
||||
|
@ -1773,7 +1773,7 @@
|
||||
"TYPE": {
|
||||
"OWNED": "Сопствени проекти",
|
||||
"OWNED_SINGULAR": "Сопствен проект",
|
||||
"GRANTED_SINGULAR": "Доделен проект"
|
||||
"GRANTED_SINGULAR": "Проект доделен на {{name}}"
|
||||
},
|
||||
"PRIVATELABEL": {
|
||||
"TITLE": "Подесувања за брендирање",
|
||||
|
@ -1772,7 +1772,7 @@
|
||||
"TYPE": {
|
||||
"OWNED": "Eigen Projecten",
|
||||
"OWNED_SINGULAR": "Eigen Project",
|
||||
"GRANTED_SINGULAR": "Verleend Project"
|
||||
"GRANTED_SINGULAR": "Project toegewezen {{name}}"
|
||||
},
|
||||
"PRIVATELABEL": {
|
||||
"TITLE": "Branding Instelling",
|
||||
|
@ -1771,7 +1771,7 @@
|
||||
"TYPE": {
|
||||
"OWNED": "Własne Projekty",
|
||||
"OWNED_SINGULAR": "Własny Projekt",
|
||||
"GRANTED_SINGULAR": "Udzielony Projekt"
|
||||
"GRANTED_SINGULAR": "Projekt przydzielony {{name}}"
|
||||
},
|
||||
"PRIVATELABEL": {
|
||||
"TITLE": "Ustawienia marka",
|
||||
|
@ -1771,7 +1771,7 @@
|
||||
"TYPE": {
|
||||
"OWNED": "Projetos Próprios",
|
||||
"OWNED_SINGULAR": "Projeto Próprio",
|
||||
"GRANTED_SINGULAR": "Projeto Concedido"
|
||||
"GRANTED_SINGULAR": "Projeto atribuído a {{name}}"
|
||||
},
|
||||
"PRIVATELABEL": {
|
||||
"TITLE": "Configuração de Marca",
|
||||
|
@ -1853,9 +1853,8 @@
|
||||
"ZITADELPROJECT": "Это принадлежит проекту ZITADEL. Осторожно: Если вы внесёте изменения, ZITADEL может вести себя не так, как предполагалось.",
|
||||
"TYPE": {
|
||||
"OWNED": "Собственные проекты",
|
||||
"GRANTED": "Проекты доступа",
|
||||
"OWNED_SINGULAR": "Собственный проект",
|
||||
"GRANTED_SINGULAR": "Допуск проекта"
|
||||
"GRANTED_SINGULAR": "Проект, предоставленный {{name}}"
|
||||
},
|
||||
"PRIVATELABEL": {
|
||||
"TITLE": "Настройка брендинга",
|
||||
|
@ -1770,7 +1770,7 @@
|
||||
"TYPE": {
|
||||
"OWNED": "拥有的项目",
|
||||
"OWNED_SINGULAR": "拥有项目",
|
||||
"GRANTED_SINGULAR": "被授予的项目"
|
||||
"GRANTED_SINGULAR": "授予{{name}}的项目"
|
||||
},
|
||||
"PRIVATELABEL": {
|
||||
"TITLE": "品牌标识设置",
|
||||
|
@ -2,7 +2,6 @@ package templates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/i18n"
|
||||
@ -40,7 +39,7 @@ func (data *TemplateData) Translate(translator *i18n.Translator, msgType string,
|
||||
data.PreHeader = translator.Localize(fmt.Sprintf("%s.%s", msgType, domain.MessagePreHeader), args, langs...)
|
||||
data.Subject = translator.Localize(fmt.Sprintf("%s.%s", msgType, domain.MessageSubject), args, langs...)
|
||||
data.Greeting = translator.Localize(fmt.Sprintf("%s.%s", msgType, domain.MessageGreeting), args, langs...)
|
||||
data.Text = html.UnescapeString(translator.Localize(fmt.Sprintf("%s.%s", msgType, domain.MessageText), args, langs...))
|
||||
data.Text = translator.Localize(fmt.Sprintf("%s.%s", msgType, domain.MessageText), args, langs...)
|
||||
data.ButtonText = translator.Localize(fmt.Sprintf("%s.%s", msgType, domain.MessageButtonText), args, langs...)
|
||||
// Footer text is neither included in i18n files nor defaults.yaml
|
||||
footerText := fmt.Sprintf("%s.%s", msgType, domain.MessageFooterText)
|
||||
|
@ -2,7 +2,9 @@ package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"html"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/i18n"
|
||||
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
|
||||
@ -42,6 +44,7 @@ func SendEmail(
|
||||
allowUnverifiedNotificationChannel bool,
|
||||
) error {
|
||||
args = mapNotifyUserToArgs(user, args)
|
||||
sanitizeArgsForHTML(args)
|
||||
data := GetTemplateData(ctx, translator, args, url, messageType, user.PreferredLanguage.String(), colors)
|
||||
template, err := templates.GetParsedTemplate(mailhtml, data)
|
||||
if err != nil {
|
||||
@ -59,6 +62,23 @@ func SendEmail(
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeArgsForHTML(args map[string]any) {
|
||||
for key, arg := range args {
|
||||
switch a := arg.(type) {
|
||||
case string:
|
||||
args[key] = html.EscapeString(a)
|
||||
case []string:
|
||||
for i, s := range a {
|
||||
a[i] = html.EscapeString(s)
|
||||
}
|
||||
case database.TextArray[string]:
|
||||
for i, s := range a {
|
||||
a[i] = html.EscapeString(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SendSMSTwilio(
|
||||
ctx context.Context,
|
||||
channels ChannelChains,
|
||||
|
Loading…
Reference in New Issue
Block a user