Ни для кого не сек­рет, что сов­ремен­ные соци­аль­ные сети пред­став­ляют собой огромные БД, содер­жащие мно­го инте­рес­ной информа­ции о час­тной жиз­ни сво­их поль­зовате­лей. Через веб‑мор­ду осо­бо мно­го дан­ных не вытянешь, но ведь у каж­дой сети есть свой API... Так давай же пос­мотрим, как этим мож­но вос­поль­зовать­ся для поис­ка поль­зовате­лей и сбо­ра информа­ции о них.

warning

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

 

OSINT

Есть в аме­рикан­ской раз­ведке такая дис­ципли­на, как OSINT (Open source intelligence), которая отве­чает за поиск, сбор и выбор информа­ции из обще­дос­тупных источни­ков. К одно­му из круп­ней­ших пос­тавщи­ков обще­дос­тупной информа­ции мож­но отнести соци­аль­ные сети. Ведь прак­тичес­ки у каж­дого из нас есть учет­ка (а у кого‑то и не одна) в одной или нес­коль­ких соц­сетях. Тут мы делим­ся сво­ими новос­тями, лич­ными фотог­рафи­ями, вку­сами (нап­ример, лай­кая что‑то или всту­пая в какую‑либо груп­пу), кру­гом сво­их зна­комств. При­чем дела­ем это по сво­ей доб­рой воле и прак­тичес­ки совер­шенно не задумы­ваем­ся о воз­можных пос­ледс­тви­ях. На стра­ницах жур­нала уже не раз рас­смат­ривали, как мож­но с помощью раз­личных уло­вок вытас­кивать из соц­сетей инте­рес­ные дан­ные. Обыч­но для это­го нуж­но было вруч­ную совер­шить какие‑то манипу­ляции. Но для успешной раз­ведки логич­нее вос­поль­зовать­ся спе­циаль­ными ути­лита­ми. Сущес­тву­ет нес­коль­ко open source ути­лит, поз­воля­ющих вытас­кивать информа­цию о поль­зовате­лях из соц­сетей.

 

Creepy

Од­на из наибо­лее популяр­ных — Creepy. Она пред­назна­чена для сбо­ра геоло­каци­онной информа­ции о поль­зовате­ле на осно­ве дан­ных из его акка­унтов Twitter, Instagram, Google+ и Flickr. К дос­тоинс­твам это­го инс­тру­мен­та, который штат­но вхо­дит в Kali Linux, сто­ит отнести понят­ный интерфейс, очень удоб­ный про­цесс получе­ния токенов для исполь­зования API сер­висов, а так­же отоб­ражение най­ден­ных резуль­татов мет­ками на кар­те (что, в свою оче­редь, поз­воля­ет прос­ледить за все­ми переме­щени­ями поль­зовате­ля). К недос­таткам я бы отнес сла­бова­тый фун­кци­онал. Тул­за уме­ет собирать геоте­ги по перечис­ленным сер­висам и выводить их на Google-кар­те, показы­вает, кого и сколь­ко раз рет­витил поль­зователь, счи­тает ста­тис­тику по устрой­ствам, с которых писались тви­ты, а так­же по вре­мени их пуб­ликации. Но за счет того, что это open source инс­тру­мент, его фун­кци­онал всег­да мож­но рас­ширить самому.
Рас­смат­ривать, как исполь­зовать прог­рамму, не будем — все отлично показа­но в офи­циаль­ном видео, пос­ле прос­мотра которо­го не дол­жно остать­ся никаких воп­росов по поводу работы с инс­тру­мен­том.

Пример работы Creepy
При­мер работы Creepy
 

fbStalker

Еще два инс­тру­мен­та, которые менее извес­тны, но обла­дают силь­ным фун­кци­она­лом и зас­лужива­ют тво­его вни­мания, — fbStalker и geoStalker.

