Last active
July 1, 2025 22:05
-
-
Save Guilhem7/bedc05a48a99c16c67e3479a1ccf028a to your computer and use it in GitHub Desktop.
Parse a tomcat web.xml file in order to see which servlet is used for which urls, and which filter(s) are processed on it
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -e | |
| ## Global var | |
| SCRIPT_NAME="${0##*/}" | |
| ## Style | |
| RED='\e[31m' | |
| GREEN='\e[32m' | |
| BLUE='\e[34m' | |
| YELLOW='\e[93m' | |
| BOLD='\e[1m' | |
| DIM='\e[2m' | |
| ITALIC='\e[3m' | |
| N='\e[0m' | |
| ## Util function | |
| die(){ | |
| if [ -n "$BASH_LINENO" ];then | |
| err "${SCRIPT_NAME}: ${BOLD}Error:Line:${BASH_LINENO[0]}${N} $*" >&2 | |
| else | |
| err "${SCRIPT_NAME}: ${BOLD}Error${N} $*" >&2 | |
| fi | |
| exit 1 | |
| } | |
| msg(){ | |
| echo -e "${GREEN}[+]${N} $*" | |
| } | |
| info(){ | |
| echo -e "${BLUE}[-]${N} $*" | |
| } | |
| err(){ | |
| echo -e "${RED}[x]${N} $*" >&2 | |
| } | |
| show_help() { | |
| echo "Usage: $SCRIPT_NAME [--xml XML] [-h] [-v]" | |
| echo | |
| echo "Options:" | |
| echo " --xml XML Specify the path to web.xml (default web.xml)" | |
| echo " -v, --verbose Increase verbosity" | |
| echo " -h, --help Display this help message" | |
| } | |
| show_progress() { | |
| local width=50 | |
| local percent=$(( 100 * $1 / $2 )) | |
| local filled=$(( width * $1 / $2 )) | |
| local empty=$(( width - filled )) | |
| local fill_char=$(printf " %.0s" {1..101}) | |
| printf "\r[" | |
| printf "\e[47m" | |
| printf "%.*s" "$filled" "$fill_char" | |
| printf "\e[0m" | |
| printf "%.*s" "$empty" "$fill_char" | |
| printf "] %3d%% (%d/%d)" "$percent" "$1" "$2" | |
| if [ "$1" -eq "$2" ]; then | |
| echo "" # move to next line when complete | |
| fi | |
| # Show progress arg 1 is count and arg 2 is the total | |
| # echo -en "\r${BLUE}[-]${N} Progress ${DIM}$percent${N}/${BOLD}100%${N}\e[K" | |
| # echo -en "\r[] $1/$2\e[K" | |
| } | |
| if ! command -v xq 2&>/dev/null;then | |
| err "Command xq not found, please run: ${BOLD}pip3 install yq${N}" | |
| fi | |
| if ! command -v jq 2&>/dev/null;then | |
| err "Command jq not found, please run: ${BOLD}sudo apt install -y jq${N}" | |
| fi | |
| WEB_XML="web.xml" | |
| VERBOSE=0 | |
| while [[ "$#" -gt 0 ]]; do | |
| case "$1" in | |
| -h) | |
| show_help | |
| exit 0 | |
| ;; | |
| --xml) | |
| if [[ -n "$2" && "$2" != -* ]]; then | |
| WEB_XML="$2" | |
| shift | |
| else | |
| echo "Error: --xml requires valid xml" | |
| exit 1 | |
| fi | |
| ;; | |
| -v|--verbose) | |
| VERBOSE=1 | |
| ;; | |
| *) | |
| echo "Unknown option: $1" | |
| show_help | |
| exit 1 | |
| ;; | |
| esac | |
| shift | |
| done | |
| if [ ! -f "$WEB_XML" ];then | |
| err "File not found: $WEB_XML" | |
| exit 1 | |
| fi | |
| if [ "$VERBOSE" -eq 1 ];then | |
| info "Processing $WEB_XML" | |
| info "Recovering ${GREEN}servlets${N} ${ITALIC}${DIM}(can take some time)${N}" | |
| fi | |
| # Parse all servlet mappings into a lookup: servlet-name -> list of url-patterns | |
| declare -A servlet_class_map | |
| declare -A servlet_urls_map | |
| xq_output=$(xq -c ' | |
| .["web-app"]["servlet"][] as $s | |
| | { | |
| name: $s["servlet-name"], | |
| class: $s["servlet-class"], | |
| urls: ( | |
| .["web-app"]["servlet-mapping"] | |
| | map(select(.["servlet-name"] == $s["servlet-name"])["url-pattern"]) | |
| | flatten | |
| ) | |
| } | |
| ' $WEB_XML) | |
| count=1 | |
| total_lines=$(printf "%s\n" "$xq_output" |wc -l) | |
| while IFS= read -r entry; do | |
| if [ "$VERBOSE" -eq 1 ];then | |
| show_progress "$count" "$total_lines" | |
| let "count=count+1" | |
| fi | |
| name=$(jq -r '.name' <<< "$entry") | |
| class=$(jq -r '.class' <<< "$entry") | |
| urls=$(jq -r '.urls[]' <<< "$entry" | paste -sd "|" -) | |
| servlet_class_map["$name"]="$class" | |
| servlet_urls_map["$name"]="$urls" | |
| done < <(printf "%s\n" "$xq_output") | |
| if [ "$VERBOSE" -eq 1 ];then | |
| info "Recovering ${GREEN}filters${N}" | |
| fi | |
| # Parse all filters and map filter-name to list of patterns | |
| declare -A filter_class_map | |
| declare -A filter_patterns_map | |
| xq_output=$(xq -c ' | |
| .["web-app"]["filter"][] as $f | |
| | { | |
| name: $f["filter-name"], | |
| class: $f["filter-class"], | |
| patterns: ( | |
| .["web-app"]["filter-mapping"] | |
| | map(select(.["filter-name"] == $f["filter-name"])["url-pattern"]) | |
| | flatten | |
| ) | |
| } | |
| ' $WEB_XML) | |
| count=1 | |
| total_lines=$(printf "%s\n" "$xq_output" |wc -l) | |
| while IFS= read -r entry; do | |
| if [ "$VERBOSE" -eq 1 ];then | |
| show_progress "$count" "$total_lines" | |
| let "count=count+1" | |
| fi | |
| name=$(jq -r '.name' <<< "$entry") | |
| class=$(jq -r '.class' <<< "$entry") | |
| patterns=$(jq -r '.patterns[]' <<< "$entry" | paste -sd "|" -) | |
| filter_class_map["$name"]="$class" | |
| filter_patterns_map["$name"]="$patterns" | |
| done < <(printf "%s\n" "$xq_output") | |
| # Process each servlet | |
| for servlet in "${!servlet_class_map[@]}"; do | |
| class="${servlet_class_map[$servlet]}" | |
| IFS='|' read -ra urls <<< "${servlet_urls_map[$servlet]}" | |
| declare -A filter_set=() | |
| # Match servlet URLs with all filter patterns | |
| for filter in "${!filter_patterns_map[@]}"; do | |
| IFS='|' read -ra patterns <<< "${filter_patterns_map[$filter]}" | |
| for u in "${urls[@]}"; do | |
| for p in "${patterns[@]}"; do | |
| regex="^${p//\*/.*}$" | |
| if [[ "$u" =~ $regex ]]; then | |
| filter_set["$filter"]=1 | |
| fi | |
| done | |
| done | |
| done | |
| # Output result | |
| echo -e "Servlet : ${GREEN}${servlet}${N}" | |
| echo -e " Class : ${BOLD}${class}${N}" | |
| echo -e " URLs :" | |
| for u in "${urls[@]}"; do echo " - $u"; done | |
| echo " Filters:" | |
| if [[ ${#filter_set[@]} -eq 0 ]]; then | |
| echo " (none)" | |
| else | |
| for f in "${!filter_set[@]}"; do | |
| echo " - $f" | |
| done | |
| fi | |
| echo | |
| done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment