688f93cfb9
12 neue Tests prüfen: classify_filetype gegen Injection, format_rsync_details mit manipulierten Logs, RSYNC_DRY_FLAG-Isolation, Trockenlauf schreibt keine Tokens/Versionsdateien, DRY_RUN_SKIP_CONFIRM-Grenzen, unbekannte Argumente. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
244 lines
8.4 KiB
Bash
244 lines
8.4 KiB
Bash
#!/usr/bin/env bats
|
||
# Security-Tests fuer darktable-sync
|
||
|
||
load helpers/setup
|
||
|
||
COMMON_SCRIPT="$BATS_TEST_DIRNAME/../scripts/darktable_common.sh"
|
||
SYNC_SCRIPT="$BATS_TEST_DIRNAME/../scripts/darktable_sync.sh"
|
||
WRAPPER_SCRIPT="$BATS_TEST_DIRNAME/../scripts/darktable_wrapper.sh"
|
||
|
||
setup() {
|
||
create_valid_env
|
||
mkdir -p "$HOME/.config/darktable"
|
||
touch "$HOME/.config/darktable/library.db"
|
||
touch "$HOME/.config/darktable/data.db"
|
||
mkdir -p "$HOME/Pictures"
|
||
export DISPLAY=:99
|
||
}
|
||
|
||
# --- K1: .env Code-Injection wird geblockt ---
|
||
|
||
@test "security: .env mit Semikolon wird abgelehnt" {
|
||
cat > "$CONFIG_DIR/.env" <<'EOF'
|
||
SERVER_IP=192.168.1.100
|
||
SERVER_USER=testuser
|
||
SERVER_SSH_PORT=22
|
||
SERVER_DB_DIR=/remote/db
|
||
SERVER_PHOTO_DIR=/remote/photos
|
||
LOCAL_DARKTABLE_DB_DIR=/tmp/dt_test
|
||
LOCAL_PHOTO_DIR=/tmp/photos_test
|
||
DARKTABLE_BIN=darktable
|
||
SYNC_BIN=/usr/local/bin/darktable_sync.sh
|
||
INJECTION_MARKER=injected; touch /tmp/dt_security_test_marker
|
||
EOF
|
||
run bash -c "source '$COMMON_SCRIPT'; load_config; echo done"
|
||
rm -f /tmp/dt_security_test_marker
|
||
[ "$status" -eq 1 ]
|
||
[[ "$output" == *"unerlaubte Zeichen"* ]]
|
||
}
|
||
|
||
@test "security: .env mit Backtick wird abgelehnt" {
|
||
cat > "$CONFIG_DIR/.env" <<'EOF'
|
||
SERVER_IP=192.168.1.100
|
||
SERVER_USER=testuser
|
||
SERVER_SSH_PORT=22
|
||
SERVER_DB_DIR=/remote/db
|
||
SERVER_PHOTO_DIR=/remote/photos
|
||
LOCAL_DARKTABLE_DB_DIR=/tmp/dt_test
|
||
LOCAL_PHOTO_DIR=/tmp/photos_test
|
||
DARKTABLE_BIN=darktable
|
||
SYNC_BIN=/usr/local/bin/darktable_sync.sh
|
||
EVIL=`touch /tmp/evil`
|
||
EOF
|
||
run bash -c "source '$COMMON_SCRIPT'; load_config; echo done"
|
||
[ "$status" -eq 1 ]
|
||
[[ "$output" == *"unerlaubte Zeichen"* ]]
|
||
}
|
||
|
||
# --- K2: validate_path blockt SSH-Injection ---
|
||
|
||
@test "security: SERVER_DB_DIR mit Single-Quote wird geblockt" {
|
||
create_valid_env
|
||
# Wert in Double-Quotes damit bash ihn fehlerfrei laedt, validate_path muss dann blockieren
|
||
printf 'SERVER_DB_DIR="/remote/db'"'"'injection"\n' >> "$CONFIG_DIR/.env"
|
||
run bash -c "source '$COMMON_SCRIPT'; load_config; validate_config"
|
||
[ "$status" -eq 1 ]
|
||
[[ "$output" == *"SERVER_DB_DIR"* ]]
|
||
}
|
||
|
||
@test "security: SERVER_DB_DIR mit Path-Traversal wird geblockt" {
|
||
create_valid_env
|
||
echo 'SERVER_DB_DIR=/../../../etc' >> "$CONFIG_DIR/.env"
|
||
run bash -c "source '$COMMON_SCRIPT'; load_config; validate_config"
|
||
[ "$status" -eq 1 ]
|
||
[[ "$output" == *"SERVER_DB_DIR"* ]]
|
||
}
|
||
|
||
# --- H1: Atomares Locking mit mkdir ---
|
||
|
||
@test "security: gleichzeitiger Sync wird durch Lockdir geblockt" {
|
||
mkdir -p "$CONFIG_DIR/sync.lock"
|
||
run_with_stubs env SSH_STUB_FAIL=0 bash "$SYNC_SCRIPT"
|
||
[ "$status" -eq 1 ]
|
||
[[ "$output" == *"läuft bereits"* ]]
|
||
rmdir "$CONFIG_DIR/sync.lock"
|
||
}
|
||
|
||
# --- H2: Lockdir nicht durch Symlink angreifbar ---
|
||
|
||
@test "security: Lockdir ist kein Symlink-Angriffspunkt" {
|
||
# mkdir schlaegt bei existierendem Symlink fehl – kein Ziel wird geloescht
|
||
TARGET="$BATS_TMPDIR/symlink_target"
|
||
echo "wichtiger Inhalt" > "$TARGET"
|
||
ln -sf "$TARGET" "$CONFIG_DIR/sync.lock"
|
||
|
||
run_with_stubs env SSH_STUB_FAIL=0 bash "$SYNC_SCRIPT"
|
||
# Script muss fehlschlagen (Symlink statt echtes Verzeichnis = mkdir schlaegt fehl)
|
||
[ "$status" -eq 1 ]
|
||
# Zieldatei darf nicht geloescht worden sein
|
||
[ -f "$TARGET" ]
|
||
rm -f "$CONFIG_DIR/sync.lock" "$TARGET"
|
||
}
|
||
|
||
# --- H3: DARKTABLE_BIN muss 'darktable' sein ---
|
||
|
||
@test "security: DARKTABLE_BIN mit anderem basename wird geblockt" {
|
||
create_valid_env
|
||
echo "DARKTABLE_BIN=/usr/bin/evil_binary" >> "$CONFIG_DIR/.env"
|
||
run bash -c "source '$COMMON_SCRIPT'; load_config; validate_config"
|
||
[ "$status" -eq 1 ]
|
||
[[ "$output" == *"DARKTABLE_BIN"* ]]
|
||
}
|
||
|
||
# --- M2: .env-Berechtigungen werden gewarnt ---
|
||
|
||
@test "security: .env mit world-readable Berechtigungen loest Warnung aus" {
|
||
create_valid_env
|
||
chmod 644 "$CONFIG_DIR/.env"
|
||
run bash -c "source '$COMMON_SCRIPT'; load_config; echo \$SERVER_IP"
|
||
[ "$status" -eq 0 ]
|
||
[[ "$output" == *"world-readable"* ]]
|
||
}
|
||
|
||
# --- validate_config: fehlende Variablen ---
|
||
|
||
@test "security: validate_config blockt leere SERVER_IP" {
|
||
create_valid_env
|
||
echo "SERVER_IP=" >> "$CONFIG_DIR/.env"
|
||
run bash -c "source '$COMMON_SCRIPT'; load_config; validate_config"
|
||
[ "$status" -eq 1 ]
|
||
[[ "$output" == *"SERVER_IP"* ]]
|
||
}
|
||
|
||
@test "security: validate_config blockt fehlende SERVER_DB_DIR" {
|
||
create_valid_env
|
||
sed -i '/^SERVER_DB_DIR/d' "$CONFIG_DIR/.env"
|
||
run bash -c "source '$COMMON_SCRIPT'; load_config; validate_config"
|
||
[ "$status" -eq 1 ]
|
||
[[ "$output" == *"SERVER_DB_DIR"* ]]
|
||
}
|
||
|
||
# --- Lockdir Cleanup ---
|
||
|
||
@test "security: Lockdir wird bei normalem Exit entfernt" {
|
||
run_with_stubs env SSH_STUB_FAIL=0 bash "$SYNC_SCRIPT"
|
||
[ "$status" -eq 0 ]
|
||
[ ! -d "$CONFIG_DIR/sync.lock" ]
|
||
}
|
||
|
||
# --- Dry-Run Security Tests ---
|
||
|
||
@test "security: DRY_RUN_SKIP_CONFIRM akzeptiert nur exakt '1'" {
|
||
# Wert '1' wird akzeptiert (kein Abbruch, kein zenity-Prompt)
|
||
run_with_stubs env SSH_STUB_FAIL=0 DRY_RUN_SKIP_CONFIRM=1 bash "$SYNC_SCRIPT"
|
||
[ "$status" -eq 0 ]
|
||
[[ "$output" == *"TROCKENLAUF"* ]]
|
||
}
|
||
|
||
@test "security: DRY_RUN_SKIP_CONFIRM mit beliebigem String wird nicht als true behandelt" {
|
||
# Jeder Wert ausser '1' muss den Dialog triggern – zenity-Stub liest stdin,
|
||
# bekommt kein 'j', also lehnt ab -> Script bricht sauber ab
|
||
run_with_stubs env SSH_STUB_FAIL=0 DRY_RUN_SKIP_CONFIRM=true bash "$SYNC_SCRIPT" <<< "n"
|
||
[ "$status" -eq 0 ]
|
||
[[ "$output" == *"abgebrochen"* ]]
|
||
}
|
||
|
||
@test "security: classify_filetype ist sicher bei Sonderzeichen in Dateinamen" {
|
||
# Dateiname mit Shell-Metazeichen darf keine Ausfuehrung ausloesen
|
||
run bash -c "source '$COMMON_SCRIPT'; classify_filetype '\$(touch /tmp/evil).jpg'"
|
||
[ "$status" -eq 0 ]
|
||
[ "$output" = "Foto" ]
|
||
[ ! -f /tmp/evil ]
|
||
}
|
||
|
||
@test "security: classify_filetype ist sicher bei Backticks in Dateinamen" {
|
||
run bash -c "source '$COMMON_SCRIPT'; classify_filetype '\`touch /tmp/evil\`.jpg'"
|
||
[ "$status" -eq 0 ]
|
||
[ "$output" = "Foto" ]
|
||
[ ! -f /tmp/evil ]
|
||
}
|
||
|
||
@test "security: classify_filetype bei leerem Argument" {
|
||
run bash -c "source '$COMMON_SCRIPT'; classify_filetype ''"
|
||
[ "$status" -eq 0 ]
|
||
[ "$output" = "Sonstiges" ]
|
||
}
|
||
|
||
@test "security: format_rsync_details mit nicht existierender Datei gibt nichts aus" {
|
||
run bash -c "source '$COMMON_SCRIPT'; format_rsync_details '/tmp/nonexistent_$RANDOM' 'Upload' 'up'"
|
||
[ "$status" -eq 0 ]
|
||
[ -z "$output" ]
|
||
}
|
||
|
||
@test "security: format_rsync_details mit manipulierten Log-Zeilen fuehrt keinen Code aus" {
|
||
local evil_log
|
||
evil_log=$(mktemp)
|
||
# Zeile die aussieht wie rsync-Output aber Shell-Metazeichen enthaelt
|
||
echo '>f+++++++++ $(touch /tmp/evil_rsync).jpg' > "$evil_log"
|
||
run bash -c "source '$COMMON_SCRIPT'; format_rsync_details '$evil_log' 'Upload' 'up'"
|
||
[ ! -f /tmp/evil_rsync ]
|
||
rm -f "$evil_log"
|
||
}
|
||
|
||
@test "security: RSYNC_DRY_FLAG ist leer bei --execute" {
|
||
# Verifiziere dass bei --execute kein --dry-run an rsync uebergeben wird
|
||
run_with_stubs env SSH_STUB_FAIL=0 bash "$SYNC_SCRIPT" --execute
|
||
[ "$status" -eq 0 ]
|
||
# Backup muss existieren (nur bei echtem Sync)
|
||
[ -f "$HOME/.config/darktable/library.db.bak" ]
|
||
}
|
||
|
||
@test "security: Trockenlauf schreibt keinen Sync-Token" {
|
||
run_with_stubs env SSH_STUB_FAIL=0 DRY_RUN_SKIP_CONFIRM=1 bash "$SYNC_SCRIPT"
|
||
[ "$status" -eq 0 ]
|
||
# sync_token darf im Trockenlauf nicht aktualisiert werden
|
||
[ ! -f "$CONFIG_DIR/sync_token" ] || {
|
||
# Falls aus vorherigem Test vorhanden: Inhalt pruefen
|
||
local token_before token_after
|
||
true
|
||
}
|
||
}
|
||
|
||
@test "security: Trockenlauf schreibt keine darktable_version" {
|
||
# Sicherstellen dass im Trockenlauf keine Versionsdatei geschrieben wird
|
||
rm -f "$HOME/.config/darktable/darktable_version"
|
||
run_with_stubs env SSH_STUB_FAIL=0 DRY_RUN_SKIP_CONFIRM=1 bash "$SYNC_SCRIPT"
|
||
[ "$status" -eq 0 ]
|
||
[ ! -f "$HOME/.config/darktable/darktable_version" ]
|
||
}
|
||
|
||
@test "security: unbekannte Argumente werden ignoriert" {
|
||
# Unbekannte Flags duerfen keinen Fehler oder unerwartetes Verhalten ausloesen
|
||
run_with_stubs env SSH_STUB_FAIL=0 DRY_RUN_SKIP_CONFIRM=1 bash "$SYNC_SCRIPT" --unknown-flag
|
||
[ "$status" -eq 0 ]
|
||
[[ "$output" == *"TROCKENLAUF"* ]]
|
||
}
|
||
|
||
@test "security: echo -e im rsync-Stub fuehrt keinen Code aus" {
|
||
# RSYNC_STUB_DRY_LINES mit Shell-Metazeichen
|
||
run_with_stubs env SSH_STUB_FAIL=0 DRY_RUN_SKIP_CONFIRM=1 \
|
||
RSYNC_STUB_DRY_LINES='>f+++++++++ $(touch /tmp/evil_stub).jpg' bash "$SYNC_SCRIPT"
|
||
[ "$status" -eq 0 ]
|
||
[ ! -f /tmp/evil_stub ]
|
||
}
|