Vulnerability Source Code Tampering Description:
Write-up
For almost all levels I will be using Burpsuite. Burpsuite is an interception proxy that lets us modify the HTTP request / response by intercepting the data between the browser and the web server.
Awesome, it’s a browser (JavaScript) game ! If we inspect the source, we see two JavaScript files, ex10.js and app.js
ex10.js
/**
* Created by Ivan on 15.3.2015 г..
*/
var ex10 = {
storageCompleted: false,
extremeCompleted: false
}
function setTarget(data) {
if (typeof data['storage'] !== "undefined") {
ex10.storageCompleted = data['storage'];
}
else if (typeof data['extreme'] !== "undefined") {
ex10.extremeCompleted = data['extreme'];
}
console.log(ex10.storageCompleted);
if (ex10.storageCompleted && ex10.extremeCompleted) {
$(".ex10-alerts").html($("<div class='text-center alert alert-success'><p class='lead'>Congrats. Now you know the basics of how to toy around with browser games. Expect redirection.</p></div>"));
levelCompleted(10);
}
}
This script outlines the conditions for a successful exploitation. To successfully complete this level, we need the storageComplete and extremeComplete variables as “true”.
app.js
// Generated by CoffeeScript 1.9.0
(function() {
$(function() {
var Memory;
Memory = {
data: {
totalColors: 11,
totalShownColors: 4,
showDuration: 2000,
preparationTime: 3000,
username: "Anonymous",
secondsLeft: 3,
points: 0,
startClick: 0,
stats: {}
},
updateStats: function() {
if (this.data.stats.wins >= 9999) {
setTarget({
storage: true
});
}
return $("#stats").html("<h3>" + this.data.username + "</h3><p>Wins: " + this.data.stats.wins + "</p><p>Loses: " + this.data.stats.loses + "</p><p>Total Points Collected: " + this.data.stats.pts + "</p>");
},
finishGame: function(action) {
if (action === "win") {
if (this.data.totalShownColors === 9) {
setTarget({
extreme: true
});
}
++this.data.stats.wins;
this.addAlert("Congrats. You won");
} else if (action === "lose") {
++this.data.stats.loses;
this.addAlert("Oops. You did not make it...");
}
this.data.stats.pts += this.data.points;
localStorage[this.data.username] = JSON.stringify(this.data.stats);
return this.updateStats();
},
points: function(action) {
var points;
if (action === "add") {
points = ++this.data.points;
}
if (action === "deduce") {
points = --this.data.points;
}
if (points >= this.data.totalShownColors) {
this.finishGame('win');
} else if ($(".color.inactive").length <= 0) {
this.finishGame("lose");
}
return this.updateStats();
},
addListeners: function() {
if ($(".color.inactive").length >= Memory.data.totalShownColors) {
return $(".color").click(function() {
var boxNumber, color, correctColor, el, numbers, _i, _ref;
el = $(this);
$(".color-picker").html("");
numbers = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eight', 'ninth', 'tenth', 'eleventh', 'twelve', 'thirteen', 'fourteen', 'fifteen'];
boxNumber = numbers[el.index()];
$(".position").text(boxNumber);
correctColor = parseInt($(this).attr("class").replace(/[^0-9]/g, ""), 10);
for (color = _i = 0, _ref = Memory.data.totalColors; 0 <= _ref ? _i <= _ref : _i >= _ref; color = 0 <= _ref ? ++_i : --_i) {
$(".color-picker").append("<div class='color color-" + color + " color-thumbnail'></div>");
}
$(".color-picker .color-thumbnail").click(function() {
el.removeClass("inactive");
$(".modal").hide("slow");
if ($(this).hasClass("color-" + correctColor)) {
Memory.addAlert("You guessed the right color!");
return Memory.points('add');
} else {
Memory.addAlert("You did not guess the right color!");
return Memory.points("deduce");
}
});
return $(".modal").show('slow');
});
}
},
addAlert: function(message) {
$("#alerts").html("<p> " + message + "</p>");
$("#alerts").show("slow");
return $("body").animate({
scrollTop: $("#alerts").offset().height
}, 'slow');
},
setDifficulty: function(difficulty) {
if (difficulty === 'easy') {
this.data.totalShownColors = 3;
this.data.showDuration = 4000;
}
if (difficulty === "medium") {
this.data.totalShownColors = 4;
this.data.showDuration = 3500;
}
if (difficulty === "hard") {
this.data.totalShownColors = 5;
this.data.showDuration = 3000;
}
if (difficulty === "extreme") {
this.data.totalShownColors = 9;
return this.data.showDuration = 500;
}
},
initialize: function() {
var color, shownColor, _i, _ref;
this.data.points = 0;
$("#game").html("");
this.data.secondsLeft = 3;
this.data.stats = localStorage[this.data.username] || {
wins: 0,
loses: 0,
pts: 0
};
if (typeof this.data.stats === "string") {
this.data.stats = JSON.parse(localStorage[this.data.username]);
}
this.setDifficulty($("#difficulty").find(":selected").text().toLowerCase());
for (shownColor = _i = 1, _ref = this.data.totalShownColors; 1 <= _ref ? _i <= _ref : _i >= _ref; shownColor = 1 <= _ref ? ++_i : --_i) {
color = Math.floor(Math.random() * this.data.totalColors);
$("<div class='color color-" + color + "'>").appendTo("#game");
}
this.updateStats();
return setTimeout(this.hideColors, this.data.showDuration);
},
prepareForGame: function() {
$("#preparation").fadeIn("slow");
if (Memory.data.secondsLeft === -1) {
Memory.initialize();
clearInterval(Memory.data.interval);
$("#preparation").fadeOut("slow");
} else {
$("#preparation").text(Memory.data.secondsLeft);
}
return --Memory.data.secondsLeft;
},
hideColors: function() {
$(".color").addClass("inactive");
return Memory.addListeners();
}
};
$("#start").click(function() {
if (new Date().getTime() > Memory.data.startClick + 1000 * 15 && started !== true) {
var started = true;
if ($(".color.inactive").length <= 0) {
Memory.data.startClick = new Date().getTime();
started = false;
return Memory.data.interval = setInterval(Memory.prepareForGame, Memory.data.preparationTime / 3);
}
}
});
return Memory.data.username = prompt("Enter your nickname") || Memory.data.username;
});
}).call(this);
Since JavaScript runs by the browser, all of this code is executed on the client side and we manipulate it as we see fit. If we modify line# 116 and 117, we can initialize our wins and points before we even start the game! This will complete our first objective. If we modify line# 27 from “=== 9” to “=== 1”, and line# 106 from “= 9” to “= 1”, the “extreme” difficulty will present us with only a single color box to guess. As a final modification, if we modify line# 48 from “points = –this.data.points;” to “points = ++this.data.points;”, no matter if we lose or win we will always get points added to our score.
Now let me show you how to edit an HTTP response before reaching the browser.
First, clear the browser’s cache in case you have already downloaded the JavaScript game. Open Burp and under the Proxy tab
go to “Options” and place a tick on “Intercept responses based on the following rules”. Now Burp will intercept every
response without explicitly telling it to intercept it.
Assuming you have cleared the cache, refresh the page, the request should be intercepted by Burp. Click on “Forward” until you have intercepted the response of “app.js”.
Modify the JavaScript in the response so it becomes like this:
// Generated by CoffeeScript 1.9.0
(function() {
$(function() {
var Memory;
Memory = {
data: {
totalColors: 11,
totalShownColors: 4,
showDuration: 2000,
preparationTime: 3000,
username: "Anonymous",
secondsLeft: 3,
points: 0,
startClick: 0,
stats: {}
},
updateStats: function() {
if (this.data.stats.wins >= 9999) {
setTarget({
storage: true
});
}
return $("#stats").html("<h3>" + this.data.username + "</h3><p>Wins: " + this.data.stats.wins + "</p><p>Loses: " + this.data.stats.loses + "</p><p>Total Points Collected: " + this.data.stats.pts + "</p>");
},
finishGame: function(action) {
if (action === "win") {
if (this.data.totalShownColors === 1) {
setTarget({
extreme: true
});
}
++this.data.stats.wins;
this.addAlert("Congrats. You won");
} else if (action === "lose") {
++this.data.stats.loses;
this.addAlert("Oops. You did not make it...");
}
this.data.stats.pts += this.data.points;
localStorage[this.data.username] = JSON.stringify(this.data.stats);
return this.updateStats();
},
points: function(action) {
var points;
if (action === "add") {
points = ++this.data.points;
}
if (action === "deduce") {
points = ++this.data.points;
}
if (points >= this.data.totalShownColors) {
this.finishGame('win');
} else if ($(".color.inactive").length <= 0) {
this.finishGame("lose");
}
return this.updateStats();
},
addListeners: function() {
if ($(".color.inactive").length >= Memory.data.totalShownColors) {
return $(".color").click(function() {
var boxNumber, color, correctColor, el, numbers, _i, _ref;
el = $(this);
$(".color-picker").html("");
numbers = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eight', 'ninth', 'tenth', 'eleventh', 'twelve', 'thirteen', 'fourteen', 'fifteen'];
boxNumber = numbers[el.index()];
$(".position").text(boxNumber);
correctColor = parseInt($(this).attr("class").replace(/[^0-9]/g, ""), 10);
for (color = _i = 0, _ref = Memory.data.totalColors; 0 <= _ref ? _i <= _ref : _i >= _ref; color = 0 <= _ref ? ++_i : --_i) {
$(".color-picker").append("<div class='color color-" + color + " color-thumbnail'></div>");
}
$(".color-picker .color-thumbnail").click(function() {
el.removeClass("inactive");
$(".modal").hide("slow");
if ($(this).hasClass("color-" + correctColor)) {
Memory.addAlert("You guessed the right color!");
return Memory.points('add');
} else {
Memory.addAlert("You did not guess the right color!");
return Memory.points("deduce");
}
});
return $(".modal").show('slow');
});
}
},
addAlert: function(message) {
$("#alerts").html("<p> " + message + "</p>");
$("#alerts").show("slow");
return $("body").animate({
scrollTop: $("#alerts").offset().height
}, 'slow');
},
setDifficulty: function(difficulty) {
if (difficulty === 'easy') {
this.data.totalShownColors = 3;
this.data.showDuration = 4000;
}
if (difficulty === "medium") {
this.data.totalShownColors = 4;
this.data.showDuration = 3500;
}
if (difficulty === "hard") {
this.data.totalShownColors = 5;
this.data.showDuration = 3000;
}
if (difficulty === "extreme") {
this.data.totalShownColors = 1;
return this.data.showDuration = 500;
}
},
initialize: function() {
var color, shownColor, _i, _ref;
this.data.points = 0;
$("#game").html("");
this.data.secondsLeft = 3;
this.data.stats = localStorage[this.data.username] || {
wins: 9999,
loses: 0,
pts: 9999
};
if (typeof this.data.stats === "string") {
this.data.stats = JSON.parse(localStorage[this.data.username]);
}
this.setDifficulty($("#difficulty").find(":selected").text().toLowerCase());
for (shownColor = _i = 1, _ref = this.data.totalShownColors; 1 <= _ref ? _i <= _ref : _i >= _ref; shownColor = 1 <= _ref ? ++_i : --_i) {
color = Math.floor(Math.random() * this.data.totalColors);
$("<div class='color color-" + color + "'>").appendTo("#game");
}
this.updateStats();
return setTimeout(this.hideColors, this.data.showDuration);
},
prepareForGame: function() {
$("#preparation").fadeIn("slow");
if (Memory.data.secondsLeft === -1) {
Memory.initialize();
clearInterval(Memory.data.interval);
$("#preparation").fadeOut("slow");
} else {
$("#preparation").text(Memory.data.secondsLeft);
}
return --Memory.data.secondsLeft;
},
hideColors: function() {
$(".color").addClass("inactive");
return Memory.addListeners();
}
};
$("#start").click(function() {
if (new Date().getTime() > Memory.data.startClick + 1000 * 15 && started !== true) {
var started = true;
if ($(".color.inactive").length <= 0) {
Memory.data.startClick = new Date().getTime();
started = false;
return Memory.data.interval = setInterval(Memory.prepareForGame, Memory.data.preparationTime / 3);
}
}
});
return Memory.data.username = prompt("Enter your nickname") || Memory.data.username;
});
}).call(this);
We have made the necessary modification and we don’t need Burp anymore, so click on the “Intercept is On” to turn off interception. Enter your name and you should be presented with “9999” initial wins and points. Select the game difficulty to extreme and play a game.
No matter which color you choose, you will always win ;).