Jak pomoc organistce skończyła się zgłoszeniem CVE, czyli o krytycznej dziurze w popularnym oprogramowaniu dla parafii

1 miesiąc temu 14

Czasem najlepsze historie zaczynają się niewinnie. Jeden z naszych Czytelników poproszony o pomoc przez znajomą organistkę przy wdrożeniu programu FARA – popularnego oprogramowania do zarządzania parafią – nie spodziewał się, że to, co odkryje, zaprowadzi go na wojenną ścieżkę z producentem i skończy się oficjalnym zgłoszeniem podatności CVE koordynowanym przez CERT Polska. Z tej historii dowiecie się, jak dziwnie niektórzy z producentów oprogramowania potrafią zareagować na zgłaszane błędy i jak tego typu reakcje mogą narazić na niebezpieczeństwo tysiące osób.

Autorem niniejszego artykułu jest nasz Czytelnik Mateusz Sirko, który chciał podzielić się z nami swoją dość ciekawą historią. Do nadesłanego materiału wprowadziliśmy drobne redakcyjne poprawki. Jeśli i Ty masz jakąś historię, którą chciałbyś się podzielić z innymi, daj nam znać.

Backdoor w prezencie, czyli 1-skrypt.php

Program FARA, używany w wielu polskich parafiach do zarządzania danymi wiernych, intencjami mszalnymi czy cmentarzami, do pełnej funkcjonalności wymaga od swoich klientów umieszczenia na serwerze WWW specjalnego pliku: 1-skrypt.php. Mateusz, analizując ten skrypt, od razu zauważył, że jest to tykająca bomba.

Skrypt ten, po umieszczeniu na serwerze parafii (często pod nazwą fara_iwg.php), stawał się otwartą furtką dla każdego, kto znał jego adres. A znalezienie go nie było trudne – wystarczyło skorzystać z publicznie dostępnych list parafii.

Grzebiemy głębiej – dekompilacja i twarde hasła

To jednak był dopiero początek. Skoro tak prosty skrypt był tak niebezpieczny, co mogło kryć się w samej aplikacji desktopowej? Mateusz postanowił to sprawdzić. Okazało się, że program FARA napisano w dość leciwym już Visual FoxPro. Użycie narzędzi do inżynierii wstecznej, takich jak ReFox, pozwoliło zajrzeć do kodu źródłowego aplikacji bez większego wysiłku. A w środku czekały prawdziwe skarby. Przyjrzyjmy się konkretnym fragmentom kodu, które stanowiły zagrożenie.

Podatność #1: Backdoor na życzenie klienta

Wspomniany skrypt 1-skrypt.php zawierał funkcję upload(), która była esencją problemu. Pozwalała ona na wgranie na serwer dowolnego pliku, bez żadnej weryfikacji jego typu, rozmiaru czy zawartości.

function upload () { if (!empty($_FILES)) { $destDir = ($_POST['class']=='pictures') ? PICTURES : (($_POST['class']=='sectors') ? MAPS_SECTORS : (($_POST['class']=='map') ? MAPS : '')); if (!$destDir) return ['result' => CLASS_FAILED]; foreach ($_FILES as $key => $file) { if ($file['error'] == UPLOAD_ERR_OK) { $uploadPath = $destDir . '/' . basename($file['name']); if (!move_uploaded_file($file['tmp_name'], $uploadPath)) { return ['result' => UPLOAD_FAILED, 'file' => $file['name']]; } } else { return ['result' => UPLOAD_FAILED, 'file' => $file['name']]; } } return ['result' => UPLOAD_SUCCESS]; } return ['result' => UPLOAD_FAILED, 'file' => '']; }

Taki kod to otwarta prośba o kłopoty. Atakujący mógł wgrać na serwer parafii web shella w PHP i uzyskać pełną kontrolę nad stroną, danymi i całym serwerem.

Podatność #2: Klucze do królestwa na serwerze FTP

Analiza zdekompilowanej aplikacji FARA ujawniła zaszyte na stałe w kodzie dane dostępowe do serwera FTP producenta.