fbStalker пред­назна­чен для сбо­ра информа­ции о поль­зовате­ле на осно­ве его Facebook-про­филя. Поз­воля­ет выцепить сле­дующие дан­ные:

  • ви­део, фото, пос­ты поль­зовате­ля;
  • кто и сколь­ко раз лай­кнул его записи;
  • ге­опри­вяз­ки фоток;
  • ста­тис­тика ком­мента­риев к его записям и фотог­рафи­ям;
  • вре­мя, в которое он обыч­но быва­ет в онлай­не.

Для работы дан­ного инс­тру­мен­та тебе понадо­бит­ся Google Chrome, ChromeDriver, который уста­нав­лива­ется сле­дующим обра­зом:

wget http://goo.gl/Kvh33W
unzip chromedriver_linux32_23.0.1240.0.zip
cp chromedriver /usr/bin/chromedriver
chmod 777 /usr/bin/chromedriver

По­мимо это­го, понадо­бит­ся уста­нов­ленный Python 2.7, а так­же pip для уста­нов­ки сле­дующих пакетов:

pip install pytz
pip install tzlocal
pip install termcolor
pip install selenium
pip install requests --upgrade
pip install beautifulsoup4

И наконец, понадо­бит­ся биб­лиоте­ка для пар­синга GraphML-фай­лов:

git clone https://github.com/hadim/pygraphml.git
cd pygraphml
python2.7 setup.py install

Пос­ле это­го мож­но будет поп­равить fbstalker.py, ука­зав там свое мыло, пароль, имя поль­зовате­ля, и прис­тупать к поис­ку. Поль­зовать­ся тул­зой дос­таточ­но прос­то:

python fbstalker.py -user [имя интересующего пользователя]
 

geoStalker

geoStalker зна­читель­но инте­рес­нее. Он собира­ет информа­цию по коор­динатам, которые ты ему передал. Нап­ример:

  • мес­тные Wi-Fi-точ­ки на осно­ве базы wigle.net (в час­тнос­ти, их essid, bssid, geo);
  • че­кины из Foursquare;
  • Instagram- и Flickr-акка­унты, с которых пос­тились фот­ки с при­вяз­кой к этим коор­динатам;
  • все тви­ты, сде­лан­ные в этом рай­оне.

Для работы инс­тру­мен­та, как и в пре­дыду­щем слу­чае, понадо­бит­ся Chrome & ChromeDriver, Python 2.7, pip (для уста­нов­ки сле­дующих пакетов: google, python-instagram, pygoogle, geopy, lxml, oauth2, python-linkedin, pygeocoder, selenium, termcolor, pysqlite, TwitterSearch, foursquare), а так­же pygraphml и gdata:

git clone https://github.com/hadim/pygraphml.git
cd pygraphml
python2.7 setup.py install
wget https://gdata-python-client.googlecode.com/files/gdata-2.0.18.tar.gz
tar xvfz gdata-2.0.18.tar.gz
cd gdata-2.0.18
python2.7 setup.py install

Пос­ле это­го редак­тиру­ем geostalker.py, запол­няя все необ­ходимые API-клю­чи и access-токены (если для какой‑либо соц­сети эти дан­ные не будут ука­заны, то она прос­то не будет учас­тво­вать в поис­ке). Пос­ле чего запус­каем инс­тру­мент коман­дой sudo python2.7 geostalker.py и ука­зыва­ем адрес или коор­динаты. В резуль­тате все дан­ные собира­ются и раз­меща­ются на Google-кар­те, а так­же сох­раня­ются в HTML-файл.

 

Переходим к действиям

До это­го речь шла о готовых инс­тру­мен­тах. В боль­шинс­тве слу­чаев их фун­кци­она­ла будет не хва­тать и при­дет­ся либо их дораба­тывать, либо писать свои тул­зы — все популяр­ные соц­сети пре­дос­тавля­ют свои API. Обыч­но они пред­ста­ют в виде отдель­ного под­домена, на который мы шлем GET-зап­росы, а в ответ получа­ем XML/JSON-отве­ты. Нап­ример, для «Инстаг­рама» это api.instagram.com, для «Кон­такта» — api.vk.com. Конеч­но, у боль­шинс­тва таких API есть свои биб­лиоте­ки фун­кций для работы с ними, но мы ведь хотим разоб­рать­ся, как это работа­ет, да и утя­желять скрипт лиш­ними внеш­ними биб­лиоте­ками из‑за одной‑двух фун­кций не комиль­фо. Итак, давай возь­мем и напишем собс­твен­ный инс­тру­мент, который бы поз­волял искать фотог­рафии из ВК и «Инстаг­рама» по задан­ным коор­динатам и про­межут­ку вре­мени.

