Cross-site scripting (XSS)

Met Cross-site scripting, afgekort als XSS, plaatsen we op onze webserver een proxyscript. Dit script haalt een extern bestand op en toont als resultaat een exacte kopie van de externe pagina. Door een AJAX call naar de proxypagina te maken, kunnen we op deze manier de same-origine policy beveiliging van de browser omzeilen.

Voor we de werking van het script bekijken gaan we nog even de PHP-configuratie controleren. De proxy maakt namelijk gebruik van de cURL-functie van PHP en deze is in de default configuratie van WAMPserver nog niet actief.

Het proxyscript

Het proxyscript is een universeel script waarmee u zowel HTML, JSON als XML kan ophalen. Geef de gewenste URL via de stringparameter url mee aan het script.

Stel dat de URL zelf ook nog stringparameters heeft, dan moeten we de string eerst coderen, anders loopt het grondig mis. Neem bijvoorbeeld: http://jquery3.test/...../proxy.php?url=https://www.google.be/search?hl=nl&q=jquery

De URL bevat een &-teken en dit wordt door het proxyscript geïnterpreteerd als een eigen variabele, waardoor de pagina een foutief resultaat toont. Om dit te vermijden gaan we de URL via de JavaScriptmethode encodeURIComponent() coderen. De gecodeerde Google URL wordt dan: https%3A%2F%2Fwww.google.be%2Fsearch%3Fhl%3Dnl%26q%3Djquery

Als we deze string als URL aan het proxyscript geven, bekomen we wel het juiste resultaat.

Merk op dat bepaalde afbeeldingen op de pagina ontbreken. Alle afbeeldingen met relatieve paden worden niet getoond. Afbeeldingen met absolute paden zijn wel zichtbaar.

Werkt u op Windows hosting, gebruik dan het proxyscript proxy.asp. De werking is identiek aan proxy.php.

In dit hoofdstuk gaan we van elk datatype (HTML, JSON en XML) een voorbeeld uitwerken.

Net zoals in voorgaand hoofdstuk zijn we ook hier volledig afhankelijk van services van derden. Het is dus best mogelijk dat bij het uittesten van de voorbeelden in dit boek bepaalde oefeningen niet meer het gewenste resultaat opleveren.

JSON: Google Geocoding

Met de Google Geocoding API kan u de coördinaten van een straatnaam of van een volledig adres opvragen. Het resultaat wordt default in JSON-formaat aangeboden. https://developers.google.com/maps/documentation/geocoding/

Indien de zoekopdracht geen resultaat oplevert, is results leeg en geeft status zero_results.

{
    "results" : [],
    "status" : "ZERO_RESULTS"
}

Bij een zoekopdracht met resultaat, staat status op OK. Results bevat nu deze structuur:

{
    "results" : [
        {
        "address_components" : [...],
        "formatted_address" : "Vogelzang, 9870 Zulte, België",
        "geometry" : {
            "bounds" : { ...},
            "southwest" : {...}, "location" : {
                "lat" : 50.93021560,
                "lng" : 3.46115240
            },
            "location_type" : "GEOMETRIC_CENTER", 
            "viewport" : {...}
        },
        "types" : [ "route" ]
        }, ...
        {...}
    ],
    "status" : "OK"
}

De waardes die ons interesseren zijn:

  • results.formatted_address

  • results.geometry.location.lat

  • results.geometry.location.lng

Net zoals in één van voorgaande toepassingen, kunnen we de coördinaten lat en lng opnieuw gebruiken om een statische map te genereren.

$(function () {
    $('#mapsForm').submit(function (e) {
        e.preventDefault();
        $('.preloader-success').show();
        $('#aantal').text('');
        $('#map').empty().hide();
        var str = $(this).serialize();
        var url = 'http://maps.googleapis.com/maps/api/geocode/json?sensor=true&';
        var url = encodeURIComponent(url + str);
        $.getJSON('proxy.php?url=' + url, function (data) {
            if (data.status != 'OK') {
                $('#aantal').text('Geen locaties gevonden');
            } else {
                var aantal = data.results.length;
                $('#aantal').text(aantal + ' locaties gevonden');
                $.each(data.results, function (index, value) {
                    var adres = this.formatted_address;
                    var lat = this.geometry.location.lat;
                    var lng = this.geometry.location.lng;
                    var map = 'https://static-maps.yandex.ru/1.x/?lang=en-US&z=14&l=map&size=600,200&ll=' + lng + ',' + lat + '&pt=' + lng + ',' + lat + ',pm2rdl1';
                    $('#map').append('<p><b>' + adres + '</b><br>' + lat + ',' + lng + '</p>');
                    $('#map').append('<p><img src="' + map + '"></p>');
                });
                $('#map').show();
            }
        }).done(function () {
            $('.preloader-success').hide();
        }).fail(function (err) {
            console.log('error', err);
        });
    });
});

