diff options
author | Alexandru DAMIAN <alexandru.damian@intel.com> | 2014-11-11 17:01:09 +0000 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2014-11-12 17:04:50 +0000 |
commit | c5d19aae55be158676eb0914bd5d0701f7d3fd3a (patch) | |
tree | b549631196198eaa89a922c1088243b25c74ecd9 /bitbake/lib/toaster/toastergui | |
parent | 326d5b1a284ca4d29f986d3d6a1cee838b841301 (diff) | |
download | ast2050-yocto-poky-c5d19aae55be158676eb0914bd5d0701f7d3fd3a.zip ast2050-yocto-poky-c5d19aae55be158676eb0914bd5d0701f7d3fd3a.tar.gz |
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>
Diffstat (limited to 'bitbake/lib/toaster/toastergui')
4 files changed, 43 insertions, 34 deletions
diff --git a/bitbake/lib/toaster/toastergui/static/js/projectapp.js b/bitbake/lib/toaster/toastergui/static/js/projectapp.js index f0569de..9f9a064 100644 --- a/bitbake/lib/toaster/toastergui/static/js/projectapp.js +++ b/bitbake/lib/toaster/toastergui/static/js/projectapp.js @@ -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 projectApp.config(function($interpolateProvider) { @@ -128,7 +128,7 @@ projectApp.filter('timediff', function() { // 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) { var deffered = $q.defer(); @@ -475,6 +475,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc var alertText = undefined; var alertZone = undefined; var oldLayers = []; + switch(elementid) { case '#select-machine': 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 ++; angular.forEach(zone, function (o) { o.close() }); o = { - id: crtid, text: $sce.trustAsHtml(text), type: type, + id: crtid, text: text, type: type, 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); }, diff --git a/bitbake/lib/toaster/toastergui/templates/project.html b/bitbake/lib/toaster/toastergui/templates/project.html index 38863a3..4e8a7e2 100644 --- a/bitbake/lib/toaster/toastergui/templates/project.html +++ b/bitbake/lib/toaster/toastergui/templates/project.html @@ -11,6 +11,8 @@ vim: expandtab tabstop=2 <script src="{% static "js/angular.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-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> @@ -365,13 +367,13 @@ angular.element(document).ready(function() { scope.urls.layers = "{% url 'layers' %}"; scope.urls.targets = "{% url 'targets' %}"; scope.urls.importlayer = "{% url 'importlayer'%}" - scope.project = {{prj|safe}}; - scope.builds = {{builds|safe}}; - scope.layers = {{layers|safe}}; - scope.targets = {{targets|safe}}; - scope.frequenttargets = {{freqtargets|safe}}; - scope.machine = {{machine|safe}}; - scope.releases = {{releases|safe}}; + scope.project = {{prj|json}}; + scope.builds = {{builds|json}}; + scope.layers = {{layers|json}}; + scope.targets = {{targets|json}}; + scope.frequenttargets = {{freqtargets|json}}; + scope.machine = {{machine|json}}; + scope.releases = {{releases|json}}; var now = (new Date()).getTime(); scope.todaydate = now - (now % 86400000); diff --git a/bitbake/lib/toaster/toastergui/templatetags/projecttags.py b/bitbake/lib/toaster/toastergui/templatetags/projecttags.py index 4a97eb7..99fd4cf 100644 --- a/bitbake/lib/toaster/toastergui/templatetags/projecttags.py +++ b/bitbake/lib/toaster/toastergui/templatetags/projecttags.py @@ -25,6 +25,7 @@ from django import template from django.utils import timezone from django.template.defaultfilters import filesizeformat import json as JsonLib +from django.utils.safestring import mark_safe register = template.Library() @@ -49,7 +50,10 @@ def mapselect(value, argument): @register.filter(name = "json") 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 def query(qs, **kwargs): diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py index 3b29dbc..5e92c24 100755 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py @@ -36,6 +36,7 @@ from django.utils import timezone from django.utils.html import escape from datetime import timedelta from django.utils import formats +from toastergui.templatetags.projecttags import json as jsonfilter import json @@ -871,7 +872,7 @@ def _get_dir_entries(build_id, target_id, start): # sort by directories first, then by name rsorted = sorted(response, key=lambda entry : entry['name']) 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): template = "dirinfo.html" @@ -1981,25 +1982,25 @@ if toastermain.settings.MANAGED: context = { "project" : prj, "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), - "builds" : json.dumps(_project_recent_build_list(prj)), - "layers" : json.dumps(map(lambda x: { + "builds" : _project_recent_build_list(prj), + "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"))), - "targets" : json.dumps(map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all())), - "freqtargets": json.dumps(freqtargets), - "releases": json.dumps(map(lambda x: {"id": x.pk, "name": x.name}, Release.objects.all())), + prj.projectlayer_set.all().order_by("id")), + "targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()), + "freqtargets": freqtargets, + "releases": map(lambda x: {"id": x.pk, "name": x.name}, Release.objects.all()), } 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: - context["machine"] = json.dumps(None) + context["machine"] = None try: context["distro"] = prj.projectvariable_set.get(name="DISTRO").value except ProjectVariable.DoesNotExist: @@ -2035,7 +2036,8 @@ if toastermain.settings.MANAGED: if 'targets' in request.POST: 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: target, task = t.split(":") else: @@ -2045,11 +2047,11 @@ if toastermain.settings.MANAGED: br = prj.schedule_build() - return HttpResponse(json.dumps({"error":"ok", + return HttpResponse(jsonfilter({"error":"ok", "builds" : _project_recent_build_list(prj), }), content_type = "application/json") 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): try: @@ -2088,7 +2090,7 @@ if toastermain.settings.MANAGED: machinevar.save() # return all project settings - return HttpResponse(json.dumps( { + return HttpResponse(jsonfilter( { "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")), "builds" : _project_recent_build_list(prj), @@ -2098,7 +2100,7 @@ if toastermain.settings.MANAGED: }), content_type = "application/json") 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 @@ -2112,7 +2114,7 @@ if toastermain.settings.MANAGED: 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(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+")")}, queryset_all[:8]) }), content_type = "application/json") @@ -2127,7 +2129,7 @@ if toastermain.settings.MANAGED: queryset_all.order_by("-up_id"); - 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+")"), "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 retval.append(i) - return HttpResponse(json.dumps( {"error":"ok", + return HttpResponse(jsonfilter( {"error":"ok", "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+")")}, retval) }), content_type = "application/json") @@ -2156,7 +2158,7 @@ if toastermain.settings.MANAGED: queryset_all = Recipe.objects.all() 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']))) - 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 "]")}, queryset_all.filter(name__icontains=request.GET.get('value',''))[:8]), @@ -2166,7 +2168,7 @@ if toastermain.settings.MANAGED: queryset_all = Machine.objects.all() 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']))) - 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 "]")}, 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")) 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 = { - '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, 'objectname' : "layers", 'default_orderby' : 'layer__name:+', @@ -2309,7 +2311,7 @@ if toastermain.settings.MANAGED: 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, 'objectname' : "targets", 'default_orderby' : 'name:+', |