de Haseeb Qureshi

De data asta a trebuit să-mi sparg propria parolă Reddit

(Oarecum.)

De data asta a trebuit sa mi sparg propria parola Reddit
Sparg planeta, tuturor.

Nu am autocontrol.

Din fericire, știu asta despre mine. Acest lucru îmi permite să-mi construiesc în mod conștient viața, astfel încât, în ciuda faptului că am maturitatea emoțională a unui șobolan de laborator dependent de heroină, ocazional pot să fac lucruri.

Pierd mult timp pe Reddit. Dacă vreau să amân ceva, voi deschide adesea o filă nouă și o să scufund într-o gaură Reddit. Dar, uneori, trebuie să porniți jaluzelele și să reduceți atenția. 2015 a fost unul dintre aceste timpuri – m-am concentrat în mod deosebit pe îmbunătățirea ca programator, iar Redditing devenea un pasiv.

Aveam nevoie de un plan de abstinență.

Așa că mi-a trecut prin minte: ce zici să mă blochez din contul meu?

Iată ce am făcut:

De data asta a trebuit sa mi sparg propria parola Reddit

Am setat o parolă aleatorie în contul meu. Apoi am rugat un prieten să-mi trimită prin e-mail această parolă la o anumită dată. Cu asta, aș avea un mod infailibil de a mă bloca din Reddit. (De asemenea, am schimbat e-mailul pentru recuperarea parolei pentru a acoperi toate bazele.)

Acest lucru ar fi trebuit să funcționeze.

Din păcate, se pare că prietenii sunt foarte susceptibili la ingineria socială. Terminologia tehnică pentru acest lucru este că acestea sunt „drăguțe cu tine” și îți vor restitui parola dacă „le cerți”.

După câteva runde ale acestui mod de eșec, aveam nevoie de o soluție mai robustă. O mică căutare pe Google și am dat peste asta:

De data asta a trebuit sa mi sparg propria parola Reddit
1612189513 62 De data asta a trebuit sa mi sparg propria parola Reddit
Pare legitim.

Perfect – o soluție automată, fără prieteni! (Pe majoritatea le-am înstrăinat până acum, așa că a fost un mare punct de vânzare.)

Un aspect cam schițat, dar hei, orice port în furtună.

1612189514 369 De data asta a trebuit sa mi sparg propria parola Reddit

Pentru o vreme am configurat această rutină – în timpul săptămânii îmi trimiteam parola, în weekend primeam parola, încărcam pe internet junk food și mă închideam din nou, odată ce săptămâna a început . A funcționat destul de bine din ceea ce îmi amintesc.

În cele din urmă am fost atât de ocupat cu programarea lucrurilor, încât am uitat complet de asta.

Taiat la doi ani mai târziu.

Acum sunt angajat câștigător la Airbnb. Și Airbnb, așa se întâmplă, are o suită de testare mare. Aceasta înseamnă așteptare, iar așteptarea desigur înseamnă găuri de iepure pe internet.

Decid să îmi cercetez vechiul cont și să îmi găsesc parola Reddit.

1612189515 476 De data asta a trebuit sa mi sparg propria parola Reddit

Oh nu. Asta nu e bine.

Nu-mi aminteam să fac asta, dar trebuie să mă fi săturat atât de mult de mine M-am blocat până în 2018. De asemenea, l-am setat la „ascundere”, așa că nu am putut vizualiza conținutul e-mailului până când nu a fost trimis.

Ce fac? Trebuie doar să creez un nou cont Reddit și să încep de la zero? Dar asta e multa munca.

Aș putea scrie la LetterMeLater și să explic că nu am vrut să fac asta. Dar probabil ar dura ceva timp să se întoarcă la mine. Am stabilit deja că sunt extrem de nerăbdător. În plus, acest site nu pare să aibă o echipă de asistență. Ca să nu mai vorbim că ar fi un schimb de e-mail jenant. Am început să creez explicații elaborate care implică rude moarte despre motivul pentru care aveam nevoie de acces la e-mail …

Toate opțiunile mele erau dezordonate. Mergeam acasă în acea noapte de la birou, meditându-mă la situația mea, când brusc m-a lovit.

Bara de căutare.

Am tras aplicația pe telefonul mobil și am încercat-o:

1612189516 442 De data asta a trebuit sa mi sparg propria parola Reddit

Hmm.

Bine. Deci, indexează subiectul cu siguranță. Dar corpul?

1612189517 709 De data asta a trebuit sa mi sparg propria parola Reddit