Ис­поль­зуя докумен­тацию к API VK и Instagram, сос­тавля­ем зап­росы для получе­ния спис­ка фотог­рафий по геог­рафичес­кой информа­ции и вре­мени.

Instagram API Request:

url = "https://api.instagram.com/v1/media/search?"
+ "lat=" + location_latitude
+ "&lng=" + location_longitude
+ "&distance=" + distance
+ "&min_timestamp=" + timestamp
+ "&max_timestamp=" + (timestamp + date_increment)
+ "&access_token=" + access_token

Vkontakte API Request:

url = "https://api.vk.com/method/photos.search?"
+ "lat=" + location_latitude
+ "&long=" + location_longitude
+ "&count=" + 100
+ "&radius=" + distance
+ "&start_time=" + timestamp
+ "&end_time=" + (timestamp + date_increment)

Здесь исполь­зуемые перемен­ные:

  • location_latitude — геог­рафичес­кая широта;
  • location_longitude — геог­рафичес­кая дол­гота;
  • distance — ради­ус поис­ка;
  • timestamp — началь­ная гра­ница интерва­ла вре­мени;
  • date_increment — количес­тво секунд от началь­ной до конеч­ной гра­ницы интерва­ла вре­мени;
  • access_token — токен раз­работ­чика.

Как выяс­нилось, для дос­тупа к Instagram API тре­бует­ся access_token. Получить его нес­ложно, но при­дет­ся нем­ного заморо­чить­ся (смот­ри врез­ку). Кон­такт же более лояль­но отно­сит­ся к нез­наком­цам, что очень хорошо для нас.

Получение Instagram Access Token

Для начала регис­три­руешь­ся в инстаг­раме. Пос­ле регис­тра­ции перехо­дишь по сле­дующей ссыл­ке:

http://instagram.com/developer/clients/manage/

Жмешь Register a New Client. Вво­дишь номер телефо­на, ждешь эсэ­мэс­ку и вво­дишь код. В открыв­шемся окне соз­дания нового кли­ента важ­ные для нас поля нуж­но запол­нить сле­дующим обра­зом:

  • OAuth redirect_uri: http://localhost/
  • Disable implicit OAuth: галоч­ка дол­жна быть сня­та

Ос­таль­ные поля запол­няют­ся про­изволь­но. Как толь­ко все запол­нил, соз­давай нового кли­ента. Теперь нуж­но получить токен. Для это­го впи­ши в адресную стро­ку бра­узе­ра сле­дующий URL:

https://instagram.com/oauth/authorize/?client_id=[CLIENT_ID]&redirect_uri=http://localhost/&response_type=token

где вмес­то [CLIENT_ID] ука­жи Client ID соз­данно­го тобой кли­ента. Пос­ле это­го делай переход по получив­шей­ся ссыл­ке, и если ты сде­лал все пра­виль­но, то тебя пере­адре­сует на http://localhost и в адресной стро­ке как раз будет написан Access Token.

http://localhost/#access_token=[Access Token]

Бо­лее под­робно про этот метод получе­ния токена можешь почитать по сле­дующей ссыл­ке: http://jelled.com/instagram/access-token.

 

Автоматизируем процесс

