E-commerce i zakupy bez rejestracji
We współczesnych aplikacjach internetowych np. typu e-commerce dość często spotykaną praktyką jest obsługa użytkowników bez wymogu autentykacji użytkownika. W uproszczeniu, chodzi o umożliwienie składania zamówień bez konieczności wcześniejszej rejestracji czy też logowania. Z punktu widzenia użytkownika jest to bardzo wygodne i patrząc po statystykach dość powszechnie wykorzystywane. Praktyka ta dotyczy różnych systemów sprzedażowych, ofertowych czy też systemów wsparcia klienta.

Od strony technicznej jest to mniej komfortowa sytuacja ponieważ zwykle po przyjęciu zamówienia konieczne jest udostępnienie użytkownikowi strony z podsumowaniem lub potwierdzeniem danej operacji. W przypadku zamówień jest to strona, do której użytkownik będzie mógł nawet po pewnym czasie powrócić i zweryfikować postęp realizacji zamówienia. Strona ta zawiera zwykle wrażliwe dane osobowe użytkownika, np. adres domowy, telefon kontaktowy lub email, generalnie wszystkie dane które są niezbędne do obsługi procesu realizacji zamówienia.
Potrzebujesz bezpiecznej aplikacji internetowej?
Większość współczesnych systemów ecommerce oferujących obsługę zamówień bez rejestracji daje użytkownikowi ciągłą możliwość podglądu stanu realizacji zamówienia i jego zawartości poprzez specjalny, unikalny (bezpieczny) link do strony zawierającej dane o zamówieniu. Oczywiście długość linku i jego złożoność będzie miało znaczenie dla bezpieczeństwa dostępu do danych ale nawet najbardziej złożony link nie będzie gwarantował bezpieczeństwa danych, ponieważ każdy jego posiadacz będzie w posiadaniu pełnych danych klienta, dostawy i informacji o statusie.
Często nie zdajemy sobie sprawy, jak łatwo jest udostępnić publicznie unikalny adres URL. Wystarczy przypadkowe wklejenie linku w okno komunikatora np. Skype lub Discord aby bot był w stanie przejrzeć i pobrać całą zawartość naszego unikalnego adresu URL. Istnieje również ryzyko indeksacji takich stron przez boty wyszukiwarek internetowych, które skrupulatnie przeczesują wszystkie dostępne strony internetowe, dokumenty i pliki w poszukiwaniu adresów URL. Nieopatrzne wklejenie linku w niewłaściwym miejscu i nasze dane osobowe mogą zostać zaindeksowane i wystawione szerokiej publice do wglądu.
Co możemy zrobić, aby poprawić bezpieczeństwo naszego systemu e-commerce?
Unikalny i bezpieczny adres URL
Po pierwsze zacznijmy od naszego unikalnego adresu URL. Poprawnie przygotowany link nie będzie zawierał identyfikatorów rekordów w postaci typu INT i może być zrealizowany w postaci pojedynczego tokena lub w formacie identyfikator + token. Nawet stosunkowo mała ilość znaków tokena może gwarantować bezpieczeństwo przed przypadkowym odkryciem adresu URL.
Unikalny URL:
https://example.lsb.com.pl/order/3xAvK12fke1kefg
Unikalny URL UUIDv4:
https://example.lsb.com.pl/order/72afc88e-c126-48b1-852b-c3ee535eb649
Format dwuskładniowy (ID + token):
https://example.lsb.com.pl/order/12/3xAvK12fke1kefg
(niezalecane, ze względu na możliwość iteracji identyfikatora obiektu istnieje ryzyko ujawnienia danych w przypadku wadliwej weryfikacji tokena)
Format dwuskładniowy (UUID + token):
https://example.lsb.com.pl/order/72afc88e-c126-48b1-852b-c3ee535eb649/3xAvK12fke1kefg
Tagi META
Po drugie, mając już poprawnie przygotowany adres URL należy poinformować boty indeksujące, że nie powinny indeksować naszej strony z danymi osobowymi lub danymi wrażliwymi w razie przypadku gdyby bot trafił na taką stronę. W tym celu należy w części HEAD strony dodać odpowiednie tagi META:
<META NAME="robots" CONTENT="noindex"> (no indexation)
<META NAME="robots" CONTENT="nofollow"> (blocks links follow)
<META NAME="robots" CONTENT="noindex,nofollow"> (no indexation, no links follow)
Maskowanie danych
Po trzecie, warto zadbać o anonimizację danych. Nawet najbardziej złożony link będzie obarczony ryzykiem przypadkowego ujawnienia. Dlatego kolejnym krokiem jaki powinniśmy wdrożyć w naszym systemie to maskowanie danych wrażliwych i prosta autentykacja oraz autoryzacja użytkownika pod kątem dostępu do danych w formie jawnej. W omawianym przypadku użytkownik nie posiada konta w naszym systemie, dlatego do jego autentykacji może być użyta jakaś treść znana wyłącznie użytkownikowi do którego kierowana jest strona ulokowana pod niejawnym, unikalnym adresem URL. W przypadku potwierdzenia zamówienia może to być np. numer telefon podany podczas składania zamówienia, adres email lub np. PIN, który prześlemy użytkownikowi na jego adres email lub w formie SMS po otrzymaniu żądania dostępu do danych. Techniczny sposób organizacji obsługi autentykacji w dużym stopniu zależy od sposobu budowy Waszej aplikacji, dlatego nie będziemy teraz rozwijać tego wątku. Skupimy się jedynie na sposobie anonimizacji danych poprzez maskowanie.

