За­дачи перебо­ра — путей, паролей, хешей или еще чего‑то — выгод­но выпол­нять парал­лель­но. И язык Go здесь под­ходит как нель­зя луч­ше. Сегод­ня мы осво­им кон­цепцию горутин — лег­ковес­ных потоков, которы­ми управля­ет собс­твен­ный пла­ниров­щик в user space, а не ОС, что радикаль­но меня­ет наг­рузку на сис­тему. Заод­но напишем свой фаз­зер под­доменов.

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

Те­ории и тер­минов будет нем­ного боль­ше, чем в пре­дыду­щих стать­ях, потому что тема сама по себе весь­ма обширная и, ска­жем пря­мо, не самая прос­тая. Я пос­тарал­ся изло­жить матери­ал так, что­бы при пер­вом чте­нии мож­но было про­пус­тить сов­сем скуч­ное, но для луч­шего понима­ния все же советую вник­нуть!

Итак, напишем прос­тень­кую ути­литу для поис­ка под­доменов (subdomain enumeration) мак­сималь­но оче­вид­ным спо­собом — брут­форсом по спис­ку.

Спи­сок нес­ложно най­ти в интерне­те, нап­ример SecLists или n0kovo_subdomains, но в учеб­ных целях, что­бы не бом­барди­ровать ни в чем не винова­тый хост зап­росами (тем более пока мы не научи­лись ни оста­нав­ливать горути­ны, ни управлять их количес­твом), я советую написать свой бук­валь­но из десят­ка позиций — это­го более чем дос­таточ­но для начала.

Соз­дадим новый модуль:

mkdir sudbdomain && cd $_
go mod init sudbdomain
touch main.go

В файл main.go вста­вим такой код:

package main
import (
"bufio"
"fmt"
"net/http"
"os"
"strings"
"sync"
"time"
)
func run() error {
const srcFileName = "subdomains.txt"
// Целевой хост аргумент запуска
if len(os.Args) <= 1 {
fmt.Fprintf(os.Stderr, "Target address not specified\n")
os.Exit(1)
}
host := os.Args[1]
// Открываем список поддоменов
srcFile, err := os.Open(srcFileName)
if err != nil {
return fmt.Errorf("opening %s: %w", srcFileName, err)
}
defer srcFile.Close()
// Настраиваем HTTP-клиент
client := &http.Client{
Timeout: 1 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// Игнорируем редиректы
return http.ErrUseLastResponse
},
}
// Построчно считываем и проверяем поддомены
scanner := bufio.NewScanner(srcFile)
for scanner.Scan() {
sub := strings.TrimSpace(scanner.Text())
if sub == "" {
continue
}
target := "https://" + sub + "." + host
resp, err := client.Get(target)
if err != nil {
continue
}
resp.Body.Close()
io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusNotFound {
// 404 нас не интересуют
continue
}
fmt.Printf("%s - %d %s\n", target, resp.StatusCode, http.StatusText(resp.StatusCode))
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("reading %s: %w", srcFileName, err)
}
return nil
}
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

Це­левой хост, для которо­го мы будем искать под­домены, зада­ется аргу­мен­том запус­ка: его зна­чение мы счи­тыва­ем как вто­рой эле­мент мас­сива os.Args (пер­вый эле­мент с индексом 0 содер­жит имя исполня­емо­го фай­ла). Спи­сок под­доменов для опро­са счи­тыва­ем из фай­ла. Для упро­щения имя фай­ла задано кон­стан­той srcFileName, но, разуме­ется, его так­же мож­но переда­вать аргу­мен­том, через перемен­ную окру­жения или задавать в кон­фиге.

Да­лее мы исполь­зуем буфери­зован­ное чте­ние средс­тва­ми пакета bufio: пос­троч­но чита­ем файл со спис­ком и для каж­дой стро­ки запус­каем про­вер­ку дос­тупнос­ти хос­та. Наконец, мы исполь­зуем пат­терн main-run для удобс­тва обра­бот­ки оши­бок.

При воз­никно­вении ошиб­ки выпол­няет­ся ран­ний выход из фун­кции run() с воз­вра­том этой ошиб­ки. В фун­кции main() про­веря­ется, воз­никла ли ошиб­ка, и в этом слу­чае воз­вра­щает­ся код 1. При этом кор­рек­тно обра­баты­вают­ся отло­жен­ные через defer вызовы, чего не про­изош­ло бы, если бы os.Exit(1) вызыва­лась сра­зу.

info

Удоб­но работать с кон­фигами поз­воля­ет пакет Viper. Он под­держи­вает JSON, YAML, INI и мно­жес­тво дру­гих вари­антов. В качес­тве аль­тер­нативы мож­но рас­смот­реть koanf и cleanenv с похожи­ми воз­можнос­тями. Кро­ме того, неред­ко исполь­зуют GoDotEnv, который поз­воля­ет заг­ружать перемен­ные окру­жения, задан­ные в фай­ле .env.

 

http.Client

Для сетево­го вза­имо­дей­ствия возь­мем http.Client из пакета стан­дар­тной биб­лиоте­ки net/http, он поз­воля­ет отправ­лять сетевые зап­росы и получать отве­ты. Обра­ти вни­мание: мы заранее соз­даем нас­тро­енный экзем­пляр струк­туры http.Client и в даль­нейшем работа­ем с ука­зате­лем на него — это сим­вол & в опре­деле­нии перемен­ной client. Мы не хотим заново копиро­вать кли­ент для каж­дого нового зап­роса и намерен­но исполь­зуем ука­затель, что­бы пере­исполь­зовать один и тот же экзем­пляр и тем самым эффектив­нее рас­ходовать ресур­сы. http.Client потоко­безо­пасен, поэто­му мы исполь­зуем один и тот же экзем­пляр через этот ука­затель даже в раз­ных горути­нах.

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии