Jak Mevo pozwalało na obserwowanie przejazdów na żywo

W poprzednim wpisie bardzo nakręcałem się na Mevo po mojej pierwszej przejażdżce. Techniczne założenia sieci rowerów elektrycznych są dla mnie nadal świetną sprawą, ale widać, że to wyzwanie logistyczne już przy ograniczonej formie działania systemu, z jaką mamy do czynienia dzisiaj. W poprzednią niedzielę Mevo miało dzień wolny, dorwanie roweru w mieście to pewnego rodzaju sztuka, którą Bartosz Cicharski na Twitterze porównał do grania w Pokemon GO.

Dzisiaj z kolei na stronie Mevo pojawił się komunikat o zawieszeniu przyjmowania wpłat od użytkowników i weryfikowaniu nowych użytkowników, co oznacza w praktyce zamknięcie dostępu dla nowych (lub takich, którym skończyły się środki) chcących używać systemu rowerów miejskich. Korzystając z tego, że API Mevo jest całkiem łatwe do przeanalizowania, pojawiły się w sieci strony takie jak GdzieJestMevo.pl, które w czasie rzeczywistym informuje o tym, ile rowerów jest aktualnie dostępnych w poszczególnych miastach (spoiler alert: za mało) i o stanie naładowania ich baterii, czy Mevo Watchdog, który podaje też dane historyczne.

Cofnijmy się jednak do poprzedniego piątku. Po przyjechaniu rowerem do pracy odkryłem, że tym razem w mojej historii przejazdów pojawił się link do trasy, którą przemierzyłem.

BTW, ten „zwrot roweru poza stacją” był na samym środku stojaków do tej stacji należących. Jeden z błędów wieku dziecięcego – źle wyznaczone obręby niektórych stacji.

Nie namyślając się wiele, otworzyłem link w nowej karcie i nieco się zdziwiłem.

Gdy kliknie się na link z trasą po prostu w kontekście strony, wyświetla się ładna mapa pokazująca te dane. Wystarczy jednak otworzyć link w nowej karcie, by mieć dostęp do ładnego jsona z danymi o przejeździe. Nie było w nich jednak danych o tym, kto jechał ani znaczników czasu, które pozwoliłyby na zidentyfikowanie, kiedy dokładnie ten przejazd miał miejsce.

Wszystko byłoby w sumie nieszkodliwe i nawet fajne, gdyby nie to, że wystarczyło zmienić w URL zawartość pola rental_id, by dostać cudze przejazdy – niezależnie od tego, czy było się zalogowanym, czy nie. Nie wiedziałem co prawda, czyj to przejazd, ale możliwe było zebranie całkiem sporej liczby statystyk o użytkowaniu Mevo. Do tego, możliwa była sytuacja, w której wynajmowało się rower w mniej więcej tym samym czasie, co ktoś inny na tej stacji. Wartości id wypożyczeń są generowane na zasadzie inkrementacji – każde kolejne wypożyczenie ma id większe o jeden. Nie było szczególnie trudną sprawą przeszukanie pobliskich przejazdów, by znaleźć, dokąd jechała ta osoba. A być może, podobnie jak ja, jechała do pracy albo z pracy do domu?

Napisałem na Facebooku do strony Mevo, aby zgłosić błąd, po czym podzieliłem się znaleziskiem z kilkoma kolegami. Jeden zainteresował się tematem bardziej i zapytał: A co, jeśli znajdziemy id trwającego wypożyczenia? Znaleźliśmy. A następnie odświeżyliśmy stronę raz, drugi… i obserwowaliśmy, jak do trasy dochodzą kolejne punkty ze współrzędnymi. Bliżej nieokreślony ktoś podjął rower w pobliżu skrzyżowania Alei Żołnierzy Wyklętych z Partyzantów, mijał Galerię Bałtycką, a następnie biurowiec Neptun. To już było znacznie grubsze.

