$ cat writeup.md…
$ cat writeup.md…
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.
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
Ред-флаг
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.
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