Итак, мы научи­лись сос­тавлять нуж­ные зап­росы, но вруч­ную раз­бирать ответ сер­вера (в виде JSON/XML) — не самое кру­тое занятие. Гораз­до удоб­нее сде­лать неболь­шой скрип­тик, который будет делать это за нас. Исполь­зовать мы будем опять же Python 2.7. Логика сле­дующая: мы ищем все фото, которые попада­ют в задан­ный ради­ус отно­ситель­но задан­ных коор­динат в задан­ный про­межу­ток вре­мени. Но учи­тывай один очень важ­ный момент — выводит­ся огра­ничен­ное количес­тво фотог­рафий. Поэто­му для боль­шого про­межут­ка вре­мени при­дет­ся делать нес­коль­ко зап­росов с про­межу­точ­ными интерва­лами вре­мени (как раз date_increment). Так­же учи­тывай пог­решность коор­динат и не ука­зывай ради­ус в нес­коль­ко мет­ров. И не забывай, что вре­мя нуж­но ука­зывать в timestamp.

На­чина­ем кодить. Для начала под­клю­чим все необ­ходимые нам биб­лиоте­ки:

import httplib
import urllib
import json
import datetime

Пи­шем фун­кции для получе­ния дан­ных с API через HTTPS. С помощью передан­ных аргу­мен­тов фун­кции мы сос­тавля­ем GET-зап­рос и воз­вра­щаем ответ сер­вера стро­кой.

def get_instagram(latitude, longitude, distance, min_timestamp, max_timestamp, access_token):
get_request = '/v1/media/search?lat=' + latitude
get_request += '&lng=' + longitude
get_request += '&distance=' + distance
get_request += '&min_timestamp=' + str(min_timestamp)
get_request += '&max_timestamp=' + str(max_timestamp)
get_request += '&access_token=' + access_token
local_connect = httplib.HTTPSConnection('api.instagram.com', 443)
local_connect.request('GET', get_request)
return local_connect.getresponse().read()
def get_vk(latitude, longitude, distance, min_timestamp, max_timestamp):
get_request = '/method/photos.search?lat=' + location_latitude
get_request += '&long=' + location_longitude
get_request += '&count=100'
get_request += '&radius=' + distance
get_request += '&start_time=' + str(min_timestamp)
get_request += '&end_time=' + str(max_timestamp)
local_connect = httplib.HTTPSConnection('api.vk.com', 443)
local_connect.request('GET', get_request)
return local_connect.getresponse().read()

Еще накодим неболь­шую фун­кцию кон­верта­ции timestamp в челове­чес­кий вид:

def timestamptodate(timestamp):
return datetime.datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S')+' UTC'

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

def parse_instagram(location_latitude, location_longitude, distance, min_timestamp, max_timestamp, date_increment, access_token):
print 'Starting parse instagram..'
print 'GEO:',location_latitude,location_longitude
print 'TIME: from',timestamptodate(min_timestamp),'to',timestamptodate(max_timestamp)
file_inst = open('instagram_'+location_latitude+location_longitude+'.html','w')
file_inst.write('<html>')
local_min_timestamp = min_timestamp
while (1):
if ( local_min_timestamp >= max_timestamp ):
break
local_max_timestamp = local_min_timestamp + date_increment
if ( local_max_timestamp > max_timestamp ):
local_max_timestamp = max_timestamp
print timestamptodate(local_min_timestamp),'-',timestamptodate(local_max_timestamp)
local_buffer = get_instagram(location_latitude, location_longitude, distance, local_min_timestamp, local_max_timestamp, access_token)
instagram_json = json.loads(local_buffer)
for local_i in instagram_json['data']:
file_inst.write('<br>')
file_inst.write('<img src='+local_i['images']['standard_resolution']['url']+'><br>')
file_inst.write(timestamptodate(int(local_i['created_time']))+'<br>')
file_inst.write(local_i['link']+'<br>')
file_inst.write('<br>')
local_min_timestamp = local_max_timestamp
file_inst.write('</html>')
file_inst.close()

HTML-фор­мат выб­ран не прос­то так. Он поз­воля­ет нам не сох­ранять кар­тинки отдель­но, а лишь ука­зать ссыл­ки на них. При запус­ке стра­ницы резуль­таты в бра­узе­ре кар­тинки авто­мати­чес­ки под­гру­зят­ся.
Пи­шем точ­но такую же фун­кцию для «Кон­такта».