DEFINE CLASS DataExporter AS csCustom ... serverurl = "ftp.signum-net.pl" serverusername "signum-net" serverpsw = "***slerFanderjA7go" serverdir 'test ek'

Te dane pozwalały na zalogowanie się na serwer FTP z prawami zapisu. To stąd program FARA pobierał aktualizacje. Atakujący mógł podmienić pliki aktualizacji na własne, zainfekowane złośliwym oprogramowaniem, które następnie zostałoby automatycznie pobrane i zainstalowane na komputerach we wszystkich parafiach korzystających z oprogramowania.

Atakowanie i Ochrona Aplikacji Webowych szkolenieJesteś programistą i chciałbyś bezpieczniej tworzyć kod swoich aplikacji webowych? A może chciałbyś nauczyć się wyszukiwać błędy w cudzych aplikacjach? Jeśli tak, to wpadnij na nasze bestsellerowe szkolenie z Atakowania i Ochrony Webaplikacji. To dwa dni solidnych labów, które pomogą Ci podnieś bezpieczeństwo Twoich aplikacji webowych. Najbliższe terminy poniżej, ale możemy też zorganizować takie szkolenie u Ciebie w firmie.

Kraków: 20-21 listopada 2025r. — zostało 5 wolnych miejsc
Ostatnio ktoś zarejestrował się 16 października 2025r. → zarejestruj się na to szkolenie

    2444 PLN netto (do 7 listopada)
    2744 PLN netto (od 8 listopada)

ZDALNIE: 24-25 listopada 2025r. — zostało 6 wolnych miejsc
Ostatnio ktoś zarejestrował się 23 października 2025r. → zarejestruj się na to szkolenie

    2444 PLN netto (do 7 listopada)
    2744 PLN netto (od 8 listopada)

ZDALNIE: 🇬🇧 04-05 grudnia 2025r. — zostało 9 wolnych miejsc
Ostatnio ktoś zarejestrował się 20 października 2025r. → zarejestruj się na to szkolenie

    2444 PLN netto (do 14 listopada)
    2744 PLN netto (od 15 listopada)

Warszawa: 11-12 grudnia 2025r. — zostało 9 wolnych miejsc
Ostatnio ktoś zarejestrował się 23 października 2025r. → zarejestruj się na to szkolenie

    2444 PLN netto (do 14 listopada)
    2744 PLN netto (od 15 listopada)

Wrocław: 15-16 grudnia 2025r. — zostało 9 wolnych miejsc
Ostatnio ktoś zarejestrował się 23 września 2025r. → zarejestruj się na to szkolenie

    2444 PLN netto (do 14 listopada)
    2744 PLN netto (od 15 listopada)

Podatność #3: Jedno hasło, by wszystkimi rządzić

Najpoważniejszym odkryciem były jednak uniwersalne, zaszyte w kodzie hasła do lokalnych baz danych SQLite, w których przechowywane były wrażliwe dane parafian. Każdy klient używał tych samych haseł.

IF NOT. dbmdest.execute("attach database "+dbmdest.textutf8(pathsource+"fara5.db")+" as sourceData key "+"****jkOk23S_@!ko%fG826K9Lo)OU_S#"+"'")

W innym miejscu można było znaleźć kolejne:

IF NOT. dba.dbconnect (cdbfile, , "Hk98!34bv%$2w105U*20013#****")

Posiadając te hasła, każdy, kto uzyskałby dostęp do pliku bazy danych (np. przez fizyczny dostęp do komputera w kancelarii parafialnej lub zdalnie, po infekcji innym wirusem), mógł bez problemu odszyfrować całą jej zawartość.

“Pan popełnił przestępstwo!” – czyli komunikacja w stylu oblężonej twierdzy

