From c5f6c712067680940ca8324837668402ab707407 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Mon, 13 Nov 2023 13:35:40 +0100 Subject: [PATCH] new: added http.vhost virtual host enumeration --- README.md | 2 +- src/plugins/http/mod.rs | 56 +++++++++++++++++++++++++---- test-servers/http-vhost-public.conf | 26 ++++++++++++++ test-servers/http-vhost-secret.conf | 26 ++++++++++++++ test-servers/http-vhost.html | 1 + test-servers/http-vhost.sh | 7 ++++ test-servers/http-vhost/index.html | 1 + 7 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 test-servers/http-vhost-public.conf create mode 100644 test-servers/http-vhost-secret.conf create mode 100644 test-servers/http-vhost.html create mode 100755 test-servers/http-vhost.sh create mode 100644 test-servers/http-vhost/index.html diff --git a/README.md b/README.md index 7948d3d..979b8d4 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ For the building instructions, usage and the complete list of options [check the ## Supported Protocols/Features: -AMQP (ActiveMQ, RabbitMQ, Qpid, JORAM and Solace), Cassandra/ScyllaDB, DNS subdomain enumeration, FTP, HTTP (basic authentication, NTLMv1, NTLMv2, multipart form, custom requests with CSRF support and files/folders enumeration), IMAP, Kerberos pre-authentication and user enumeration, LDAP, MongoDB, MQTT, Microsoft SQL, MySQL, Oracle, PostgreSQL, POP3, RDP, Redis, SSH / SFTP, SMTP, STOMP (ActiveMQ, RabbitMQ, HornetQ and OpenMQ), TCP port scanning, Telnet, VNC. +AMQP (ActiveMQ, RabbitMQ, Qpid, JORAM and Solace), Cassandra/ScyllaDB, DNS subdomain enumeration, FTP, HTTP (basic authentication, NTLMv1, NTLMv2, multipart form, custom requests with CSRF support, files/folders enumeration, virtual host enumeration), IMAP, Kerberos pre-authentication and user enumeration, LDAP, MongoDB, MQTT, Microsoft SQL, MySQL, Oracle, PostgreSQL, POP3, RDP, Redis, SSH / SFTP, SMTP, STOMP (ActiveMQ, RabbitMQ, HornetQ and OpenMQ), TCP port scanning, Telnet, VNC. ## Benchmark diff --git a/src/plugins/http/mod.rs b/src/plugins/http/mod.rs index d741a79..35836a4 100644 --- a/src/plugins/http/mod.rs +++ b/src/plugins/http/mod.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use ctor::ctor; use rand::seq::SliceRandom; use reqwest::{ - header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE, COOKIE, USER_AGENT}, + header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE, COOKIE, HOST, USER_AGENT}, multipart, redirect, Client, Method, RequestBuilder, Response, }; use url::Url; @@ -29,6 +29,7 @@ fn register() { crate::plugins::manager::register("http.ntlm1", Box::new(HTTP::new(Strategy::NLTMv1))); crate::plugins::manager::register("http.ntlm2", Box::new(HTTP::new(Strategy::NLTMv2))); crate::plugins::manager::register("http.enum", Box::new(HTTP::new(Strategy::Enumeration))); + crate::plugins::manager::register("http.vhost", Box::new(HTTP::new(Strategy::VHostEnum))); } fn method_requires_payload(method: &Method) -> bool { @@ -43,6 +44,7 @@ pub(crate) enum Strategy { NLTMv1, NLTMv2, Enumeration, + VHostEnum, } struct Success { @@ -404,6 +406,47 @@ impl HTTP { } } } + + async fn http_vhost_enum_attempt( + &self, + creds: &Credentials, + timeout: Duration, + ) -> Result, Error> { + let url = self.get_target_url(&creds.target)?; + let mut headers = self.setup_headers(); + + // set host + headers.remove(HOST); + headers.insert(HOST, HeaderValue::from_str(&creds.username).unwrap()); + + // build base request object + let request = self + .client + .request(self.method.clone(), &url) + .headers(headers) + .timeout(timeout); + + // execute + match request.send().await { + Err(e) => Err(e.to_string()), + Ok(res) => { + if let Some(success) = self.is_success(res).await { + Ok(Some(Loot::new( + "http.vhost", + &creds.target, + [ + ("vhost".to_owned(), creds.username.to_owned()), + ("status".to_owned(), success.status.to_string()), + ("size".to_owned(), success.content_length.to_string()), + ("type".to_owned(), success.content_type), + ], + ))) + } else { + Ok(None) + } + } + } + } } #[async_trait] @@ -416,11 +459,12 @@ impl Plugin for HTTP { Strategy::NLTMv1 => "NTLMv1 authentication over HTTP.", Strategy::NLTMv2 => "NTLMv2 authentication over HTTP.", Strategy::Enumeration => "HTTP pages enumeration.", + Strategy::VHostEnum => "HTTP virtual host enumeration.", } } fn single_credential(&self) -> bool { - self.strategy == Strategy::Enumeration + matches!(self.strategy, Strategy::Enumeration | Strategy::VHostEnum) } fn setup(&mut self, opts: &Options) -> Result<(), Error> { @@ -545,10 +589,10 @@ impl Plugin for HTTP { } async fn attempt(&self, creds: &Credentials, timeout: Duration) -> Result, Error> { - if self.strategy == Strategy::Enumeration { - self.http_enum_attempt(creds, timeout).await - } else { - self.http_request_attempt(creds, timeout).await + match self.strategy { + Strategy::Enumeration => self.http_enum_attempt(creds, timeout).await, + Strategy::VHostEnum => self.http_vhost_enum_attempt(creds, timeout).await, + _ => self.http_request_attempt(creds, timeout).await, } } } diff --git a/test-servers/http-vhost-public.conf b/test-servers/http-vhost-public.conf new file mode 100644 index 0000000..259f9ae --- /dev/null +++ b/test-servers/http-vhost-public.conf @@ -0,0 +1,26 @@ +server { + listen [::]:80; + listen 80; + server_name public.company.com; + + sendfile off; + tcp_nodelay on; + absolute_redirect off; + access_log off; + error_log off; + + root /var/www/html; + index index.html; + + # Redirect server error pages to the static page /50x.html + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /var/lib/nginx/html; + } + + # Deny access to . files, for security + location ~ /\. { + log_not_found off; + deny all; + } +} \ No newline at end of file diff --git a/test-servers/http-vhost-secret.conf b/test-servers/http-vhost-secret.conf new file mode 100644 index 0000000..7dda9da --- /dev/null +++ b/test-servers/http-vhost-secret.conf @@ -0,0 +1,26 @@ +server { + listen [::]:80; + listen 80; + server_name internal.company.com; + + sendfile off; + tcp_nodelay on; + absolute_redirect off; + access_log off; + error_log off; + + root /var/www/secret; + index index.html; + + # Redirect server error pages to the static page /50x.html + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /var/lib/nginx/html; + } + + # Deny access to . files, for security + location ~ /\. { + log_not_found off; + deny all; + } +} \ No newline at end of file diff --git a/test-servers/http-vhost.html b/test-servers/http-vhost.html new file mode 100644 index 0000000..86b5935 --- /dev/null +++ b/test-servers/http-vhost.html @@ -0,0 +1 @@ +Public vhost \ No newline at end of file diff --git a/test-servers/http-vhost.sh b/test-servers/http-vhost.sh new file mode 100755 index 0000000..4be9bb6 --- /dev/null +++ b/test-servers/http-vhost.sh @@ -0,0 +1,7 @@ +clear && docker run \ + -p 8888:80 \ + -v `pwd`/http-vhost:/var/www/secret \ + -v `pwd`/http-vhost.html:/var/www/html/index.html \ + -v `pwd`/http-vhost-public.conf:/etc/nginx/conf.d/public.conf \ + -v `pwd`/http-vhost-secret.conf:/etc/nginx/conf.d/secret.conf \ + trafex/php-nginx \ No newline at end of file diff --git a/test-servers/http-vhost/index.html b/test-servers/http-vhost/index.html new file mode 100644 index 0000000..4250717 --- /dev/null +++ b/test-servers/http-vhost/index.html @@ -0,0 +1 @@ +Secret internal vhost! \ No newline at end of file