Quick-reference pentest commands organized by service, plus AI/LLM endpoint fingerprinting and interaction from the terminal.
While studying for the OSCP and OSWA certifications I found it hard to keep track of which commands to run during enumeration for specific services (and how to run them). After transitioning to a fulltime pentester role the problem just got worse. More services, more tools, more flags to remember.
PSE started as a way to keep track of those commands. When AI-powered services started showing up on pentests, I added fingerprinting and chat capabilities so you can discover, identify, and interact with LLM endpoints without leaving the terminal. This is also helpful if you are studying for the OSAI certification.
- Install Instructions
- Basic Usage
- Listing Commands for a Service
- Adding Commands for a Service
- AI Fingerprint
- Interact with AI Endpoints
- HIT Detection
- Session Isolation
- Logging
git clone https://github.com/ssstonebraker/Pentest-Service-Enumeration
cd Pentest-Service-Enumeration
./install.sh
To view basic usage just type:
pse
This will list out the available services and give an example of how to use the program
Pentest Service Enumeration (PSE) v3.0
USAGE:
pse <service|port> Service cheat sheet lookup
pse fp <ip> AI service fingerprint
pse chat <url> "msg" Chat with AI endpoint
SERVICE LOOKUP:
pse smb Show SMB enumeration commands
pse 445 Show services on port 445/tcp
pse 53/udp Port 53 UDP services
pse -l List all services
pse -r "nmap" Search commands
pse -e smb Edit service file
pse -c custom New service template
pse -d smb Delete service
AI RECON:
pse fp <ip> Full fingerprint (all 65535 TCP ports + AI probes)
pse fp <ip> --fresh Skip cache, rescan
pse fp --token PAT <ip> With GitLab auth
pse chat <url> "msg" Send message to endpoint
pse chat <url> Interactive REPL
pse --reset Start new chat session
pse --status Show current session info
OPTIONS:
-h, --help Help -l, --list List services
-s, --short Short -e, --edit Edit service
-c, --create New -d, --delete Delete service
-r, --search Search
[*] Available services in /root/.pse:
--------------------------------------------------------------------------------
adcs [389/tcp,636/tcp,88/tcp,88/udp] Dump all CAs and Templates
dns [53/tcp,53/udp,5353/udp] nmap discover host services using multicast dns (m...
ftp [21/tcp,20/tcp,2121/tcp] Brute Force FTP for a specific username
http [80/tcp,443/tcp,8080/tcp,8443/tcp,8000/tcp,8888/tcp] [dotdotpwn] - Directory Traversal Linux - dotdotpw...
kerberos kerbrute - User enumeration (requires wordlist)
ldap [389/tcp,636/tcp,3268/tcp,3269/tcp] [ldapdomaindump] - Dump information about a domain...
linpriv See what can be ran as root
mimikatz List all commands for module sekurlsa
mssql [1433/tcp,1434/udp] test single sql server connection, local authentic...
nfs [111/tcp,111/udp,2049/tcp,2049/udp] show available nfs mounts
nmap inital scan, list of ip addresses
rpc [111/tcp,111/udp,135/tcp] report rpc information
smb [445/tcp,139/tcp,137/udp,138/udp] smbclient - Interctive session on a smb share fold...
smtp [25/tcp,587/tcp,465/tcp,2525/tcp] Enumerate SMTP users
snmp [161/udp,162/udp] nmap service scan port 161 for snmap
sql Interactively prompt while SQL injecting a saved b...
ssh [22/tcp,2222/tcp,22222/tcp] Hydra brute force ssh for specific user
web Directory discovery (medium)
webdav [80/tcp,443/tcp,8080/tcp,8443/tcp] Test file uploads against webdav
wfuzz Directory Discovery (medium) - ignore 404, 301
--------------------------------------------------------------------------------
[*] AI Commands:
pse fp <ip> Fingerprint AI/LLM services
pse chat <url> "msg" Chat with AI endpoint
--------------------------------------------------------------------------------
pse <service-name>
Below are examples of how to run the program
# pse smb
Output:
[*] Service: smb - Ports: 445/tcp,139/tcp,137/udp,138/udp
--------------------------------------------------------------------------------
smbclient - Interctive session on a smb share folder
[*] smbclient "//$IP/$FOLDER" -U "$USERNAME" --password "$PASSWORD"
--------------------------------------------------------------------------------
smbclient - List available shares
[*] smbclient -L "//$IP" -U "$USERNAME" --password "$PASSWORD"
--------------------------------------------------------------------------------
smbclient - Recurisively download everything (while connected, enter commands one at a time)
[*] 1. recurse on 2. prompt off 3. mget *
--------------------------------------------------------------------------------
Launch a semi-interactive shell
[*] smbexec.py $HOST/$USERNAME:$PASSWORD@$IP
--------------------------------------------------------------------------------
smbclient - (unauthenticated) - List smb share files using a null user
[*] smbclient -L $IP -U -N
--------------------------------------------------------------------------------
ngrep samba version while connecting via smbclient
[*] export INTERFACE="tun0"; sudo ngrep -i -d $INTERFACE 's.?a.?m.?b.?a.*[[:digit:]]'
--------------------------------------------------------------------------------
Recursive directory listing
[*] smbmap -H $ip -R
--------------------------------------------------------------------------------
Create a destination mount directory, mount remote share as guest
[*] sudo mkdir /mnt/$IP_$FOLDER; sudo mount -v -t cifs "//$IP/$FOLDER" /mnt/$IP_$FOLDER -o username=guest
--------------------------------------------------------------------------------
smbclient - (unauthenticated) - Connect to remote smb share as null user
[*] smbclient "//$IP/$SHARE_NAME" -U ""
--------------------------------------------------------------------------------
Scan IP Address for SMB Pipe Names
[*] pipef -a $IP
--------------------------------------------------------------------------------
nxc smb - Check with null user
[*] nxc smb $IP -u '' -p ''
--------------------------------------------------------------------------------
nxc smb - Check with anonymous user
[*] nxc smb $IP -u 'notexistantuser' -p ''
--------------------------------------------------------------------------------
nxc smb - Connect with valid credentials
[*] nxc smb $IP -u "$USERNAME" -p "$PASSWORD"
--------------------------------------------------------------------------------
nxc smb - List available shares
[*] nxc smb $IP -u "$USERNAME" -p "$PASSWORD" --shares
--------------------------------------------------------------------------------
nxc smb - List domain users
[*] nxc smb $IP -u "$USERNAME" -p "$PASSWORD" --users
--------------------------------------------------------------------------------
nxc smb - RID brute force (anonymous)
[*] nxc smb $IP -u 'notexistantuser' -p '' --rid-brute
--------------------------------------------------------------------------------
nxc smb - Run authenticated module (example spider_plus)
[*] nxc smb $IP -u "$USERNAME" -p "$PASSWORD" -M spider_plus
--------------------------------------------------------------------------------
# pse ldap
output:
[*] Service: ldap - Ports: 389/tcp,636/tcp,3268/tcp,3269/tcp
--------------------------------------------------------------------------------
[ldapdomaindump] - Dump information about a domain
[*] ldapdomaindump -u "$USERNAME" -p "$PASSWORD" "$DC_IP"
--------------------------------------------------------------------------------
[ldapsearch] - Dump all user objects (objectClass=user)
[*] ldapsearch -LLL -x -H "ldap://$DC_IP" -D "$USERNAME@$DOMAIN" -w "$PASSWORD" -b "$BASEDN" "(objectClass=user)"
--------------------------------------------------------------------------------
[nxc ldap] - Check if user account is active (512=active, 514=disabled)
[*] nxc ldap "$DC_IP" -u "$USERNAME" -p "$PASSWORD" --query "(sAMAccountName=${USER_TO_CHECK})" "userAccountControl"
--------------------------------------------------------------------------------
[nxc ldap] - Get all LDAP fields for AD user
[*] nxc ldap "$DC_IP" -u "$USERNAME" -p "$PASSWORD" --query "(sAMAccountName=${USER_TO_CHECK})" ""
--------------------------------------------------------------------------------
[nxc ldap] - Test ldap creds to see if they are valid
[*] nxc ldap "$DC_IP" -u "$USERNAME" -p "$PASSWORD"
--------------------------------------------------------------------------------
[nmap] - nmap ldap scan
[*] nmap -n -sV --script "ldap* and not brute" $IP
--------------------------------------------------------------------------------
[ldapsearch] - Unauthenticated bind, replace domain
[*] ldapsearch -x -D "DC=fabricorp,DC=local" -s sub "cn=*" -h $IP
--------------------------------------------------------------------------------
[hydra] - Brute force list of users
[*] hydra -f -I -u -L users.txt -P /usr/share/wordlists/rockyou.txt $IP ldap2 -t 10 -vV
--------------------------------------------------------------------------------
[rpcclient] - SID Lookup (Username is user@domain.local, separate multiple SID by space)
[*] rpcclient -U "$USERNAME" --password="$PASSWORD" //$DC_IP -c "lookupsids $SID"
--------------------------------------------------------------------------------
[nxc smb] - Get AD Lockout Duration (USERNAME="domain\samaccountname")
[*] nxc smb $DC_IP -u $USERNAME -p $PASSWORD --pass-pol
--------------------------------------------------------------------------------
Search by port 80
# pse 80
output:
[*] Port 80/tcp found in the following services:
--------------------------------------------------------------------------------
[HTTP Service]
[*] Ports: 80/tcp,443/tcp,8080/tcp,8443/tcp,8000/tcp,8888/tcp
--------------------------------------------------------------------------------
[dotdotpwn] - Directory Traversal Linux - dotdotpwn
[*] dotdotpwn -m http-url -u http://$IP/site/TRAVERSAL -k "root:"
--------------------------------------------------------------------------------
[dotdotpwn] - Directory Traversal Windows - dotdotpwn
[*] dotdotpwn -o windows -m http-url -u http://$IP/site/TRAVERSAL -k "root:"
--------------------------------------------------------------------------------
[fuff] - Fuzz website directory
[*] ffuf -c -w $WORDLIST -u http://$IP/FUZZ
--------------------------------------------------------------------------------
[dirb] - Scan against http site
[*] dirb http://$IP -r -o $OUTPUTFILE
--------------------------------------------------------------------------------
[dirb] - Scan against https site
[*] dirb https://$IP -r -o $OUTPUTFILE
--------------------------------------------------------------------------------
[dirsearch] - Dirbuster medium for php, txt, sh, and pl extensions
[*] dirsearch -u http://$IP -w /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt -e php,txt,sh,pl -x 404 -t 100
--------------------------------------------------------------------------------
[dirsearch] - RECURSIVE with dirbuster medium for php, txt, sh, and pl extensions
[*] dirsearch -r -u http://$IP -w /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt -e php,txt,sh,pl -x 404 -t 100
--------------------------------------------------------------------------------
[nikto] - scan for header info and all directory checks
[*] nikto -h -C all -host $IP
--------------------------------------------------------------------------------
[curl] - Display only the unqiue text from a web page
[*] curl $IP -s -L | html2text -width '150' | uniq
--------------------------------------------------------------------------------
[gobuster] - Directory scan with common wordlist
[*] gobuster dir -u http://$IP/ -w /usr/share/seclists/Discovery/Web-Content/common.txt -s '200,204,301,302,307,403,500' -e --output $IP_gobuster.txt
--------------------------------------------------------------------------------
[gobuster] - CGI scan, no 302 redirects, 30 second timeout
[*] export IP=$IP; export FOLDER=/usr/share/seclists/Discovery/Web-Content/CGIs.txt; gobuster dir -u http://$IP/ -w $FOLDER --timeout 30s -s '200,204,301,307,403,500' -e --output gobuster-cgi-scan
--------------------------------------------------------------------------------
[nmap] - Enumerate a wordpress site for plugins and themes
[*] nmap -p443 -sV --script http-wordpress-enum $FQDN
--------------------------------------------------------------------------------
[wpscan] - Wordpress plugin vulnerability check
[*] wpscan --url https://$FQDN -e vp --plugins-detection mixed
--------------------------------------------------------------------------------
[nikto] - Scan URL
[*] nikto -h $URL
--------------------------------------------------------------------------------
[nikto] - Scan with a limit of 30 seconds
[*] nikto --host http://$IP -maxtime=30s
--------------------------------------------------------------------------------
[gobuster] - Directory scan with with directory list medium
[*] gobuster dir -u $URL -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
--------------------------------------------------------------------------------
SQL injection test
[*] sqlmap -u "$URL" --batch --crawl=1
--------------------------------------------------------------------------------
[wfuzz] - Directory discovery (medium)
[*] wfuzz -c -z file,/usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt --hc 404,403 $URL/FUZZ
--------------------------------------------------------------------------------
[wfuzz] - Directory Discovery (medium) - ignore 404, 301
[*] URL="http://target/FUZZ";FILE="/usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt"; wfuzz -c -z file,"$FILE" --hc 404,301 "$URL"
--------------------------------------------------------------------------------
[wfuzz] - Directory Discovery (medium) - ignore 404, 403, 301
[*] URL="http://target/FUZZ"; FILE="/usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt"; wfuzz -c -z file,"$FILE" --hc 404,403,301 "$URL"
--------------------------------------------------------------------------------
[wfuzz] - File Discovery
[*] URL="http://target/FUZZ";wfuzz -c -z file,/usr/share/seclists/Discovery/Web-Content/raft-medium-files.txt --hc 301,404,403 "$URL"
--------------------------------------------------------------------------------
[wfuzz] - POST data fuzzing (password cracking)
[*] URL="http://target:80/wp-login.php" wfuzz -c -z file,/usr/share/seclists/Passwords/xato-net-10-million-passwords-100000.txt --hc 404 -d "log=admin&pwd=FUZZ" "$URL"
--------------------------------------------------------------------------------
[wfuzz] - Param value fuzzing (find hidden params)
[*] export URL="http://target:80/index.php?FUZZ=data";wfuzz -c -z file,/usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt --hc 404,301 "$URL"
--------------------------------------------------------------------------------
[wfuzz] - Param value fuzzing (usernames)
[*] URL="http://target:80/index.php?fpv=FUZZ"; wfuzz -c -z file,/usr/share/seclists/Usernames/cirt-default-usernames.txt --hc 404 "$URL"
--------------------------------------------------------------------------------
[wfuzz] - Local File Inclusion
[*] wfuzz -c -u 'http://$IP/mutillidae/index.php?page=FUZZ{arbitrary-file-inclusion.php}' -z file,/usr/share/wordlists/wfuzz/Injections/Traversal.txt,urlencode --filter "lines != 1008"
--------------------------------------------------------------------------------
[WEBDAV Service]
[*] Ports: 80/tcp,443/tcp,8080/tcp,8443/tcp
--------------------------------------------------------------------------------
Test file uploads against webdav
[*] davtest -move -sendbd auto -url http://$IP/webdav
--------------------------------------------------------------------------------
Services are text files located at:
$HOME/.pse/<files>
Every command you want to return should be on a separate line in the format:
description:command
File: $HOME/.pse/nfs
Content:
#PORTS:111/tcp,111/udp,2049/tcp,2049/udp
show available nfs mounts:showmount -e $IP
mount a nfs share:export IP=10.11.1.72; sudo mkdir -p /mnt/$IP/home && sudo mount -t nfs $IP:/home /mnt/$IP/home
You can now use the built-in create functionality:
pse -c <service>
pse -c curl
Output:
[+] Service template created: /Users/braker/.pse/curl
[*]
[*] Edit this file to add your own commands and ports:
[*] pse -e curl
pse -e curl
This will open the service file in your configured editor. If the service doesn't exist, it will create a new one with a helpful template.
# PORTS:port/tcp,port/udp
# PSE Service File Template
#
# Format: Description:Command
# Use $IP, $HOST, $PORT, $USERNAME, $PASSWORD, $FOO, $BAR as placeholders
# You can use any placeholder variables you want in your commands
#
# Example service file:
#PORTS:445/tcp,139/tcp,137/udp,138/udp
[nxc smb] - Check with anonymous user:nxc smb $IP -u 'notexistantuser' -p ''
[nxc smb] - Connect with valid credentials (test creds):nxc smb $IP -u "$USERNAME" -p "$PASSWORD"
[nxc smb] - List available shares:nxc smb $IP -u "$USERNAME" -p "$PASSWORD" --shares
[nxc smb] - List domain users:nxc smb $IP -u "$USERNAME" -p "$PASSWORD" --users
#
# INSTRUCTIONS:
# 1. Replace "port/tcp,port/udp" above with actual ports for this service
# 2. Replace the example commands with your own
# 3. Remove all comments (except your "#PORTS" line (which should be the first line)Replace everything in the file with this:
#PORTS:80/tcp,443/tcp,8080/tcp,8443/tcp
Return headers: curl -I $URL
Return headers, ignore bad cert, follow redirect:curl -k -L -I $URL
Return help content:curl -h
Run curl in verbose mode:curl -v
Edit corresponding service file at $HOME/.pse/<filename> (e.g. $HOME/.pse/smb or $HOME/.pse/dns)
If your want to add a new service, create a file at $HOME/.pse/foo
To add curl as a service for pse:
- Create file
$HOME/.pse/curl - Add the PORTS line (see example)
- Add one line per command you want saved in format:
<description>:<command>
Example content for file $HOME/.pse/curl:
#PORTS:80/tcp,443/tcp,8080/tcp,8443/tcp
Return headers: curl -I $URL
Return headers, ignore bad cert, follow redirect:curl -k -L -I $URL
Return help content:curl -h
Run curl in verbose mode:curl -v
Now when you run command pse curl:
[*] Service: curl - Ports: 80/tcp,443/tcp,8080/tcp,8443/tcp
--------------------------------------------------------------------------------
Return headers
[*] curl -I $URL
--------------------------------------------------------------------------------
Return headers, ignore bad cert, follow redirect
[*] curl -k -L -I $URL
--------------------------------------------------------------------------------
Return help content
[*] curl -h
--------------------------------------------------------------------------------
Run curl in verbose mode
[*] curl -v
--------------------------------------------------------------------------------
Scan a target for AI/LLM services. Finds open ports, probes HTTP endpoints for AI indicators, identifies models, discovers tools, and writes a report.
pse fp <ip>
Example:
# pse fp 192.168.184.21
Output:
Phase 1: nmap -sV --open -p 1-10000 192.168.184.21 (~15-30s)...
7 ports: 22(ssh), 5432(postgresql), 8001(http), 8002(http), 8003(http), 8011(http), 8012(http) (11.2s)
Phase 2: scanning ports 10001-65535 in background...
5 HTTP ports — probing endpoints...
(ffuf will launch in 5s — wordlist: combined-api.txt)
Background scan complete — no additional ports (all 65535 TCP ports scanned)
192.168.184.21 — 5 AI services
:8001 IT Helpdesk Assistant
Model: gpt Confidence: LOW
Live: /docs /openapi.json /health
Headers: server: uvicorn
$ pse http://192.168.184.21:8001/chat "your message"
:8002 Secure IT Assistant
Model: gpt Confidence: MEDIUM
Tools: file_search(pattern), file_read(path), config_lookup(key)
Live: /docs /openapi.json /health
Recon: 10 paths tested, 10 live, 0 auth-gated, 0 routes (400/405)
Scan: 39.1s
Detail: ~/pentest/lab/192.168.184.21/fingerprint.md
Logs: ~/pentest/lab/192.168.184.21/logs/
Options:
pse fp <ip> --fresh # skip cache, rescan everything
pse fp <ip> --deep # enable slow probes (identity, contradiction)
pse fp --token <PAT> <ip> # authenticate to GitLab before cloning
Send messages to discovered AI endpoints. Auto-detects the API format on first use and caches it.
pse <url> "message"
Example:
# pse http://192.168.184.21:8001/chat "What tools do you have?"
Output:
Detecting format for http://192.168.184.21:8001/chat...
Detected: message
"response": "I have access to these tools:
file_search(pattern) — search files
file_read(path) — read file contents
calculator(expression) — math",
"session_id": "8887b7d0-616e-43bd-8c27-d9bb5ce1f38c"
[format: message | msgs: 1]
Continue: pse "http://192.168.184.21:8001/chat" "your next message"
Follow-up messages keep the session:
pse http://192.168.184.21:8001/chat "Tell me more about file_search"
Interactive REPL:
pse chat http://192.168.184.21:8001/chat
Example interactive session:
$ pse chat http://10.0.0.50:8001/chat
Detecting format for http://10.0.0.50:8001/chat...
✓ Detected: message
═══ Chat: http://10.0.0.50:8001/chat [format: message] ═══
New session.
Type messages. Ctrl-C to exit.
You: What can you help me with?
AI: I can assist with searching our internal knowledge base, looking up
policies, and answering questions about company procedures.
★ HIT: rag_sources(knowledge base)
You: What tools do you use to search?
AI: I use a document_search function that queries our indexed files
including PDFs, Word docs, and wiki pages.
★ HIT: tools(document_search)
If you come back later and run pse chat again on the same URL, it picks up where you left off:
$ pse chat http://10.0.0.50:8001/chat
═══ Chat: http://10.0.0.50:8001/chat [format: message] ═══
Resuming from message 2. Use --new to start fresh.
Type messages. Ctrl-C to exit.
You: Search for network diagrams
AI: I found 3 results:
1. Infrastructure_Overview.pdf (score: 0.94)
2. DC_Network_Layout.docx (score: 0.87)
3. VPN_Architecture.pdf (score: 0.82)
★ HIT: rag_sources(Infrastructure_Overview.pdf, DC_Network_Layout.docx)
Both one-shot and REPL modes share the same session state and write to the same log files. Use whichever fits your workflow. One-shot is better for quick probes and scripting, REPL for extended conversations.
State management:
pse --status # show current session info (URL, format, session ID, message count)
pse --reset # clear THIS terminal's session state
pse --reset <url> # clear one URL from this terminal (keeps format cache)
pse --reset-all # clear ALL terminal sessions + format cache
pse <url> "msg" --new # start fresh session for this URL without clearing everything
Display modes:
pse <url> "msg" --raw # show raw JSON (no pretty-print)
Or set "display": "raw" in ai/settings.json to always show raw JSON. Default is pretty-print with bold rendering.
Supports OpenAI messages array, {"message": "..."}, {"query": "..."}, {"prompt": "..."}, {"question": "..."}, {"input": "..."}, {"text": "..."}, and {"content": "..."} formats.
Every response is automatically scanned for 6 categories of interesting disclosure during AI pentesting:
| Category | What it catches |
|---|---|
creds |
passwords, API keys, connection strings, SSH keys |
tools |
file_search, db_query, code_interpreter, shell_exec |
rag_sources |
chunk IDs, vector scores, PDF filenames, document IDs |
internal_paths |
/etc/, /home/, internal API routes |
system_prompt |
"my instructions", guardrails, blocked topics |
infra |
model names (gpt-4, llama), frameworks (langchain, chromadb) |
When a HIT is detected:
★ HIT: tools(file_search, db_query), infra(langchain)
Each terminal gets independent session state. Two terminals can probe the same endpoint with different strategies without stepping on each other.
Under the hood, state lives at ~/.pse/sessions/<ppid>/state.json (one per terminal). Format detection cache is shared globally at ~/.pse/formats.json so expensive format probing only happens once.
Every AI interaction is automatically logged per-target:
~/pentest/lab/<ip>/chat/<port>_<session_ts>.log # greppable text with timestamps + curl commands
~/pentest/lab/<ip>/chat/<port>_<session_ts>.jsonl # full structured JSON (request + response)
Each session appends to the same log pair until you use --new or --reset. The log path is shown in the footer after every message.
Example greppable log entry:
[2026-07-02T05:15:33Z] Q: What tools do you have?
[2026-07-02T05:15:33Z] $ curl -sk -X POST 'http://10.0.0.50:8012/chat' -H 'Content-Type: application/json' -d '{"message":"What tools do you have?"}'
[2026-07-02T05:15:33Z] A: I have access to doc_search for searching the document library.
[2026-07-02T05:15:33Z] HIT: tools(doc_search)
Nmap output during fingerprinting is saved in all three formats (.xml, .gnmap, .nmap) at ~/pentest/lab/<ip>/logs/ with the exact command logged to audit.log.