Z taką wiedzą Mateusz postanowił postąpić zgodnie ze sztuką i zgłosić problem producentowi. 17 lutego wysłał maila. Odpowiedzi nie było. Po kilku dniach zadzwonił – rozmowa była nieprzyjemna, a zagrożenie zostało zbagatelizowane. Wobec takiej postawy, sprawa trafiła do CERT Polska. Reakcja producenta na kontakt ze strony CERT Polska była następująca:

  1. Całkowite zaprzeczenie. Na pierwszą wiadomość od CERT, informującą o zgłoszeniu, producent odpowiedział krótko: “Nie potwierdzamy istnienia zgłoszonych podatności
  2. Atak na znalazcę. Gdy CERT Polska, po własnej analizie, potwierdził istnienie luk i przesłał szczegółowy raport, producent zamiast odnieść się do meritum, zaatakował znalazcę, zarzucając mu popełnienie przestępstwa: “program ReFox użyty przez Państwa dokonuje deszyfracji i dekompilacji kodu wynikowego aplikacji […] podczas gdy umowa licencyjna, którą Państwo zaakceptowaliście […] wyraźnie tego zabrania, co oznacza, iż dostarczony mi raport powstał na drodze popełnienia przestępstwa.

CERT Polska cierpliwie wyjaśnił, że jako narodowy zespół CSIRT działa na podstawie Ustawy o krajowym systemie cyberbezpieczeństwa, która uprawnia go do prowadzenia takich badań. Instytucja przypomniała, że jest uprawniona do przeprowadzania takich badań na podstawie art. 33 pkt 1 ustawy o krajowym systemie cyberbezpieczeństwa…

Była też Dezorientacja i kwestionowanie procedur. Producent oprogramowania, pomimo jasnych wyjaśnień ze strony CERT Polska, zdawał się być całkowicie zdezorientowany co do roli CERT jako koordynatora oraz podważał podstawy prawne ich działań. Znacząco wydłużył proces usuwania krytycznych zagrożeń. CERT Polska musiał po raz kolejny wyjaśniać, że działa jako koordynator (CNA – CVE Numbering Authority), będący zaufanym pośrednikiem między znalazcą a producentem. Podkreślił również kluczową zasadę, że dla procesu koordynowanego ujawniania podatności nie ma znaczenia, czy zostały one  nalezione w sposób zgodny z prawem, czy nie, ponieważ nie ma to wpływu na istnienie tych słabości.

Naprawa (w bólach)

Po długiej i trudnej koordynacji prowadzonej przez CERT Polska, producent przystąpił na szczęście do naprawy błędów.

  1. Problem FTP został rozwiązany po stronie serwera – producent zmienił hasło dostępowe, co uniemożliwiło dalsze logowanie przy użyciu danych z aplikacji.
  2. Problem haseł do baz danych wymagał większych zmian. W poprawionej wersji 5.0.80.35 zrezygnowano ze statycznych haseł. Zamiast tego program generuje unikalne hasło dla każdej
    instalacji na podstawie numeru seryjnego maszyny. Dodatkowo wprowadzono zabezpieczenia przed debugowaniem aplikacji w locie, co utrudnia jej dalszą analizę.

Proces łatania dziur był długi i wyboisty. Trwał od marca do końca czerwca. Pierwsza “poprawiona” wersja, którą producent udostępnił, nadal zawierała hardkodowane hasła do baz danych, jedynie przeniesione w inne miejsce. Co więcej, podczas dogłębnej analizy tej wersji, Mateusz natknął się na intrygujący, zakodowany ciąg Base64 – T2Jhd2lhbSBzacSZLCDFvGUgamVzdGXFmyB3IGR1cGll. Sami sobie sprawdźcie co to znaczy :)

Ten niecodzienny, zaszyty w aplikacji komunikat był jak środkowy palec, który z kpiną przypominał o trudach kontaktu z producentem (a może jego programistą?). Ta mała, zuchwała niespodzianka utwierdziła Mateusza w przekonaniu, że sprawa wymaga dalszego nacisku. Dopiero po kolejnych interwencjach i szczegółowych raportach ze strony znalazcy, podatności zostały ostatecznie usunięte w wersji 5.0.80.35. Ostatecznie, 21 lipca 2025 roku, po ponad pięciu miesiącach od pierwszego zgłoszenia, opublikowano oficjalny identyfikator podatności: CVE-2025-4049, opisujący problem z hardkodowanymi hasłami do baz SQLite.

Co dalej? Cisza w eterze…

