miscfreemedium

Ред-флаг / redflag

alfactf

Task: an SSH Bash/whiptail tierlist service stored each list as a directory and enforced restrictions through root metadata. Solution: pre-create the join destination, abuse mv's existing-directory behavior to nest the admin list inside an attacker list, then read recursively exposed items checked against the wrong .meta file.

$ ls tags/ techniques/
destination_collision_abusemetadata_confusionnested_directory_disclosurerecursive_listing_abuse

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

Ред-флаг / redflag — alfactf

Description

Ред-флаг

English summary: the challenge provides SSH access to a Bash + whiptail tierlist manager. Tierlists live as directories under /data/tierlists, source is available at https://alfactf.ru/files/redflag.sh, and the goal is to recover a flag hidden in the admin's private list.

Analysis

The bug is a directory move / authorization mismatch.

Each tierlist is a directory with a root .meta file. Restricted items are listed there by filename, and read access is derived from that root metadata only:

is_restricted() { local restricted restricted=$(meta_get "$1" "restricted") || return 1 [[ -z "$restricted" ]] && return 1 local IFS=',' for r in $restricted; do [[ "$r" == "$2" ]] && return 0 done return 1 } can_read_file() { if is_restricted "$1" "$2"; then is_owner "$1" else return 0 fi }

When viewing a tierlist, the UI recursively walks all nested tier_* directories:

while IFS= read -r -d '' td; do tier_dirs+=("$td") done < <(find "$tl_dir" -type d -name 'tier_*' -print0 2>/dev/null | sort -z)

The important detail is that later checks still use the original top-level tl_dir rather than the real parent of the nested file:

if ! can_read_file "$tl_dir" "$item_fname"; then msg "[LOCKED] $item_name\n\nДоступ ограничен владельцем тир-листа." return fi

So if a foreign tierlist can be placed under our own root, every nested item is evaluated against our .meta, not the victim tierlist's .meta.

The join operation is what makes that possible:

join_tierlist_by_name() { local tl_name="$1" local new_name="${tl_name}__with__${CURRENT_USER}" mv "$TIERLISTS_DIR/$tl_name" "$TIERLISTS_DIR/$new_name" ... }

This assumes mv will always rename the directory. That is false: if $TIERLISTS_DIR/$new_name already exists and is a directory, mv places the source tierlist inside it.

So if we first create our own tierlist named:

gorpcore_redflags__with__rfbkqsl

...

$ grep --similar

Similar writeups