def parse_vk(location_latitude, location_longitude, distance, min_timestamp, max_timestamp, date_increment):
print 'Starting parse vkontakte..'
print 'GEO:',location_latitude,location_longitude
print 'TIME: from',timestamptodate(min_timestamp),'to',timestamptodate(max_timestamp)
file_inst = open('vk_'+location_latitude+location_longitude+'.html','w')
file_inst.write('<html>')
local_min_timestamp = min_timestamp
while (1):
if ( local_min_timestamp >= max_timestamp ):
break
local_max_timestamp = local_min_timestamp + date_increment
if ( local_max_timestamp > max_timestamp ):
local_max_timestamp = max_timestamp
print timestamptodate(local_min_timestamp),'-',timestamptodate(local_max_timestamp)
vk_json = json.loads(get_vk(location_latitude, location_longitude, distance, local_min_timestamp, local_max_timestamp))
for local_i in vk_json['response']:
if type(local_i) is int:
continue
file_inst.write('<br>')
file_inst.write('<img src='+local_i['src_big']+'><br>')
file_inst.write(timestamptodate(int(local_i['created']))+'<br>')
file_inst.write('http://vk.com/id'+str(local_i['owner_id'])+'<br>')
file_inst.write('<br>')
local_min_timestamp = local_max_timestamp
file_inst.write('</html>')
file_inst.close()

И конеч­но же, сами вызовы фун­кций:

parse_instagram(location_latitude, location_longitude, distance, min_timestamp, max_timestamp, date_increment, instagram_access_token)
parse_vk(location_latitude, location_longitude, distance, min_timestamp, max_timestamp, date_increment)
Результат работы нашего скрипта в консоли
Ре­зуль­тат работы нашего скрип­та в кон­соли
Один из результатов парсинга Инстаграма
Один из резуль­татов пар­синга Инстаг­рама
Результат парсинга «Контакта»
Ре­зуль­тат пар­синга «Кон­такта»
 

Боевое крещение

Скрипт готов, оста­лось его толь­ко опро­бовать в дей­ствии. И тут мне приш­ла в голову одна идея. Те, кто был на PHD’14, навер­няка запом­нили очень сим­патич­ных про­моде­вочек от Mail.Ru. Что ж, давай поп­робу­ем навер­стать упу­щен­ное — най­ти их и поз­накомить­ся.

Собс­твен­но, что мы зна­ем об PHD14:

  • мес­то про­веде­ния — Digital October — 55.740701,37.609161;
  • да­та про­веде­ния — 21–22 мая 2014 года — 1400619600–1400792400.

По­луча­ем сле­дующий набор дан­ных:

location_latitude = '55.740701'
location_longitude = '37.609161'
distance = '100'
min_timestamp = 1400619600
max_timestamp = 1400792400
date_increment = 60*60*3 # every 3 hours
instagram_access_token = [Access Token]

Полезные советы

Ес­ли в резуль­тате работы скрип­та фотог­рафий будет слиш­ком мало, можешь про­бовать изме­нять перемен­ную date_increment, пос­коль­ку имен­но она отве­чает за интерва­лы вре­мени, по которым собира­ются фотог­рафии. Если мес­то популяр­ное, то и интерва­лы дол­жны быть час­тыми (умень­шаем date_increment), если же мес­то глу­хое и фотог­рафии пуб­лику­ют раз в месяц, то и сбор фотог­рафий интерва­лами в час не име­ет смыс­ла (уве­личи­ваем date_increment).

За­пус­каем скрипт и идем раз­бирать получен­ные резуль­таты. Ага, одна из девочек выложи­ла фот­ку, сде­лан­ную в зер­кале в туале­те, с при­вяз­кой по коор­динатам! Естес­твен­но, API не прос­тил такой ошиб­ки, и вско­ре были най­дены стра­нич­ки всех осталь­ных про­моде­вочек. Как ока­залось, две из них близ­няшки :).

Та самая фотография промо-девочки с PHD’14, сделанная в туалете
Та самая фотог­рафия про­мо‑девоч­ки с PHD’14, сде­лан­ная в туале­те
 