Mogłoby się wydawać, że opublikowanie CVE kończy sprawę. Nic bardziej mylnego. Mimo załatania krytycznej luki, kluczowy element układanki wciąż pozostaje nierozwiązany: komunikacja z klientami. Zgodnie z ostatnią korespondencją w tej sprawie, producent do końca nie poinformował swoich klientów o zaistniałym incydencie ani o dostępności krytycznej aktualizacji.

Taka postawa uniemożliwia parafiom, które korzystają z oprogramowania, przeprowadzenie własnej, wewnętrznej analizy ryzyka. Nie wiedzą, czy ich dane były zagrożone i jakie kroki powinny podjąć, aby zabezpieczyć się na przyszłość. Sprawę pogarsza fakt, że w umowie licencyjnej oprogramowania FARA próżno szukać zapisów o odpowiedzialności producenta za tego typu incydenty bezpieczeństwa.

Klienci zostali pozostawieni sami sobie. Skala potencjalnego zagrożenia była ogromna. Dostęp do serwera FTP, z którego pobierane były aktualizacje, oznaczał, że atakujący mógł w każdej chwili podmienić oficjalną wersję programu na zainfekowaną. Taki złośliwy update trafiłby do wszystkich użytkowników, co mogło spowodować masowy wyciek danych osobowych z ponad 4 tysięcy parafii w całej Polsce.

Od redakcji

Historia programu FARA to przykład tego, że nie wszystkie reakcje na zgłoszenie błędu są idealne. Lekceważenie problemu, atakowanie osoby, która go zgłasza czy opieszałość w działaniu to prosta droga do utraty zaufania klientów i poważnych konsekwencji, nie tylko wizerunkowych. Dzięki determinacji jednej osoby i interwencji CERT Polska udało się załatać dziury, które mogły prowadzić do masowego wycieku wrażliwych danych. Szacuneczek dla Mateusza za wytrwałość.

Pozostaje mieć nadzieję, że producent wyciągnął z tej lekcji wnioski na przyszłość, a jego klienci zaktualizowali oprogramowanie do bezpiecznej wersji. Historia ta jest przypomnieniem, że nawet najbardziej prozaiczne prośby o pomoc mogą prowadzić do odkrycia poważnych luk w zabezpieczeniach. I że w wielu popularnych aplikacjach takie luki wciąż się znajdują — dlatego warto nauczyć się jak je skutecznie namierzać i usuwać.


Aktualizacja (03.10.2025 21:14)
Producent aplikacji FARA podesłał nam takie oświadczenie, publikujemy je w całości:

Opisany przez Państwa plik fara_iwg.php (kod pod treścią) to zwykły endpoint, służący do przesyłania na serwer klienta zdjęć nagrobków z cmentarza, które wcześniej zostały zarejestrowane w bazie danych – jest on umieszczany ręcznie przez klienta na jego serwerze, a nie naszym i ma generowany przez program unikalny dla klienta token. Nie może on prowadzić do żadnych wycieków (choć czytelnicy i tak wiedzą swoje). Na naszym serwerze jest kopia tego skryptu, dla naszych celów testowych. Ten skrypt nie jest backdoorem. Na naszym serwerze nie ma danych klientów – FARA nie jest aplikacją webową, tylko desktopową i baza danych znajduje się na komputerze klienta, a nie na serwerze WWW. Po włamaniu się na serwer atakujący mógł umieścić na nim co chciał, ale to jednak za mało. Pobierana przez program FARA aktualizacja jest skryptem, uruchamianym przez Farę i posługującym się jej wewnętrznymi stałymi, zmiennymi i obiektami – żeby wykorzystać to do włamania do bazy i wycieku danych trzeba by najpierw dobrze poznać cały kod źródłowy i odpowiednio kod aktualizacji spreparować – marne szanse, ale oczywiście teoretycznie jest to możliwe.
Easteregg o treści „Obawiam się, że jesteś w dupie” jest przeznaczony dla hackera, próbującego dekodować stringi w pliku EXE i szukać tam haseł – nie jest to Wersal, ale celem takich wstawek nie jest demonstracja kultury tylko zniechęcenie włamywacza. Moja kultura osobista zależy od tego, jak wobec mnie zachowuje się druga strona – staram się dopasować do jej poziomu. Ideałem w tej materii nie jestem i nie będę.
Państwa informator stosował wobec mnie dość pretensjonalny i pełen wyższości ton – raczej odrzucający, niż zachęcający do jakieś wymiany zdań. Pomoc organistce ?????
Kod opisanego przez Państwa pliku fara_iwg.php:

