fix(custom-templates): XSS issue in Custom Template Note <EE-1054> (#5766)

fix(custom-templates): XSS issue in Custom Template Note <EE-1054> (#5766)
pull/5789/head
Marcelo Rydel 2021-09-29 16:47:39 -03:00 committed by GitHub
parent fe8f50512c
commit fce885901f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 40 additions and 8 deletions

View File

@ -3,6 +3,7 @@ package customtemplates
import ( import (
"errors" "errors"
"net/http" "net/http"
"regexp"
"strconv" "strconv"
"github.com/asaskevich/govalidator" "github.com/asaskevich/govalidator"
@ -129,9 +130,20 @@ func (payload *customTemplateFromFileContentPayload) Validate(r *http.Request) e
if payload.Type != portainer.KubernetesStack && payload.Type != portainer.DockerSwarmStack && payload.Type != portainer.DockerComposeStack { if payload.Type != portainer.KubernetesStack && payload.Type != portainer.DockerSwarmStack && payload.Type != portainer.DockerComposeStack {
return errors.New("Invalid custom template type") return errors.New("Invalid custom template type")
} }
if !isValidNote(payload.Note) {
return errors.New("Invalid note. <img> tag is not supported")
}
return nil return nil
} }
func isValidNote(note string) bool {
if govalidator.IsNull(note) {
return true
}
match, _ := regexp.MatchString("<img", note)
return !match
}
func (handler *Handler) createCustomTemplateFromFileContent(r *http.Request) (*portainer.CustomTemplate, error) { func (handler *Handler) createCustomTemplateFromFileContent(r *http.Request) (*portainer.CustomTemplate, error) {
var payload customTemplateFromFileContentPayload var payload customTemplateFromFileContentPayload
err := request.DecodeAndValidateJSONPayload(r, &payload) err := request.DecodeAndValidateJSONPayload(r, &payload)
@ -218,6 +230,9 @@ func (payload *customTemplateFromGitRepositoryPayload) Validate(r *http.Request)
if payload.Type != portainer.DockerSwarmStack && payload.Type != portainer.DockerComposeStack { if payload.Type != portainer.DockerSwarmStack && payload.Type != portainer.DockerComposeStack {
return errors.New("Invalid custom template type") return errors.New("Invalid custom template type")
} }
if !isValidNote(payload.Note) {
return errors.New("Invalid note. <img> tag is not supported")
}
return nil return nil
} }
@ -285,6 +300,9 @@ func (payload *customTemplateFromFileUploadPayload) Validate(r *http.Request) er
payload.Logo = logo payload.Logo = logo
note, _ := request.RetrieveMultiPartFormValue(r, "Note", true) note, _ := request.RetrieveMultiPartFormValue(r, "Note", true)
if !isValidNote(note) {
return errors.New("Invalid note. <img> tag is not supported")
}
payload.Note = note payload.Note = note
typeNumeral, _ := request.RetrieveNumericMultiPartFormValue(r, "Type", true) typeNumeral, _ := request.RetrieveNumericMultiPartFormValue(r, "Type", true)

View File

@ -51,6 +51,9 @@ func (payload *customTemplateUpdatePayload) Validate(r *http.Request) error {
if govalidator.IsNull(payload.Description) { if govalidator.IsNull(payload.Description) {
return errors.New("Invalid custom template description") return errors.New("Invalid custom template description")
} }
if !isValidNote(payload.Note) {
return errors.New("Invalid note. <img> tag is not supported")
}
return nil return nil
} }

View File

@ -1,4 +1,6 @@
import _ from 'lodash-es';
export function TagViewModel(data) { export function TagViewModel(data) {
this.Id = data.ID; this.Id = data.ID;
this.Name = data.Name; this.Name = _.escape(data.Name);
} }

View File

@ -1,5 +1,7 @@
import _ from 'lodash-es';
export function TeamViewModel(data) { export function TeamViewModel(data) {
this.Id = data.Id; this.Id = data.Id;
this.Name = data.Name; this.Name = _.escape(data.Name);
this.Checked = false; this.Checked = false;
} }

View File

@ -3,15 +3,21 @@ import angular from 'angular';
angular.module('portainer.app').factory('CustomTemplateService', CustomTemplateServiceFactory); angular.module('portainer.app').factory('CustomTemplateService', CustomTemplateServiceFactory);
/* @ngInject */ /* @ngInject */
function CustomTemplateServiceFactory(CustomTemplates, FileUploadService) { function CustomTemplateServiceFactory($sanitize, CustomTemplates, FileUploadService) {
var service = {}; var service = {};
service.customTemplate = function customTemplate(id) { service.customTemplate = function customTemplate(id) {
return CustomTemplates.get({ id }).$promise; return CustomTemplates.get({ id }).$promise;
}; };
service.customTemplates = function customTemplates(type) { service.customTemplates = async function customTemplates(type) {
return CustomTemplates.query({ type }).$promise; const templates = await CustomTemplates.query({ type }).$promise;
templates.forEach((template) => {
if (template.Note) {
template.Note = $('<p>').html($sanitize(template.Note)).find('img').remove().end().html();
}
});
return templates;
}; };
service.remove = function remove(id) { service.remove = function remove(id) {

View File

@ -1,3 +1,4 @@
import _ from 'lodash-es';
import toastr from 'toastr'; import toastr from 'toastr';
angular.module('portainer.app').factory('Notifications', [ angular.module('portainer.app').factory('Notifications', [
@ -7,11 +8,11 @@ angular.module('portainer.app').factory('Notifications', [
var service = {}; var service = {};
service.success = function (title, text) { service.success = function (title, text) {
toastr.success($sanitize(text), $sanitize(title)); toastr.success($sanitize(_.escape(text)), $sanitize(title));
}; };
service.warning = function (title, text) { service.warning = function (title, text) {
toastr.warning($sanitize(text), $sanitize(title), { timeOut: 6000 }); toastr.warning($sanitize(_.escape(text)), $sanitize(title), { timeOut: 6000 });
}; };
service.error = function (title, e, fallbackText) { service.error = function (title, e, fallbackText) {
@ -44,7 +45,7 @@ angular.module('portainer.app').factory('Notifications', [
console.error(e); console.error(e);
if (msg !== 'Invalid JWT token') { if (msg !== 'Invalid JWT token') {
toastr.error($sanitize(msg), $sanitize(title), { timeOut: 6000 }); toastr.error($sanitize(_.escape(msg)), $sanitize(title), { timeOut: 6000 });
} }
}; };