Поучительный пример

В качес­тве вто­рого при­мера хочет­ся вспом­нить одно из заданий с финала CTF на PHD’14. Собс­твен­но, имен­но пос­ле него я заин­тересо­вал­ся дан­ной темой. Суть его зак­лючалась в сле­дующем.

Есть злой хац­кер, который раз­работал некую мал­варь. Нам дан набор коор­динат и соот­ветс­тву­ющих им вре­мен­ных меток, из которых он выходил в интернет. Нуж­но добыть имя и фот­ку это хац­кера. Коор­динаты были сле­дующие:

55.7736147,37.6567926 30 Apr 2014 19:15 MSK;
55.4968379,40.7731697 30 Apr 2014 23:00 MSK;
55.5625259,42.0185773 1 May 2014 00:28 MSK;
55.5399274,42.1926434 1 May 2014 00:46 MSK;
55.5099579,47.4776127 1 May 2014 05:44 MSK;
55.6866654,47.9438484 1 May 2014 06:20 MSK;
55.8419686,48.5611181 1 May 2014 07:10 MSK

Пер­вым делом мы, естес­твен­но, пос­мотре­ли, каким мес­там соот­ветс­тву­ют эти коор­динаты. Как ока­залось, это стан­ции РЖД, при­чем пер­вая коор­дината — это Казан­ский вок­зал (Мос­ква), а пос­ледняя — Зеленый Дол (Зелено­доль­ск). Осталь­ные — это стан­ции меж­ду Мос­квой и Зелено­доль­ском. Получа­ется, что он выходил в интернет из поез­да. По вре­мени отправ­ления был най­ден нуж­ный поезд. Как ока­залось, стан­цией при­бытия поез­да явля­ется Казань. И тут встал глав­ный воп­рос: где искать имя и фот­ку. Логика зак­лючалась в сле­дующем: пос­коль­ку тре­бует­ся най­ти фот­ку, то впол­не разум­но пред­положить, что искать ее нуж­но где‑то в соци­аль­ных сетях. Основны­ми целями были выб­раны «ВКон­такте», «Фей­сбук», «Инстаг­рам» и «Твит­тер». В сорев­новани­ях помимо рус­ских команд учас­тво­вали инос­тран­цы, поэто­му мы пос­читали, что орга­низа­торы вряд ли бы выб­рали «ВКон­такте». Решено было начать с «Инстаг­рама».

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

В ито­ге, что­бы най­ти поезд и недос­тающие стан­ции, а так­же логику даль­нейше­го поис­ка, понадо­билось не боль­ше часа. А вот на поиск нуж­ной фотог­рафии — очень мно­го вре­мени. Это еще раз под­черки­вает, нас­коль­ко важ­но иметь пра­виль­ные и удоб­ные прог­раммы в сво­ем арсе­нале.

www

Ис­ходный код рас­смот­ренно­го скрип­та ты можешь най­ти в моем Bitbucket-репози­тории.

 

Выводы

Статья подош­ла к завер­шению, и нас­тало вре­мя делать вывод. А вывод прос­той: заливать фотог­рафии с геоп­ривяз­кой нуж­но обду­ман­но. Кон­курен­тные раз­ведчи­ки готовы зацепить­ся за любую воз­можность получить новую информа­цию, и API соци­аль­ных сетей им в этом могут очень неп­лохо помочь. Ког­да писал эту статью, я изу­чил еще нес­коль­ко сер­висов, в том чис­ле Twitter, Facebook и LinkedIn, — есть ли подоб­ный фун­кци­онал. Положи­тель­ные резуль­таты дал толь­ко «Твит­тер», что, несом­ненно, раду­ет. А вот Facebook и LinkedIn огор­чили, хотя еще не все потеря­но и, воз­можно, в будущем они рас­ширят свои API. В общем, будь вни­матель­нее, вык­ладывая свои фото с геоп­ривяз­кой, — вдруг их най­дет кто‑нибудь не тот :).

Оставить мнение