How to Find Hidden Web and API Endpoints on Kali Linux

Tested on Kali GNU/Linux Rolling 2026.2 (kali-rolling)
Package katana 1.7.0
gobuster 3.8.2-1
ffuf 2.1.0-1+b11
dirb 2.22+dfsg-7
nikto 1:2.6.0-0kali4
curl 8.21.0
wget 1.25.0
Lab environment Pentest lab
Cert prep CompTIA PenTest+ PT0-003 — Domain 2 (Reconnaissance and Enumeration)
Privilege Normal user for scans; sudo for package installs
Scope Hidden endpoint discovery with API documentation probes, browser or proxy traffic review, JavaScript crawling with Katana, directory brute force (Gobuster, ffuf, dirb), robots.txt and sitemap.xml checks, wget spidering, curl validation, Nikto fingerprinting, and WAF versus origin planning on Metasploitable 2 Apache. Does not cover deep recursive fuzzing, archive OSINT, or full CDN bypass exploitation.
Related guides Learn hacking with Metasploitable 2
Short on time?Beta Get the gist of this 15-minute read in seconds, then ask follow-up questions

Modern applications expose more than linked HTML pages. REST routes, GraphQL handlers, and admin folders often sit outside navigation and documentation. After banner grabbing shows HTTP on port 80, you build a URL and endpoint inventory before parameter fuzzing or login attacks.

This walkthrough finds hidden web and API endpoints on Metasploitable 2 in an authorized pentest lab. You probe API specs, review browser traffic, crawl JavaScript with Katana, and brute-force directories with Gobuster, ffuf, and dirb.

IMPORTANT
Discover and probe endpoints only on applications covered by your engagement authorization. Confirm scope and rate limits in your written rules of engagement before you brute-force paths or crawl JavaScript on client or production hosts.

Hidden endpoint discovery quick start

Export these variables once per shell session. These exports last for the current shell only — open a new terminal and re-run the block.

bash
export TARGET=192.168.56.127
export WEB_URL="http://${TARGET}"
export LAB=/tmp/hidden-endpoints-lab
mkdir -p "$LAB"
# Proxmox Group 2 lab: export TARGET=10.10.10.30

Before you run the commands below:

  • Lab target — TARGET defaults to 192.168.56.127 (Metasploitable 2 on the VirtualBox host-only lab). Confirm the address in your pentest lab guide if your layout differs.
  • Base URL — WEB_URL is http://${TARGET} on the default lab image unless your engagement uses HTTPS or a non-standard port.
  • Root vs non-root — The commands here run as a normal Kali user. Package installs need sudo.
  • Missing tools — Install the enumeration packages in the tools section if katana, gobuster, or ffuf is not found.

Once network reconnaissance shows port 80 open on $TARGET, probe common API documentation paths before you send a full wordlist:

bash
for path in openapi.json swagger.json swagger-ui api-docs v3/api-docs graphql graphiql; do
  curl -s -o /dev/null -w "%{http_code} /${path}\n" "${WEB_URL}/${path}"
done
output
404 /openapi.json
404 /swagger.json
404 /swagger-ui
404 /api-docs
404 /v3/api-docs
404 /graphql
404 /graphiql

Metasploitable returns 404 for every spec path on the default image. On modern targets, 200 means inspect the file, 401 or 403 often means the route exists but access is restricted, and 404 means no route at that exact location.

Directory brute force still finds classic lab paths Gobuster reports with 200 or 301:

bash
gobuster dir -u "$WEB_URL" -w /usr/share/wordlists/dirb/common.txt -q -t 20 --timeout 5s
output
dav                  (Status: 301) [Size: 319] [--> http://192.168.56.127/dav/]
phpMyAdmin           (Status: 301) [Size: 326] [--> http://192.168.56.127/phpMyAdmin/]
phpinfo.php          (Status: 200) [Size: 48026]
test                 (Status: 301) [Size: 320] [--> http://192.168.56.127/test/]
twiki                (Status: 301) [Size: 321] [--> http://192.168.56.127/twiki/]

Combine API spec probes, browser traffic, JavaScript crawling, and wordlist scans. Each method surfaces different endpoint types.


Web paths, files, and API endpoints

Beginners often treat every URL as a "directory." In penetration testing you record what each route is:

Term Example Meaning
Web path /admin/ URL path served by the application
File /phpinfo.php Specific web resource
API endpoint /api/v1/users URL plus HTTP method used by an API
GraphQL endpoint /graphql API endpoint accepting GraphQL operations
Hidden endpoint /api/internal/status Reachable route not obvious from navigation or docs

Directory brute force can discover some hidden endpoints, especially on older PHP apps. Modern single-page applications also expose API routes through JavaScript bundles, browser network requests, OpenAPI specifications, and archived content. OWASP API reconnaissance guidance recommends combining documentation review, proxy traffic, crawling, and source analysis rather than relying on wordlists alone.


Install endpoint discovery tools

Install the enumeration packages if your Kali image is missing a tool. Use apt to refresh indexes and install the stack:

bash
sudo apt update
sudo apt install -y katana gobuster ffuf dirb nikto curl wget

apt exits silently when the install succeeds.


Compare endpoint discovery methods

Method Best for Typical success signal
API spec probes OpenAPI, Swagger, GraphQL discovery 200 on /openapi.json or /graphql
Browser DevTools / Burp Live API calls the frontend makes GET /api/v1/... in Fetch/XHR
Katana (-jc) Endpoints embedded in JavaScript New /api/ or app paths in crawl output
Gobuster Fast directory scans with status lines phpMyAdmin (Status: 301)
ffuf Tunable fuzzing with match filters Path names in silent output
dirb Classic reporting and optional recursion ==> DIRECTORY: .../phpMyAdmin/
curl Spec checks, robots.txt, validation 200, 301, or 403 status codes
wget Shallow link crawling from home page Linked paths in spider output
Nikto Known sensitive files and version headers Plugin ID lines in stdout

Start with API documentation and browser traffic on modern apps. Add robots.txt, JavaScript crawling, and directory brute force. Escalate to recursive fuzzing in the URL fuzzer tools guide when scope allows larger wordlists.


Verify HTTP port 80 on $TARGET

Discovery tools waste time when Apache is down. Confirm port 80 responds before you probe endpoints:

bash
nc -zv "$TARGET" 80
output
(UNKNOWN) [192.168.56.127] 80 (http) open

Port 80 is open on the lab target. Probe API documentation paths next, then run crawling and brute-force tools.


Probe API documentation paths

OpenAPI and Swagger files can expose most of an API surface in one response. Older specifications sometimes list deprecated routes that still answer on the server.

Check common documentation locations with curl:

bash
for path in openapi.json swagger.json swagger-ui api-docs v3/api-docs graphql graphiql; do
  curl -s -o /dev/null -w "%{http_code} /${path}\n" "${WEB_URL}/${path}"
done
output
404 /openapi.json
404 /swagger.json
404 /swagger-ui
404 /api-docs
404 /v3/api-docs
404 /graphql
404 /graphiql

Read the status codes this way:

  • 200 — download and parse the spec for paths and methods
  • 401 or 403 — route likely exists; try authenticated access when scope allows
  • 404 — no resource at that exact path; try versioned prefixes such as /api/v1/openapi.json

When a spec returns 200, save it for your report and add every listed path to your validation queue.


Capture endpoints from browser DevTools

On single-page applications, the browser Network tab shows API calls the frontend already makes. These endpoints tend to be higher confidence than random wordlist guesses because the application uses them in normal operation.

Use this workflow on any authorized web target (Metasploitable's older apps show fewer XHR calls, but the steps are the same on modern sites):

  1. Open the site in Firefox or Chrome.
  2. Press F12 and select the Network tab.
  3. Enable Fetch/XHR filtering.
  4. Reload the page and click through login, search, profile, filters, and checkout flows.
  5. Record each request URL, HTTP method, and status code.
  6. Look for patterns such as /api/, /v1/, /graphql, or hostnames on API subdomains.

Example traffic you might log from a modern application:

text
GET  /api/v1/profile
POST /api/v1/search
GET  /api/v2/orders/123
POST /graphql

Burp Suite or OWASP ZAP captures the same requests when the browser routes traffic through a proxy. Browse as much functionality as the engagement allows — lazy loading and code splitting often expose endpoints only after you interact with a feature.


Extract endpoints from JavaScript with Katana

Frontend bundles often embed /api/v1/... strings, GraphQL URLs, internal route names, and deprecated calls. Katana crawls pages and parses JavaScript when you pass -jc.

Run a shallow crawl against the lab web root:

bash
katana -u "$WEB_URL" -d 1 -jc -silent

Trimmed output from the lab run:

output
http://192.168.56.127
http://192.168.56.127/dav/
http://192.168.56.127/twiki/
http://192.168.56.127/twiki/bin/view/Main/WebHome
http://192.168.56.127/dvwa/
http://192.168.56.127/dvwa/login.php
http://192.168.56.127/mutillidae/
http://192.168.56.127/mutillidae/index.php?page=login.php

Katana surfaced dvwa and mutillidae paths linked from JavaScript or embedded references — routes a short wordlist pass might miss. Save longer crawls when you need a file for reporting:

bash
katana -u "$WEB_URL" -d 2 -jc -silent -o "$LAB/katana.txt"

Katana writes discovered URLs to $LAB/katana.txt with no extra terminal output when the run finishes cleanly.


Read robots.txt and sitemap.xml

robots.txt and sitemap.xml are passive-first checks. They cost one HTTP request each and sometimes list admin paths or API prefixes operators forgot to hide elsewhere.

Request robots.txt from the web root:

bash
curl -s "$WEB_URL/robots.txt" | head -15
output
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL /robots.txt was not found on this server.</p>
<hr>
<address>Apache/2.2.8 (Ubuntu) DAV/2 Server at 192.168.56.127 Port 80</address>

Metasploitable returns 404 for robots.txt on the default image. On production sites you may see Disallow rules that name sensitive folders — add each path to your validation list.

Check whether a sitemap exists:

bash
curl -s -o /dev/null -w 'sitemap.xml %{http_code}\n' "$WEB_URL/sitemap.xml"
output
sitemap.xml 404

A 404 here means there is no sitemap to mine. When a site returns 200, pipe the XML through grep -oE '<loc>[^<]+' to extract URLs before you send new traffic.


Brute-force paths with Gobuster

Gobuster walks a wordlist against $WEB_URL and prints HTTP status codes for each guess. Directory brute force is one endpoint-discovery technique — use it after API and JavaScript passes on modern targets, or earlier on classic Apache apps like Metasploitable.

Run a quiet directory scan against the lab web root:

bash
gobuster dir -u "$WEB_URL" -w /usr/share/wordlists/dirb/common.txt -q -t 20 --timeout 5s

Trimmed output from the lab run:

output
dav                  (Status: 301) [Size: 319] [--> http://192.168.56.127/dav/]
phpMyAdmin           (Status: 301) [Size: 326] [--> http://192.168.56.127/phpMyAdmin/]
phpinfo.php          (Status: 200) [Size: 48026]
phpinfo              (Status: 200) [Size: 48014]
test                 (Status: 301) [Size: 320] [--> http://192.168.56.127/test/]
twiki                (Status: 301) [Size: 321] [--> http://192.168.56.127/twiki/]

phpMyAdmin and phpinfo.php are classic Metasploitable findings. A small wordlist will not list every lab application — escalate to larger lists when scope allows.

Save results when you need a file for reporting:

bash
gobuster dir -u "$WEB_URL" -w /usr/share/wordlists/dirb/common.txt -q -t 20 --timeout 5s -o "$LAB/gobuster.txt"

Gobuster writes the hit list to $LAB/gobuster.txt with no extra terminal output when the run finishes cleanly.


Fuzz directories with ffuf

ffuf sends the same wordlist with flexible match rules. Use it when you want to filter noise or tune threads without switching tools.

Fuzz the first path segment with common status codes enabled:

bash
ffuf -u "${WEB_URL}/FUZZ" -w /usr/share/wordlists/dirb/common.txt -mc 200,301,302,403 -t 20 -timeout 5 -s

Trimmed silent output:

output
cgi-bin/
dav
phpMyAdmin
phpinfo
phpinfo.php
server-status
test
twiki

ffuf listed the same high-value paths Gobuster found. Paths ending in 403 such as server-status still prove the route exists even when the server blocks your request.


Enumerate paths with dirb

dirb predates Gobuster and ffuf but remains useful for classic reporting and optional recursion. The -S flag hides 404 lines so you can read hits faster.

Run dirb against the lab host with silent mode:

bash
dirb "$WEB_URL" /usr/share/wordlists/dirb/common.txt -S -r -z 20

Trimmed output:

output
+ http://192.168.56.127/cgi-bin/ (CODE:403|SIZE:295)
==> DIRECTORY: http://192.168.56.127/dav/
+ http://192.168.56.127/phpinfo.php (CODE:200|SIZE:48104)
==> DIRECTORY: http://192.168.56.127/phpMyAdmin/
==> DIRECTORY: http://192.168.56.127/test/
==> DIRECTORY: http://192.168.56.127/twiki/

dirb marked directory entries with ==> DIRECTORY: and file hits with +. Compare its list with Gobuster, ffuf, and Katana before you move to validation.


Validate paths with curl

A scanner hit is a lead until you confirm the response. Use curl with a write-out format to record status codes without dumping HTML bodies.

Check paths the scanners reported:

bash
curl -s -o /dev/null -w 'phpMyAdmin %{http_code}\n' "$WEB_URL/phpMyAdmin/"
output
phpMyAdmin 200

Confirm a second discovered directory the same way:

bash
curl -s -o /dev/null -w 'twiki %{http_code}\n' "$WEB_URL/twiki/"
output
twiki 200

200 means the path answered. 401 and 403 still prove the route exists but blocked your request. 404 may mean the path is gone or hidden by routing rules.


Crawl linked pages with wget

Brute force finds unlinked folders. Crawling follows anchors on pages you already reach and surfaces paths wordlists miss.

Run a shallow spider against the lab home page with the wget command:

bash
wget --spider -r -l 1 -p -e robots=off "$WEB_URL" 2>&1 | head -12
output
Spider mode enabled. Check if remote file exists.
--2026-08-30 22:18:06--  http://192.168.56.127/
Connecting to 192.168.56.127:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Remote file exists and could contain links to other resources -- retrieving.

The Metasploitable home page is small, so the spider confirms connectivity more than it expands the path list. On larger applications, increase -l depth carefully and stay inside scope.


Fingerprint common files with Nikto

Nikto checks hundreds of known sensitive paths and version headers. It complements brute force by flagging configuration issues around paths you already found.

Run a time-limited pass against the lab web server:

bash
nikto -h "$WEB_URL" -maxtime 60s 2>&1 | head -15
output
+ Target IP:          192.168.56.127
+ Target Port:        80
+ Server: Apache/2.2.8 (Ubuntu) DAV/2
+ [999986] /: Retrieved x-powered-by header: PHP/5.2.4-2ubuntu5.10.
+ [750500] /icons/: Directory indexing found.
+ [600625] PHP/5.2.4-2ubuntu5.10 appears to be outdated (current is at least 8.5.1).
+ [600050] Apache/2.2.8 appears to be outdated (current is at least 2.4.66).

Nikto flagged outdated PHP and Apache builds plus directory indexing on /icons/. Cross-check those paths with your endpoint inventory before you research CVEs in scope.


Plan scans around WAF and origin IP

Production sites often sit behind a WAF or CDN. Directory brute force against the public hostname hits the edge network first. Responses may differ from the origin Apache or nginx server behind it.

On Metasploitable, your scans connect directly to Apache on $TARGET — there is no CDN in the lab path. On customer engagements, compare these signals before you interpret 403 floods as dead ends:

  • DNS and certificates — historical A records or certificate transparency may reveal a direct origin IP inside scope
  • Response headers — Server, cf-ray, and X-CDN style headers show whether you reached an edge node
  • Dual testing — when your rules of engagement include a confirmed origin IP, repeat a small wordlist against http://ORIGIN_IP with the Host header set to the site name

Stay inside written scope. Passive OSINT platforms such as Shodan can help map exposure, but never point active brute force at IPs you are not authorized to test.


Troubleshooting

Symptom Likely cause Fix
API spec paths all return 404 No published OpenAPI/Swagger on target Shift to browser traffic and JavaScript crawling
DevTools shows no XHR traffic Static HTML app or features not exercised Click through login, search, and filters; try Burp proxy
Katana finds only external URLs Shallow depth or few JS files Increase -d; seed from login or dashboard URLs
Gobuster finds almost nothing Wordlist too small or wrong base URL Try /usr/share/wordlists/dirb/big.txt; confirm WEB_URL has no trailing path
ffuf prints thousands of lines Match filter too broad Tighten -mc or add -fc 404 to hide not-found responses
dirb runs for a long time Full wordlist on slow lab VM Use -z 20 delay or -S silent mode; try Gobuster first
robots.txt returns HTML 404 page File not deployed on target Continue with brute force; note absence in your report
curl returns 404 for a scanner hit Trailing slash or redirect mismatch Retry with and without /; follow -L when redirects are in scope
All tools return 403 immediately WAF or IP block on edge Confirm scope; test from allowed network or origin IP when authorized
Nikto scan runs a long time Default test depth Keep -maxtime on lab hosts

Summary

You built a hidden endpoint inventory on Metasploitable 2 using the layered workflow real engagements expect: probe API documentation paths, capture live traffic in browser DevTools, crawl JavaScript with Katana, then brute force directories with Gobuster, ffuf, and dirb. Validate every hit with curl, spider shallow links with wget, and fingerprint the host with Nikto.

Metasploitable returned 404 for OpenAPI, Swagger, and GraphQL paths, which is typical for this lab image. Katana still surfaced dvwa and mutillidae routes that wordlists alone might miss. On modern targets, browser Network traffic and JavaScript analysis usually outperform blind directory brute force for REST and GraphQL discovery.

Plan WAF versus origin testing on production targets so you do not misread edge 403 responses as missing endpoints. Run these steps only on authorized applications. When you need recursive forced browsing, custom filters, or authenticated crawling, continue with URL fuzzer tools and manual testing in Burp Suite.


References


Frequently Asked Questions

What is the difference between a web path and an API endpoint?

A web path is any URL the server answers, such as /admin/ or /phpinfo.php. An API endpoint is a URL paired with an HTTP method (for example GET /api/v1/users) that returns or changes application data, often in JSON. GraphQL uses a single /graphql path with POST operations.

How do I find hidden API endpoints?

Start with API documentation probes (/openapi.json, /swagger-ui, /graphql), then review browser Network traffic for Fetch/XHR calls the frontend makes. Crawl JavaScript with Katana, check robots.txt and sitemap.xml, and use directory brute force for unlinked folders. Validate every lead with curl before you report it.

When should I use Gobuster versus ffuf or dirb?

Gobuster is straightforward for directory scans with clear status lines. ffuf is faster to tune with match and filter flags and scales to large wordlists. dirb is older but still useful when you want recursive mode and built-in reporting. Run more than one tool on lab targets and compare hits with Katana and browser traffic.

Are robots.txt and sitemap.xml reliable for finding hidden paths?

They are low-cost first checks when the files exist. Some sites omit them, return 404, or list only public marketing paths while sensitive admin routes stay unlisted. Treat Disallow entries and sitemap URLs as leads and validate each path with curl.

Why do I get 403 instead of 404 during directory brute force?

A 403 means the path likely exists but the server denied your request. WAF rules, IP blocks, and missing authentication often return 403. Record the path as a finding and try validation from an allowed network or authenticated session when scope permits.

Active path guessing is appropriate only on applications you own or are explicitly authorized to test. High request rates can trigger WAF blocks and incident response, so confirm scope, rate limits, and logging rules before you run Gobuster or ffuf against production hosts.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)