Telkens we nieuwe coördinaten opvragen, gaan we eerst de oude gegevens uit de section-tag #map wissen. Vervolgens wordt de URL van de op te vragen pagina samengesteld. Merk op dat de URL een &-teken bevat waardoor we de URL moeten coderen met encodeURIComponent().

Binnen de methode $.getJSON() wordt eerst de status gecontroleerd. Enkel indien de response resultaten bevat, gaan we de gewenste waardes binnen een each-lus uitlezen. De afbeelding wordt opgehaald en #map wordt opnieuw gevuld.

XML: iTunes feed

In het laatste voorbeeld gaan we een iTunes RSS-feed in onze pagina verwerken.

Via de link http://itunes.apple.com/rss kan u desgewenst een eigen feed maken en deze aan de keuzelijst toevoegen.

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns:im="http://itunes.apple.com/rss" ...>
    ...
    <title>iTunes Store: Top Albums</title>
    <entry>
        <im:image height="55">...</im:image>
        <im:image height="60">...</im:image>
        <im:image height="170">...</im:image>
        <content type="html">&lt;table ... /table&gt; </content>
    </entry>
    ...
</feed>

Het document is gestructureerd volgens een ATOM-feed. Apple heeft echter een eigen namespace aan de feed toegevoegd. Alle iTunes gerelateerde nodes beginnen met im:.

In onze toepassing gaan we drie nodes verwerken. De titel boven de thumbnails halen we uit de node titel. Het pad voor de thumbnails is de eerste im:img node. De volledige omschrijving staat in html binnen de node content.

Klik op een thumbnail om de omschrijving te tonen.

$('#thumbs, #detail').hide();

$('#url').change(function (e) {
    $('.preloader-success').show();
    $('#thumbs, #detail').empty();
    var url = $(this).val();
    $.get('proxy.php?url=' + url, function (data) {
        $('#thumbs, #detail').show();
        $('h2').text($(data).find('title:first').text());
        var entry = $(data).find('entry');
        $.each(entry, function (index, value) {
            //Eerste selector (im\\:image:eq(1))  voor FF en IE, tweede (image:eq(1)) voor Chrome en Safari
            var img = $(this).find('im\\:image:eq(1), image:eq(1)').text();
            var title = $(this).find('title').text();
            var content = $(this).find('content').text();
            $('#thumbs').append('<img src="' + img + '" data-id="' + index + '">');
            $('#detail').append('<div id="entry' + index + '"><h3>' + title + '</h3>' + content + '</div>');
        });
        $('#detail div').not(':first').hide();
    }, 'xml').done(function () {
        $('.preloader-success').hide();
    }).fail(function (err) {
        console.error('error', err);
    });
}).change();

$('#thumbs').on('click', 'img', function () {
    var id = $(this).data('id');
    $('#detail div:visible').hide();
    $('#entry' + id).show();
});

Het uitlezen van de afbeelding im:image vraagt wat extra uitleg. Voor Chrome en Safari hoeft u geen rekening te houden met de prefix. Omdat de pagina drie dezelfde nodes bevat en we enkel de eerste node met de kleinste afbeelding gebruiken, wordt de selector image:eq(1). Voor Firefox en Internet Explorer is de prefix wel belangrijk. Een dubbelpunt moet u wel altijd laten voorafgaan door twee backslashes. De selector wordt dus im\\:image:eq(1). Door beide te combineren, werkt de selector find('im\\:image:eq(1), image:eq(1)') in alle browsers.

Binnen de each-lus worden alle afbeeldingen aan #thumbs toegevoegd. De index wordt via het attribuut data-id aan de afbeelding toegevoegd. Voor de eerste thumbnail wordt dit: <img src="https://...xxx.jpg" data-id="0" >. Dit nummer gebruiken we straks om de juiste details te tonen.

Alle content nodes komen in een div-tag binnen #detail te staan. Ook hier verwerken we de index in het id van de div-tag. Voor de eerste div-tag wordt dit: <div id="entry0">...</div>.

Op het einde van de each-lus worden alle div-tags, behalve de eerste, onzichtbaar gemaakt.

Telkens men op een thumbnail klikt, wordt de oude div-tag onzichtbaar en de geselecteerde div-tag wordt zichtbaar. Klik bijvoorbeeld op de tweede thumbnail. De variabele id is dan 1 en div-tag #entry1 wordt zichtbaar.

Last updated