← retour aux snippets

tar: extraire en sécurité sans traversal

Extraire une archive tar sans écrire hors dossier cible: contrôle chemins, temp dir, permissions et symlinks.

bash sécurité #tar#extract#security#cli

objectif

Extraire un artefact sans risque de “path traversal” (../, chemins absolus), sans écraser des permissions inattendues et sans suivre des symlinks malveillants.

code minimal

# extraction sûre: valide les chemins, extrait en temp, synchronise vers dest
safe_tar_extract() {
  local archive="$1" dest="$2"
  [ -r "$archive" ] || { echo "archive introuvable: $archive" >&2; return 1; }
  [ -d "$dest" ] || mkdir -p "$dest"

  # 1) liste et validation des chemins
  mapfile -t entries < <(tar -tf "$archive")
  for p in "${entries[@]}"; do
    # interdits: absolus, traversal, backslashes Windows, chemins vides
    [[ "$p" =~ ^/ ]] && { echo "chemin absolu interdit: $p" >&2; return 2; }
    [[ "$p" == "" ]] && { echo "entree vide" >&2; return 2; }
    [[ "$p" == *".."* ]] && { echo "traversal detecte: $p" >&2; return 2; }
    [[ "$p" == *$'\r'* ]] && { echo "CR dans le nom: $p" >&2; return 2; }
    [[ "$p" == *'\'* ]] && { echo "backslash interdit: $p" >&2; return 2; }
  done

  # 2) extraire dans un dossier temporaire local
  local parent tmp
  parent="$(cd "$dest/.." && pwd)"
  tmp="$(mktemp -d -p "$parent" .untar.XXXXXX)"
  # options: pas d'owner/permissions, retard des perms dir (GNU tar), pas d'overwrite dangereux
  tar -xpf "$archive" -C "$tmp" --no-same-owner --no-same-permissions --delay-directory-restore

  # 3) sync vers dest en neutralisant les symlinks sortants
  # -a conserve les liens, --safe-links interdit les liens pointant hors source
  rsync -a --delete --safe-links "$tmp"/ "$dest"/

  # 4) cleanup
  rm -rf "$tmp"
}
# usage:
# safe_tar_extract app.tar.gz /srv/data.pm/current

utilisation

# déployer un build de data.pm
artefact="site-data.pm.tar.zst"
dest="/srv/data.pm/releases/2025-08-15_1200"
mkdir -p "$dest"

# décompression en flux -> tar (zstd)
zstd -dc "$artefact" | tar -xpf - -C "$dest" --no-same-owner --no-same-permissions --delay-directory-restore

# validation supplémentaire (aucun chemin absolu/..)
zstd -dc "$artefact" | tar -tf - | awk '
  /^\/|(^|\/)\.\.(\/|$)/ { print "chemin suspect: " $0; bad=1 }
  END { exit bad ? 1 : 0 }
'

# alternative: via safe_tar_extract (plus strict, rsync --safe-links)
safe_tar_extract "$artefact" "$dest"

# déployer en "current" via symlink atomique
ln -sfn "$dest" /srv/data.pm/current.new && mv -f /srv/data.pm/current.new /srv/data.pm/current

variante(s) utile(s)

# extraire en supprimant le premier niveau (strip-components=1) dans un temp, puis rsync
tmp="$(mktemp -d -p /srv/data.pm .untar.XXXXXX)"
tar -xpf app.tar.gz -C "$tmp" --strip-components=1 --no-same-owner --no-same-permissions --delay-directory-restore
rsync -a --delete --safe-links "$tmp"/ /srv/data.pm/current/
rm -rf "$tmp"

# refuser les fichiers spéciaux (devices, fifos) en filtrant la liste
tar -tvf app.tar.gz | awk '
  /^[cbp]/ { print "special file refuse: " $0; bad=1 }
  END { exit bad ? 1 : 0 }
' && tar -xpf app.tar.gz -C /srv/data.pm/current --no-same-owner --no-same-permissions

# macOS (bsdtar): options équivalentes sans --delay-directory-restore
bsdtar -xpf app.tar.gz -C /srv/data.pm/current --uid 0 --gname wheel >/dev/null 2>&1 || true
# (bsdtar ignore par défaut les / initiaux; conservez la validation via tar -tf | awk)

# empêcher l’écrasement de fichiers existants (mode conservateur)
tar -xkpf app.tar.gz -C /srv/data.pm/current --no-same-owner --no-same-permissions

notes

  • validez toujours les chemins listés par tar -tf avant extraction: refusez absolus (/^/) et traversals (../).
  • extraire dans un répertoire temporaire puis synchroniser avec rsync --safe-links neutralise les symlinks qui sortent de l’arborescence.
  • utilisez --no-same-owner et --no-same-permissions pour ne pas appliquer des propriétaires/permissions inattendus.
  • --delay-directory-restore (GNU tar) applique les permissions répertoires en fin d’extraction, limitant certains contournements.
  • combinez avec une bascule de symlink atomique vers current pour un déploiement sans interruption.