Wyjaśnienie konkursu od Gynvael Coldwind

12

(Właściwie nie mam pomysłu do jakiego działu to wrzucić)

Kilka osób było zainteresowanych, więc bardzo proszę.

Disclaimer: przedstawiam rozwiązania z punktu widzenia kogoś, kto zna się na kryptografii, trochę zna się na ogólnie rozumianym bezpieczeństwie, natomiast na pewno nie zna się na javascript. Dla mnie właśnie to stanowiło największy problem, większość czasu spędziłem grzebiąc w dokumentacji js. Smutne. Nie odpowiadam zatem za błędy merytoryczne związane z js. Mimo wszystko uważam, że moje rozwiązania pokazują ciekawe podejście, tzn okazuje się, że bez znajomości języka można zrobić dość zaawansowaną analize kodu w tym języku i coś w stylu reverse engineer, co na pewno łatwe nie jest.

Zachęcam do zrobienia tego samodzielnie, dużo funu jest moim zdaniem w takich zagadkach, więc jeśli ktoś w ogóle tego nie ruszał, to niech chociaż spróbuje.

CZĘŚĆ 1: http://gynvael.coldwind.pl/xacrackme/
Można to rozwiązać co najmniej na 3 sposoby (a przynajmniej ja na 3 sposoby wpadłem). Przedstawię rozwiązanie najszybsze, najsprytniejsze.
Zobaczmy co dzieje się od samego początku, tj. od momentu kliknięcia przycisku Submit/Prześlij

