Having Fun With Misconfigured Virtual Hosts

I had too much time on my hand a few weeks ago, so I decided to play around with Shodan to find some juicy stuff. Specifically, exposed directory listing that contain bash history, environment variables file, exposed version control directory and other configuration files stored in plain text format.

They mostly look like this:

Why did this happen?

Misconfigured virtual host (or server blocks as they call it in nginx)! These conditions must be satisfied:

  1. The default virtual host is also used to serve the application.
  2. Directory listing is enabled on the default virtual host.

If directory listing is disabled, it is still possible to access sensitive files, but you kind of have to brute force the file name (or use a dictionary).

What is the default virtual host?

The default virtual host is responsible as the “catch-all” virtual host. You can read this article to understand how nginx decides which server block to match the request to.

Steps to reproduce

For the sake of this article, I built nginx version 1.22.1 from source because I did not want to touch my default nginx installation. You should be able to replicate this issue by installing nginx using your package manager.

$ ./configure \
    --prefix=/opt/nginx-1.22.1 \
    --with-threads \
    --with-http_ssl_module \
    --with-stream \
    --with-stream_ssl_module \
    --with-http_secure_link_module \
    --with-debug \

$ sudo make install

After running the commands above, nginx will be installed at /opt/nginx-1.22.1

For the sake of simplicity, this nginx installation will only serve static files. So, no need to configure PHP-FPM, etc.

# /opt/nginx-1.22.1/conf/nginx.conf
worker_processes  3;
pid        logs/nginx.pid;

events {
    worker_connections  1024;

http {
    server_names_hash_bucket_size 64;
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    include sites_enabled/*.conf;
# /opt/nginx-1.22.1/conf/sites_available/default.conf

server {
    listen       80 default_server;
    server_name  _;

    location / {
        root   html; # sets /opt/nginx-1.22.1/html as the document root
        autoindex on;

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
# /etc/hosts	localhost	awesomewebsite.local

Then, /opt/nginx-1.22.1/conf/sites_available/default.conf needs to be symlinked to /opt/nginx-1.22.1/conf/sites_enabled/default.conf to enable the virtual host.

# ln -s /opt/nginx-1.22.1/conf/sites_available/default.conf /opt/nginx-1.22.1/conf/sites_enabled/default.conf

Note that awesomewebsite.local does not have its own virtual host configuration.

Then, start nginx:

$ sudo /opt/nginx-1.22.1/sbin/nginx -t # test the configuration
$ sudo /opt/nginx-1.22.1/sbin/nginx
$ sudo /opt/nginx-1.22.1/sbin/nginx -s reload # to reload nginx after config changes

Now, send a HTTP request to the server using 3 methods:

  1. Direct to IP (
  2. localhost
  3. awesomewebsite.local

They all should return the same response.

What can I do with this information?

If the server that you found happens to be hosting a Laravel application:

  1. Check for .env file
  2. Check for exposed version control folder (.git, .hg)
  3. Check whether it is vulnerable to CVE-2017-9841

In general:

  1. Check for juicy information in .bash_history
  2. Find database backup
  3. Find archive of source code
  4. Find database client
.env file
exposed source code (*.js is treated as plain text by the web server, so the whole content is displayed)
AWS keys

If you are lucky enough, you can find Azure configs, AWS configs, database credentials (sometimes they allow remote login) and a database client (Adminer/PHPMyAdmin).

I once found a server that allows SSH login using password AND the password is written in .bash_history 😀

If you can access the .git directory, you can probably dump the source code using git-dumper.


Modify your NGINX configuration so that all request to dotfiles will be rejected:

# /opt/nginx-1.22.1/conf/sites_available/default.conf

server {
    listen       80 default_server;
    server_name  _;

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;

    # add this
    # blocks all request to anything that starts with a dot
    location ~ /\. {
        autoindex off;
        deny all;

    # for sensitive files that don't start with a dot
    location = /super_sensitive_file.txt {
        deny all;
        return 403;

    location / {
        root   html; # sets /opt/nginx-1.22.1/html as the document root
        autoindex on;

In general, you should:

  1. Disable directory listing.
  2. Disable the default virtual host.
  3. Create specific virtual host or server block per website
  4. chmod sensitive files (wp-config.php, .env, etc) to 0400 (not completely related to this article but this will protect against symlink attacks)
  5. Store your backups somewhere else.
  6. Do NOT upload/install a database client. You are screwed if your database creds are exposed.
  7. If you are deploying a Laravel app, do NOT use a shared hosting. Make sure you follow the deployment steps as described in the documentation.


3 thoughts on “Having Fun With Misconfigured Virtual Hosts

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.