33 lines
1.7 KiB
Bash
33 lines
1.7 KiB
Bash
#!/bin/bash
|
|
# Запуск только в одноразовом Docker-контейнере от root, без host state/socket.
|
|
set -eu
|
|
test "$(id -u)" = 0
|
|
test ! -e /srv/preland-admin
|
|
test ! -e /usr/bin/docker
|
|
base=/srv/preland-admin
|
|
install -d -m 700 "$base" "$base/secrets" "$base/deploy" /tmp/catalog-victim
|
|
install -d -m 700 -o 997 -g 987 "$base/state"
|
|
printf 'unchanged\n' > /tmp/catalog-victim/original
|
|
printf 'not executed\n' > "$base/deploy/export-catalog.php"
|
|
cat > /usr/bin/docker <<'SH'
|
|
#!/bin/sh
|
|
printf '%s\n' '{"host":"zoomzilla.ru","generated_at":"2026-10-25T00:00:00Z","groups":{"degr":[],"tp":[],"cons":[]}}'
|
|
SH
|
|
chmod 755 /usr/bin/docker
|
|
# Унаследованный cwd моделирует доступ к корню bind mount внутри приложения,
|
|
# минуя закрытый host-parent /srv/preland-admin.
|
|
(cd "$base/state" && setpriv --reuid=997 --regid=987 --clear-groups ln -s /tmp/catalog-victim catalog.json)
|
|
sed 's/\r$//' /work/admin/deploy/refresh-catalog.sh > /tmp/catalog-refresh.sh
|
|
bash /tmp/catalog-refresh.sh
|
|
test ! -L "$base/state/catalog.json"
|
|
test -f "$base/state/catalog.json"
|
|
test "$(stat -c %u /tmp/catalog-victim/original)" = 0
|
|
test "$(cat /tmp/catalog-victim/original)" = unchanged
|
|
test "$(find /tmp/catalog-victim -type f | wc -l)" = 1
|
|
test "$(stat -c '%u:%g:%a' "$base/state/catalog.json")" = 997:987:600
|
|
if setpriv --reuid=997 --regid=987 --clear-groups ls "$base/secrets" >/dev/null 2>&1; then
|
|
echo 'FAIL: app UID can inspect root preparation directory' >&2
|
|
exit 1
|
|
fi
|
|
echo 'PASS root catalog publisher replaces destination symlink without following it; victim and owner intact; preparation inaccessible to app UID'
|