1. form.addEventListener('submit', function (e) {
2.           e.preventDefault();
3.           password = getPass().toLowerCase();
4.           if (sha256(password) === HASH) {
5.             console.info('Valid password: %s', password);
6.             alert('Success!');
7.             location.href = password + "k.html";
8.           } else {
9.             error.classList.add('visible');
10.            clearTimeout(tid);
11.            tid = setTimeout(function () {
12.              error.classList.remove('visible');
13.            }, 1500);
14.            console.warn('Wrong password: %s', password);
15.          }
16.        }

Widzimy, że wielkość liter w haśle prawdopodobnie nie ma znaczenia. Nie wiemy jeszcze co robi funkcja getPass, nie jest to natomiast nam potrzebne, zresztą tak samo jak ta uwaga (co okaże się poźniej), ale jest ona potencjalnie bardzo ważna, dlatego o tym wspominam.
Można przypuszczać, że stroną docelową, naszym celem jest location.href. Hasło sprawdzane jest w sposób tradycyjny: przechowywany jest hasz hasła, poźniej haszowane jest wpisane hasło (w tym wypadku wynik funkcji getPass) i owe hashe są porównywane. Hash jednak jest zazwyczaj problemem nie do przeskoczenia.
const HASH = "aaff6f579e5125ce8c8824b9dbc4c2bec20727f0558b914bd6ee910e1b3348ce";
Mimo wszystko spróbujmy szczęścia. Oczywiście istnieją przecież jakies "bazy danych" dla sha256...
No i okazuje się, że https://crackstation.net/ jest przekonany, że odpowiedzią jest basilis.
location.href = password + "k.html";
Sprawdźmy zatem: http://gynvael.coldwind.pl/xacrackme/basilisk.html
Bingo. Jeśli ktoś w tym momencie zwątpił w bezpieczeństwo sha256 i swoich haseł, to uspokajam.
Trochę o samych funkcjach haszujących:
funkcja haszująca to najczęsciej funkcja z jakiegoś zbioru X w skończony podzbiór liczb naturalnych P z założeniem, że moc zbioru X jest sporo większa od mocy zbioru P. X'em może być cokolwiek - inne liczby, stringi, hasła. Proste funkcje hashujące są używane w tablicach haszujących (std::unordered_map w C++), natomiast sha2 jest przykładem funkcji haszującej kryptograficznie bezpiecznej. Co to znaczy? To, że funkcja ta może być uważana za "jednokierunkową", tzn łatwo jest obliczyć sha(jakis_string), natomiast mając wynik tej operacji odzyskanie jakis_string jest praktycznie niemożliwe. Dlatego jest to bezpieczne. Matematycznie ściśle mówiąc, nie wiemy, czy jest to funkcja jednokierunkowa, nie wiemy w ogóle, czy takowe istnieją. W praktyce były przypadki wykazania słabości funkcji kiedyś uważanych za kryptograficznie bezpieczne (md2, md4, md5, sha1). sha2 (sha256 i sha512) jednak trzyma się dobrze.
Mamy 3 podstawowe sposoby przechowywania haseł użytkowników.

  • sposób bardzo głupi: plaintext. Niestety część serwisów dalej z tego korzysta (raz miałem sytuację, w której zapomniałem hasła i wysłali mi je na maila Oo)
  • sposób głupi: trzymanie haszy haseł. Jasne jest jak weryfikujemy poprawność hasła, którym próbuje uwierzytelnić się użytkownik. Nie jest to jednak najlepszy sposób z dwóch powodów (a przynajmniej dwa w tym momencie przychodzą mi na myśl). Po pierwsze - słabe, krótkie hasła można złamać korzystając chociażby z tej bazy, którą wykorzystałem do złamania etapu pierwszego tego konkursu. Po drugie - mając identyczne hasze wiemy, że hasła również są identyczne. Tutaj dorzucamy troche heurystyki, jakieś statystyki najpopularniejszych haseł i już.
  • sposób mądry (trochę w uproszczeniu): dla każdego hasła p trzymamy unikalny ciąg "losowych" znaków s. Tak zwyczajnie, w plaintexcie. Obok tego trzymamy hasz z h + s. Teraz kiedy użytkownik się loguje, doklejamy s do jego hasła i sprawdzamy się się zgadza. Bardzo proste, a eliminuje dwa najpoważniejsze słabości metody powyżej.
    Każdy poważny serwis / os / program / cokolwiek używa trzeciego sposobu.

Wracając do konkursu.
CZĘŚĆ 2:
Tak jak wcześniej, spróbujmy analizować wykonanie kodu od momentu wciśnięcia "Decode".

var decodingFn = function () {};

1. document.querySelector('form').addEventListener('submit', function (e) {
2.             e.preventDefault();
3.             runDecoding();
4.           }

1.       function runDecoding() {
2.         createDecodingFn();
3.         decodingFn();
4.       }

1.       function createDecodingFn() {
2.         try {
3.           eval(XORCipher.decode(document.querySelector(p2.value).innerHTML.substr(0,13) + '...', SECRET1));
4.         } catch(e) {
5.           window.decodingFn = function () {}
6.         }
7.       }

Widzimy ciekawą rzecz. Funkcja decodingFn nie jest explicite podana, jest tworzona dynamicznie (createDecodingFn). Najbardziej interesuje nas zatem:

eval(XORCipher.decode(document.querySelector(p2.value).innerHTML.substr(0,13) + '...', SECRET1))

Funkcja eval wykonuje kod, jaki dostała jako argument. Można zatem przypuszczać, że będzie to coś w stylu window.decodingFn = function () {[...]. Okej, wiemy mniej więcej co ma być argumentem. Lecimy dalej.
http://gynvael.coldwind.pl/xacrackme/XORCipher.js
Z tego potrzebne będzie nam jedynie to, że pierwszym argumentem funkcji z XORCipher jest klucz do szyfrowania / odszyfrowania.

XORCipher.decode(document.querySelector(p2.value).innerHTML.substr(0,13) + '...', SECRET1)

Z tego co zrozumiałem, funkcja querySelector "chwyta" obiekt, którego id, nazwe, cośtam podaliśmy jako argument i nam go oddaje. Mały research i mamy http://www.w3schools.com/jquery/jquery_ref_selectors.asp
W polu p2 (pass 2) musi być zatem jakiegoś typu selector. Można strzelać, że chodzi o wybranie konkretnego elementu po jego id, sensownie to brzmi. Kiedy już mamy nasz obiekt, robimy na nim innerHTML. Działa to chyba troche tak, jakbyśmy brali kod HTML, który jest pod spodem naszego obiektu (który tworzy, definiuje ten obiekt). To ma sens. Próbujemy, odpalamy konsole w przeglądarce, w pass 2 wklepujemy jakiś id-selector, strzelamy i dla wartości "#legend": bingo2. Mamy jedno hasło i funkcję dekodującą.

1. window.decodingFn = function() {
2.   try { decodedData = self[p4.value](basilisk, p3.value) || []; }
3.   catch(e) { decodedData = []; }
4. 
5.   ctx2.clearRect(0, 0, C_SIZE, C_SIZE);
6. 
7.   if (XORCipher.encode(document.title, genKey()) === SECRET2) { yaaay(); }
8.   else { boooo(); }
9. }

W sumie dalej nie rozumiem linii 2,3, tego self, skąd ono się wzięło i dlaczego jest tablicą(?), więc uznałem, że zajmę się linią 7. A ta jest wyjątkowo prosta i niesie dużo informacji.

1.       function genKey() {
2.         if (!arguments.length) {
3.           return [arguments.length === 0, p2.value, p3.value, p4.value].join(':');
4.         }
5.       }

arguments też nie rozumiem, ale ważne jest to, że wynikiem wywołania tej funkcji jest coś, z czego łatwo odzyskamy p3.value, p4.value. Tutaj wystarczy nie być łomem z matematyki, żeby zauważyć, że wystarczy po prostu wykonać komende

XORCipher.decode(document.title, SECRET2)

żeby odzyskać zakodowany "poprawny" wynik funkcji genKey. No i mamy:
"true:#legend:btoa:decodeImage"
Mamy hasło trzecie i hasło czwarte. Prawie koniec. Poszukajmy gdzie używane jest hasło 1.

ctx2.globalAlpha = +p1.value || 0;

strzelam, że chodzi o jakąś transparencje, czysto techniczna rzecz związana z rysowaniem obrazu. Ustawiam na 1. Bo tak mi się wydaję.
Działa? Działa. Mamy bazyliszka. Rzeczywiście najładniejszy to on nie jest ;D

wołam @Shalom @Rev @msm @Gynvael Coldwind @Hepek @TLesiu @Sh4rk1 bo chyba zainteresowani.

0

dobra robota @pingwindyktator :)

0

@winerfresh

  1. "Żadna funkcja ze zbioru nieskończonego na skończony nie może być funkcją odwracalną. "
    To racja - niestety tutaj prawdziwość Twoich wywodów się kończy. Dawno nie rozdziabiłem tam bardzo ust czytając coś, czego nie pisał anonimowy burak / troll.

1.1 " To jest matematycznie niemożliwe ze względu na zasadę szufladkowania Dirichleta."
Bzdury - zasada ta mówi WYŁĄCZNIE o zbiorach skończonych.

  1. "Jeśli jesteś w stanie mi udowodnić, że jest inaczej, to chętnie to zobaczę."
    To coś w stylu "jeśli jesteś w stanie udowodnić, że 2+2 != 4 w ciele liczb rzeczywistych z tradycyjnym dodawaniem i mnożeniem, to chętnie to zobaczę". Bezsens kompletny.

  2. "ta funkcja jest jednokierukowa", "wiemy, że są jednokierunkowe i wiemy, że takowe funkcje istnieją".
    Uściślając: funkcja jednokierunkowa f: X -> Y to taka funkcja, której wartość f(x) znajdziemy w czasie wielomianowym dla każdego x, natomiast znalezienie wartości f^(-1)(y) wymaga czasu dłuższego niż wielomianowy ("dłuższego" w rozumieniu notacji Theta) dla dowolnego y. Twierdzisz, że takie funkcje istnieją. Jeśli potrafisz - udowodnij proszę.
    https://en.wikipedia.org/wiki/P_versus_NP_problem
    Teza istnienia takowych funkcji implikuje wprost, że P != NP. Zgłoś się po swoje milion dolarów.
    https://en.wikipedia.org/wiki/Millennium_Prize_Problems
    Niestety kontr-dowodu nie mam. Bo nie istnieje. Cały świat matematyków nie wie czy takie funkcje są.
    wiki: "If f is a one-way function, then the inversion of f would be a problem whose output is hard to compute (by definition) but easy to check (just by computing f on it). Thus, the existence of a one-way function implies that FP≠FNP, which in turn implies that P≠NP. However, it is not known whether P≠NP implies the existence of one-way functions."
    "The existence of such one-way functions is still an open conjecture."
    https://en.wikipedia.org/wiki/One-way_function

Poza tym - mieszasz terminy. funkcja nieodwracalnej to funkcja nie będąca bijekcją, czyli funkcja, której odwrotność nie istnieje. Jeżeli chcesz podać mi przykład takiej funkcji to nie, dzięki.

  1. "Przetrzymywanie (nawet kryptograficznych) szybkich haszy haseł to zły pomysł. Od tego mamy osobną grupę funkcji o nazwie KDF (też hashe, ale o dodatkowych właściwościach, np. są bardzo wolne)." twierdzisz, że sha2 jest jednokierunkowe, ale z jakiegoś powodu powinno używać się czegoś innego do przechowywania haseł w bazie danych. Dlaczego? Coś tu nie gra.

wołam @bogdans bo z tego co zdążyłem zauważyć, to znasz się na matematyce. Może się zainteresujesz.

1

@pingwindyktator

1.1 Zasada ta mówi o czymkolwiek, zwłaszcza o liczbach kardynalnych.
2. i 3. Funkcja jednokierunkowa znaczy, że nie istnieje funkcja do niej odwrotna (a nie nic o złożoności obliczeniowej tejże, a nawet jeśli, to matematyka dalej temu przeczy). Przykładem takiej funkcji jest funkcja stała, signum, etc. Każda z nich ma taką samą własność jak każda funkcja skrótu - zamienia wartość ze zbioru nieskończonego w skończony. Jeśli za to jest milion dolarów, to ja chętnie.
4. scrypt, bcrypt, a jeśli bozia tamtych nie dała, to od biedy może być PBKDF2: https://gist.github.com/paragonie-scott/e9319254c8ecbad4f227#i-general-mistakes lub http://codahale.com/how-to-safely-store-a-password/

PS Mimo wszystko gratulacje za rozwiązanie.

0

@winerfresh koleś, ja mówie o czymś zupełnie innym. Nie ma czegoś takiego jak funkcja jednostronna chryste panie. Mówię o funkcji ** jednokierunkowej **, której definicję Ci przedstawiłem.

  1. zgadzam się, że istnieją lepsze metody przechowywania haszy, natomiast po pierwsze nie powiesz mi, że sha2 + salt jest głupie, po drugie chciałem odnieść się konkretnie do sha2, coby nie okazało się, że jest durnym wymysłem (bo przecież "złamałem" w 10 sekund).
1

Krótkie sprostowanie:
Organizatorem konkursu (i jednocześnie fundatorem nagród, które już były rozdane przedwczoraj) jest Xa (Sebastian Rosik), czyli autor okładki.
Więc technicznie to jest "Wyjaśnienie konkursu od Xa" ;)

0

Tak w zasadzie to organizatorem lub współorganizatorem konkursu jest chyba firma RST (rst.com.pl), ponieważ to jej adres e-mail był podany jako kontaktowy ;) Kto wysłał wiadomość z rowiązaniem ten zapewne już dostał informację zwrotną, w tym i ja ;), lub niebawem dostanie, że rozwiązaniem jest w.w obrazek z bazyliszkiem, a nagrody dostało pierwsze 15 osób. Niestety ja się nie załapałem ;)

1 użytkowników online, w tym zalogowanych: 0, gości: 1