fix(test): correct due to commit 43cf4a2bg
- MKCOL is now better protected
- Webdav now handle 201 (created) 204 (overritten)
This commit is contained in:
+26
-12
@@ -299,14 +299,25 @@ jsonpath "$.items[*].resource.name" not contains "bob-attack-2"
|
|||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
# Step 16 – Bob crafts a path that looks like it targets admin's
|
# Step 16 – Bob crafts a path that looks like it targets admin's
|
||||||
# home. The WebDAV handler rewrites the path to live
|
# home. Pre-43cf4a2b the WebDAV handler silently
|
||||||
# under bob's home, so the request succeeds (201) but
|
# rewrote `My Folder - admin/...` into the caller's own
|
||||||
# the new folders land in BOB's tree — never admin's.
|
# home folder, so this MKCOL succeeded with 201 but the
|
||||||
|
# new folders landed in BOB's tree (defense via
|
||||||
|
# redirect). 43cf4a2b made MKCOL strictly RFC 4918
|
||||||
|
# §9.3.1 compliant: 409 when the parent collection is
|
||||||
|
# missing, no auto-creation of ancestors. Bob's MKCOL
|
||||||
|
# now fails because `My Folder - admin` is not a folder
|
||||||
|
# bob can reach — defense via rejection rather than
|
||||||
|
# silent rewrite. The 4xx range allows for 403/404/409
|
||||||
|
# depending on which gate fires first.
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
MKCOL {{base_url}}/webdav/My%20Folder%20-%20admin/bob-webdav-attack
|
MKCOL {{base_url}}/webdav/My%20Folder%20-%20admin/bob-webdav-attack
|
||||||
Authorization: Bearer {{bob_token}}
|
Authorization: Bearer {{bob_token}}
|
||||||
|
|
||||||
HTTP 201
|
HTTP *
|
||||||
|
[Asserts]
|
||||||
|
status >= 400
|
||||||
|
status < 500
|
||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
@@ -324,13 +335,16 @@ HTTP 201
|
|||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
# Step 18 – Bob's home now contains:
|
# Step 18 – Bob's home contains "bob-webdav-own" (from Step 17's
|
||||||
# - "bob-webdav-own" (from Step 17, normal MKCOL)
|
# legitimate MKCOL) and does NOT contain "My Folder -
|
||||||
# - "My Folder - admin" (from Step 16 — the prefix
|
# admin". Pre-43cf4a2b the path-prefix rewrite would
|
||||||
# rewrite turned admin's home name into a literal
|
# have created that name literally as a sub-folder in
|
||||||
# sub-folder name inside bob's tree).
|
# bob's tree (defense via redirect); post-43cf4a2b the
|
||||||
# This proves the path prefix re-rooted the attack
|
# MKCOL is rejected outright (defense via rejection),
|
||||||
# into bob's own namespace.
|
# so no such folder exists in bob's namespace either.
|
||||||
|
# Both are correct security outcomes — the wire signal
|
||||||
|
# just changed from "succeeded but didn't reach admin"
|
||||||
|
# to "didn't succeed at all."
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
GET {{base_url}}/api/folders/{{bob_home_id}}/resources?resource_types=folder
|
GET {{base_url}}/api/folders/{{bob_home_id}}/resources?resource_types=folder
|
||||||
Authorization: Bearer {{bob_token}}
|
Authorization: Bearer {{bob_token}}
|
||||||
@@ -338,7 +352,7 @@ Authorization: Bearer {{bob_token}}
|
|||||||
HTTP 200
|
HTTP 200
|
||||||
[Asserts]
|
[Asserts]
|
||||||
jsonpath "$.items[*].resource.name" contains "bob-webdav-own"
|
jsonpath "$.items[*].resource.name" contains "bob-webdav-own"
|
||||||
jsonpath "$.items[*].resource.name" contains "My Folder - admin"
|
jsonpath "$.items[*].resource.name" not contains "My Folder - admin"
|
||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -116,18 +116,24 @@ for REMOTE in "$FILE_A" "$FILE_B"; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# ── Step 1: Upload file A ─────────────────────────────────────────────────────
|
# ── Step 1: Upload file A ─────────────────────────────────────────────────────
|
||||||
|
# Post commit 43cf4a2b, PUT distinguishes create (201) from overwrite (204)
|
||||||
|
# per RFC 7231 §4.3.4. Both files are NEW here (the purge_from_trash loop
|
||||||
|
# above wiped any leftover state), so we expect 201 on each PUT.
|
||||||
|
|
||||||
echo " step 1: PUT $FILE_A..."
|
echo " step 1: PUT $FILE_A..."
|
||||||
STATUS=$(webdav_put "$FILE_A" "$FIXTURE" "video/mp4")
|
STATUS=$(webdav_put "$FILE_A" "$FIXTURE" "video/mp4")
|
||||||
[[ "$STATUS" == "204" ]] || fail "PUT $FILE_A expected 204, got $STATUS"
|
[[ "$STATUS" == "201" ]] || fail "PUT $FILE_A expected 201, got $STATUS"
|
||||||
pass "PUT $FILE_A → 204 (new manifest, 8 chunk blobs created)"
|
pass "PUT $FILE_A → 201 (new manifest, 8 chunk blobs created)"
|
||||||
|
|
||||||
# ── Step 2: Upload file B (same content, different name → dedup hit) ──────────
|
# ── Step 2: Upload file B (same content, different name → dedup hit) ──────────
|
||||||
|
# File B is a distinct resource (new path), so PUT still emits 201 even though
|
||||||
|
# the underlying blob is dedup'd. 201 vs 204 reflects "is this a new HTTP
|
||||||
|
# resource at this URL", not "is the byte content novel".
|
||||||
|
|
||||||
echo " step 2: PUT $FILE_B (same bytes → dedup hit)..."
|
echo " step 2: PUT $FILE_B (same bytes → dedup hit)..."
|
||||||
STATUS=$(webdav_put "$FILE_B" "$FIXTURE" "video/mp4")
|
STATUS=$(webdav_put "$FILE_B" "$FIXTURE" "video/mp4")
|
||||||
[[ "$STATUS" == "204" ]] || fail "PUT $FILE_B expected 204, got $STATUS"
|
[[ "$STATUS" == "201" ]] || fail "PUT $FILE_B expected 201, got $STATUS"
|
||||||
pass "PUT $FILE_B → 204 (dedup hit: manifest ref_count → 2, chunks unchanged)"
|
pass "PUT $FILE_B → 201 (dedup hit: manifest ref_count → 2, chunks unchanged)"
|
||||||
|
|
||||||
# ── Resolve file IDs ──────────────────────────────────────────────────────────
|
# ── Resolve file IDs ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -122,18 +122,23 @@ for REMOTE in "$FILE_A" "$FILE_B"; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# ── Step 1: Upload file A ─────────────────────────────────────────────────────
|
# ── Step 1: Upload file A ─────────────────────────────────────────────────────
|
||||||
|
# Post commit 43cf4a2b, PUT distinguishes create (201) from overwrite (204)
|
||||||
|
# per RFC 7231 §4.3.4. The wipe loop above ensures A and B are NEW resources
|
||||||
|
# here, so we expect 201. Step 5 below tests the overwrite case (expects 204).
|
||||||
|
|
||||||
echo " step 1: PUT $FILE_A (dedup-test.jpg)..."
|
echo " step 1: PUT $FILE_A (dedup-test.jpg)..."
|
||||||
STATUS=$(webdav_put "$FILE_A" "$FIXTURE_A" "image/jpeg")
|
STATUS=$(webdav_put "$FILE_A" "$FIXTURE_A" "image/jpeg")
|
||||||
[[ "$STATUS" == "204" ]] || fail "PUT $FILE_A expected 204, got $STATUS"
|
[[ "$STATUS" == "201" ]] || fail "PUT $FILE_A expected 201, got $STATUS"
|
||||||
pass "PUT $FILE_A → 204"
|
pass "PUT $FILE_A → 201"
|
||||||
|
|
||||||
# ── Step 2: Upload file B (identical content, different name) ─────────────────
|
# ── Step 2: Upload file B (identical content, different name) ─────────────────
|
||||||
|
# Distinct resource (new path), so PUT emits 201 even though the underlying
|
||||||
|
# blob dedup-hits. 201 vs 204 reflects URL freshness, not byte freshness.
|
||||||
|
|
||||||
echo " step 2: PUT $FILE_B (dedup-test-2.jpg, same bytes)..."
|
echo " step 2: PUT $FILE_B (dedup-test-2.jpg, same bytes)..."
|
||||||
STATUS=$(webdav_put "$FILE_B" "$FIXTURE_B" "image/jpeg")
|
STATUS=$(webdav_put "$FILE_B" "$FIXTURE_B" "image/jpeg")
|
||||||
[[ "$STATUS" == "204" ]] || fail "PUT $FILE_B expected 204, got $STATUS"
|
[[ "$STATUS" == "201" ]] || fail "PUT $FILE_B expected 201, got $STATUS"
|
||||||
pass "PUT $FILE_B → 204"
|
pass "PUT $FILE_B → 201"
|
||||||
|
|
||||||
# ── Step 3: Resolve file IDs and assert two distinct records ──────────────────
|
# ── Step 3: Resolve file IDs and assert two distinct records ──────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -140,22 +140,21 @@ pass "M2: 5 responses, trailing-slash semantics correct on native /webdav/ surfa
|
|||||||
# the lifecycle (which the existing test_dedup_webdav_* scripts
|
# the lifecycle (which the existing test_dedup_webdav_* scripts
|
||||||
# also exercise at root) actually validates.
|
# also exercise at root) actually validates.
|
||||||
|
|
||||||
echo " M3: PUT /webdav/m3-sample.txt (pinned: native always 204, NC would be 201 on new)"
|
echo " M3: PUT /webdav/m3-sample.txt → 201 (new resource, post 43cf4a2b)"
|
||||||
|
# Post commit 43cf4a2b, the native WebDAV handler differentiates
|
||||||
|
# new-vs-overwrite per RFC 7231 §4.3.4: 201 Created for a fresh PUT,
|
||||||
|
# 204 No Content when replacing an existing resource. Aligns with the
|
||||||
|
# NC handler — there's no more native-vs-NC split on this point.
|
||||||
|
# (Prior to 43cf4a2b the native handler returned 204 for both; the M3
|
||||||
|
# `case` block was a forward-looking trip-wire telling the next reader
|
||||||
|
# to update this pin once the split happened. That moment is now.)
|
||||||
STATUS=$(dav_curl -o /dev/null -w "%{http_code}" -X PUT \
|
STATUS=$(dav_curl -o /dev/null -w "%{http_code}" -X PUT \
|
||||||
-H "Content-Type: text/plain" \
|
-H "Content-Type: text/plain" \
|
||||||
--data-binary 'sample contents — exactly 31 bytes' \
|
--data-binary 'sample contents — exactly 31 bytes' \
|
||||||
"$DAV_BASE/m3-sample.txt")
|
"$DAV_BASE/m3-sample.txt")
|
||||||
case "$STATUS" in
|
[[ "$STATUS" == "201" ]] \
|
||||||
204)
|
|| fail "M3: native PUT new expected 201, got $STATUS"
|
||||||
pass "M3: native PUT new → 204 (pinned current behaviour; differs from NC's 201/204 split)"
|
pass "M3: native PUT new → 201"
|
||||||
;;
|
|
||||||
201)
|
|
||||||
fail "M3: native PUT now returns 201 for new — handler differentiates new-vs-overwrite. Update pin if intentional."
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
fail "M3: unexpected status $STATUS"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
# M4 — Range GET bytes=0-9 → 206 + 10 bytes
|
# M4 — Range GET bytes=0-9 → 206 + 10 bytes
|
||||||
|
|||||||
Reference in New Issue
Block a user