mirror of https://github.com/portainer/portainer
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
parent
fe8f50512c
commit
fce885901f
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue