Private object is accessible through REST geteway when must not be #1199

Closed
opened 2025-12-28 17:22:09 +00:00 by sami · 5 comments
Owner

Originally created by @cthulhu-rider on GitHub (Mar 28, 2024).

i was playing with NeoFS DevEnv and various access settings. I encountered situtation when private object is expectedly inacessible to anonymous subject incl. REST gateway, but can be downloaded throught REST gateway itself

Expected Behavior

$ curl http://rest.neofs.devenv:8090/v1/objects/A74xJsQNjnBpWTscB8kgEf3s2jZmgJJ5MDzPv8w7YmUu/5gHCdcBR5Lgnz3DGgbVWvU8m6bWC1WihpYFVEPxbypp9
{"code":2048,"message":"head object: status: code = 2048 message = access to object operation denied","type":"API"}

Current Behavior

$ curl http://rest.neofs.devenv:8090/v1/objects/EPx35S7TLMXm1ZgSjUZVzXdTtibjYGi5Je4JGrBonAw5/4smVsw84ADigT4Vcp9B2Fp3qn9a9yb4nwXk6hGuinLSd
{"attributes":[{"key":"FileName","value":"Makefile"},{"key":"Timestamp","value":"1711624178"}],"containerId":"EPx35S7TLMXm1ZgSjUZVzXdTtibjYGi5Je4JGrBonAw5","objectId":"4smVsw84ADigT4Vcp9B2Fp3qn9a9yb4nwXk6hGuinLSd","objectSize":4218,"ownerId":"Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn"

Possible Solution

1st i thought that maybe REST communicates with node that cached allowed access settings that hadnt expired yet. But then I polled all the storage nodes via CLI, and they all denied. And REST is still a success

seems like REST unintentionally found security loophole

Steps to Reproduce (for bugs)

  1. run NeoFS DevEnv with REST gateway (default)
  2. create public-read container
$ neofs-cli -r s01.neofs.devenv:8080 -w ../devenv/services/chain/node-wallet.json container create --policy 'REP 2 CBF 1' --basic-acl eacl-public-read  --await
  1. upload object into it
$ neofs-cli -r s01.neofs.devenv:8080 -w ../devenv/services/chain/node-wallet.json object put --cid FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3 --file Makefile
Enter password > 
 4218 / 4218 [============================================================================================================================================================================================================================================] 100.00% 0s
[Makefile] Object successfully stored
  OID: CZxSG4keAyJqVAUa1ghBysryhZDKngEs7j3fxZ9ZsSG6
  CID: FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3
  1. extend access rules to deny OTHERS access (create blank eACL and set it for the container)
$ neofs-cli acl extended create --cid FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3 -r 'deny get others' --out eacl.json
$ neofs-cli -r s01.neofs.devenv:8080 -w ../devenv/services/chain/node-wallet.json container set-eacl --cid FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3 --table eacl.json --await
Enter password > 
Checking the ability to modify access rights in the container...
ACL extension is enabled in the container, continue processing.
eACL modification request accepted for processing (the operation may not be completed yet)
awaiting...
EACL has been persisted on sidechain
  1. download object through CLI with REST account
$ neofs-cli -r s01.neofs.devenv:8080 -w ../devenv/services/rest_gate/wallet.json object get --cid FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3  --oid CZxSG4keAyJqVAUa1ghBysryhZDKngEs7j3fxZ9ZsSG6
Enter password > 
rpc error: init object reading on client: header: status: code = 2048 message = access to object operation denied: access to operation OBJECT_GET is denied by extended ACL check: denied by rule
  1. download object through REST gate
$ curl http://rest.neofs.devenv:8090/v1/objects/FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3/CZxSG4keAyJqVAUa1ghBysryhZDKngEs7j3fxZ9ZsSG6
Helper Go test
func TestDownload(t *testing.T) {
	const strCID = "FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3"
	const strOID = "CZxSG4keAyJqVAUa1ghBysryhZDKngEs7j3fxZ9ZsSG6"

	var cnr cid.ID
	require.NoError(t, cnr.DecodeString(strCID))
	var obj oid.ID
	require.NoError(t, obj.DecodeString(strOID))

	cl, err := client.New(client.PrmInit{})
	require.NoError(t, err)

	var dialPrm client.PrmDial
	dialPrm.SetServerURI("s01.neofs.devenv:8080")

	require.NoError(t, cl.Dial(dialPrm))
	t.Cleanup(func() { cl.Close() })

	signer := user.NewSigner(test.RandomSigner(t), usertest.ID(t))
	_, _, err = cl.ObjectGetInit(context.Background(), cnr, obj, signer, client.PrmObjectGet{})
	require.Error(t, err)
}

Regression

idk yet

Your Environment

Originally created by @cthulhu-rider on GitHub (Mar 28, 2024). i was playing with NeoFS DevEnv and various access settings. I encountered situtation when private object is expectedly inacessible to anonymous subject incl. REST gateway, but can be downloaded throught REST gateway itself ## Expected Behavior ```json $ curl http://rest.neofs.devenv:8090/v1/objects/A74xJsQNjnBpWTscB8kgEf3s2jZmgJJ5MDzPv8w7YmUu/5gHCdcBR5Lgnz3DGgbVWvU8m6bWC1WihpYFVEPxbypp9 {"code":2048,"message":"head object: status: code = 2048 message = access to object operation denied","type":"API"} ``` ## Current Behavior ```json $ curl http://rest.neofs.devenv:8090/v1/objects/EPx35S7TLMXm1ZgSjUZVzXdTtibjYGi5Je4JGrBonAw5/4smVsw84ADigT4Vcp9B2Fp3qn9a9yb4nwXk6hGuinLSd {"attributes":[{"key":"FileName","value":"Makefile"},{"key":"Timestamp","value":"1711624178"}],"containerId":"EPx35S7TLMXm1ZgSjUZVzXdTtibjYGi5Je4JGrBonAw5","objectId":"4smVsw84ADigT4Vcp9B2Fp3qn9a9yb4nwXk6hGuinLSd","objectSize":4218,"ownerId":"Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn" ``` ## Possible Solution 1st i thought that maybe REST communicates with node that cached allowed access settings that hadnt expired yet. But then I polled all the storage nodes via CLI, and they all denied. And REST is still a success seems like REST unintentionally found security loophole ## Steps to Reproduce (for bugs) 1. run NeoFS DevEnv with REST gateway (default) 2. create public-read container ``` $ neofs-cli -r s01.neofs.devenv:8080 -w ../devenv/services/chain/node-wallet.json container create --policy 'REP 2 CBF 1' --basic-acl eacl-public-read --await ``` 3. upload object into it ``` $ neofs-cli -r s01.neofs.devenv:8080 -w ../devenv/services/chain/node-wallet.json object put --cid FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3 --file Makefile Enter password > 4218 / 4218 [============================================================================================================================================================================================================================================] 100.00% 0s [Makefile] Object successfully stored OID: CZxSG4keAyJqVAUa1ghBysryhZDKngEs7j3fxZ9ZsSG6 CID: FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3 ``` 4. extend access rules to deny OTHERS access (create blank eACL and set it for the container) ``` $ neofs-cli acl extended create --cid FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3 -r 'deny get others' --out eacl.json $ neofs-cli -r s01.neofs.devenv:8080 -w ../devenv/services/chain/node-wallet.json container set-eacl --cid FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3 --table eacl.json --await Enter password > Checking the ability to modify access rights in the container... ACL extension is enabled in the container, continue processing. eACL modification request accepted for processing (the operation may not be completed yet) awaiting... EACL has been persisted on sidechain ``` 5. download object through CLI with REST account ``` $ neofs-cli -r s01.neofs.devenv:8080 -w ../devenv/services/rest_gate/wallet.json object get --cid FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3 --oid CZxSG4keAyJqVAUa1ghBysryhZDKngEs7j3fxZ9ZsSG6 Enter password > rpc error: init object reading on client: header: status: code = 2048 message = access to object operation denied: access to operation OBJECT_GET is denied by extended ACL check: denied by rule ``` 6. download object through REST gate ``` $ curl http://rest.neofs.devenv:8090/v1/objects/FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3/CZxSG4keAyJqVAUa1ghBysryhZDKngEs7j3fxZ9ZsSG6 ``` <details> <summary>Helper Go test</summary> <br> ```go func TestDownload(t *testing.T) { const strCID = "FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3" const strOID = "CZxSG4keAyJqVAUa1ghBysryhZDKngEs7j3fxZ9ZsSG6" var cnr cid.ID require.NoError(t, cnr.DecodeString(strCID)) var obj oid.ID require.NoError(t, obj.DecodeString(strOID)) cl, err := client.New(client.PrmInit{}) require.NoError(t, err) var dialPrm client.PrmDial dialPrm.SetServerURI("s01.neofs.devenv:8080") require.NoError(t, cl.Dial(dialPrm)) t.Cleanup(func() { cl.Close() }) signer := user.NewSigner(test.RandomSigner(t), usertest.ID(t)) _, _, err = cl.ObjectGetInit(context.Background(), cnr, obj, signer, client.PrmObjectGet{}) require.Error(t, err) } ``` </details> ## Regression idk yet ## Your Environment * [neofs-dev-env@155e022bd96cdde14b4fdf4a6d2c125147c9bcb8](https://github.com/nspcc-dev/neofs-dev-env/commit/155e022bd96cdde14b4fdf4a6d2c125147c9bcb8) * [neofs-cli@v0.40.1](https://github.com/nspcc-dev/neofs-node/releases/tag/v0.40.1)
sami 2025-12-28 17:22:09 +00:00
Author
Owner

@roman-khimov commented on GitHub (Mar 28, 2024):

It works as expected in mainnet (0.40.1): https://rest.fs.neo.org/v1/objects/5fNrip1bbWxtvKTwUXGrVcPBVXB3FQZ9T6jYihzZi2q2/EW2fuWx6eSgNiXcGSyrTLmTccojyCN439HviJh3B2a4X

Either it's a regression or something more specific.

@roman-khimov commented on GitHub (Mar 28, 2024): It works as expected in mainnet (0.40.1): https://rest.fs.neo.org/v1/objects/5fNrip1bbWxtvKTwUXGrVcPBVXB3FQZ9T6jYihzZi2q2/EW2fuWx6eSgNiXcGSyrTLmTccojyCN439HviJh3B2a4X Either it's a regression or something more specific.
Author
Owner

@cthulhu-rider commented on GitHub (Mar 28, 2024):

Either it's a regression

node version is the same, which REST is served in the mainnet?

@cthulhu-rider commented on GitHub (Mar 28, 2024): > Either it's a regression node version is the same, which REST is served in the mainnet?
Author
Owner

@roman-khimov commented on GitHub (Mar 28, 2024):

The latest one, 0.8.3.

@roman-khimov commented on GitHub (Mar 28, 2024): The latest one, 0.8.3.
Author
Owner

@cthulhu-rider commented on GitHub (Mar 28, 2024):

if in step 4. add deny head others and/or deny getrange others - object becomes unavailable

seems like REST is smart enough to make do with HEAD and GETRANGE even w/o GET

@cthulhu-rider commented on GitHub (Mar 28, 2024): if in step 4. add `deny head others` and/or `deny getrange others` - object becomes unavailable seems like REST is smart enough to make do with HEAD and GETRANGE even w/o GET
Author
Owner

@cthulhu-rider commented on GitHub (Mar 28, 2024):

this turned out to be expected behavior for /objects/<cid>/<oid> cuz it calls HEAD and GETRANGE separately. Instead, /get/<cid>/<oid> calls GET, which fails as expected

@cthulhu-rider commented on GitHub (Mar 28, 2024): this turned out to be expected behavior for `/objects/<cid>/<oid>` cuz it calls `HEAD` and `GETRANGE` separately. Instead, `/get/<cid>/<oid>` calls `GET`, which fails as expected
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
nspcc-dev/neofs-node#1199
No description provided.