Пешком по Firebase. Находим открытые базы данных, которые прячет Google

Об­лачная база дан­ных — шту­ка удоб­ная: счи­тай, вся работа по деп­лою и нас­трой­ке сер­вера за тебя уже сде­лана, оста­лось толь­ко поль­зовать­ся! Адми­нов это рас­слаб­ляет нас­толь­ко, что такие базы час­то оста­ются незащи­щен­ными и ищут­ся с помощью поис­ковиков. Один нюанс — этим поис­ковиком не может быть Google!

WARNING

Вся информа­ция пре­дос­тавле­на исклю­читель­но в озна­коми­тель­ных целях. Автор и редак­ция не несут ответс­твен­ности за любой воз­можный вред, при­чинен­ный с исполь­зовани­ем све­дений из этой статьи.

Firebase — это облачная плат­форма, которую раз­работа­ла ком­пания Envolve в 2011 году. Изна­чаль­но это была база дан­ных для при­ложе­ний‑чатов, но поз­же ее полюби­ли раз­работ­чики мно­гополь­зователь­ских онлай­новых игр. Это под­вигло осно­вате­лей раз­делить Firebase на две час­ти: модуль чата и модуль игро­вого фрей­мвор­ка.

Два года спус­тя, в 2014 году, Google при­обре­ла Firebase и про­дол­жила раз­вивать. Прог­рам­мные интерфей­сы для этой базы дан­ных есть для мно­жес­тва плат­форм и язы­ков прог­рамми­рова­ния.

INFO

С тех­ничес­кой точ­ки зре­ния Firebase — кру­тая и удоб­ная шту­ка. Кажет­ся, что ничего док­ручивать и допили­вать здесь не нуж­но. Но безопас­ная нас­трой­ка облачной базе таки нуж­на, и мно­гие вла­дель­цы об этом забыва­ют, черес­чур рас­сла­бив­шись. Нас­толь­ко, что забыва­ют о прос­тей­шей вещи — аутен­тифика­ции.

ИЩЕМ ОТКРЫТЫЕ БАЗЫ ДАННЫХ

По­луча­ется, что в интерне­те мас­са не зак­рытых паролем баз дан­ных, а это лег­кая добыча для зло­умыш­ленни­ков. Толь­ко вот взять и нагуг­лить их не получит­ся, потому что в Google решили, что эту проб­лему мож­но решить, прос­то исклю­чив эти базы из поис­ковой выдачи. Лов­ко! Но край­не ненадеж­но.

Нич­то не меша­ет нам вос­поль­зовать­ся дру­гим поис­ковиком — нап­ример, Bing или DuckDuckGo. Они выда­ют уже нам­ного боль­ше полез­ной информа­ции.

Что мож­но сде­лать даль­ше, пос­ле того как най­дены домены с уяз­вимыми базами? Откры­ваем любую ссыл­ку — нап­ример, https://hacker-news.firebaseio.com/v0/topstories.json. Информа­ция о ней бес­полез­на, но если убрать из ссыл­ки наз­вание таб­лицы topstories и оста­вить толь­ко .json, то мож­но про­верить, защище­на база или нет. В этом слу­чае резуль­тат выг­лядит вот так:

{
  "error" : "Permission denied"
}

Все вер­но, лич­но я бы знат­но уди­вил­ся, если бы вла­дель­цы это­го сай­та допус­тили нас­толь­ко вопи­ющую оплошность. Но некото­рые ее таки допус­кают. Минут десять перебо­ра ссы­лок, и поиск увен­чает­ся успе­хом.

Я нашел что‑то более инте­рес­ное — учет­ки с хешами паролей. Выб­рать их из фай­ла нес­ложно прос­тым скрип­том на Python или ути­литой jq.

При помощи HashID опре­деля­ем тип хешей (это был MD5) и загоня­ем в hashcat. Если нет дос­таточ­но мощ­ного железа, мож­но вос­поль­зовать­ся онлай­новым сер­висом — тул­за FindMyHash авто­мати­чес­ки их под­бира­ет. Все эти ути­литы пре­дус­танов­лены в Kali Linux.

Ждем десять минут, и перед нами логины и пароли в откры­том виде.

АВТОМАТИЗАЦИЯ

Си­деть, менять поис­ковики и переби­рать все URL вруч­ную очень нуд­но. Слиш­ком час­то видишь «error : Permission denied». Зна­чит, пора авто­мати­зиро­вать! Прог­рамми­ровать, прав­да, не пот­ребу­ется, потому что это уже сде­лали до нас. Возь­мем, к при­меру, скрипт за авторс­твом Фран­цеска Эрре­ры.

