99085adc76
- Flask backend with 23 API routes (entertainment, telegram, docker, system) - Alpine.js + Tailwind CSS dark-mode SPA with 4 tabs - pos-dashboard CLI tool with port/config management - Mobile slide-in drawer with swipe-to-close - Sticky header stays pinned on scroll - Tab completion fixes for category-less tools - POST /api/telegram/commands endpoint for adding commands
269 lines
6.9 KiB
Bash
Executable File
269 lines
6.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
# POS: network scan — Parallel ping sweep of CIDR
|
|
|
|
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: pos network scan <cidr> [--full] [--retries N]
|
|
|
|
Two-phase network scan using nmap.
|
|
|
|
Phase 1: Fast host discovery (finds alive hosts)
|
|
Phase 2: Full metadata scan on alive hosts only (--full only)
|
|
|
|
Options:
|
|
--full Detailed scan: OS, ports, services, NSE scripts (slower)
|
|
--retries N Retries per host in discovery (default: 1)
|
|
|
|
Examples:
|
|
pos network scan 192.168.1.0/24
|
|
pos network scan 10.0.0.0/28 --full
|
|
pos network scan 172.1.1.104
|
|
pos network scan 192.168.1.0/24 --retries 3
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
# Deps guard before -h|--help (help also errors without nmap).
|
|
command -v nmap &>/dev/null || err "nmap not found — install with: sudo apt install nmap"
|
|
|
|
case "${1:-}" in
|
|
-h|--help|"") usage ;;
|
|
esac
|
|
|
|
net=""
|
|
full=0
|
|
retries=1
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--full) full=1; shift ;;
|
|
--retries)
|
|
if [[ -z "${2:-}" || "$2" == --* ]]; then
|
|
err "--retries requires a number"
|
|
fi
|
|
retries="$2"; shift 2 ;;
|
|
*) net="$1"; shift ;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$net" ]]; then
|
|
err "CIDR missing — e.g. pos network scan 192.168.1.0/24"
|
|
fi
|
|
|
|
# ── Input validation ───────────────────────────────────────────
|
|
# Bare IP → /32
|
|
if [[ "$net" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
|
net="${net}/32"
|
|
# Valid CIDR
|
|
elif [[ "$net" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then
|
|
: # ok
|
|
else
|
|
err "bad target '$net' — expected IP (172.1.1.104) or CIDR (192.168.1.0/24)"
|
|
fi
|
|
|
|
# ── Estimate host count ────────────────────────────────────────
|
|
cidr_bits="${net##*/}"
|
|
if [[ "$cidr_bits" -ge 24 ]]; then
|
|
host_estimate=$(( 1 << (32 - cidr_bits) ))
|
|
elif [[ "$cidr_bits" -ge 16 ]]; then
|
|
host_estimate="$(( 1 << (32 - cidr_bits) ))+"
|
|
else
|
|
host_estimate="many"
|
|
fi
|
|
|
|
is_root=0
|
|
[[ $EUID -eq 0 ]] && is_root=1
|
|
|
|
can_sudo=0
|
|
if [[ "$is_root" -eq 1 ]]; then
|
|
can_sudo=1
|
|
elif sudo -n nmap -V &>/dev/null; then
|
|
can_sudo=1
|
|
elif [[ "$full" -eq 1 && -t 0 ]]; then
|
|
can_sudo=1
|
|
fi
|
|
|
|
tmpfile=$(mktemp /tmp/scan-XXXXXX.txt)
|
|
trap 'rm -f "$tmpfile"' EXIT
|
|
|
|
# ── Phase 1: Fast host discovery ───────────────────────────────
|
|
nmap_cmd="nmap"
|
|
[[ "$can_sudo" -eq 1 ]] && nmap_cmd="sudo nmap"
|
|
|
|
echo "Discovering hosts in $net (~$host_estimate) ..."
|
|
echo
|
|
|
|
$nmap_cmd -sn -T5 -n \
|
|
--min-rate 1000 \
|
|
--min-parallelism 1024 \
|
|
--min-hostgroup 1024 \
|
|
--max-retries "$retries" \
|
|
--host-timeout 5s \
|
|
"$net" 2>/dev/null | awk '/^Nmap scan report for/ {
|
|
ip = $(NF);
|
|
gsub(/[()]/, "", ip);
|
|
print ip;
|
|
}' > "$tmpfile"
|
|
|
|
host_count=$(wc -l < "$tmpfile")
|
|
|
|
if [[ "$host_count" -eq 0 ]]; then
|
|
echo "No hosts found."
|
|
exit 0
|
|
fi
|
|
|
|
echo "Found $host_count host(s)."
|
|
echo
|
|
|
|
if [[ "$full" -eq 0 ]]; then
|
|
cat "$tmpfile"
|
|
echo
|
|
echo "Done."
|
|
exit 0
|
|
fi
|
|
|
|
# ── Phase 2: Full metadata scan ────────────────────────────────
|
|
echo "Scanning $host_count host(s) (full) ..."
|
|
echo
|
|
|
|
nmap_args="-sV --version-intensity 9 -sC -T4 -n"
|
|
nmap_args="$nmap_args --min-parallelism 256 --max-parallelism 512"
|
|
nmap_args="$nmap_args --min-hostgroup 256 --max-hostgroup 512"
|
|
nmap_args="$nmap_args --min-rate 1000 --max-retries 1"
|
|
nmap_args="$nmap_args --host-timeout 60s --max-rtt-timeout 200ms"
|
|
nmap_args="$nmap_args --script ssh-hostkey,ssl-cert,http-title,http-server-header,smb-os-discovery,nbstat,rpcinfo"
|
|
[[ "$can_sudo" -eq 1 ]] && nmap_args="$nmap_args -O --osscan-guess"
|
|
|
|
# shellcheck disable=SC2086
|
|
$nmap_cmd $nmap_args -iL "$tmpfile" 2>/dev/null | \
|
|
awk -v cy="$CYAN" -v bo="$BOLD" -v di="$DIM" -v re="$RESET" '
|
|
BEGIN { ip_count = 0; has_os = 0; hc = cy bo; dc = di }
|
|
|
|
/^Nmap scan report for/ {
|
|
ip = $(NF);
|
|
gsub(/[()]/, "", ip);
|
|
hostname = "";
|
|
if ($(NF) ~ /^\(/) {
|
|
hostname = $(NF-1);
|
|
} else if (NF > 4) {
|
|
if (ip != $(NF-1) && $(NF-1) !~ /^(for|[0-9])/) {
|
|
hostname = $(NF-1);
|
|
}
|
|
}
|
|
if (ip_count > 0) printf "\n";
|
|
ip_count++;
|
|
has_os = 0;
|
|
if (hostname != "" && hostname != ip)
|
|
printf "%s%s (%s)%s\n", hc, ip, hostname, re;
|
|
else
|
|
printf "%s%s%s\n", hc, ip, re;
|
|
}
|
|
|
|
/^MAC Address/ {
|
|
vendor = $0;
|
|
sub(/.*\(/, "", vendor);
|
|
sub(/\).*/, "", vendor);
|
|
printf " %s%-10s%s %s %s\n", dc, "MAC:", re, $3, vendor;
|
|
}
|
|
|
|
/^Aggressive OS guesses:/ {
|
|
has_os = 1;
|
|
line = $0;
|
|
sub(/.*guesses: /, "", line);
|
|
gsub(/\s*\(.*/, "", line);
|
|
printf " %s%-10s%s %s\n", dc, "OS:", re, line;
|
|
}
|
|
|
|
/^OS details:/ {
|
|
has_os = 1;
|
|
sub(/.*OS details: /, "");
|
|
printf " %s%-10s%s %s\n", dc, "OS:", re, $0;
|
|
}
|
|
|
|
/^Running:/ {
|
|
sub(/.*Running: /, "");
|
|
printf " %s%-10s%s %s\n", dc, "OS:", re, $0;
|
|
}
|
|
|
|
/^Service Info:/ {
|
|
line = $0;
|
|
sub(/.*Service Info:/, "", line);
|
|
gsub(/^ +/, "", line);
|
|
if (has_os == 0)
|
|
printf " %s%-10s%s %s\n", dc, "Info:", re, line;
|
|
}
|
|
|
|
/^\| ssh-hostkey:/ {
|
|
line = $0;
|
|
sub(/.*ssh-hostkey:/, "", line);
|
|
gsub(/^ +/, "", line);
|
|
if (line ~ /SHA256/) {
|
|
match(line, /SHA256:[A-Za-z0-9+\/=]+/);
|
|
key = substr(line, RSTART, RLENGTH);
|
|
printf " %s%-10s%s %s\n", dc, "SSH:", re, key;
|
|
}
|
|
}
|
|
|
|
/^\| http-title:/ {
|
|
line = $0;
|
|
sub(/.*http-title:/, "", line);
|
|
gsub(/^ +/, "", line);
|
|
gsub(/\s*\[.*$/, "", line);
|
|
if (line != "" && line !~ /^No/)
|
|
printf " %s%-10s%s %s\n", dc, "HTTP Title:", re, line;
|
|
}
|
|
|
|
/^\| http-server-header:/ {
|
|
line = $0;
|
|
sub(/.*http-server-header:/, "", line);
|
|
gsub(/^ +/, "", line);
|
|
printf " %s%-10s%s %s\n", dc, "HTTP Server:", re, line;
|
|
}
|
|
|
|
/^\|_?NetBIOS name:/ {
|
|
line = $0;
|
|
sub(/.*NetBIOS name:/, "", line);
|
|
sub(/,.*$/, "", line);
|
|
gsub(/^ +/, "", line);
|
|
printf " %s%-10s%s %s\n", dc, "NetBIOS:", re, line;
|
|
}
|
|
|
|
/^\|_?SMB OS:/ {
|
|
line = $0;
|
|
sub(/.*SMB OS:/, "", line);
|
|
gsub(/^ +/, "", line);
|
|
printf " %s%-10s%s %s\n", dc, "SMB:", re, line;
|
|
}
|
|
|
|
/^\|_?Domain:/ {
|
|
line = $0;
|
|
sub(/.*Domain:/, "", line);
|
|
gsub(/^ +/, "", line);
|
|
if (line != "" && line !~ /^WORKGROUP/)
|
|
printf " %s%-10s%s %s\n", dc, "Domain:", re, line;
|
|
}
|
|
|
|
/^\| [0-9]+\/tcp/ {
|
|
line = $0;
|
|
gsub(/^ *\| */, "", line);
|
|
printf " %s%-10s%s %s\n", dc, "RPC:", re, line;
|
|
}
|
|
|
|
/^[0-9]+\/tcp[[:space:]]+open/ {
|
|
port = $1; service = $3;
|
|
version = "";
|
|
for (i = 4; i <= NF; i++) version = version " " $i;
|
|
gsub(/^ +/, "", version);
|
|
if (version != "")
|
|
printf " %s%-10s%s %s — %s\n", dc, port, re, service, version;
|
|
else
|
|
printf " %s%-10s%s %s\n", dc, port, re, service;
|
|
}
|
|
'
|
|
|
|
echo
|
|
echo "Done."
|