Încerc câteva litere și voila. Cu siguranță are corpul indexat. Amintiți-vă: corpul a constat în întregime din parola mea.

În esență, mi s-a oferit o interfață pentru a efectua interogări cu șiruri de caractere. Introducând un șir în bara de căutare, rezultatele căutării vor confirma dacă parola mea conține acest șir de caractere.

Suntem în afaceri.

Mă grăbesc în apartamentul meu, îmi las geanta și îmi scot laptopul.

Problema algoritmilor: vi se oferă o funcție substring?(str), care returnează adevărat sau fals în funcție de faptul dacă o parolă conține un șir dat. Având în vedere această funcție, scrieți un algoritm care poate deduce parola ascunsă.

Algoritmul

Deci, să ne gândim la asta. Câteva lucruri pe care le știu despre parola mea: știu că a fost un șir lung cu câteva caractere aleatorii, probabil ceva de genul asgoihej2409g. Eu probabil nu a inclus niciun caracter majuscul (iar Reddit nu aplică acest lucru ca o constrângere a parolei), deci să presupunem deocamdată că nu – în caz că am făcut-o, putem extinde spațiul de căutare mai târziu dacă algoritmul inițial eșuează.

De asemenea, avem o linie de subiect ca parte a șirului pe care îl interogăm. Și știm că subiectul este „parolă”.

1612189517 782 De data asta a trebuit sa mi sparg propria parola Reddit

Să ne prefacem că corpul are 6 caractere. Deci, avem șase sloturi de personaje, dintre care unele pot apărea în subiect, dintre care cu siguranță nu. Deci, dacă luăm toate caracterele care nu fac parte din subiect și încercăm să le căutăm pe fiecare dintre ele, știm sigur că vom lovi o literă unică care se află în parolă. Gândiți-vă ca un joc al Roții Norocului.

1612189517 880 De data asta a trebuit sa mi sparg propria parola Reddit

Continuăm să încercăm litere una câte una până când ajungem la o potrivire pentru ceva care nu se află în subiectul nostru. Să spunem că l-am lovit.

1612189518 135 De data asta a trebuit sa mi sparg propria parola Reddit

Odată ce am găsit prima mea scrisoare, nu știu de fapt unde sunt în acest șir. Dar știu că pot începe să construiesc un subșir mai mare prin adăugarea diferitelor personaje la sfârșitul acestuia până când voi lovi un alt meci de subșir.

Va trebui să parcurgem fiecare caracter din alfabetul nostru pentru a-l găsi. Oricare dintre aceste caractere ar putea fi corect, așa că, în medie, va atinge undeva la mijloc, așa că având un alfabet de mărime A, ar trebui să medieze până la A/2 presupuneri pe literă (să presupunem că subiectul este mic și nu există modele repetate de 2+ caractere).

1612189518 89 De data asta a trebuit sa mi sparg propria parola Reddit

Voi continua să construiesc acest șir până când ajunge în cele din urmă la sfârșit și niciun personaj nu îl poate extinde mai departe.

1612189518 719 De data asta a trebuit sa mi sparg propria parola Reddit

Dar asta nu este suficient – cel mai probabil, va exista un prefix la șir pe care l-am ratat, pentru că am început într-un loc aleatoriu. Destul de ușor: tot ce trebuie să fac este să repet acum procesul, cu excepția mersului înapoi.

1612189519 502 De data asta a trebuit sa mi sparg propria parola Reddit

Odată ce procesul se încheie, ar trebui să pot reconstrui parola. În total, va trebui să-mi dau seamaL personaje (unde L este lungimea) și trebuie să cheltuiți în medie A/2 ghici pe personaj (unde A este dimensiunea alfabetului), deci ghici total = A/2 * L.

Pentru a fi precis, trebuie să adaug și altul 2A la numărul de presupuneri pentru a se asigura că șirul s-a terminat la fiecare capăt. Deci totalul este A/2 * L + 2A, pe care le putem lua în calcul A(L/2 + 2).

Să presupunem că avem 20 de caractere în parola noastră și un alfabet format din a-z (26) și 0–9 (10), deci o dimensiune totală a alfabetului de 36. Deci, ne uităm la o medie de 36 * (20/2 + 2) = 36 * 12 = 432 iterații.

La naiba.

Acest lucru este de fapt realizabil.

Implementarea

În primul rând, trebuie să scriu un client care poate interoga programatic caseta de căutare. Aceasta îmi va servi drept oracol substring. Evident, acest site nu are API, așa că va trebui să răzuiesc site-ul direct.

