port-service-discovery について
Webアプリケーション周辺のサービスを、範囲を限定してNmapで調査するセキュリティ評価スキルです。ポートスキャン、サービス確認、露出状況の確認、結果の記録を支援します。
主な機能
- 01範囲を限定したNmapポートスキャン
- 02サービスバージョン検出
- 03重要なサービス露出の確認
- 04ファイアウォールとWAFの識別
- 05結果の検証ガイド
活用シーン
- 01Web偵察後のインフラ調査
- 02ステージングホストのバックエンドポート確認
- 03露出したデータベースや管理サービスの検証
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill port-service-discoveryインストール後、Claude Code で/port-service-discoveryを入力して読み込まれたか確認します。
詳細
ポートとサービスの検出スキル
スコープを限定した Nmap 検出を使用して、データベース、リモート管理、メール、キャッシュ、標準外の API ポートなど、Web アプリケーションに隣接するサービスを特定します。ポートが開いていることは観測結果の一つです。サービスの特定と不正アクセスの確認には、それぞれ別途検証が必要です。
使用する場面
- 価値の高いターゲットで
deep-invadeのフェーズ 6 を実行するとき。 - 表層の偵察で悪用可能な Web の脆弱性が見つからなかった後に、インフラへ調査対象を切り替えるとき。
- ターゲットが、標準外のポートでバックエンド API を提供する SaaS のとき。
- ステージング用サブドメインを発見した後に、データベース/管理ポートを確認するとき。
前提条件
- 実行環境で
nmapが利用可能であること。 - ターゲットのドメインまたは IP アドレス。
- 全ポートスキャンを行う場合は、時間に余裕があること(65535 ポートすべてのスキャンには 10~60 分かかる場合があります)。
実行方法
# Fast top-100 port scan (30 seconds)
nmap -F --open -T4 TARGET -oN nmap_fast.txt
# Top 1000 ports (2-3 minutes)
nmap --top-ports 1000 --open -T4 TARGET -oN nmap_1000.txt
# Service version detection on open ports
nmap -sV -p $(grep 'open' nmap_fast.txt | cut -d/ -f1 | tr '\n' ',') TARGET
# Firewall bypass: fragment packets
nmap -f -F TARGET
クイックリファレンス
| ポート | サービス | 検出結果の重大度 |
|---|---|---|
| 3306 | MySQL | 致命的(インターネットに公開されている場合) |
| 27017 | MongoDB | 致命的(認証がない場合) |
| 6379 | Redis | 致命的(認証がない場合) |
| 5432 | PostgreSQL | 高 |
| 1433 | MS SQL Server | 高 |
| 21 | FTP | 高(匿名ログインが可能か?) |
| 22 | SSH | 情報(脆弱な認証がないか確認) |
| 8080/8081/8082/8084 | 内部 API | 高(バックエンドサービスが公開されている) |
| 9200 | Elasticsearch | 高(データ漏えい) |
| 25/587 | SMTP | 中(オープンリレーか?) |
| 110/143/993 | POP3/IMAP | 情報 |
| 8443 | HTTPS 代替ポート(管理パネル) | 中 |
| 9090 | Prometheus/Cockpit | 中 |
| 3000 | Grafana/Node.js 開発用 | 中 |
手順
ステップ 1 — 上位 100 ポートの高速スキャン
TARGET="$1"
OUTDIR="$OUTDIR/ports"
mkdir -p "$OUTDIR"
echo "[*] Fast scan (top 100 ports) on $TARGET..."
nmap -F --open -T4 --max-retries 1 --host-timeout 60s "$TARGET" -oN "$OUTDIR/${TARGET}_fast.nmap" 2>/dev/null
echo "[*] Open ports:"
grep 'open' "$OUTDIR/${TARGET}_fast.nmap" || echo " None found"
ステップ 2 — サービスのバージョン検出
TARGET="$1"
OUTDIR="$OUTDIR/ports"
# Get comma-separated list of open ports
OPEN_PORTS=$(grep '^[0-9]' "$OUTDIR/${TARGET}_fast.nmap" | awk -F/ '{print $1}' | tr '\n' ',' | sed 's/,$//')
if [[ -n "$OPEN_PORTS" ]]; then
echo "[*] Service detection on ports: $OPEN_PORTS"
nmap -sV --version-intensity 5 -p "$OPEN_PORTS" "$TARGET" -oN "$OUTDIR/${TARGET}_services.nmap" 2>/dev/null
echo "[*] Services:"
grep 'open' "$OUTDIR/${TARGET}_services.nmap"
else
echo "[-] No open ports found"
fi
ステップ 3 — 重大な露出の確認
TARGET="$1"
OUTDIR="$OUTDIR/ports"
echo "[*] Critical exposure assessment:"
# MySQL (3306)
if grep -q '3306.*open' "$OUTDIR/${TARGET}_fast.nmap" 2>/dev/null; then
echo ""
echo "[CRITICAL] MySQL 3306 OPEN — attempting banner grab..."
# Try to get MySQL version
mysql_info=$(timeout 5 nc -w 3 "$TARGET" 3306 </dev/null 2>/dev/null | head -1)
[[ -n "$mysql_info" ]] && echo " Banner: $mysql_info"
# Test for no-auth MySQL
echo " Testing anonymous access..."
# This typically requires mysql client; note methodology
echo " Manual check: mysql -h $TARGET -u root --skip-ssl"
echo " Manual check: mysql -h $TARGET -u admin --skip-ssl"
fi
# MongoDB (27017)
if grep -q '27017.*open' "$OUTDIR/${TARGET}_fast.nmap" 2>/dev/null; then
echo ""
echo "[CRITICAL] MongoDB 27017 OPEN"
echo " Manual check: mongosh mongodb://$TARGET:27017"
fi
# Redis (6379)
if grep -q '6379.*open' "$OUTDIR/${TARGET}_fast.nmap" 2>/dev/null; then
echo ""
echo "[HIGH] Redis 6379 OPEN — testing no-auth access..."
redis_test=$(timeout 5 bash -c "echo -e 'PING\r\nINFO\r\n' | nc -w 3 '$TARGET' 6379 2>/dev/null")
if echo "$redis_test" | grep -q "PONG"; then
echo " [CRITICAL] Redis NO AUTH — full access!"
echo " Response: $(echo "$redis_test" | head -5)"
else
echo " Redis requires auth (or connection failed)"
fi
fi
# Internal API ports (8080-8089)
for port in 8080 8081 8082 8084 8088 8443 3000 5000 9000 9090; do
if grep -q "${port}.*open" "$OUTDIR/${TARGET}_fast.nmap" 2>/dev/null; then
echo ""
echo "[HIGH] Port $port OPEN — probing HTTP..."
http_code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 "http://$TARGET:$port/" 2>/dev/null)
if [[ "$http_code" != "000" ]]; then
echo " HTTP $http_code on port $port"
title=$(curl -sk --max-time 5 --connect-timeout 5 "http://$TARGET:$port/" 2>/dev/null | grep -Eo '<title>\K[^<]+')
[[ -n "$title" ]] && echo " Title: $title"
# Check for Swagger/API docs
for api_path in "swagger.json" "api-docs" "swagger-ui.html" "graphql" "actuator/health"; do
api_code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 "http://$TARGET:$port/$api_path")
[[ "$api_code" == "200" ]] && echo " [API] http://$TARGET:$port/$api_path (HTTP 200)"
sleep 0.2
done
fi
fi
done
# FTP (21) — check for anonymous login
if grep -q '21.*open' "$OUTDIR/${TARGET}_fast.nmap" 2>/dev/null; then
echo ""
echo "[HIGH] FTP 21 OPEN — testing anonymous login..."
anon_test=$(timeout 10 bash -c "echo -e 'anonymous\nanonymous\n' | nc -w 3 '$TARGET' 21 2>/dev/null")
if echo "$anon_test" | grep -qi "230"; then
echo " [CRITICAL] Anonymous FTP login successful!"
elif echo "$anon_test" | grep -qi "530\|331"; then
echo " Anonymous FTP: requires auth"
fi
fi
# SSH (22) — check for weak ciphers
if grep -q '22.*open' "$OUTDIR/${TARGET}_fast.nmap" 2>/dev/null; then
echo ""
echo "[INFO] SSH 22 OPEN"
ssh_banner=$(timeout 5 nc -w 3 "$TARGET" 22 </dev/null 2>/dev/null | head -1)
[[ -n "$ssh_banner" ]] && echo " Banner: $ssh_banner"
fi
ステップ 4 — 拡張ポート範囲(高速スキャンで何も見つからなかった場合)
TARGET="$1"
OUTDIR="$OUTDIR/ports"
# If no ports found in top-100, expand to top-1000
if ! grep -q 'open' "$OUTDIR/${TARGET}_fast.nmap" 2>/dev/null; then
echo "[*] No ports in top-100, scanning top-1000..."
nmap --top-ports 1000 --open -T4 --max-retries 1 --host-timeout 120s "$TARGET" -oN "$OUTDIR/${TARGET}_1000.nmap" 2>/dev/null
OPEN=$(grep 'open' "$OUTDIR/${TARGET}_1000.nmap")
if [[ -z "$OPEN" ]]; then
# Try SYN scan with fragmentation for firewall evasion
echo "[*] Still nothing — trying fragmented SYN scan..."
nmap -f -sS -F --open -T4 "$TARGET" -oN "$OUTDIR/${TARGET}_frag.nmap" 2>/dev/null
fi
fi
ステップ 5 — ファイアウォール/WAF のフィンガープリント
TARGET="$1"
echo "[*] WAF/CDN detection:"
# Check for Cloudflare
cf_header=$(curl -skI --max-time 5 --connect-timeout 5 "https://$TARGET/" 2>/dev/null | grep -i "cf-ray\|cloudflare")
[[ -n "$cf_header" ]] && echo " Cloudflare detected"
# Check for AWS CloudFront
cf_header=$(curl -skI --max-time 5 --connect-timeout 5 "https://$TARGET/" 2>/dev/null | grep -i "x-amz-cf\|cloudfront")
[[ -n "$cf_header" ]] && echo " AWS CloudFront detected"
# Check origin IP (bypass CDN)
echo "[*] Historical DNS records for origin IP discovery:"
# SecurityTrails, DNSDumpster, etc. — use HTTP fetch for these
echo " Manual: securitytrails.com/domain/$TARGET/dns/a"
echo " Manual: dnsdumpster.com"
# Try direct IP connection (if known)
# curl -sk --resolve "$TARGET:443:ORIGIN_IP" "https://$TARGET/"
注意点
- nmap の SYN スキャンには raw ソケット権限が必要です。 実行環境でその権限が付与されていない場合は、
-sT(TCP 接続スキャン)を使用してください。 - ポートスキャンのレート制限に注意してください。 一部のプロバイダー(AWS、Cloudflare)は、ポートスキャンにレート制限を適用したり、ブロックしたりします。機密性の高いターゲットでは、
-T2(控えめなタイミング設定)と--max-retries 1を使用してください。 - MySQL/MongoDB のバナー取得に失敗する場合があります。 一部のデータベースは TLS ネゴシエーションを必要とします(MySQL 8.0+ では
caching_sha2_passwordがデフォルトです)。バナーが空の場合もあります。 - 内部 API のポートがタイムアウトする場合があります。 特定の Host ヘッダーや有効な HTTP リクエストにしか応答しないサービスもあります。さまざまな Host ヘッダーを指定して
curlを使用してください。
検証
- 開いているすべてのポートについて、サービスのバージョン検出(
nmap -sV)で必ず確認してください。 - MySQL へのアクセスは、ポートが開いていることの確認だけでなく、実際の接続試行で必ずテストしてください。
- Redis の認証なしアクセスでは、
PINGコマンドに対してPONGが返ることを必ず確認してください。 - FTP の匿名ログインでは、コード 230(ログイン成功)が返ることを必ず確認してください。
- すべての検出結果について、正確なポート、サービス、バージョン、アクセスレベルを必ず記録してください。
コメント
Markdown 対応