.+:::::::::::::::::::::::::::::::::::::::::/
.//`+++++++++++++++++++++++++++++++++++++// s
-+/.h+..................................-os s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h/                                   ss s
-+/.h+                                   ss s
-++-yo////////////////////////////////////y`s
-+o-:--------------------------::::-:://:---s
-+/                            :-.: +:oo-   s
-+/```````````````````````````````````:-````s
./o//////////+o:::::::::::::::::s+/////////::
``://////////+s:::::::::::::::::y+/////////:.
  s                        .::::/osso::::. .+
`.y                        +yyyyhNNNmyyyy+ .+
`:h:::::/+:::::::::::::+:///////////////////+--------------------
 .o                    s s::::::::::::s.+ - +
-/y     .o             s ::::::::::::oss+::.+
`-y     .o             y            `+//:  .+
`.+/y/::/+:::::::::::::+:::::::::::::::::++:.
    `////////////////////////////////////:/.       


    




Tool-assisted Tour

On twitter I found out about a new work by the great !Mediengruppe Bitnik.

The work was created as part of an exhibition by Neuer Berliner Kunstverein.
More info can be found here: https://www.nbk.org/en/ausstellungen/tomasschmit.html

This is the project website: https://wwwwwwwwwwwwwwwwwwwwww.bitnik.org/nonguidedtour/ There is even an explanation video: https://www.youtube.com/watch?v=O09OTZd6u6E

!Mediengruppe Bitnik writes about their work the following:

The piece is based on a work by German fluxus artist Tomas Schmit from 1962: "A bus carries the audience 100 km away. There the audience is deposited."

Fluxus

Fluxus is an art movement that emerged in the 1960s around the work of young experimental New York artists (cue: John Cage, Yoko Ono etc.). Many tried to characterise Fluxus as anything between radical art and a philosophy of experience. The only common definition, however, is that there is no such thing. Fluxus now spans a period of several decades and is practised by artists worldwide from a wide variety of backgrounds. In addition, anything imaginable can serve as material. Sounds familiar somehow.

An often used stylistic element of this movement are instructions that involve the recipients in a performance or let the performance emerge from it in the first place. The performances can thus become independent of time and place, since the instructions are to be understood as a kind of manual for executing out the performance.

In the physical world, the instructions are bound by physical laws and even more to the will of the performers to execute them. A Fluxus-inspired performance transferred into the digital world adds further instructions to the rules imagined by the artist. The algorithms that are the basis of the possibility to interact with digital systems.

In the following, we will examine the possibilities of using one set of instructions to execute the other set of instructions in a new and unexpected way.



Tomas Schmit perfomance instrunctions source
Walk Back (Fast)

But now back to the actual work.

When you go to the website, you are told that you have been dropped off 100 kilometres away from the Neuer Berliner Kunstverein and that you should find your way back. To walk back, you can navigate with the arrow keys. There is also a minimap.



Dropoff circle around the destination at the websites landingpage

!Mediengruppe Bitnik is starting something of a competition theirself by writing:

Usually it takes around 45-60min to get back to Berlin

Our personal high score is 26:45min

High score sounds like a challenge. Time to take a closer look at the technology behind the work. Maybe it's possible to crack the high score if we understand what exactly is happening on the website.

Intercept

In order to understand what a browser actually does when a website is visited, a web proxy is an excellent analysis tool.

A web proxy is basically an additional station that is placed between the browser and the "internet". Everything that a browser sends or receives from the web must first pass through the proxy.



Beautiful schematic illustration of a proxy

There are tools that are specialised in processing this traffic that flows through the proxy for further analysis. For example, a request to the server can be stopped and changed at the protocol level.

The request with the corresponding response when the nonguidedtour website is accessed for the first time looks like this:



HTTP protocol request and response

However, modern websites are not only loaded after a single request. Components of the page are often reloaded from other sources. The HTML code of the original page defines which requests the browser must also send to other servers or endpoints in order to build up the full functionality of the page. In most cases, JavaScript is additionally loaded, as well as media files. If a page embeds a YouTube video, for example, a request to YouTube will also be found in the traffic log of the proxy.

If we now take a look at what requests were sent by the browser after the web page was first accessed, we can see a few more interesting requests.



Some requests loading JavaScript, some loading Youtube etc.

In this list of requests, one stands out directly. A request to "api.bitnik.org" with the URL "/nbk/get_random_location".

Random Location

Is this maybe the request to get a location where a user is dropped off before the return journey starts?

If we look at the response of the request, we see coordinates.
And if we call the endpoint directly in the browser or resend the request in the proxy, we can see that the coordinates actually change each time.

https://api.bitnik.org/nbk/get_random_location

{
    "type":"Feature",
    "geometry":{
        "type":"Point",
        "coordinates":[
            13.822463750839233,
            51.67286453694737
        ]
    },
    "properties":{
        "captured_at":1582209760825,
        "compass_angle":326.32336425781,
        "id":1148256262344964,
        "is_pano":false,
        "sequence_id":"n952o528vo4z3ud8yugr1j"
    }
}
    

Ok, that would mean that if we change the coordinates, we make the page drop us right in front of the Neuer Berliner Kunstverein.

The coordinates can be easily changed in the proxy by intercepting the response from the server in the proxy and editing the values. The browser knows nothing about the intermediate proxy and thinks the values come directly from the server. Therefore, it will try to process them according to the code on the website.

Unfortunately, nothing seems to happen if you simply change the coordinates at random.

So let's take a step back and look at how the website is actually constructed.

Moving works by clicking on the arrows that let the user jump back and forth. Each jump is a separate picture. On the minimap there are dots that show where the pictures were taken.

minimap

Minimap

Apparently we not only need the right coordinates, but above all we have to "land" in the right picture.

Nevertheless, there is some starting point that is defined. We can continue to work from there. It just doesn't seem to be the coordinates that matter, but maybe one of the other data in the response of the get_random_location query.

At this point it is time to take a look at the JavaScript of the website.

JavaScript Analysis

The HTTP history of the proxy showed that /nonguidedtour/dist/js/app.js was loaded.

There are some functions that are not important in detail. Fortunately, the functions all have meaningful names. So we can focus on the one that is most likely to indicate the functionality we are interested in.

For example, one function is called load_game(). And there you can also see that a request is made to https://api.bitnik.org/nbk/get_random_location to get the start location.

function load_game() {
  var API_URL = "https://api.bitnik.org/nbk/get_random_location";
  fetch(API_URL).then(function (res) {
    return res.json();
  }).then(function (out) {
    var location = out;
    embed_map(location);
  })["catch"](function (err) {
    throw err;
  });
    

The next function is called embed_map(). And here you can see that a variable "image_key" is inserted into an iframe, which in turn is inserted into the web page.

function embed_map(location) {
  [...]
  var image_key = location.properties.id; //let image_key = 3912648735529508;

  var iframe = '<iframe id="mapillary" src="https://www.mapillary.com/embed?map_style=Mapillary \
    satellite&image_key=' + image_key + '&style=classic" frameborder="0"></iframe>';
  var div_game = document.getElementById("game");
  div_game.innerHTML = iframe;
      

There is a number in the comment behind the //. Probably this was used for early tests and later, when the random location query was finished, it was commented out and the generated value was taken. This indicates what the value we are looking for probably looks like. The value location.properties.id is loaded into the image_key variable. If we look back at the answer from https://api.bitnik.org/nbk/get_random_location, we can see a corresponding value. The length of the number also corresponds to that in the comment in the script.

"properties":{
  "captured_at":1582209760825,
  "compass_angle":326.32336425781,
  "id":1148256262344964,
  "is_pano":false,
  "sequence_id":"n952o528vo4z3ud8yugr1j"
}
More Dynamics

Ok, but to be really sure and understand what is going on, we can debug it a bit more dynamically.

To debug the JavaScript or to see what it does at runtime, it can be manipulated before it is passed to the browser.
For this purpose, it can be set in the proxy (in this case Burp) that requests to JavaScript should also be intercepted. This is not the default setting.

Settings in Burp Suite

Settings in Burp Suite

Now the request to /nonguidedtour/dist/js/app.js becomes visible.

    GET /nonguidedtour/dist/js/app.js HTTP/2
    Host: wwwwwwwwwwwwwwwwwwwwww.bitnik.org
  

To change the JavaScript before it is passed to the browser, the response from the request must be intercepted.
To do this, we can right-click and select "Do intercept > Response to this Request".
Changes can now be made in the intercepted JavaScript. For example, we can "comment in" the commented out lines again.
Above all, however, a few console.log() can't hurt to see what is being executed.
For example console.log("THIS IS IMAGE_KEY: " + image_key); to see if it really is the value from the api query.

But to debug it even better, there is another more flexible method.
Breakpoints can be set in JavaScript in Chrome's Devtools.
This way it can be stopped at any point and then the console can be used interactively to display or change variables.
So let's set a breakpoint in line 92. Directly after the variable image_key has received its value. (Attention there are two app.js in the Chrome devtools.)

Add a breakpoint

Add a breakpoint

The value can now be changed via the JavaScript Console. If we change the image key, for example by counting down a number, and then let the browser continue to execute the script, an error is displayed.

Image data does not exists

Image data does not exists.

So basically we just need the right image_key, would have to insert it and should come out directly at Neuer Berliner Kunstverein.

Drop at NBK

Where do we get the key?

According to the iframe into which the image_key is inserted, the map that is integrated into the website is from https://www.mapillary.com/.
So let's have a look at the website https://www.mapillary.com/. There you can view a map of the world. We can now zoom into Berlin to the right address and then go into Street View.

Apart from the fact that Berlin (just like other German cities) is overcrowded with disgustingly fat dirty cars that shouldn't be in the city centre, we can also see different values in the URL.

https://www.mapillary.com/app/?lat=52.52821475738&lng=13.385775228975035&z=18.915683857343563&pKey=149068087245310&focus=photo&x=0.5060722407376654&y=0.48548477518527716&zoom=0

Exactly the following:

    lat=52.52821475738
    lng=13.385775228975035
    z=18.915683857343563
    pKey=149068087245310
    focus=photo
    x=0.5060722407376654
    y=0.48548477518527716
  

The coordinates look familiar somehow. Could the pKey then actually be the image_key we are looking for?

Let's try.

The breakpoint in the script should still be set.
So we can just enter image_key = 149068087245310, let the script continue to run and start the game.

And indeed. We start directly at the desired address at Neuer Berliner Kunstverein.

Hackers gonna hack

https://twitter.com/bitnk/status/1470503605195218945
Bonus

However, there was a critique of my hack....
https://twitter.com/back_face/status/1470508745847447552
"It obviously skips the drop"
So let's see if we can manage to drop 100km away first and then make a jump to the destination.

The page embeds an iframe. We can retrieve this with JavaScript in the Console.

      document.getElementById("mapillary")
    

In the same way, we can also reload the whole IFrame after the game has already started and the first drop has taken place.

      document.getElementById("mapillary").src = \
      "https://www.mapillary.com/embed?map_style=Mapillary satellite&image_key=149068087245310&style=classic"
    

If we now use the trick from before, that the JavaScript can be intercepted and manipulated in the proxy, we can insert this command into the following function.

  function start_game() {
    show("blocker");
    hide("game-intro");
    show("game-overlay");
    document.getElementById("game").classList.remove("blur");
    resetTimer();
    startTimer();
    show("timer");

      //insert hack
      document.getElementById("mapillary").src = \
      "https://www.mapillary.com/embed?map_style=Mapillary satellite&image_key=149068087245310&style=classic"

  }

Now the game is started as it should after the drop, but as soon as the timer runs, the jump happens.

The time is now no longer completely 00:00, because reloading the IFrame takes 1-2 seconds. To manipulate this as well, you could start the "startTimer();" function after the jump, but I'm happy with the result.