An example of data masking can be done in the following way:
Masked (anonymized) content
Treść w formie jawnej:
W tym przykładzie użyliśmy “*” jako znaku maskującego, przy czym ilość znaków maskujących nie powinna pokrywać się z realną ilością zamaskowanych znaków. To dodatkowa forma zabezpieczenia, która nie pozwala określić rzeczywistej ilości znaków danych zamaskowanych przez osoby postronne. Warto podkreślić, że sposób maskowania powinien pozwolić docelowemu odbiorcy w jakiś sposób dopasować zamaskowane dane do swoich rzeczywistych danych. Pozwoli to użytkownikowi jasno stwierdzić, czy wyświetlana strona jest faktycznie stroną, na którą chciał wyświetlić.

Zamaskowane dane

Dane w postaci jawnej

Używanie adnotacji do maskowania danych
W przypadku aplikacji webowych opartych o framework Symfony3 lub Symfony4 z pomocą przychodzi bundle: https://packagist.org/packages/superbrave/gdpr-bundle.
Używając dostarczonego serwisu anonimizującego i adnotacji możemy w prosty sposób wskazać własności encji, które mają zostać zanonimizowane. Bundle posiada co prawda wbudowane metody anonimizacji danych dla różnych typów danych np. dla adresów IP, dat, kolekcji danych ale pozwala również na obsługę własnych klas anonimizujących.
W jednym z naszych projektów wykorzystaliśmy opisane wyżej podejście i przygotowaliśmy trzy dedykowane anonimizery danych:
- EmailAnonymizer
- MaskAnonymizer
- ZipCodeAnonymizer
Anonimizery maskują dane w taki sposób, że jedynie ich rzeczywisty posiadacz jest w stanie z dużą dozą prawdopodobieństwa ocenić, czy zamaskowane dane należą właśnie do niego.
Przykłady
<?php
declare(strict_types=1);
namespace LSB\UtilBundle\Anonymizer;
use Superbrave\GdprBundle\Anonymize\Type\AnonymizerInterface;
/**
* Maskowanie stringów
*
* Class MaskAnonymizer
* @package LSB\UtilBundle\Anonymizer
*/
class MaskAnonymizer implements AnonymizerInterface
{
/**
* {@inheritdoc}
*/
public function anonymize($propertyValue, array $options = [])
{
return $this->maskProperty($propertyValue);
}
/**
* @param $propertyValue
* @param string $maskChar
* @param int|null $prefixLength
* @return string|null
*/
private function maskProperty($propertyValue, string $maskChar = '*', ?int $prefixLength = null): ?string
{
if (!$propertyValue) {
return null;
}
try {
$propertyValue = (string) $propertyValue;
} catch (\Exception $e) {
return str_repeat($maskChar, rand(5, 15));
}
//Sprawdzamy długość ciągu
$propertyLength = mb_strlen($propertyValue);
if ($prefixLength !== null && intval($prefixLength)) {
$showChars = (int) $prefixLength;
} elseif ($propertyLength < 3) {
$showChars = 0;
} elseif ($propertyLength < 8) {
$showChars = 2;
} else {
$showChars = 3;
}
if ($showChars) {
$prefix = mb_substr($propertyValue, 0, $showChars);
} else {
$prefix = '';
}
$maskLength = rand(($propertyLength > 4 ? $propertyLength - 3 : $propertyLength), ($propertyLength + 1));
$mask = str_repeat($maskChar, $maskLength);
return $prefix.$mask;
}
}
Konfiguracja nowego anonimizera w pliku services.yml:
LSB\UtilBundle\Anonymizer\MaskAnonymizer:
tags:
- { name: superbrave_gdpr.anonymizer, type: mask }
Po instalacji bundla i zadeklarowaniu własnych klas anonimizujących, możemy w encjach użyć adnotacji z własnym typem np. mask: @GDPR\Anonymize(type="mask"):
@GDPR\Anonymize (type = "mask")
Przykład użycia adnotacji w encji:
/**
* @var string
* @Assert\Length(max=255)
* @ORM\Column(type="string", name="customer_address", length=255, nullable=true)
* @GDPR\Anonymize(type="mask")
*/
protected $customerAddress;
W ten sposób możemy wskazać własności encji, która ma podlegać anonimizacji. Anonimizacja nie przebiega w sposób całkowicie automatyczny i konieczne jest ręczne wywołanie metody anonymize() dostępnej w ramach serwisu “superbrave_gdpr.anonymizer” i przekazanie obiektu, który chcemy zanonimizować.
Przykład użycia serwisu w ramach akcji controllera dla posiadanego obiektu klasy Order:
$anonymizer = $this->get('superbrave_gdpr.anonymizer')
$anonymizer->anonymize($order);
W ten sposób zanonimizowane dane pozostaną bezpieczne nawet w przypadku zaindeksowania lub wycieku “bezpiecznego” adresu URL.
Ważną cechą anonimizera jest fakt, że obsługuje on obiekty powiązane, dlatego nawet obiekty zależne lub kolekcje obiektów zależnych naszej encji mogą zostać w razie potrzeby automatycznie zanonimizowane jeżeli tylko ich własności zostaną opatrzone specjalną adnotacją (@GDPR\Anonymize)
Zarówno w przypadku REST API jak i klasycznych aplikacji webowych z mechanizmami szablonów TWIG opisane wyżej podejście gwarantuje znaczne poprawienie bezpieczeństwa danych użytkowników i chroni ich prywatność przed niezamierzonym dostępem.
Zamaskowana odpowiedź w formacie JSON

Masz pomysł? Porozmawiajmy