Kolega wysłał wiadomość do Niebezpiecznika, który publicznie tematu nie podjął, ale trzeba przyznać, że wcześniej napisali artykuł o innym ciekawym problemie związanym z Mevo. Ze strony Mevo kontaktu nie było (podejrzewam zresztą, że nie byliśmy jedynymi osobami, które zainteresowały się wystawianymi przez Mevo danymi ;)), ale błąd został naprawiony:

Nie jest to pierwszy raz, gdy w niespecjalnie skomplikowany sposób znalazłem ciekawe dane wystawiane przez serwisy związane z współdzieleniem środków transportu. Pod koniec 2017 roku zainteresowałem się mapą dostępnych Traficarów – okazało się, że wszystkie dostępne w danej chwili w Trójmieście pojazdy są zwracane z jednego endpointu API, który zawierał całkiem sporo ciekawych danych:

Wtedy jeszcze jedna informacja była dostępna – pola distanceAccumulated i distanceCounter zawierały wyliczony (najwyraźniej w dwa różne sposoby) dystans, jaki dany samochód jak dotąd w barwach Traficara przejechał. Wystarczyło napisać skrypt, który odpytywałby to API regularnie i przechowywał dane, by oszacować całkiem dokładnie wykorzystanie, flotę pojazdów i przychody Traficara z wynajmu. Na zgłoszenie ode mnie Traficar nie odpisał, ale jak widać, po pewnym czasie wszystkie pojazdy zaczęły pokazywać przejechany dystans o wartości tysiąca kilometrów, a mapa z dostępnością pojazdów jest teraz tylko dla zalogowanych (sam endpoint API już nie…).

Jeśli potrzebujecie fajnych danych, warto zawsze obserwować nowe serwisy, które Was interesują. Duże szanse, że mają całkiem sporo luk w API 😉

Content-Security-Policy: manage security settings of your app

There are already lots of ways to prevent XSS. We can make sure the data input by the user is never presented with markup the user might have provided, encode the data, prevent any scripts from saving to the DB, add X-XSS-Protection header. Some of the instructions are now incorporated into modern frameworks, so adding a team that contained a script in the team name didn’t cause any harm on Matchlogger.

One of the still less known ways to secure your application more is a Content-Security-Policy header. As it states in the reference guide,

The new Content-Security-Policy HTTP response header helps you reduce XSS risks on modern browsers by declaring what dynamic resources are allowed to load via a HTTP Header.

The header is now supported by all modern browsers (note: IE11 supports only deprecated X-Content-Security-Policy header). It is fairly easy to add to Spring Boot application – the only part I’ve added to the config is headers() section.

Let’s check what happens if we use a default policy on Matchlogger:

Boom. We lose all styles, images, and JS as the browser blocked loading resources because of the Content-Security-Policy header in the response. We still can add teams, though, which is good – the application seems to work without all that.

Each of the issues mentioned in the developer console shows us something else which might be important from application security perspective. While in such a small app all of those issues can be dealt with – with less or more effort – in a big application with lots of legacy code we might take a different approach. Here the best approach would be to download all the used libraries and styles into the application and instead of allowing users to put any crest URL, allow them to upload pictures (make sure the upload is secured too then!).

Content-Security-Policy allows us to both define host whitelist for script/image sources and provides some sources we could use. For example,

script-src 'self' unpkg.com 'unsafe-inline' 'unsafe-eval'

would allow us to use both JS from our origin, unpkg.com, inline scripts and eval() instruction. It still maintains a whitelist of script sources but might be useful in cases where we either explicitly want to allow inline scripts and eval(), or we know we have them in our codebase and we want to manage them from now on as a risk we would like to mitigate in future.

So, is this header helpful? From two perspectives it is.

First of all, we add a new layer of protection – the browser will compare the content it obtained from the server with its security policy, and in the case of unauthorized adding of scripts, block them.

Another good thing here is that while implementing Content-Security-Policy, we get to know our application and its potential security risks better. That’s always a good thing as we want to make sure we don’t harm our users.