Skip to content

Web Servers Attacks

A little overview about reconnaissance and misconfiguration-identification phase of web application testing.

Identifying Web Servers

The web server software itself shapes which misconfigurations are possible, which paths are worth checking, and which tools will be most effective. Identifying the server is not a formality. It directly influences every decision that follows.

The Server Response Header

# -s suppresses the progress bar
# -I sends a HEAD request, returning only response headers
curl -sI http://TARGET_MACHINE:80

The -sI flag sends a HEAD request, which returns headers only and no body. To see default error pages, I need a GET request switch to -s without the -I

# HEAD request: headers only, no body
curl -sI http://10.113.172.79:PORT/

# GET request: full response including body
curl -s http://10.113.172.79:PORT/nonexistent-page-xyz

Python HTTP Server

Python ships with a built-in HTTP server.

The Python HTTP server is a realistic finding because it requires no exploitation. There is no vulnerability to trigger. The server is functioning exactly as designed.

# This command serves the current working directory over HTTP on port 8000
python3 -m http.server 8000

Directory Listing

Python HTTP server generates an HTML page listing every file it can see. Browse to the root of the server using the following command or directly browse

http://TARGET_MACHINE:8000 in the browser

#Accessing Dotfiles
curl -s http://10.113.172.79:8000/.env
#Downloading and Inspecting Archives
curl -s http://10.113.172.79:8000/backup.zip -o backup.zip

Apache

Version Disclosure

Apache on Ubuntu defaults to ServerTokens OS, which includes the OS label alongside the version number. Knowing the exact version helps check for known CVEs and understand the server's capabilities.

curl -SI http://TARGET_MACHINE:80 | grep -i server

#accessing file
http://TARGET_MACHINE/files/

The mod_status Page

Apache includes a built-in status page powered by the mod_status module. When correctly configured, it is accessible only from localhost. When misconfigured with Require all granted, it is accessible from any IP. You can access it using

http://TARGET_MACHINE:80/server-status

Finding Unlinked Files with Gobuster

# -u is the target URL
# -w is the wordlist path
# -x tells gobuster to also append these extensions to each word
gobuster dir -u http://TARGET_IP:80 -w /usr/share/wordlists/SecLists/Discovery/Web-Content/common.txt -x bak,txt,html -t 20
Apache uses .htpasswd to store usernames and hashed passwords for HTTP Basic Authentication.

curl -s http://TARGET_MACHINE:80/backup.bak

Node.js (Express)

#Verbose Errors
curl -s http://TARGET_IP:3000/api/users | python3 -m json.tool

# Enumerating Routes via Debug Endpoints
curl -s http://TARGET_IP:3000/api/routes

#Exposed Environment Variables
curl -s http://TARGET_IP:3000/api/debug/env

#Static File Serving
curl -s http://TARGET_MACHINE:3000/static/config.js

Nginx

It is most commonly used as a reverse proxy, a load balancer, or a high-performance static file server. In production environments, Nginx often sits in front of an application server and handles the public-facing traffic.

Directory Listing with Autoindex

Nginx does not enable directory listing by default. When a developer wants to expose a directory listing, they add autoindex to a location block in the configuration:

location /files/ {
    autoindex on;
    root /var/www/nginx/;
}

curl -s http://10.113.172.79:8080/files/

Nginx's stub_status module exposes real-time connection metrics at a configurable URL. The secure configuration restricts access to localhost only. The misconfigured version allows access from any IP:

location /nginx_status {
    stub_status;
    allow all;  # Should be: allow 127.0.0.1; deny all;
}

Tip

Tip: Nginx configuration files live in /etc/nginx/ on Ubuntu. If you ever have shell access to a machine running Nginx, reading the site configuration in /etc/nginx/sites-available/ will show you exactly what directories are exposed and what modules are enabled.

Common Misconfigurations Accros Web Server

Security Headers

Security headers are HTTP response headers that instruct the browser on how to handle the page content. They protect against a range of client-side attacks, including clickjacking, MIME sniffing, and cross-site scripting.

