Ethical Hacking
Banner Grabbing
Automated scanners are fast. They are also blunt. Banner grabbing is what you do when you want to slow down, connect directly to a service, and read exactly what it tells you — in its own words, at the protocol level. Sometimes that is where the most useful intelligence lives.
When automation is not enough
Vulnerability scanners work at scale — they send standardised probes and pattern-match against known signatures. That works most of the time. But services with unusual configurations, custom banners, or software versions that do not match standard fingerprints slip through the pattern-matching. Manual banner grabbing fills that gap.
There is also something inherently more trustworthy about reading raw service output directly. When Nmap tells you a service is "OpenSSH 4.7p1," it is inferring that from a version probe. When you connect with netcat and the SSH daemon sends back its version string unprompted, you have read it yourself from the source. These usually match — but not always, and when they do not, the discrepancy tells you something important.
Beyond version identification, manual interaction with a service can reveal operational details that automated tools never ask for — error message formats, supported commands, authentication mechanisms, and sometimes sensitive information that only surfaces when you engage with the protocol properly.
Tools for manual banner grabbing
Three tools cover the vast majority of manual banner grabbing work. Each one has a slightly different sweet spot — knowing which to reach for saves time when you are mid-engagement and focused on a specific service.
netcat
Raw TCP and UDP connections. No protocol interpretation — just bytes in and bytes out. The universal tool for any service that does not require a handshake before sending data. FTP, SMTP, telnet, custom ports, anything that talks plaintext.
Best for: FTP, SMTP, telnet, raw TCP services, quick port connectivity tests.
curl
HTTP and HTTPS aware. Speaks the protocol fluently — sends proper HTTP requests, follows redirects, handles cookies, and reads response headers cleanly. Indispensable for web service enumeration. Also supports FTP, SFTP, and dozens of other protocols.
Best for: HTTP/HTTPS headers, web server identification, API endpoint probing.
telnet
Older than most operating systems still in active use, but still useful for text-based protocol interaction. Handles terminal negotiation better than netcat for some services. Avoid using it for actual remote login — use SSH. Its value here is as an interactive protocol testing tool.
Best for: Interactive text protocol testing where terminal control codes matter.
Reading SSH version strings directly
SSH is one of the few services that announces its exact version before any authentication takes place. The server sends a version string immediately after the TCP connection is established — the client's identity does not matter at that point. This is protocol behaviour, not a misconfiguration, and it means you can read SSH version information without logging in or presenting any credentials.
The OpenSSH version on Metasploitable — 4.7p1 — is notable. OpenSSH 4.7 was released in 2007. Several vulnerabilities in versions that old relate to key exchange weaknesses and memory handling issues that have long since been patched in modern releases. The version string alone narrows the CVE search considerably before a single vulnerability scanner has run.
The scenario: You want to verify the SSH version on Metasploitable directly from the protocol rather than relying on Nmap's inference. You also want to check whether any unusual SSH software is running — some organisations replace OpenSSH with alternatives like Dropbear or libssh, which carry their own distinct vulnerability sets.
# Read the SSH version string directly from the SSH handshake
# SSH sends its version banner immediately on connection — before authentication
# We do not need credentials — the server identifies itself to any connecting client
# nc connects and immediately receives the SSH version banner
# The connection will then hang waiting for an SSH client handshake
# Press Ctrl+C after reading the banner — do not attempt to type SSH commands
# netcat cannot speak SSH — this is purely for reading the initial version string
nc -v -n -w 3 192.168.56.101 22
(UNKNOWN) [192.168.56.101] 22 (ssh) open SSH-2.0-OpenSSH_4.7p1 Debian-8ubuntu1
Breaking it down:
The protocol version prefix. SSH-2.0 means the server is running SSH protocol version 2 — the current standard. If you ever see SSH-1.99 or SSH-1.5, the server also supports the older SSH protocol version 1, which has fundamental cryptographic weaknesses and should be flagged as a critical finding immediately.
The software string confirms OpenSSH version 4.7, patch level 1, built for Debian/Ubuntu. The "Debian-8ubuntu1" suffix is the distribution-specific package version — it tells you the OS package maintainer applied their own patches on top of the upstream OpenSSH source. Distribution packages sometimes backport security fixes without changing the upstream version number, which is one reason why banner version numbers alone cannot fully determine patch status.
Interrogating HTTP services — going beyond the Server header
The curl -I request from Lesson 16 showed the Server header. That is the starting point — not the destination. Web servers often reveal considerably more through their error responses, redirect behaviour, and non-standard headers than they do through the standard HEAD request. The way a server handles a malformed request, a request for a non-existent resource, or a request with unusual headers can fingerprint it more reliably than the Server header alone.
This matters because hardened web servers are often configured to suppress or falsify the Server header — but they cannot suppress all the behavioural signatures that distinguish Apache from Nginx from IIS without breaking their own functionality.
# Extended web server fingerprinting using curl
# We use different request methods and paths to extract more information
# Each response reveals something different about the server's behaviour
# -v verbose — shows the full request and response including all headers
# more detailed than -I which only shows response headers
# -X OPTIONS asks the server which HTTP methods it supports
# a server that supports PUT or DELETE may allow file uploads or deletions
curl -v -X OPTIONS http://192.168.56.101/ 2>&1 | grep -E "Allow:|Server:|X-Powered"
# Request a page that definitely does not exist
# The 404 error page often reveals the web framework and version
# Some servers include the full software stack in their error pages
curl -s http://192.168.56.101/this-page-definitely-does-not-exist-xyz
# Check for a robots.txt file — it sometimes reveals paths the admin wanted hidden
# Listing a path in robots.txt tells search engines to ignore it
# It also tells pen testers exactly where the interesting directories are
curl -s http://192.168.56.101/robots.txt
Allow: GET,HEAD,POST,OPTIONS,TRACE Server: Apache/2.2.8 (Ubuntu) DAV/2 X-Powered-By: PHP/5.2.4-2ubuntu5.10 --- 404 error page body --- <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html><head> <title>404 Not Found</title> </head><body> <h1>Not Found</h1> <address>Apache/2.2.8 (Ubuntu) Server at 192.168.56.101 Port 80</address> </body></html> --- robots.txt --- # $Id: robots.txt 6831 2007-01-11 13:42:21Z sarah $ User-agent: * Disallow: /cgi-bin/
Breaking it down:
TRACE is enabled — that is a finding. The HTTP TRACE method echoes the request back to the client and can be used in a Cross-Site Tracing (XST) attack to steal session cookies that would otherwise be protected by the HttpOnly flag. It should be disabled on any server that handles authenticated sessions. Most modern Apache configurations have it off by default — seeing it enabled here is a configuration issue worth noting.
The error page footer repeats the full server version string. Even if the Server header had been suppressed, this error page would give it away. A server that hides its version in the standard response headers but reveals it in error pages is only half-hardened — the server version information is still accessible to anyone who asks for a non-existent page.
The administrator tried to hide the /cgi-bin/ directory from search engines. What they actually did was tell every pen tester who reads the robots.txt that /cgi-bin/ exists and is worth looking at. CGI scripts are historically one of the most vulnerability-prone areas of web server configuration — and we already know from the Shellshock finding in Lesson 17 that CGI is exploitable on this host.
Three curl commands, three distinct pieces of intelligence — none of which required visiting an actual page on the site, triggering any application logic, or generating anything more suspicious than a standard web browser request. This is the kind of low-noise, high-yield work that makes manual enumeration worth the time it takes.
Database service fingerprinting
Database services are worth interrogating manually because they often respond differently to connection attempts than a scanner expects. MySQL, PostgreSQL, and MSSQL all have distinct handshake protocols — and connecting to them directly sometimes surfaces version information, authentication method details, and error messages that a scanner's standardised probe misses.
MySQL in particular sends a plaintext greeting packet during the initial handshake that contains the server version, connection ID, and supported capabilities — all before the client sends any authentication data. You can read this by connecting with netcat, even though the response will contain non-printable binary characters alongside the readable text.
# Read the MySQL greeting packet using netcat
# MySQL sends version info during the initial handshake — before any auth
# The output contains binary characters — the version string is readable within it
# -q 1 tells netcat to wait 1 second after receiving data then exit cleanly
# without this, netcat stays open after MySQL sends its greeting
nc -v -n -q 1 192.168.56.101 3306
# A cleaner way to just extract the version string from MySQL's greeting
# mysql client connects, immediately shows the version, and we disconnect
# -h specifies the host IP
# -u connects as root (we know from Lesson 15 root has no password)
# -e executes a single SQL command and exits — "SELECT VERSION();" returns
# the exact database version as a clean string with no binary noise
mysql -h 192.168.56.101 -u root -e "SELECT VERSION();"
(UNKNOWN) [192.168.56.101] 3306 (mysql) open N5.0.51a-3ubuntu5...^@^P............ +-----------+ | VERSION() | +-----------+ | 5.0.51a-3ubuntu5 | +-----------+
Breaking it down:
The netcat output shows the raw MySQL greeting packet — version string readable at the start, followed by binary control characters. This is what a scanner sees and parses automatically. When you see it directly, you understand what the scanner is actually receiving and why version identification from MySQL works even without authentication.
The mysql client command produced clean output — version 5.0.51a confirmed. Notice we connected as root with no password. This is the MySQL empty password finding from Lesson 15 in practice — we just queried the database directly. In a real engagement the next step from here would be enumerating all databases, tables, and users using SQL commands inside this session.
Building a manual enumeration checklist
By this point in the course you have run automated scans, validated findings manually, and interrogated individual services directly. The value of having a mental checklist — things to manually verify on every service before moving to exploitation — is that it prevents gaps. Automated tools miss things. Checking the list catches them.
Connect with netcat and read the raw banner
Does the service announce itself? What version does it claim? Does the version match what Nmap reported? Note any discrepancies — they often indicate custom builds or backported patches.
Check supported methods and capabilities
For HTTP use OPTIONS. For SMTP use EHLO to list extensions. For FTP use FEAT. Each service has a command that lists what it supports — use it. Enabled capabilities are potential attack surface.
Read error messages carefully
Request something that does not exist. Provide invalid input. What does the error response reveal? Stack traces, internal paths, software versions, database names — error messages are frequently more revealing than the normal happy-path responses.
Test for anonymous or default access
Does FTP accept anonymous login? Does the database connect without a password? Does the web admin panel accept admin:admin? Try the obvious before you try anything clever. Default credentials and anonymous access remain among the most common findings in real engagements.
Cross-reference with your existing intelligence
Does what you just found contradict anything from earlier scans? Does the version string match the vulnerability scanner's assessment? Does the configuration make sense given what you know about the organisation? Inconsistencies are worth pursuing.
That checklist takes five to fifteen minutes per service depending on how talkative the service is. On a target with twenty open ports, that is a couple of hours of focused work — productive hours, because every step either confirms something you already knew or surfaces something new. Either outcome moves the engagement forward.
Teacher's Note: robots.txt is one of those things that security teams keep meaning to clean up but never quite do. On real engagements it regularly reveals admin panels, internal tool paths, and legacy application directories that would otherwise take hours to find through directory brute-forcing. Always check it. It takes two seconds and it is completely passive.
Practice questions
Scenario:
Scenario:
Scenario:
Quiz
Scenario:
Scenario:
Scenario:
Up Next · Lesson 20
Recon Best Practices
Section II closes — pulling together everything from DNS enumeration through banner grabbing into a professional recon workflow that holds up in real engagements.