Legend:
[CLI]= Run in terminal |[SCRIPT]= Inside a script |[REPL]= Interactive shell/REPL |[WIN]= Windows-specific
1. Introduction — Parsing Concepts
What it means: Text parsing is extracting structured or semi-structured data from raw text, files, or data streams. In recon and red teaming, you'll constantly handle JSON API responses, XML configs, raw logs, CSV exports, and HTML pages — each needing different tools.
Format → Tool Map
| Format | Best CLI Tool | Best Python Lib | Use Case |
|---|---|---|---|
| JSON | jq |
json, jmespath |
API responses, configs |
| XML | xmllint, xpath |
lxml, xml.etree |
SOAP, configs, nmap output |
| YAML | yq |
pyyaml |
Kubernetes, Ansible, configs |
| HTML | pup |
BeautifulSoup |
Web scraping, recon |
| CSV/TSV | awk, mlr |
csv, pandas |
Exports, scan results |
| Plain text / logs | grep, awk, sed |
re |
Log analysis, recon output |
Parsing vs Extraction vs Transformation
| Term | Meaning |
|---|---|
| Parsing | Reading structured data and navigating its tree/fields |
| Extraction | Pulling specific values from parsed data |
| Transformation | Reshaping or reformatting the data |
| Filtering | Selecting only records that match a condition |
2. jq — JSON Parsing
What it means: jq is a lightweight CLI tool to slice, filter, map, and transform JSON. It's the sed/awk equivalent for JSON. Essential for working with API responses, nmap JSON output, curl results, and any JSON-heavy recon data.
2.1 Installation
# Debian/Ubuntu/Kali
sudo apt install jq
# Arch
sudo pacman -S jq
# macOS
brew install jq
2.2 Basic Field Access
# Pretty-print entire JSON
[CLI] cat data.json | jq '.'
[CLI] curl -s https://api.example.com/users | jq '.'
# Access a top-level field
[CLI] jq '.name' data.json
# Nested field access
[CLI] jq '.user.email' data.json
# Access array element by index
[CLI] jq '.' data.json # first element
[CLI] jq '.[-1]' data.json # last element
[CLI] jq '.[2:5]' data.json # slice index 2 to 4
2.3 Iterating Arrays
# Iterate all elements of an array
[CLI] jq '.[]' data.json
# Extract a field from every object in an array
[CLI] jq '.[].name' data.json
[CLI] jq '.[].ip' hosts.json
# Compact output (one result per line, no pretty-print)
[CLI] jq -r '.[].name' data.json # -r = raw string (no quotes)
2.4 Filtering & Conditions
# Select objects matching a condition
[CLI] jq '.[] | select(.status == "open")' data.json
[CLI] jq '.[] | select(.port == 443)' nmap.json
[CLI] jq '.[] | select(.age > 30)' users.json
# Multiple conditions (and)
[CLI] jq '.[] | select(.status == "open" and .port == 80)' data.json
# Contains / string match
[CLI] jq '.[] | select(.name | contains("admin"))' data.json
[CLI] jq '.[] | select(.host | startswith("192.168"))' data.json
2.5 Transforming Output
# Build a new object from fields
[CLI] jq '.[] | {host: .address, port: .port}' data.json
# Extract specific fields as CSV
[CLI] jq -r '.[] | [.name, .ip, .port] | @csv' data.json
# Extract as TSV
[CLI] jq -r '.[] | [.name, .ip] | @tsv' data.json
# Join array of strings
[CLI] jq '.tags | join(", ")' data.json
# Convert keys to array
[CLI] jq 'keys' data.json
# Get all values
[CLI] jq 'values' data.json
2.6 Sorting & Grouping
# Sort array by a field
[CLI] jq 'sort_by(.port)' data.json
[CLI] jq 'sort_by(.name) | reverse' data.json
# Group by a field
[CLI] jq 'group_by(.status)' data.json
# Unique values of a field
[CLI] jq '[.[].status] | unique' data.json
# Count elements
[CLI] jq 'length' data.json
[CLI] jq '.[] | select(.open == true) | .port' data.json | wc -l
2.7 Useful One-Liners (Red Team)
# Extract all open ports from nmap JSON output
[CLI] jq -r '.[] | .ports[]? | select(.state.state=="open") | .portid' nmap.json
# Get all hostnames from nmap
[CLI] jq -r '.[].hostnames[]?.name' nmap.json
# Extract IPs from a JSON array of hosts
[CLI] jq -r '.[].address.addr' nmap.json
# Parse curl API response and grab token
[CLI] curl -s -X POST https://target/api/login -d '{"user":"admin","pass":"pass"}' \
| jq -r '.token'
# Pretty print + filter in one pipeline
[CLI] cat response.json | jq '.data.users[] | select(.role == "admin") | .username'
# Read from file, filter, write to new file
[CLI] jq '[.[] | select(.port == 445)]' hosts.json > smb_hosts.json
2.8 jq Variables & Functions
# Store a value in a variable
[CLI] jq '.users[] | .name as $n | .roles[] | {user: $n, role: .}' data.json
# Define and call a function
[CLI] jq 'def double: . * 2; .value | double' data.json
# Map shorthand
[CLI] jq '[.[] | .port] | map(. + 1000)' data.json
# Reduce
[CLI] jq 'reduce .[] as $x (0; . + $x.count)' data.json
3. xmllint & xpath — XML Parsing
What it means: xmllint is a CLI tool from the libxml2 suite for parsing, validating,
and querying XML. xpath expressions let you navigate the XML tree just like CSS selectors
do for HTML. Common targets: nmap XML output, SOAP responses, config files, Active Directory
LDAP exports.
3.1 Installation
# Debian/Ubuntu/Kali
sudo apt install libxml2-utils # provides xmllint
# Arch
sudo pacman -S libxml2
# Python xpath (lxml)
pip install lxml
3.2 Basic xmllint Usage
# Pretty-print / format XML
[CLI] xmllint --format file.xml
# Validate XML structure
[CLI] xmllint --noout file.xml
# (no output = valid; errors printed if invalid)
# Validate against a schema (XSD)
[CLI] xmllint --schema schema.xsd file.xml --noout
# Output to file
[CLI] xmllint --format file.xml -o formatted.xml
3.3 xpath Queries with xmllint
# General syntax
[CLI] xmllint --xpath "<XPATH_EXPRESSION>" file.xml
# Get text content of an element
[CLI] xmllint --xpath "//title/text()" page.xml
# Get attribute value
[CLI] xmllint --xpath "//host/@name" nmap.xml
[CLI] xmllint --xpath "string(//user/email)" data.xml
# Get all elements matching a tag
[CLI] xmllint --xpath "//port" nmap.xml
# Filter by attribute value
[CLI] xmllint --xpath '//port[@protocol="tcp"]' nmap.xml
# Get child elements
[CLI] xmllint --xpath '//host/address/@addr' nmap.xml
# Grab all open ports from nmap XML
[CLI] xmllint --xpath '//port[state/@state="open"]/@portid' nmap.xml
3.4 xpath Quick Reference
| Expression | Meaning |
|---|---|
/root/child |
Absolute path from root |
//tag |
Any tag anywhere in document |
//tag[@attr] |
Tag with attribute present |
//tag[@attr="val"] |
Tag where attr equals value |
//tag/text() |
Text content of tag |
//tag/@attr |
Value of attribute |
//tag[1] |
First matching element (1-indexed) |
//tag[last()] |
Last matching element |
//tag[contains(@attr,"val")] |
Attribute contains string |
count(//tag) |
Count matching nodes |
3.5 Python xml.etree (Built-in)
import xml.etree.ElementTree as ET
tree = ET.parse('nmap.xml')
root = tree.getroot()
# Iterate all host elements
for host in root.findall('.//host'):
addr = host.find('address').get('addr')
print(addr)
# Find all open ports
for port in root.findall('.//port'):
state = port.find('state').get('state')
if state == 'open':
print(port.get('portid'))
# XPath with lxml (more powerful)
from lxml import etree
tree = etree.parse('data.xml')
# Full XPath support
results = tree.xpath('//port[state/@state="open"]/@portid')
print(results)
3.6 Python lxml — Advanced
from lxml import etree
tree = etree.parse('file.xml')
# Get all text content of matching nodes
titles = tree.xpath('//title/text()')
# Get elements with namespace
ns = {'ns': 'http://example.com/namespace'}
items = tree.xpath('//ns:item', namespaces=ns)
# Modify and serialize back
root = tree.getroot()
for elem in root.iter('password'):
elem.text = '[REDACTED]'
print(etree.tostring(root, pretty_print=True).decode())
# Parse from string
xml_string = b'<root><item id="1">value</item></root>'
root = etree.fromstring(xml_string)
print(root.find('.//item[@id="1"]').text)
3.7 Useful One-Liners (Red Team)
# Extract all IPs from nmap XML
[CLI] xmllint --xpath '//address[@addrtype="ipv4"]/@addr' scan.xml \
| tr ' ' '\n' | grep -oP '[\d.]+'
# Extract all open port numbers
[CLI] xmllint --xpath '//port[state/@state="open"]/@portid' scan.xml \
| tr ' ' '\n' | grep -oP '\d+'
# Get all service names
[CLI] xmllint --xpath '//service/@name' scan.xml | tr ' ' '\n' | sort -u
# Extract all hostnames
[CLI] xmllint --xpath '//hostname/@name' scan.xml | tr ' ' '\n'
# Parse SOAP response and get value
[CLI] curl -s https://target/service | xmllint --xpath '//ResponseValue/text()' -
4. awk — Columnar Text Parsing
What it means: awk is a full programming language built for processing structured text,
especially space/tab/delimiter-separated columns. It's the primary tool for log analysis,
extracting specific fields, and performing calculations on text data.
4.1 Core Syntax
# awk 'PATTERN { ACTION }' file
# Runs ACTION for every line matching PATTERN
# No PATTERN = run on every line
# Built-in variables:
# $0 = entire line
# $1, $2, ... = fields (columns)
# NF = number of fields
# NR = current line number (record number)
# FS = field separator (default: whitespace)
# OFS = output field separator
# RS = record separator (default: newline)
4.2 Field Extraction
# Print first column
[CLI] awk '{print $1}' file.txt
# Print multiple columns
[CLI] awk '{print $1, $3, $5}' file.txt
# Print last column
[CLI] awk '{print $NF}' file.txt
# Print second-to-last
[CLI] awk '{print $(NF-1)}' file.txt
# Custom delimiter — extract username from /etc/passwd
[CLI] awk -F: '{print $1}' /etc/passwd
# Custom delimiter — extract IPs from CSV
[CLI] awk -F, '{print $2}' hosts.csv
# Tab-separated
[CLI] awk -F'\t' '{print $1}' data.tsv
4.3 Filtering with Patterns
# Lines containing a word
[CLI] awk '/admin/' /etc/passwd
# Lines NOT containing a word
[CLI] awk '!/root/' /etc/passwd
# Lines where field equals value
[CLI] awk '$3 == "open"' ports.txt
# Lines where field is greater than
[CLI] awk '$5 > 1000' data.txt
# Lines where field matches regex
[CLI] awk '$1 ~ /^192\.168/' hosts.txt
# Print lines from line 5 to 10
[CLI] awk 'NR>=5 && NR<=10' file.txt
# Skip header (first line)
[CLI] awk 'NR>1' file.txt
4.4 Calculations & Aggregation
# Sum a column
[CLI] awk '{sum += $3} END {print sum}' data.txt
# Average
[CLI] awk '{sum += $2; count++} END {print sum/count}' data.txt
# Count lines matching pattern
[CLI] awk '/FAILED/{count++} END {print count}' auth.log
# Count unique values in column 1
[CLI] awk '{count[$1]++} END {for (k in count) print k, count[k]}' data.txt | sort -k2 -rn
4.5 Output Formatting
# Print with custom separator
[CLI] awk '{print $1 "," $2 "," $3}' file.txt
# printf for formatted output
[CLI] awk '{printf "%-20s %s\n", $1, $2}' file.txt
# Change output separator globally
[CLI] awk 'BEGIN{OFS=","} {print $1, $2, $3}' file.txt
# Add header
[CLI] awk 'BEGIN{print "IP,Port,Status"} {print $1","$2","$3}' data.txt
4.6 Red Team One-Liners
# Extract usernames from shadow/passwd
[CLI] awk -F: '$3 >= 1000 {print $1}' /etc/passwd # non-system users
# Extract failed SSH IPs from auth.log
[CLI] awk '/Failed password/{print $11}' /var/log/auth.log | sort | uniq -c | sort -rn
# Extract open ports from nmap plain output
[CLI] awk '/open/{print $1}' nmap_output.txt
# Extract unique C2 domains from a proxy log
[CLI] awk '{print $7}' proxy.log | grep -oP 'https?://[^/]+' | sort -u
# Print lines where column 2 is an IP
[CLI] awk '$2 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ {print $2}' data.txt
# Replace field and print
[CLI] awk '{$3="REDACTED"; print}' log.txt
5. sed — Stream Editing
What it means: sed (stream editor) processes text line-by-line and applies
substitution, deletion, insertion, and transformation operations using regex. It's ideal
for cleaning, modifying, and reformatting text data in pipelines.
5.1 Core Syntax
# sed 'COMMAND' file
# sed -i 'COMMAND' file (edit in place)
# sed -n 'COMMAND' file (suppress default output)
# Substitute: s/PATTERN/REPLACEMENT/FLAGS
# Flags: g=global, i=case-insensitive, p=print
5.2 Substitution
# Replace first occurrence per line
[CLI] sed 's/foo/bar/' file.txt
# Replace ALL occurrences per line (g flag)
[CLI] sed 's/foo/bar/g' file.txt
# Case-insensitive replace
[CLI] sed 's/foo/bar/gi' file.txt
# Replace only on lines matching a pattern
[CLI] sed '/admin/s/password/REDACTED/g' file.txt
# Replace and print only changed lines
[CLI] sed -n 's/foo/bar/gp' file.txt
# Edit file in place
[CLI] sed -i 's/old/new/g' file.txt
# Edit in place with backup
[CLI] sed -i.bak 's/old/new/g' file.txt
5.3 Deletion & Selection
# Delete lines matching pattern
[CLI] sed '/^#/d' config.txt # delete comments
[CLI] sed '/^$/d' file.txt # delete blank lines
[CLI] sed '/password/d' file.txt
# Print only lines matching pattern (-n + p)
[CLI] sed -n '/error/p' log.txt
# Print specific line number
[CLI] sed -n '5p' file.txt
# Print range of lines
[CLI] sed -n '5,10p' file.txt
# Delete first line (header)
[CLI] sed '1d' file.txt
# Delete last line
[CLI] sed '$d' file.txt
5.4 Insertion & Append
# Insert line BEFORE matching pattern
[CLI] sed '/pattern/i\New line before' file.txt
# Append line AFTER matching pattern
[CLI] sed '/pattern/a\New line after' file.txt
# Insert at line 3
[CLI] sed '3i\Inserted line' file.txt
# Add a header to a file
[CLI] sed -i '1i\# Generated by script' file.txt
5.5 Red Team One-Liners
# Remove ANSI color codes from tool output
[CLI] sed 's/\x1B\[[0-9;]*[mK]//g' colored_output.txt
# Extract IP addresses (print only lines with IPs)
[CLI] sed -n '/[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}/p' output.txt
# Remove blank lines and comments from config
[CLI] sed '/^$/d;/^#/d' config.conf
# Convert Windows line endings (CRLF → LF)
[CLI] sed -i 's/\r//' file.txt
# Mask passwords in a log file
[CLI] sed 's/password=[^ ]*/password=REDACTED/gi' app.log
# Extract URLs from a file
[CLI] sed -n 's/.*\(https\?:\/\/[^ "]*\).*/\1/p' file.txt
6. grep & regex — Pattern Matching
What it means: grep searches files/streams for lines matching a pattern. With -E
(extended regex) or -P (Perl-compatible regex / PCRE), it becomes a powerful extraction
tool. Essential for log triage, IOC hunting, and recon output processing.
6.1 grep Flags Reference
| Flag | Meaning |
|---|---|
-i |
Case-insensitive |
-r |
Recursive (search all files in dir) |
-l |
Print only filenames with matches |
-n |
Show line numbers |
-v |
Invert match (lines NOT matching) |
-c |
Count matching lines |
-o |
Print only the matched portion |
-E |
Extended regex (ERE) |
-P |
Perl-compatible regex (PCRE) |
-A N |
N lines after match |
-B N |
N lines before match |
-C N |
N lines before and after |
--color |
Highlight matches |
6.2 Common Patterns
# IP addresses (IPv4)
[CLI] grep -oP '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b' file.txt
# Email addresses
[CLI] grep -oP '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' file.txt
# URLs
[CLI] grep -oP 'https?://[^\s"<>]+' file.txt
# MD5 hashes
[CLI] grep -oP '\b[a-fA-F0-9]{32}\b' file.txt
# NTLM / SHA256 hashes
[CLI] grep -oP '\b[a-fA-F0-9]{64}\b' file.txt
# Windows file paths
[CLI] grep -oP '[A-Za-z]:\\[^\s"]+' file.txt
# Linux file paths
[CLI] grep -oP '(/[a-zA-Z0-9._-]+)+' file.txt
# Port numbers
[CLI] grep -oP ':\d{1,5}\b' file.txt
# Base64 strings
[CLI] grep -oP '[A-Za-z0-9+/]{20,}={0,2}' file.txt
# AWS keys
[CLI] grep -oP 'AKIA[0-9A-Z]{16}' file.txt
# Private keys
[CLI] grep -r 'BEGIN.*PRIVATE KEY' /etc/ 2>/dev/null
6.3 Recursive Recon Searches
# Find passwords in config files
[CLI] grep -rni 'password\|passwd\|pwd\|secret\|token\|api_key' /var/www/ 2>/dev/null
# Find hardcoded credentials in code
[CLI] grep -rn --include="*.php" 'mysql_connect\|PDO\|password' /var/www/
# Find SSH keys
[CLI] find / -name "id_rsa" 2>/dev/null | xargs grep -l 'PRIVATE'
# Find SUID binaries mentioning interesting strings
[CLI] find / -perm -4000 2>/dev/null | xargs ls -la
# Search in all log files
[CLI] grep -rn 'Failed password' /var/log/
# Find all IPs in a directory of scan results
[CLI] grep -rhoP '\b10\.\d+\.\d+\.\d+\b' ./scans/ | sort -u
7. Python Parsing
What it means: Python is the most versatile parsing environment. With built-in libraries
(json, csv, xml, re) and third-party libs (lxml, BeautifulSoup, pyyaml,
pandas), it handles any format. Use it when CLI tools aren't enough.
7.1 JSON — Built-in
import json
# Load from file
with open('data.json') as f:
data = json.load(f)
# Load from string
data = json.loads('{"name": "admin", "port": 445}')
# Access fields
print(data['name'])
print(data.get('missing', 'default')) # safe access with default
# Iterate array
for item in data['hosts']:
print(item['ip'], item['port'])
# Filter
open_ports = [h for h in data['hosts'] if h['state'] == 'open']
# Write to file
with open('output.json', 'w') as f:
json.dump(data, f, indent=2)
# Pretty print to stdout
print(json.dumps(data, indent=2))
7.2 Regex — re module
import re
text = "Found host 192.168.1.10 on port 445 (SMB open)"
# Search (first match)
match = re.search(r'\b\d{1,3}(?:\.\d{1,3}){3}\b', text)
if match:
print(match.group()) # 192.168.1.10
# Find all matches
ips = re.findall(r'\b\d{1,3}(?:\.\d{1,3}){3}\b', text)
# Find with groups
match = re.search(r'port (\d+) \((\w+)', text)
if match:
port = match.group(1) # 445
service = match.group(2) # SMB
# Named groups
match = re.search(r'host (?P<ip>[\d.]+) on port (?P<port>\d+)', text)
if match:
print(match.group('ip'))
print(match.group('port'))
# Substitute
cleaned = re.sub(r'password=\S+', 'password=REDACTED', text, flags=re.IGNORECASE)
# Split on multiple delimiters
parts = re.split(r'[,;|]', "a,b;c|d")
7.3 Subprocess + Parsing Pipeline
import subprocess
import json
# Run a command and parse JSON output
result = subprocess.run(
['nmap', '-oJ', '-', '192.168.1.0/24'],
capture_output=True, text=True
)
data = json.loads(result.stdout)
# Run and grep output
result = subprocess.run(
['cat', '/etc/passwd'],
capture_output=True, text=True
)
users = [line.split(':') for line in result.stdout.splitlines()]
print(users)
8. CSV & TSV Parsing
What it means: CSV (comma-separated) and TSV (tab-separated) are the most common
export formats from databases, scan tools, and spreadsheets. Python's csv module and
the miller (mlr) CLI tool handle them cleanly.
8.1 awk for CSV/TSV
# TSV — extract column 2
[CLI] awk -F'\t' '{print $2}' data.tsv
# CSV — extract column 3 (careful: won't handle quoted commas)
[CLI] awk -F',' '{print $3}' data.csv
# Skip header, print columns 1 and 4
[CLI] awk -F',' 'NR>1 {print $1","$4}' data.csv
# Filter rows where column 3 > 100
[CLI] awk -F',' 'NR>1 && $3 > 100 {print}' data.csv
8.2 Python csv Module
import csv
# Read CSV
with open('data.csv', newline='') as f:
reader = csv.DictReader(f) # header row becomes keys
for row in reader:
print(row['ip'], row['port'])
# Filter rows
with open('hosts.csv', newline='') as f:
reader = csv.DictReader(f)
open_hosts = [r for r in reader if r['state'] == 'open']
# Write CSV
with open('output.csv', 'w', newline='') as f:
fieldnames = ['ip', 'port', 'state']
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(open_hosts)
# Read plain CSV (no header)
with open('data.csv') as f:
reader = csv.reader(f)
for row in reader:
print(row, row)
8.3 miller (mlr) — CSV Power Tool
# Install
sudo apt install miller # or: brew install miller
# Pretty-print CSV as table
[CLI] mlr --csv pretty data.csv
# Filter rows
[CLI] mlr --csv filter '$state == "open"' data.csv
# Select specific columns
[CLI] mlr --csv cut -f ip,port,state data.csv
# Sort by column
[CLI] mlr --csv sort-within-records -f port data.csv
[CLI] mlr --csv sort -f port data.csv
# Count by field value
[CLI] mlr --csv stats1 -a count -f ip -g state data.csv
# Convert CSV to JSON
[CLI] mlr --csv --ojson cat data.csv
# Add a computed column
[CLI] mlr --csv put '$full = $ip . ":" . $port' data.csv
# Rename columns
[CLI] mlr --csv rename ip,host,port,service_port data.csv
9. YAML Parsing — yq
What it means: YAML is the config format for Kubernetes, Ansible, Docker Compose,
GitHub Actions, and many modern tools. yq is to YAML what jq is to JSON — a CLI
query/transform tool with near-identical syntax.
9.1 Installation
# Linux (binary)
wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O yq
chmod +x yq && sudo mv yq /usr/local/bin/
# Arch
sudo pacman -S go-yq
# pip (Python version)
pip install yq # wraps jq syntax for YAML
9.2 Basic Queries
# Read a field
[CLI] yq '.name' config.yaml
[CLI] yq '.spec.containers.image' pod.yaml
# Read all elements of an array
[CLI] yq '.hosts[]' inventory.yaml
# Filter array
[CLI] yq '.hosts[] | select(.state == "open")' data.yaml
# Get array length
[CLI] yq '.items | length' data.yaml
# Output as JSON
[CLI] yq -o=json '.' config.yaml
# Output as CSV
[CLI] yq -o=csv '.hosts[] | [.ip, .port]' data.yaml
9.3 Modification
# Set a value
[CLI] yq '.replicas = 3' deployment.yaml
# Edit in place
[CLI] yq -i '.replicas = 3' deployment.yaml
# Add an item to an array
[CLI] yq '.hosts += ["192.168.1.50"]' config.yaml
# Delete a key
[CLI] yq 'del(.password)' config.yaml
# Merge two YAML files
[CLI] yq '. * load("override.yaml")' base.yaml
9.4 Python pyyaml
import yaml
# Load YAML file
with open('config.yaml') as f:
data = yaml.safe_load(f)
print(data['database']['host'])
print(data['services']['name'])
# Load multi-document YAML
with open('multi.yaml') as f:
docs = list(yaml.safe_load_all(f))
# Write YAML
with open('output.yaml', 'w') as f:
yaml.dump(data, f, default_flow_style=False)
# From string
data = yaml.safe_load("name: admin\nport: 443")
10. HTML Parsing — pup & python
What it means: HTML parsing is essential for web recon — extracting links, forms,
hidden fields, email addresses, and metadata from web pages. pup is a CSS-selector-based
CLI tool; BeautifulSoup is the Python standard.
10.1 pup — CLI HTML Parser
# Install
go install github.com/ericchiang/pup@latest
# Pipe curl output into pup
[CLI] curl -s https://target.com | pup 'SELECTOR'
# Extract all links
[CLI] curl -s https://target.com | pup 'a attr{href}'
# Extract all form inputs
[CLI] curl -s https://target.com | pup 'input attr{name}'
# Get hidden field values
[CLI] curl -s https://target.com | pup 'input[type=hidden] attr{value}'
# Extract page title
[CLI] curl -s https://target.com | pup 'title text{}'
# Get all script src URLs
[CLI] curl -s https://target.com | pup 'script attr{src}'
# All emails in mailto links
[CLI] curl -s https://target.com | pup 'a[href^=mailto] attr{href}' | sed 's/mailto://'
# Output as JSON
[CLI] curl -s https://target.com | pup 'a json{}'
10.2 Python BeautifulSoup
from bs4 import BeautifulSoup
import requests
r = requests.get('https://target.com')
soup = BeautifulSoup(r.text, 'html.parser')
# Find by tag
title = soup.find('title').text
# Find all links
links = [a['href'] for a in soup.find_all('a', href=True)]
# Find by CSS class
items = soup.find_all('div', class_='result')
# Find by ID
form = soup.find(id='login-form')
# Find by attribute
hidden = soup.find_all('input', {'type': 'hidden'})
for field in hidden:
print(field.get('name'), field.get('value'))
# Get all text content
text = soup.get_text(separator='\n', strip=True)
# Extract meta tags (for recon)
metas = soup.find_all('meta')
for m in metas:
print(m.get('name'), m.get('content'))
# Find all script tags (JS recon)
scripts = [s.get('src') for s in soup.find_all('script') if s.get('src')]
# CSS selector (like pup)
emails = soup.select('a[href^="mailto:"]')
11. Log Parsing — Real-World Patterns
What it means: Logs are unstructured or semi-structured text that record system events. In red teaming and forensics, you parse logs to find credentials, recon attacker activity, identify pivots, and extract IOCs.
11.1 Auth Log Parsing
# Failed SSH logins — IP addresses
[CLI] grep 'Failed password' /var/log/auth.log \
| awk '{print $11}' | sort | uniq -c | sort -rn
# Successful SSH logins
[CLI] grep 'Accepted password\|Accepted publickey' /var/log/auth.log \
| awk '{print $9, $11}'
# All unique IPs that attempted SSH
[CLI] grep -oP '(\d{1,3}\.){3}\d{1,3}' /var/log/auth.log | sort -u
# sudo usage
[CLI] grep 'sudo' /var/log/auth.log | grep 'COMMAND' | awk '{print $6, $NF}'
# New user creation
[CLI] grep 'useradd\|adduser' /var/log/auth.log
11.2 Web Server Log Parsing
# Apache / Nginx access.log format:
# IP - - [date] "METHOD /path HTTP/1.1" status bytes
# Top 10 IPs by request count
[CLI] awk '{print $1}' /var/log/nginx/access.log \
| sort | uniq -c | sort -rn | head 10
# All 404 errors
[CLI] awk '$9 == 404 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# All POST requests (potential login bruteforce / upload)
[CLI] grep '"POST ' /var/log/nginx/access.log | awk '{print $1, $7, $9}'
# User-agents (detect scanners)
[CLI] awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# Requests to /admin, /login, /wp-admin
[CLI] grep -E '/(admin|login|wp-admin|phpmyadmin)' /var/log/nginx/access.log \
| awk '{print $1, $7, $9}'
# Extract all unique paths
[CLI] awk '{print $7}' /var/log/nginx/access.log | cut -d'?' -f1 | sort -u
11.3 Windows Event Log Parsing (evtx → text)
# Convert .evtx to XML first with python-evtx or chainsaw
pip install python-evtx
# Parse evtx to XML
[CLI] python3 -m evtx /path/to/Security.evtx > security.xml
# Extract Event ID 4624 (logon) from XML
[CLI] xmllint --xpath '//Event[System/EventID=4624]//Data[@Name="IpAddress"]/text()' security.xml
# With chainsaw (fast EVTX hunting)
[CLI] ./chainsaw hunt /path/to/logs --rules rules/ --output results.json
[CLI] cat results.json | jq '.[] | select(.name | contains("Pass-the-Hash"))'
11.4 Structured Log Parsing (JSON Logs)
# Many modern apps (Nginx, cloud tools) log in JSON format
# Extract all errors
[CLI] cat app.json.log | jq 'select(.level == "error")'
# Get unique error messages
[CLI] cat app.json.log | jq -r 'select(.level == "error") | .message' | sort -u
# Filter by time range
[CLI] cat app.json.log | jq 'select(.timestamp > "2025-01-01T00:00:00")'
# Extract request path and status
[CLI] cat access.json.log | jq -r '[.path, (.status | tostring)] | @tsv'
12. Cheat Sheet
Format → Tool Decision Tree
What format is your data?
├── JSON → jq (CLI) | json (Python)
├── XML → xmllint --xpath (CLI) | lxml (Python)
├── YAML → yq (CLI) | pyyaml (Python)
├── HTML → pup (CLI) | BeautifulSoup (Python)
├── CSV → mlr / awk (CLI) | csv / pandas (Python)
├── Logs → grep + awk + sed (CLI) | re (Python)
└── Mixed/Unknown → Python (most flexible)
Need to FILTER? → jq select() | awk pattern | grep
Need to TRANSFORM? → jq map/del | awk print | sed s///
Need to AGGREGATE? → jq group_by | awk sum/count | mlr stats
Need to EXTRACT (regex)?→ grep -oP | sed -n | Python re.findall
Quick One-Liners
# JSON — extract all IPs
jq -r '.[] | .ip' data.json
# XML — extract all open ports from nmap
xmllint --xpath '//port[state/@state="open"]/@portid' nmap.xml | tr ' ' '\n' | grep -oP '\d+'
# CSV — get column 3, skip header
awk -F',' 'NR>1{print $3}' data.csv
# Log — top IPs from nginx log
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head 20
# HTML — extract all links
curl -s https://target.com | pup 'a attr{href}'
# YAML — read a nested field
yq '.spec.containers.image' pod.yaml
# Regex — extract all emails from file
grep -oP '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' file.txt
# Remove ANSI colors from output
sed 's/\x1B\[[0-9;]*[mK]//g' output.txt
# Base64 decode all matches
grep -oP '[A-Za-z0-9+/]{20,}={0,2}' file.txt | while read b; do echo "$b" | base64 -d 2>/dev/null; done
Tool Limitations
| Tool | Limitation | Workaround |
|---|---|---|
jq |
No XML/HTML support | Use xmllint or pup |
xmllint |
No YAML support | Use yq |
awk CSV |
Fails on quoted commas | Use mlr or Python csv |
grep -P |
PCRE not always available (macOS) | Use ggrep (brew) or Python |
pup |
Can't execute JS | Use Selenium/Playwright for dynamic pages |
sed |
Not for binary data | Use xxd + grep |
| proxychains | TCP only | Ligolo-ng for UDP |