Header What It Protects Against Example Value
X-Frame-Options Clickjacking (prevents the page from being embedded in an iframe on another domain) DENY or SAMEORIGIN
X-Content-Type-Options MIME sniffing (prevents the browser from guessing content types) nosniff
Content-Security-Policy Restricts where scripts, stylesheets, and other resources can load from default-src 'self'
Referrer-Policy Controls what is sent in the Referer header when navigating to another page no-referrer or strict-origin
Strict-Transport-Security Forces HTTPS for subsequent requests (only meaningful on HTTPS servers) max-age=31536000

Note

Note: X-Frame-Options is technically superseded by Content-Security-Policy: frame-ancestors, which provides finer-grained control. Hardened modern sites may handle clickjacking protection through CSP alone and intentionally omit X-Frame-Options. When writing findings, check for both.

Audit each server with curl:

for port in 80 8000 3000 8080; do echo "=== Port $port ==="; curl -sI http://TARGET_MACHINE:$port/ | grep -iE "x-frame-options|x-content-type|content-security-policy|strict-transport|referrer-policy" || echo "(no security headers found)"; done

Automated Scanning with Nikto

Nikto is a web server scanner that checks for known misconfigurations, outdated software, exposed admin interfaces, and missing security headers. It is not subtle; it generates a lot of traffic and is easy to detect

# The -nointeractive flag suppresses prompts so the scan runs without waiting for input. 
nikto -h http://TARGET_IP:80 -nointeractive

Patterns That Apply Everywhere

Misconfiguration Apache Python HTTP Node.js Nginx
Version disclosure in headers Yes Yes Partial Yes
Directory listing /files/ Root path N/A /files/
Exposed status or debug endpoint /server-status N/A /api/debug/env, /api/routes /nginx_status
Sensitive files accessible backup.bak, internal-notes.txt .env, notes.txt, backup.zip config.js server-config.txt, deploy-notes.txt
Missing security headers All All All All

Takeaways

  • Response headers are the fastest fingerprinting surface. The Server and X-Powered-By headers reveal software type and sometimes version before you have made a single request to an actual application path.
  • Python's built-in HTTP server has no access controls of any kind. When it appears in an engagement, assume the entire working directory is readable, including dotfiles such as .env.
  • Apache's mod_status and directory listing are enabled by default on Ubuntu installs and require active configuration to restrict them. Their presence is common and worth checking on every Apache engagement.
  • Node.js Express applications in development mode leak stack traces, route lists, and environment variables. Each of those is a finding in its own right, and together they give a complete picture of the application's internals.
  • Nginx autoindex and stub_status mirror Apache's directory listing and mod_status patterns. The directives differ, but the investigation approach is the same.

Internet Information Services (IIS)

Is Microsoft's web server platform built into Windows Server. It hosts websites, web applications, and services such as Exchange OWA, SharePoint, and ADFS.

ISS fingerprinting and Enumeration

HTTP.sys is a kernel-mode driver that receives all HTTP traffic before any IIS process touches it.

HTTP Banner Grabbing

curl -I http://TARGET_MACHINE

Web Distributed Authoring and Versioning (WebDAV) is an HTTP extension that adds file management verbs: PUT (upload), DELETE, COPY, MOVE, PROPFIND, and LOCK.

# The HTTP OPTIONS method asks the server to return its supported methods. A single command tells you whether WebDAV is active
curl -X OPTIONS http://10.113.144.60 -sv 2>&1 | grep -E "Allow:|DAV:"

Testing What File Types Can Be Uploaded and Executed

PUT a test file, then GET it and observe the response. A 200 with output means the server executed it; a 200 with your raw file content means it was served statically.

curl -s -o /dev/null -w "PUT aspx: %{http_code}\n" -X PUT --data '<%@ Page Language=Jscript%><%Response.Write(1+1)%>' http://TARGET_MACHINE/webdav/test.aspx
PUT aspx: 401