1
0
mirror of https://git.yoctoproject.org/poky synced 2026-05-30 00:20:08 +00:00

bitbake: toastergui: fix XSS injection points in projects page

We close XSS injection points in Projects page.

* modify the json filter to properly escape HTML tags in strings
* enable $sanitize to automatically sanitize dangerous HTML in
user-supplied input
* clean dangerous characters in targets field, as that field contents
will be directly passed to a shell command

Based on the vulnerability discovered and the patch provided by Michael Wood.

(Bitbake rev: 23c440db9c076ca37e651bdbbdbefee54998e1dc)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Alexandru DAMIAN
2014-11-11 17:01:09 +00:00
committed by Richard Purdie
parent 326d5b1a28
commit c5d19aae55
4 changed files with 43 additions and 34 deletions
@@ -101,7 +101,7 @@ function _diffArrays(existingArray, newArray, compareElements, onAdded, onDelete
} }
var projectApp = angular.module('project', ['ngCookies', 'ngAnimate', 'ui.bootstrap' ], angular_formpost); var projectApp = angular.module('project', ['ngCookies', 'ngAnimate', 'ui.bootstrap', 'ngRoute', 'ngSanitize'], angular_formpost);
// modify the template tag markers to prevent conflicts with Django // modify the template tag markers to prevent conflicts with Django
projectApp.config(function($interpolateProvider) { projectApp.config(function($interpolateProvider) {
@@ -128,7 +128,7 @@ projectApp.filter('timediff', function() {
// main controller for the project page // main controller for the project page
projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $location, $cookies, $q, $sce, $anchorScroll, $animate) { projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $location, $cookies, $q, $sce, $anchorScroll, $animate, $sanitize) {
$scope.getSuggestions = function(type, currentValue) { $scope.getSuggestions = function(type, currentValue) {
var deffered = $q.defer(); var deffered = $q.defer();
@@ -475,6 +475,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
var alertText = undefined; var alertText = undefined;
var alertZone = undefined; var alertZone = undefined;
var oldLayers = []; var oldLayers = [];
switch(elementid) { switch(elementid) {
case '#select-machine': case '#select-machine':
alertText = "You have changed the machine to: <strong>" + $scope.machineName + "</strong>"; alertText = "You have changed the machine to: <strong>" + $scope.machineName + "</strong>";
@@ -594,7 +595,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
var crtid = zone.maxid ++; var crtid = zone.maxid ++;
angular.forEach(zone, function (o) { o.close() }); angular.forEach(zone, function (o) { o.close() });
o = { o = {
id: crtid, text: $sce.trustAsHtml(text), type: type, id: crtid, text: text, type: type,
close: function() { close: function() {
zone.splice((function(id){ for (var i = 0; i < zone.length; i++) if (id == zone[i].id) { return i}; return undefined;})(crtid), 1); zone.splice((function(id){ for (var i = 0; i < zone.length; i++) if (id == zone[i].id) { return i}; return undefined;})(crtid), 1);
}, },
@@ -11,6 +11,8 @@ vim: expandtab tabstop=2
<script src="{% static "js/angular.min.js" %}"></script> <script src="{% static "js/angular.min.js" %}"></script>
<script src="{% static "js/angular-animate.min.js" %}"></script> <script src="{% static "js/angular-animate.min.js" %}"></script>
<script src="{% static "js/angular-cookies.min.js" %}"></script> <script src="{% static "js/angular-cookies.min.js" %}"></script>
<script src="{% static "js/angular-route.min.js" %}"></script>
<script src="{% static "js/angular-sanitize.min.js" %}"></script>
<script src="{% static "js/ui-bootstrap-tpls-0.11.0.js" %}"></script> <script src="{% static "js/ui-bootstrap-tpls-0.11.0.js" %}"></script>
@@ -365,13 +367,13 @@ angular.element(document).ready(function() {
scope.urls.layers = "{% url 'layers' %}"; scope.urls.layers = "{% url 'layers' %}";
scope.urls.targets = "{% url 'targets' %}"; scope.urls.targets = "{% url 'targets' %}";
scope.urls.importlayer = "{% url 'importlayer'%}" scope.urls.importlayer = "{% url 'importlayer'%}"
scope.project = {{prj|safe}}; scope.project = {{prj|json}};
scope.builds = {{builds|safe}}; scope.builds = {{builds|json}};
scope.layers = {{layers|safe}}; scope.layers = {{layers|json}};
scope.targets = {{targets|safe}}; scope.targets = {{targets|json}};
scope.frequenttargets = {{freqtargets|safe}}; scope.frequenttargets = {{freqtargets|json}};
scope.machine = {{machine|safe}}; scope.machine = {{machine|json}};
scope.releases = {{releases|safe}}; scope.releases = {{releases|json}};
var now = (new Date()).getTime(); var now = (new Date()).getTime();
scope.todaydate = now - (now % 86400000); scope.todaydate = now - (now % 86400000);
@@ -25,6 +25,7 @@ from django import template
from django.utils import timezone from django.utils import timezone
from django.template.defaultfilters import filesizeformat from django.template.defaultfilters import filesizeformat
import json as JsonLib import json as JsonLib
from django.utils.safestring import mark_safe
register = template.Library() register = template.Library()
@@ -49,7 +50,10 @@ def mapselect(value, argument):
@register.filter(name = "json") @register.filter(name = "json")
def json(value): def json(value):
return JsonLib.dumps(value) # JSON spec says that "\/" is functionally identical to "/" to allow for HTML-tag embedding in JSON strings
# unfortunately, I can't find any option in the json module to turn on forward-slash escaping, so we do
# it manually here
return mark_safe(JsonLib.dumps(value, ensure_ascii=False).replace('</', '<\\/'))
@register.assignment_tag @register.assignment_tag
def query(qs, **kwargs): def query(qs, **kwargs):
+25 -23
View File
@@ -36,6 +36,7 @@ from django.utils import timezone
from django.utils.html import escape from django.utils.html import escape
from datetime import timedelta from datetime import timedelta
from django.utils import formats from django.utils import formats
from toastergui.templatetags.projecttags import json as jsonfilter
import json import json
@@ -871,7 +872,7 @@ def _get_dir_entries(build_id, target_id, start):
# sort by directories first, then by name # sort by directories first, then by name
rsorted = sorted(response, key=lambda entry : entry['name']) rsorted = sorted(response, key=lambda entry : entry['name'])
rsorted = sorted(rsorted, key=lambda entry : entry['isdir'], reverse=True) rsorted = sorted(rsorted, key=lambda entry : entry['isdir'], reverse=True)
return json.dumps(rsorted, cls=LazyEncoder) return json.dumps(rsorted, cls=LazyEncoder).replace('</', '<\\/')
def dirinfo(request, build_id, target_id, file_path=None): def dirinfo(request, build_id, target_id, file_path=None):
template = "dirinfo.html" template = "dirinfo.html"
@@ -1981,25 +1982,25 @@ if toastermain.settings.MANAGED:
context = { context = {
"project" : prj, "project" : prj,
"completedbuilds": Build.objects.filter(project = prj).exclude(outcome = Build.IN_PROGRESS), "completedbuilds": Build.objects.filter(project = prj).exclude(outcome = Build.IN_PROGRESS),
"prj" : json.dumps({"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name, "desc": prj.release.description}}), "prj" : {"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name, "desc": prj.release.description}},
#"buildrequests" : prj.buildrequest_set.filter(state=BuildRequest.REQ_QUEUED), #"buildrequests" : prj.buildrequest_set.filter(state=BuildRequest.REQ_QUEUED),
"builds" : json.dumps(_project_recent_build_list(prj)), "builds" : _project_recent_build_list(prj),
"layers" : json.dumps(map(lambda x: { "layers" : map(lambda x: {
"id": x.layercommit.pk, "id": x.layercommit.pk,
"orderid": x.pk, "orderid": x.pk,
"name" : x.layercommit.layer.name, "name" : x.layercommit.layer.name,
"url": x.layercommit.layer.layer_index_url, "url": x.layercommit.layer.layer_index_url,
"layerdetailurl": reverse("layerdetails", args=(x.layercommit.layer.pk,)), "layerdetailurl": reverse("layerdetails", args=(x.layercommit.layer.pk,)),
"branch" : { "name" : x.layercommit.up_branch.name, "layersource" : x.layercommit.up_branch.layer_source.name}}, "branch" : { "name" : x.layercommit.up_branch.name, "layersource" : x.layercommit.up_branch.layer_source.name}},
prj.projectlayer_set.all().order_by("id"))), prj.projectlayer_set.all().order_by("id")),
"targets" : json.dumps(map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all())), "targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()),
"freqtargets": json.dumps(freqtargets), "freqtargets": freqtargets,
"releases": json.dumps(map(lambda x: {"id": x.pk, "name": x.name}, Release.objects.all())), "releases": map(lambda x: {"id": x.pk, "name": x.name}, Release.objects.all()),
} }
try: try:
context["machine"] = json.dumps({"name": prj.projectvariable_set.get(name="MACHINE").value}) context["machine"] = {"name": prj.projectvariable_set.get(name="MACHINE").value}
except ProjectVariable.DoesNotExist: except ProjectVariable.DoesNotExist:
context["machine"] = json.dumps(None) context["machine"] = None
try: try:
context["distro"] = prj.projectvariable_set.get(name="DISTRO").value context["distro"] = prj.projectvariable_set.get(name="DISTRO").value
except ProjectVariable.DoesNotExist: except ProjectVariable.DoesNotExist:
@@ -2035,7 +2036,8 @@ if toastermain.settings.MANAGED:
if 'targets' in request.POST: if 'targets' in request.POST:
ProjectTarget.objects.filter(project = prj).delete() ProjectTarget.objects.filter(project = prj).delete()
for t in request.POST['targets'].strip().split(" "): s = str(request.POST['targets'])
for t in s.translate(None, ";%|\"").split(" "):
if ":" in t: if ":" in t:
target, task = t.split(":") target, task = t.split(":")
else: else:
@@ -2045,11 +2047,11 @@ if toastermain.settings.MANAGED:
br = prj.schedule_build() br = prj.schedule_build()
return HttpResponse(json.dumps({"error":"ok", return HttpResponse(jsonfilter({"error":"ok",
"builds" : _project_recent_build_list(prj), "builds" : _project_recent_build_list(prj),
}), content_type = "application/json") }), content_type = "application/json")
except Exception as e: except Exception as e:
return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
def xhr_projectedit(request, pid): def xhr_projectedit(request, pid):
try: try:
@@ -2088,7 +2090,7 @@ if toastermain.settings.MANAGED:
machinevar.save() machinevar.save()
# return all project settings # return all project settings
return HttpResponse(json.dumps( { return HttpResponse(jsonfilter( {
"error": "ok", "error": "ok",
"layers" : map(lambda x: {"id": x.layercommit.pk, "orderid" : x.pk, "name" : x.layercommit.layer.name, "url": x.layercommit.layer.layer_index_url, "layerdetailurl": reverse("layerdetails", args=(x.layercommit.layer.pk,)), "branch" : { "name" : x.layercommit.up_branch.name, "layersource" : x.layercommit.up_branch.layer_source.name}}, prj.projectlayer_set.all().order_by("id")), "layers" : map(lambda x: {"id": x.layercommit.pk, "orderid" : x.pk, "name" : x.layercommit.layer.name, "url": x.layercommit.layer.layer_index_url, "layerdetailurl": reverse("layerdetails", args=(x.layercommit.layer.pk,)), "branch" : { "name" : x.layercommit.up_branch.name, "layersource" : x.layercommit.up_branch.layer_source.name}}, prj.projectlayer_set.all().order_by("id")),
"builds" : _project_recent_build_list(prj), "builds" : _project_recent_build_list(prj),
@@ -2098,7 +2100,7 @@ if toastermain.settings.MANAGED:
}), content_type = "application/json") }), content_type = "application/json")
except Exception as e: except Exception as e:
return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
@@ -2112,7 +2114,7 @@ if toastermain.settings.MANAGED:
prj = Project.objects.get(pk = request.session['project_id']) prj = Project.objects.get(pk = request.session['project_id'])
queryset_all = queryset_all.filter(up_branch__release = prj.release).exclude(pk__in = map(lambda x: x.layercommit_id, prj.projectlayer_set.all())) queryset_all = queryset_all.filter(up_branch__release = prj.release).exclude(pk__in = map(lambda x: x.layercommit_id, prj.projectlayer_set.all()))
queryset_all = queryset_all.filter(layer__name__icontains=request.GET.get('value','')) queryset_all = queryset_all.filter(layer__name__icontains=request.GET.get('value',''))
return HttpResponse(json.dumps( { "error":"ok", return HttpResponse(jsonfilter( { "error":"ok",
"list" : map( lambda x: {"id": x.pk, "name": x.layer.name, "detail": "(" + x.layer.layer_source.name + (")" if x.up_branch == None else " | "+x.up_branch.name+")")}, "list" : map( lambda x: {"id": x.pk, "name": x.layer.name, "detail": "(" + x.layer.layer_source.name + (")" if x.up_branch == None else " | "+x.up_branch.name+")")},
queryset_all[:8]) queryset_all[:8])
}), content_type = "application/json") }), content_type = "application/json")
@@ -2127,7 +2129,7 @@ if toastermain.settings.MANAGED:
queryset_all.order_by("-up_id"); queryset_all.order_by("-up_id");
return HttpResponse(json.dumps( { "error":"ok", return HttpResponse(jsonfilter( { "error":"ok",
"list" : map( "list" : map(
lambda x: {"id": x.pk, "name": x.layer.name, "detail": "(" + x.layer.layer_source.name + (")" if x.up_branch == None else " | "+x.up_branch.name+")"), lambda x: {"id": x.pk, "name": x.layer.name, "detail": "(" + x.layer.layer_source.name + (")" if x.up_branch == None else " | "+x.up_branch.name+")"),
"layerdetailurl" : reverse('layerdetails', args=(x.pk,))}, "layerdetailurl" : reverse('layerdetails', args=(x.pk,))},
@@ -2146,7 +2148,7 @@ if toastermain.settings.MANAGED:
if lv.count() != 1: # there is no layer_version with the new release id, and the same name if lv.count() != 1: # there is no layer_version with the new release id, and the same name
retval.append(i) retval.append(i)
return HttpResponse(json.dumps( {"error":"ok", return HttpResponse(jsonfilter( {"error":"ok",
"list": map( "list": map(
lambda x: {"id": x.layercommit.pk, "name": x.layercommit.layer.name, "detail": "(" + x.layercommit.layer.layer_source.name + (")" if x.layercommit.up_branch == None else " | "+x.layercommit.up_branch.name+")")}, lambda x: {"id": x.layercommit.pk, "name": x.layercommit.layer.name, "detail": "(" + x.layercommit.layer.layer_source.name + (")" if x.layercommit.up_branch == None else " | "+x.layercommit.up_branch.name+")")},
retval) }), content_type = "application/json") retval) }), content_type = "application/json")
@@ -2156,7 +2158,7 @@ if toastermain.settings.MANAGED:
queryset_all = Recipe.objects.all() queryset_all = Recipe.objects.all()
if 'project_id' in request.session: if 'project_id' in request.session:
queryset_all = queryset_all.filter(layer_version__layer__in = map(lambda x: x.layercommit.layer, ProjectLayer.objects.filter(project_id=request.session['project_id']))) queryset_all = queryset_all.filter(layer_version__layer__in = map(lambda x: x.layercommit.layer, ProjectLayer.objects.filter(project_id=request.session['project_id'])))
return HttpResponse(json.dumps({ "error":"ok", return HttpResponse(jsonfilter({ "error":"ok",
"list" : map ( lambda x: {"id": x.pk, "name": x.name, "detail":"[" + x.layer_version.layer.name+ (" | " + x.layer_version.up_branch.name + "]" if x.layer_version.up_branch is not None else "]")}, "list" : map ( lambda x: {"id": x.pk, "name": x.name, "detail":"[" + x.layer_version.layer.name+ (" | " + x.layer_version.up_branch.name + "]" if x.layer_version.up_branch is not None else "]")},
queryset_all.filter(name__icontains=request.GET.get('value',''))[:8]), queryset_all.filter(name__icontains=request.GET.get('value',''))[:8]),
@@ -2166,7 +2168,7 @@ if toastermain.settings.MANAGED:
queryset_all = Machine.objects.all() queryset_all = Machine.objects.all()
if 'project_id' in request.session: if 'project_id' in request.session:
queryset_all = queryset_all.filter(layer_version__layer__in = map(lambda x: x.layercommit.layer, ProjectLayer.objects.filter(project_id=request.session['project_id']))) queryset_all = queryset_all.filter(layer_version__layer__in = map(lambda x: x.layercommit.layer, ProjectLayer.objects.filter(project_id=request.session['project_id'])))
return HttpResponse(json.dumps({ "error":"ok", return HttpResponse(jsonfilter({ "error":"ok",
"list" : map ( lambda x: {"id": x.pk, "name": x.name, "detail":"[" + x.layer_version.layer.name+ (" | " + x.layer_version.up_branch.name + "]" if x.layer_version.up_branch is not None else "]")}, "list" : map ( lambda x: {"id": x.pk, "name": x.name, "detail":"[" + x.layer_version.layer.name+ (" | " + x.layer_version.up_branch.name + "]" if x.layer_version.up_branch is not None else "]")},
queryset_all.filter(name__icontains=request.GET.get('value',''))[:8]), queryset_all.filter(name__icontains=request.GET.get('value',''))[:8]),
@@ -2174,7 +2176,7 @@ if toastermain.settings.MANAGED:
raise Exception("Unknown request! " + request.GET.get('type', "No parameter supplied")) raise Exception("Unknown request! " + request.GET.get('type', "No parameter supplied"))
except Exception as e: except Exception as e:
return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
@@ -2216,7 +2218,7 @@ if toastermain.settings.MANAGED:
context = { context = {
'projectlayerset' : json.dumps(map(lambda x: x.layercommit.id, prj.projectlayer_set.all())), 'projectlayerset' : jsonfilter(map(lambda x: x.layercommit.id, prj.projectlayer_set.all())),
'objects' : layer_info, 'objects' : layer_info,
'objectname' : "layers", 'objectname' : "layers",
'default_orderby' : 'layer__name:+', 'default_orderby' : 'layer__name:+',
@@ -2309,7 +2311,7 @@ if toastermain.settings.MANAGED:
context = { context = {
'projectlayerset' : json.dumps(map(lambda x: x.layercommit.id, prj.projectlayer_set.all())), 'projectlayerset' : jsonfilter(map(lambda x: x.layercommit.id, prj.projectlayer_set.all())),
'objects' : target_info, 'objects' : target_info,
'objectname' : "targets", 'objectname' : "targets",
'default_orderby' : 'name:+', 'default_orderby' : 'name:+',