Скрипт сам под­бира­ет URL и ищет уяз­вимые базы дан­ных.

Ка­чаем его и уста­нав­лива­ем зависи­мос­ти:

git clone https://github.com/Turr0n/firebase.git
cd firebase
pip install -r requirements.txt

И запус­каем:

python3 firebase.py -p 4 -c 150 –dnsdumpster

Клю­чи:

  • p — ука­зыва­ет количес­тво потоков (по умол­чанию 1, мак­симум 4);
  • dnsdumpster — генери­рует URL самос­тоятель­но;
  • с — какое количес­тво доменов генери­ровать.

Да, скрипт уме­ет генери­ровать ссыл­ки самос­тоятель­но. Точ­нее, дела­ет это не сам, а обра­щает­ся за помощью к ути­лите DNSdumpster.

По резуль­тату вид­но, что из най­ден­ных баз:

  • 37 урлов «битые» или боль­ше не сущес­тву­ют;
  • 171 база име­ет аутен­тифика­цию при обра­щении к дан­ным и защище­на;
  • од­на база с подоз­рени­ем на уяз­вимость;
  • 25 баз не защище­ны или уяз­вимы.

Мож­но скор­мить скрип­ту и свой спи­сок. В нем дол­жны быть толь­ко под­домены треть­его уров­ня. Нап­ример, ты дела­ешь вот такой вход­ной спи­сок:

xxx
yyy
zzz

Тог­да скрипт про­верит вот эти URL:

https://xxx.firebaseio.com
https://yyy.firebaseio.com
https://zzz.firebaseio.com

В поисках поддоменов

Что­бы получить наибо­лее эффектив­ный спи­сок, мож­но вос­поль­зовать­ся скрип­том sublist3r, который исполь­зует раз­ные тех­ники поис­ка и OSINT, что­бы подоб­рать наибо­лее прав­доподоб­ные вари­анты.

За­пус­каем:

python3 sublist3r.py -d firebaseio.com

И на выходе получа­ем око­ло 650 доменов. Работа­ет очень быс­тро.

Еще одна ути­лита для генера­ции доменов — subbrute. Она выдала мне в рай­оне 100 под­доменов, но работа­ла 30–40 минут.

Censys-subdomain-finder, на который я воз­лагал боль­шие надеж­ды, выдал все­го семь доменов. К сло­ву, и сам сер­вис выдал нем­ного — 25 урлов.

Все перечис­ленные ути­литы не вхо­дят в сос­тав дис­три­бути­ва Kali Linux, их приш­лось качать отдель­но.

Из онлай­новых сер­висов мож­но вос­поль­зовать­ся nmmapper, DNSdumpster, Pentest-Tools.

Ес­ли все еще мало, мож­но задей­ство­вать зна­ния о том, что нас­трой­ки интегра­ции про­исхо­дят в фай­ле google-services.json, и поис­кать в гите зап­росом site:github.com google-services.json. Этот вари­ант идет враз­рез со сло­вом «авто­мати­зация», зато мож­но докопать­ся до уни­каль­ных баз.

Ковыряем и дописываем скрипт

Те­перь мы воору­жены доб­ротным набором урлов и зна­ем, что какие‑то из них могут быть уяз­вимы. Даже можем запус­тить скрипт и про­верить количес­тво невер­но нас­тро­енных баз из нашего спис­ка. Но цель в таких слу­чаях — не соб­рать ста­тис­тику, а получить уяз­вимые цели. Поэто­му откро­ем код скрип­та и слег­ка поп­равим.

Пос­мотри вот на этот кусок кода:

urls = set()
with open(args_.list, 'r') as f:
[urls.add('https://{}.firebaseio.com/.json'.format(line.rstrip())) for line in f]

Из него понят­но, как фор­миру­ется пол­ный адрес. А в кон­це фор­миру­ется отчет:

print('404 DBs: {}'.format(l['-2']))
print('Secure DBs: {}'.format(l['-1']))
print('Possible vulnerable DBs: {}'.format(l['0']))
print('Vulnerable DBs: {}'.format(l['1']))

Ку­сок кода с про­вер­кой я при­водить не буду. Там нет ничего любопыт­ного, мне нуж­но было най­ти само прис­воение. И вот оно:

with open(args_.fn, 'w') as f:
json.dump(loot, f)
l = {'1':0, '0':0, '-1':0, '-2':0}
for result in loot:
l[str(result['status'])] += 1

