#11 Black Hat Rust. Наш первый сканер на Rust.
Здравствуйте, дорогие друзья.
Программное обеспечение, используемое для отображения поверхностей атак, называется сканером. Сканер портов, сканер уязвимостей, сканер поддоменов, сканер SQL-инъекций… Они автоматизируют длительную и кропотливую задачу, которой может быть разведка, и предотвращение человеческих ошибок (например, забывание поддомена или сервера). Вы должны иметь в виду, что сканеры не являются панацеей: они могут быть очень шумными и, таким образом, раскрывать Ваши намерения, блокироваться системами защиты от нежелательной почты или сообщать неполные данные.
Мы начнем с простого сканера, целью которого является поиск поддоменов определенной цели, а затем сканирование наиболее распространенных портов для каждого поддомена. Затем, по мере продвижения, мы будем добавлять все больше и больше функций, чтобы находить более интересные материалы автоматизированным способом. Поскольку наши программы становятся все более и более сложными, нам сначала нужно углубить наше понимание обработки ошибок в Rust.
Обработка ошибок
Будь то для библиотек или для приложений, ошибки в Rust строго типизированы, и, в большинстве случаев, представлены в виде перечислений с одним вариантом для каждого вида ошибок, с которыми может столкнуться наша библиотека или программа. Для библиотек в настоящее время хорошей практикой является использование этой ошибки crate. Для программ рекомендуется использовать anyhow crate, это улучшит ошибки, возвращаемые основной функцией.
Мы будем использовать оба в нашем сканере, чтобы посмотреть, как они сочетаются.Давайте определим все случаи ошибок нашей программы. Здесь все просто, поскольку единственной фатальной ошибкой является неправильное использование командной строки.
1 2 3 4 5 6 7 8 9 10 11 12 |
error.rs use thiserror::Error; #[derive(Error, Debug, Clone)] pub enum Error { #[error("Usage: tricoder <target.com>")] CliUsage, } main.rs fn main() -> Result<(), anyhow::Error> { |
Перечисление поддоменов
Мы будем использовать api, предоставляемый crt.sh , который можно запросить, вызвав следующую конечную точку: https://crt.sh/?q=%25.[domain.com]&output=json»
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
subdomains.rs pub fn enumerate(http_client: &Client, target: &str) -> Result<Vec<Subdomain>, Error> { let entries: Vec<CrtShEntry> = http_client .get(&format!("https://crt.sh/?q=%25.{}&output=json", target)) .send()? .json()?; // clean and dedup results let mut subdomains: HashSet<String> = entries .into_iter() .map(|entry| { entry .name_value .split("\n") .map(|subdomain| subdomain.trim().to_string()) .collect::<Vec<String>>() }) .flatten() .filter(|subdomain: &String| subdomain != target) .filter(|subdomain: &String| !subdomain.contains("*")) .collect(); subdomains.insert(target.to_string()); let subdomains: Vec<Subdomain> = subdomains .into_iter() .map(|domain| Subdomain { domain, open_ports: Vec::new(), }) .filter(resolves) .collect(); Ok(subdomains) } |
Сканирование портов
Подсчет поддоменов и IP-адресов — это только одна часть обнаружения ресурсов. Следующее — сканирование портов: как только Вы определили, какие серверы общедоступны, Вам нужно выяснить, какие службы общедоступны на этих серверах. Сканированию портов посвящены целые книги. В зависимости от того, чего Вы хотите: большей скрытности, большей скорости, более надежных результатов и так далее.
Существует множество различных методик, поэтому, чтобы не увеличивать сложность нашей программы, мы будем использовать самую простую технику: пытаться открыть сокет TCP. Когда сокет открыт — это означает, что сервер готов принимать соединения. С другой стороны, если сервер отказывается принимать соединения, это означает, что ни одна служба не прослушивает данный порт. В этой ситуации важно использовать тайм-аут, иначе наш сканер может зависнуть (почти) на неопределенный срок, сканируя порты, заблокированные брандмауэрами. Но у нас проблема. Выполнение всех наших запросов в определенной последовательности происходит чрезвычайно медленно.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
ports.rs use crate::{ common_ports::MOST_COMMON_PORTS_10, model::{Port, Subdomain}, }; use std::net::{SocketAddr, ToSocketAddrs}; use std::{net::TcpStream, time::Duration}; use rayon::prelude::*; pub fn scan_ports(mut subdomain: Subdomain) -> Subdomain { subdomain.open_ports = MOST_COMMON_PORTS_10 .into_iter() .map(|port| scan_port(&subdomain.domain, *port)) .filter(|port| port.is_open) // filter closed ports .collect(); subdomain } fn scan_port(hostname: &str, port: u16) -> Port { let timeout = Duration::from_secs(3); let socket_addresses: Vec<SocketAddr> = format!("{}:{}", hostname, port) .to_socket_addrs() .expect("port scanner: Creating socket address") .collect(); if socket_addresses.len() == 0 { return Port { port: port, is_open: false, }; } let is_open = if let Ok(_) = TcpStream::connect_timeout(&socket_addresses[0], timeout) { true } else { false }; Port { port: port, is_open, } } |
Многопоточность
Каждый поток процессора можно рассматривать как независимого работника: рабочая нагрузка может быть распределена между работниками.
Это особенно важно, поскольку сегодня, в силу законов физики, процессорам трудно масштабировать мощность с точки зрения количества операций в секунду (ГГц), поэтому вместо этого производители увеличивают количество ядер и потоков. Разработчикам приходится адаптироваться и разрабатывать свои программы таким образом, чтобы распределять рабочую нагрузку между доступными потоками, вместо того чтобы пытаться выполнять все операции в одном потоке, поскольку рано или поздно они могут достичь предела производительности процессора.
В нашей ситуации мы отправим задачу на сканирование каждого порта. Таким образом, если у нас есть 100 портов для сканирования, мы создадим 100 задач. Если у нас есть 10 потоков с тайм-аутом в 3 секунды, сканирование всех портов на наличие одного хоста может занять до 30 секунд (10 * 3). Если мы увеличим это число до 100 потоков, то сможем сканировать 100 портов на 1 хосте каждые 3 секунды.
На этом все. Всем хорошего дня!