define ("API_TOKEN", "tu jest token generowany u klienta"); if (!isset ($_POST['token']) or $_POST['token'] !== API_TOKEN) die(); if (!isset ($_POST['key'])) die(); if (!isset ($_POST['action'])) die(); define ("PERMISSIONS", 0755); define ("DIR", $_POST['key']); define ("MAPS_SECTORS", DIR."/mapa_podklady"); define ("MAPS_SECTORS_THUBNAILS", DIR."/mapa_podklady/cmentarz"); define ("MAPS", DIR."/mapa"); define ("PICTURES", DIR."/foto"); define ("CSS", DIR."/css"); define ("SETUP_FAILED", -1); define ("SETUP_SUCCESS", 0); define ("CLASS_FAILED", -2); define ("UPLOAD_FAILED", 0); define ("UPLOAD_SUCCESS", 1); define ("NO_FILE", -3); define ("FILES_IDENTICAL", 1); define ("FILES_NOT_IDENTICAL", 0); header('Content-Type: application/json'); $controller = new FARA_IWG (); if (($_POST['action']=="reinstall") or !is_dir (DIR)) { if (!$controller->install()) { $data = ["result" => SETUP_FAILED]; echo json_encode($data); return; } } if ($_POST['action']=="compare") { echo json_encode ($controller->filesAreIdentical()); return; } if ($_POST['action']=="upload") { echo json_encode ($controller->upload()); return; } class FARA_IWG { function install () { $dirs = array (DIR, MAPS_SECTORS, MAPS_SECTORS_THUBNAILS, MAPS, PICTURES, CSS); foreach ($dirs as $dirName) { if (!is_dir ($dirName)) if (!mkDir ($dirName, PERMISSIONS)) return false; if (!file_exists ($dirName."/index.php")) file_put_contents ($dirName."/index.php", "<?php // Silence is golden.?>"); } return true; } function filesAreIdentical () { $destDir = ($_POST["class"]=="pictures") ? PICTURES : (($_POST["class"]=="sectors") ? MAPS_SECTORS : (($_POST["class"]=="map") ? MAPS : "")); if (!$destDir) { $data = ["result" => CLASS_FAILED]; return $data; } $filePath = $destDir . '/' . $_POST["filename"]; if (!file_exists ($filePath)) { $data = ["result" => NO_FILE]; return $data; } $data = ["result" => ($_POST["hash"] === hash_file('sha256', $filePath)) ? FILES_IDENTICAL : FILES_NOT_IDENTICAL]; return $data; } function upload () { if (!empty($_FILES)) { $destDir = ($_POST["class"]=="pictures") ? PICTURES : (($_POST["class"]=="sectors") ? MAPS_SECTORS : (($_POST["class"]=="map") ? MAPS : "")); if (!$destDir) { $data = ["result" => CLASS_FAILED]; return $data; } foreach ($_FILES as $key => $file) { if ($file['error'] == UPLOAD_ERR_OK) { $uploadPath = $destDir . '/' . basename($file['name']); if (!move_uploaded_file($file['tmp_name'], $uploadPath)) { $data = ["result" => UPLOAD_FAILED, "file" => $file['name']]; return $data; } } else { $data = ["result" => UPLOAD_FAILED, "file" => $file['name']]; return $data; } } $data = ["result" => UPLOAD_SUCCESS]; return $data; } $data = ["result" => UPLOAD_FAILED, "file" => ""]; return $data; } }

Informacja o krytycznej aktualizacji od dawna znajduje się na naszej stronie (https://fara.pl/fara-aktualizacje-2/)

Przeczytaj źródło