Вот в этом мес­те я соз­даю свой мас­сив по ста­тусу 1. Так как я еще не понимаю, чем его запол­нять, записы­ваю все под­ряд. Получи­лось при­мер­но сле­дующее:

l = {'1':0, '0':0, '-1':0, '-2':0}
Vulnerable = []
for result in loot:
l[str(result['status'])] += 1
if str(result['status']) == '1':
Vulnerable.append(result)

И в кон­це добав­ляю вывод резуль­тата в кон­соль:

print('404 DBs: {}'.format(l['-2']))
print('Secure DBs: {}'.format(l['-1']))
print('Possible vulnerable DBs: {}'.format(l['0']))
print('Vulnerable DBs: {}'.format(l['1']))
print(Vulnerable)

Пос­ле запус­ка вижу такую кар­тину.

Мне высыпа­лось все, что хра­нилось в базах. Зато теперь я знаю, что записы­вать в Vulnerable. Пра­вим код, как надо:

l = {'1':0, '0':0, '-1':0, '-2':0}
Vulnerable = []
for result in loot:
l[str(result['status'])] += 1
if str(result['status']) == '1':
Vulnerable.append(result['url'])
...
print('404 DBs: {}'.format(l['-2']))
print('Secure DBs: {}'.format(l['-1']))
print('Possible vulnerable DBs: {}'.format(l['0']))
print('Vulnerable DBs: {}'.format(l['1']))
print(Vulnerable)

На этот раз при запус­ке видим то, что было нуж­но, — спи­сок уяз­вимых баз.

Осо­бен­но меня заин­тересо­вала вот эта ссыл­ка: https://covid-19-tracker-e76ca.firebaseio.com/.json. В Малай­зии так спе­шили отсле­живать переме­щения боль­ных ковидом, что не пос­тавили пароль на базу дан­ных с их коор­дината­ми...

КАК НЕ ДОПУСТИТЬ УТЕЧКУ

Наз­вать эту проб­лему уяз­вимостью — некото­рое пре­уве­личе­ние. Все сво­дит­ся к тому, что Google поз­воля­ет открыть дос­туп к содер­жимому базы всем неав­торизо­ван­ным поль­зовате­лям, и некото­рые раз­работ­чики так и пос­тупа­ют.

По­это­му, что­бы защитить­ся, дос­таточ­но про­писать полити­ки безопас­ности Firebase. Google пред­лага­ет при­дер­живать­ся сле­дующих пра­вил.

Да­же во вре­мя раз­работ­ки (на тес­те и на stage) не сто­ит откры­вать дос­туп ко всем дан­ным в Firebase неав­торизо­ван­ным поль­зовате­лям, но есть воз­можность открыть их при авто­риза­ции:

service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}

Это пра­вило необ­ходимо, ког­да нес­коль­ким поль­зовате­лям нуж­но работать с одним кон­тентом.

Сле­дующее пра­вило дает дос­туп к дан­ным толь­ко вла­дель­цам этих дан­ных. Осталь­ные поль­зовате­ли не смо­гут их видеть или обра­баты­вать:

service cloud.firestore {
match /databases/{database}/documents {
// Allow only authenticated content owners access
match /some_collection/{userId}/{documents=**} {
allow read, write: if request.auth != null && request.auth.uid == userId
}
}
}

И третье пра­вило уста­нав­лива­ет дос­туп на чте­ние некото­рых дан­ных некото­рым поль­зовате­лям, но редак­тирова­ние — толь­ко его вла­дель­цу:

service cloud.firestore {
  match /databases/{database}/documents {
    // For attribute-based access control, Check a boolean `admin` attribute
    allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
    allow read: true;
    // Alterntatively, for role-based access, assign specific roles to users
    match /some_collection/{document} {
      allow read: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == "Reader"
      allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == "Writer"
    }
  }
}

ЗАКЛЮЧЕНИЕ

Итак, мы рас­смот­рели, как про­исхо­дит утеч­ка дан­ных, разоб­рались с поис­ком уяз­вимых URL, научи­лись авто­мати­зиро­вать про­цесс и узна­ли, как избе­жать этой проб­лемы.

В докумен­тации по безопас­ности Firebase при­веде­ны и сами пра­вила, и раз­ные спо­собы защитить дан­ные. Не сто­ит ими пре­неб­регать, перек­ладывая ответс­твен­ность на какие‑нибудь гей­ты или прок­си‑сер­веры. Адрес базы может быть обна­ружен.