Se pare că formatul URL pentru căutare este doar un șir de interogare simplu, www.lettermelater.com/account.php?qe=#{query_here}. Este destul de ușor.

Să începem să scriem acest script. Voi folosi bijuteria Faraday pentru a face cereri web, deoarece are o interfață simplă pe care o cunosc bine.

Voi începe prin a face o clasă API.

Desigur, nu ne așteptăm să funcționeze încă, deoarece scriptul nostru nu va fi autentificat în niciun cont. După cum putem vedea, răspunsul returnează o redirecționare 302 cu un mesaj de eroare furnizat în modul cookie.

[10] pry(main)> Api.get(“foo”)
=> #<Faraday::Response:0x007fc01a5716d8
...
{“date”=>”Tue, 04 Apr 2017 15:35:07 GMT”,
“server”=>”Apache”,
“x-powered-by”=>”PHP/5.2.17",
“set-cookie”=&gt;”msg_error=You+must+be+signed+in+to+see+this+page.”,
“location”=>”.?pg=account.php”,
“content-length”=>”0",
“connection”=>”close”,
“content-type”=>”text/html; charset=utf-8"},
status=302>

Deci, cum ne conectăm? Trebuie să le trimitem cookie-uri în antet, desigur. Folosind inspectorul Chrome, îi putem apuca în mod banal.

1612189520 566 De data asta a trebuit sa mi sparg propria parola Reddit

(Evident că nu îmi voi arăta cookie-ul adevărat aici. Interesant, pare că se stochează user_id client, ceea ce este întotdeauna un semn excelent.)

Prin procesul de eliminare, îmi dau seama că are nevoie de ambele code și user_id să mă autentifice … suspin.

Așa că le adaug la script. (Acesta este un cookie fals, doar pentru ilustrare.)

[29] pry(main)> Api.get(“foo”)=> “n<!DOCTYPE HTML PUBLIC ”-//W3C//DTD HTML 4.01//EN” ”http://www.w3.org/TR/html4/strict.dtd">n<html>n<head>nt<meta http-equiv=”content-type” content=”text/html; charset=UTF-8” />nt<meta name=”Description” content=”LetterMeLater.com allows you to send emails to anyone, with the ability to have them sent at any future date and time you choose.” />nt<meta name=”keywords” content=”schedule email, recurring, repeating, delayed, text messaging, delivery, later, future, reminder, date, time, capsule” />nt<title>LetterMeLater.com — Account Information</title>…
[30] pry(main)> _.include?(“Haseeb”)=> true

Mi-a intrat numele, așa că suntem autentificați!

Avem răzuirea, acum trebuie doar să analizăm rezultatul. Din fericire, este destul de ușor – știm că este un succes dacă rezultatul e-mailului apare pe pagină, deci trebuie doar să căutăm orice șir care este unic atunci când rezultatul este prezent. Șirul „parolă” nu apare nicăieri altundeva, așa că se va descurca frumos.

1612189521 298 De data asta a trebuit sa mi sparg propria parola Reddit

Asta este tot ce avem nevoie pentru clasa noastră API. Acum putem face interogări de sub-șir în întregime în Ruby.

[31] pry(main)> Api.include?('password')
=> true
[32] pry(main)> Api.include?('f')
=> false
[33] pry(main)> Api.include?('g')
=> true

Acum, că știm că funcționează, să eliminăm API-ul în timp ce dezvoltăm algoritmul nostru. Efectuarea de cereri HTTP va fi foarte lentă și s-ar putea să declanșăm o limitare a ratei în timp ce experimentăm. Dacă presupunem că API-ul nostru este corect, odată ce restul algoritmului funcționează, totul ar trebui să funcționeze doar după ce schimbăm din nou API-ul real.

Deci, iată API-ul stubbed, cu un șir secret aleatoriu:

Vom injecta API-urile în clasă în timp ce testăm. Apoi, pentru rularea finală, vom folosi API-ul real pentru a interoga parola reală.

Deci, să începem cu această clasă. De la un nivel ridicat, amintindu-mi diagrama algoritmică, se parcurge în trei pași:

  1. Mai întâi, găsiți prima literă care nu se află în subiect, dar există în parolă. Acesta este punctul nostru de plecare.
  2. Construiți acele litere înainte până cădem de la capătul șirului.
  3. Construiește acel șir înapoi până când lovim începutul șirului.

Atunci am terminat!

Să începem cu inițializarea. Vom injecta API-ul și, în afară de asta, trebuie doar să inițializăm fragmentul curent de parolă pentru a fi un șir gol.

Acum să scriem trei metode, urmând pașii pe care i-am subliniat.

Perfect. Acum, restul implementării poate avea loc în metode private.

Pentru a găsi prima literă, trebuie să repetăm ​​fiecare caracter din alfabet care nu este cuprins în subiect. Pentru a construi acest alfabet, vom folosi a-z și 0–9. Ruby ne permite să facem acest lucru destul de ușor cu intervale:

ALPHABET = ((‘a’..’z’).to_a + (‘0’..’9').to_a).shuffle

Prefer să amestec acest lucru pentru a elimina orice prejudecată din distribuirea scrisorilor parolei. Acest lucru va face ca interogarea noastră de algoritm să fie de 2 ori în medie pe caracter, chiar dacă parola este distribuită în mod aleatoriu.

De asemenea, dorim să stabilim subiectul ca o constantă:

SUBJECT = ‘password’

Asta este tot setarea de care avem nevoie. Acum este timpul să scriu find_starting_letter. Aceasta trebuie să parcurgă fiecare literă candidată (în alfabet, dar nu în subiect) până când găsește o potrivire.

La testare, se pare că acest lucru funcționează perfect:

PasswordCracker.new(ApiStub).send(:find_starting_letter!) # => 'f'

Acum pentru ridicarea grea.

Voi face acest lucru recursiv, deoarece face structura foarte elegantă.

Codul este surprinzător de simplu. Să vedem dacă funcționează cu API-ul nostru stub.

[63] pry(main)> PasswordCracker.new(ApiStub).crack!
f
fj
fjp
fjpe
fjpef
fjpefo
fjpefoj
fjpefoj4
fjpefoj49
fjpefoj490
fjpefoj490r
fjpefoj490rj
fjpefoj490rjg
fjpefoj490rjgs
fjpefoj490rjgsd
=> “fjpefoj490rjgsd”

Minunat. Avem un sufix, acum doar pentru a construi înapoi și a completa șirul. Acest lucru ar trebui să arate foarte asemănător.

De fapt, există doar două linii de diferență aici: modul în care construim guess, și numele apelului recursiv. Există o refacere evidentă aici, așa că hai să o facem.

Acum, aceste alte apeluri se reduc pur și simplu la:

Și să vedem cum funcționează în acțiune:

Apps-MacBook:password-recovery haseeb$ ruby letter_me_now.rb
Current password: 9
Current password: 90
Current password: 90r
Current password: 90rj
Current password: 90rjg
Current password: 90rjgs
Current password: 90rjgsd
Current password: 90rjgsd
Current password: 490rjgsd
Current password: j490rjgsd
Current password: oj490rjgsd
Current password: foj490rjgsd
Current password: efoj490rjgsd
Current password: pefoj490rjgsd
Current password: jpefoj490rjgsd
Current password: fjpefoj490rjgsd
Current password: pfjpefoj490rjgsd
Current password: hpfjpefoj490rjgsd
Current password: 0hpfjpefoj490rjgsd
Current password: 20hpfjpefoj490rjgsd
Current password: 420hpfjpefoj490rjgsd
Current password: g420hpfjpefoj490rjgsd
g420hpfjpefoj490rjgsd

Frumos. Acum, să adăugăm câteva instrucțiuni de imprimare și un pic de jurnalizare suplimentară și vom termina PasswordCracker.

Și acum … momentul magic. Să schimbăm stub-ul cu API-ul real și să vedem ce se întâmplă.

Momentul adevărului

Incruciseaza-ti degetele…

PasswordCracker.new(Api).crack!

1612189522 774 De data asta a trebuit sa mi sparg propria parola Reddit
(Accelerat de 3 ori)

Boom. 443 iterații.

Am încercat-o pe Reddit și autentificarea a avut succes.

Wow.

A funcționat.

Reamintim formula noastră originală pentru numărul de iterații: A(N/2 + 2). Adevărata parolă avea 22 de caractere, deci formula noastră ar estima 36 * (22/2 + 2) = 36 * 13 = 468 iterații. Parola noastră reală a efectuat 443 de iterații, deci estimarea noastră a fost în termen de 5% din timpul de execuție observat.

Matematica.

Funcționează.

E-mail de sprijin jenant evitat. Reddit iepure-holing restaurat. Acum este confirmat: programarea este, într-adevăr, magie.

(Dezavantajul este că va trebui să găsesc o nouă tehnică pentru a mă bloca din conturile mele.)

Și cu asta, mă voi întoarce la găurile mele de iepure de pe internet. Mulțumim pentru lectură și dă-i un like dacă ți-a plăcut asta!

—Haseeb