OSINT — Guided walkthrough
One target. A real one. We will send it nothing. And at the end, we will know: its subdomains, its technologies, its key employees, its leaks, and its five likely weak points.
We use a public bug bounty program, where the company explicitly authorizes testing inside a defined scope. You can do the same at home by picking a program on HackerOne or Bugcrowd. In the walkthrough that follows, we use a fictional name demo-corp.com to illustrate — but everything you see is reproducible on a real, authorized target.
What you will be able to do after this lesson
- Go from a bare domain name to an infrastructure map in 90 minutes.
- Use
crt.sh,subfinder,amass -passive,dnsx(passive mode),theHarvester,whatweb. - Tell live subdomains from dead ones without touching them.
- Cross-check three independent sources before you conclude.
- Produce a 2-page target sheet you can use as soon as module 4.
Step 0 — The starting point
A single line:
Cible : demo-corp.com
Programme : bug bounty public, scope in-scope = *.demo-corp.com
That is all. We start from there.
Create the working folder:
mkdir -p ~/osint/demo-corp/{dns,subs,tech,people,leaks,archive}
cd ~/osint/demo-corp
Each family of sources gets its own subdirectory. Discipline from the first minute.
Step 1 — Basic DNS (2 minutes)
The essential records, one by one:
for t in A AAAA MX NS TXT SOA CAA; do
echo "=== $t ==="
dig +short demo-corp.com $t
done | tee dns/racine.txt
Typical output:
=== A ===
104.21.44.15
172.67.187.219
=== AAAA ===
=== MX ===
1 aspmx.l.google.com.
5 alt1.aspmx.l.google.com.
=== NS ===
ada.ns.cloudflare.com.
kirk.ns.cloudflare.com.
=== TXT ===
"v=spf1 include:_spf.google.com include:mailgun.org -all"
"google-site-verification=abc123def456..."
"MS=ms84726311"
"stripe-verification=xyz789..."
=== SOA ===
ada.ns.cloudflare.com. dns.cloudflare.com. 2354...
=== CAA ===
0 issue "letsencrypt.org"
0 issue "digicert.com"
Reading it:
- A: two IPs in the Cloudflare pool (104.21.x and 172.67.x). The site sits behind Cloudflare — you will not see the real IP without extra work.
- MX: Google Workspace. No mail server of their own, directly.
- NS:
ada.ns.cloudflare.com— Cloudflare manages DNS as well. Consistent. - TXT:
- SPF allows Google and Mailgun. Two SaaS services identified.
- google-site-verification: they use Google Search Console.
- MS=: they use Microsoft (often Bing Webmaster).
- stripe-verification: they process Stripe payments. That means PCI-DSS lives somewhere.
- CAA: only Let's Encrypt and DigiCert may issue. A good hygiene signal.
In 2 minutes, we already know: Cloudflare, Google Workspace, Mailgun, Stripe. We sent no packet to demo-corp.com.
Step 2 — DMARC and email posture (1 minute)
dig +short _dmarc.demo-corp.com TXT
Output:
"v=DMARC1; p=none; rua=mailto:dmarc-reports@demo-corp.com"
Reading it: p=none. The DMARC policy is there but does nothing. Concrete result: an attacker can spoof emails that pretend to come from demo-corp.com; those emails will still reach the recipient even when they fail DMARC. That is a phishing vector to note for module 6.
Save that in dns/dmarc.txt.
Step 3 — Subdomains via crt.sh (3 minutes)
Certificate Transparency, the best source for the first wave of subdomains. We query in JSON and clean with jq:
curl -s 'https://crt.sh/?q=%25.demo-corp.com&output=json' \
| jq -r '.[].name_value' \
| tr '[:upper:]' '[:lower:]' \
| sed 's/^\*\.//' \
| sort -u \
> subs/from-crt.txt
wc -l subs/from-crt.txt
Typical output:
127 subs/from-crt.txt
An excerpt:
admin.demo-corp.com
api.demo-corp.com
api-dev.demo-corp.com
api-staging.demo-corp.com
beta.demo-corp.com
blog.demo-corp.com
cdn.demo-corp.com
customer.demo-corp.com
demo-corp.com
dev.demo-corp.com
docs.demo-corp.com
help.demo-corp.com
internal.demo-corp.com
jenkins.demo-corp.com
jira.demo-corp.com
mail.demo-corp.com
monitoring.demo-corp.com
old-admin.demo-corp.com
partners.demo-corp.com
prod.demo-corp.com
sso.demo-corp.com
staging.demo-corp.com
status.demo-corp.com
support.demo-corp.com
vpn.demo-corp.com
web.demo-corp.com
www.demo-corp.com
Three lines that make an attacker smile:
internal.demo-corp.com— a subdomain that should never have leaked out.jenkins.demo-corp.com— a CI/CD server, historically a mine of flaws.old-admin.demo-corp.com— the old admin panel. That one smells like something forgotten.
In 3 minutes, we went from 1 name to 127.
Step 4 — Cross-check with subfinder and amass -passive
subfinder queries about twenty third-party APIs (Shodan, VirusTotal, SecurityTrails, DNSDumpster…) and returns a list. 100% passive.
subfinder -d demo-corp.com -silent -all -o subs/from-subfinder.txt
wc -l subs/from-subfinder.txt
143 subs/from-subfinder.txt
Same idea, amass in passive mode:
amass enum -passive -d demo-corp.com -o subs/from-amass.txt
Then we merge:
cat subs/from-*.txt | sort -u > subs/all-passive.txt
wc -l subs/all-passive.txt
178 subs/all-passive.txt
From 127 to 178. Each source brought a few finds. That is why we cross-check.
Step 5 — Passive resolution with dnsx
Watch this: dnsx resolves names. Each resolution goes through a public DNS resolver (Google 8.8.8.8 by default). It is still passive for the target if you use a resolver that does not belong to the target. Check that the NS of demo-corp.com are not your resolvers (they are not, here: Cloudflare manages their DNS, but your dnsx will query Cloudflare Public 1.1.1.1 — a different service).
dnsx -l subs/all-passive.txt -a -resp -silent -r 1.1.1.1,8.8.8.8 \
-o subs/all-resolved.txt
head subs/all-resolved.txt
Output:
admin.demo-corp.com [104.21.44.15]
api.demo-corp.com [104.21.44.15]
api-dev.demo-corp.com [3.98.14.202]
api-staging.demo-corp.com [104.21.44.15]
internal.demo-corp.com [192.168.10.5]
jenkins.demo-corp.com [3.98.14.209]
old-admin.demo-corp.com [66.42.11.198]
Three lines that talk:
internal.demo-corp.comresolves to 192.168.10.5 — a private IP published in public DNS. The subdomain is meant to be reached over VPN, but its presence in DNS confirms the service exists. Flag it.api-dev.demo-corp.comon3.98.x.x— AWS ca-central-1 block, not behind Cloudflare. That is the real server, exposed. Priority target for the active phase.old-admin.demo-corp.comon66.42.x.x— Vultr. A forgotten server, outside the main CDN. Priority target as well.
Note these three lines. They will steer the whole of module 4.
Step 6 — Passive technology fingerprinting
Watch this: whatweb and wappalyzer load the page — that is active. To stay strictly passive, we use:
- BuiltWith (through the web UI from your browser — but your browser loads BuiltWith, not the target).
- Shodan:
shodan host <IP>returns what Shodan has already collected.
shodan host 3.98.14.202 > tech/shodan-api-dev.txt
cat tech/shodan-api-dev.txt
Typical output:
3.98.14.202
Hostnames: ec2-3-98-14-202.ca-central-1.compute.amazonaws.com
Country: Canada
City: Montréal
Organization: Amazon.com, Inc.
Open Ports: 22, 80, 443, 8080
22/tcp SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.5
80/tcp nginx/1.18.0
443/tcp nginx/1.18.0 — TLSv1.3 — CN=api-dev.demo-corp.com
8080/tcp Django DEBUG page found
The gold line: Django DEBUG page found. Shodan has already seen a Django debug mode exposed on :8080. A Django debug mode exposes:
- The full stack on error.
- Environment variables (so often secret keys).
- Sometimes the complete
SECRET_KEY.
We just found, without sending a packet to demo-corp.com, a probably critical flaw. We will confirm it in week 4.
Note the finding in tech/decouvertes.md:
- [CRITICAL] api-dev.demo-corp.com:8080 — Django in DEBUG (source: Shodan)
- [INFO] api-dev.demo-corp.com : Nginx 1.18, OpenSSH 8.2 on Ubuntu 20
Step 7 — Key people (theHarvester + LinkedIn)
theHarvester -d demo-corp.com -b linkedin,bing,duckduckgo -l 500 \
-f people/harvester.html
theHarvester queries third-party APIs and LinkedIn (usually through Bing). Typical output:
[*] Emails found: 34
admin@demo-corp.com
contact@demo-corp.com
j.dupuis@demo-corp.com
m.tremblay@demo-corp.com
...
[*] Hosts found:
(Same, redundant with what we already have)
[*] LinkedIn profiles (via Bing):
Marie Tremblay - CTO at Demo Corp
Jean Dupuis - Senior DevOps at Demo Corp
Étienne Roy - Support Lead at Demo Corp
...
The email pattern jumps out at once: initiale.nom@demo-corp.com. From LinkedIn, you can guess the email of most of the client's employees, without ever asking them.
Consolidate into people/personnes.csv:
prenom,nom,titre,email,linkedin
Marie,Tremblay,CTO,m.tremblay@demo-corp.com,https://www.linkedin.com/in/marietremblay
Jean,Dupuis,Senior DevOps,j.dupuis@demo-corp.com,...
Étienne,Roy,Support Lead,e.roy@demo-corp.com,...
This file feeds module 6 (social engineering).
Step 8 — Public leaks
# HaveIBeenPwned by domain — requires an API key (paid, but cheap).
curl -s -H "hibp-api-key: $HIBP_KEY" \
"https://haveibeenpwned.com/api/v3/breacheddomain/demo-corp.com" \
| jq . > leaks/hibp.json
# Preview:
jq -r 'keys[] as $k | "\($k) → \(.[$k] | join(", "))"' leaks/hibp.json | head
Output:
j.dupuis → Adobe, Collection#1, MyFitnessPal
support → Collection#1
etienne.roy → LinkedIn, Dropbox
Two technical accounts (j.dupuis, etienne.roy) have already leaked in public dumps. If the client reuses passwords — and statistically, a pentester can count on that — we will be able to try password spraying in week 5.
Follow up with GitHub Search:
- Go to github.com/search?type=commits&q=%22demo-corp.com%22.
- Go to
github.com/<employe>?tab=repositoriesfor each listed employee.
Look for repositories named demo-corp-*, scripts, backups, dotfiles. You regularly find:
- A
.envwith secrets. - Scripts with hardcoded passwords.
- Internal URLs.
In our walkthrough, a public dotfiles repo from a former employee contains a deploy.sh file with:
ssh deploy@old-admin.demo-corp.com -i ~/.ssh/deploy-key
Confirmed target: old-admin.demo-corp.com, account deploy. To exploit in week 4.
Step 9 — Archives (Wayback Machine)
curl -s "https://web.archive.org/cdx/search/cdx?url=demo-corp.com/*&output=json&limit=200" \
| jq -r '.[1:] | .[] | .[2]' \
| sort -u > archive/urls-historiques.txt
wc -l archive/urls-historiques.txt
Output:
612 archive/urls-historiques.txt
A grep on revealing keywords:
grep -iE 'admin|backup|test|debug|swagger|api-doc|internal' archive/urls-historiques.txt
https://demo-corp.com/admin-old/
https://demo-corp.com/api/v1/debug
https://demo-corp.com/backup.sql (2019)
https://demo-corp.com/swagger.json
backup.sql in 2019 — probably deleted since, but we will check in module 4: the Wayback Machine does not guarantee deletion on the server.
Step 10 — Consolidate the sheet
Open ~/osint/demo-corp/fiche-cible.md and fill it with what precedes. Priority targets go in bold.
# Target sheet — demo-corp.com (2026-04-15)
## 1. Confirmed assets
- Root domain: demo-corp.com (Cloudflare DNS + proxy)
- Subdomains: 178 collected, including:
- **api-dev.demo-corp.com → AWS ca-central-1 (3.98.14.202), Django DEBUG on :8080**
- **old-admin.demo-corp.com → Vultr (66.42.11.198), presumed deploy account**
- internal.demo-corp.com → 192.168.10.5 (private), published by mistake
- jenkins.demo-corp.com → AWS
- jira.demo-corp.com → Atlassian Cloud (scope to confirm)
## 2. Scope to negotiate with the client
- internal.demo-corp.com (published private IP): add to the RoE?
- Atlassian and Google subdomains: out of scope.
## 3. Technologies
- Front (www): Next.js behind Cloudflare
- Prod API: unknown, filtered by Cloudflare
- Dev API: Django, Nginx 1.18, Ubuntu 20 (source: Shodan)
- Mail: Google Workspace
- Related SaaS: Stripe, Mailgun, Atlassian, Sentry
## 4. Likely flaws (to confirm in week 4)
- CRITICAL : Django DEBUG exposed on api-dev:8080
- HIGH : old-admin.demo-corp.com — inherited admin panel
- MEDIUM : DMARC p=none → spoofed phishing possible
- MEDIUM : backup.sql (2019) — still on the current server?
## 5. Key people (source: LinkedIn + theHarvester)
- Marie Tremblay, CTO — m.tremblay@demo-corp.com
- Jean Dupuis, Sr DevOps — j.dupuis@demo-corp.com [HIBP leak × 3]
- Étienne Roy, Support Lead — e.roy@demo-corp.com [HIBP leak × 2]
+ 21 others in people/personnes.csv
## 6. Public leaks
- HIBP: 2 high-value accounts with likely password reuse
- GitHub: a former employee's dotfiles cite old-admin.demo-corp.com and the deploy account
## 7. Next step (module 4)
1. Confirm Django DEBUG on api-dev:8080 — 30 min.
2. Map old-admin.demo-corp.com: ports, versions — 45 min.
3. Check /backup.sql on www — 5 min.
4. Careful password spraying on the 2 HIBP accounts — 30 min.
Wrap-up
In 90 minutes, without sending a single packet to demo-corp.com:
- We have 178 subdomains.
- We have one almost certain critical flaw (Django DEBUG exposed).
- We have two high-value secondary targets (
old-admin,internal). - We have an attack plan for week 4.
- We have leaked accounts we can use later.
- The target knows nothing.
That is the power of well-run OSINT. It is the module that returns the most per minute invested.
Next lesson: your turn. You pick a bug bounty program and you do the same thing, on your own.