netbox-community / netbox

The premier source of truth powering network automation. Open source under Apache 2. Try NetBox Cloud free: https://netboxlabs.com/free-netbox-cloud/
http://netboxlabs.com/oss/netbox/
Apache License 2.0
16.02k stars 2.57k forks source link

Authentication Bypass in GraphQL Queries for Users/Tokens Lacking Permissions #16292

Closed kiraum closed 1 month ago

kiraum commented 4 months ago

Deployment Type

Self-hosted

NetBox Version

v4.0.3

Python Version

3.11

Steps to Reproduce

Test user does not have any permissions associated with it (I am using the admin token to make the query):

% curl -s -H "Authorization: Token 28a079fb8aa5e107583583dbeb7deb027121a15a" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
http://localhost:8000/api/users/users/\?username\=test | jq -r '.results[] | .username, .permissions'
test
[]

Test user token:

% curl -s -H "Authorization: Token 28a079fb8aa5e107583583dbeb7deb027121a15a" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
http://localhost:8000/api/users/tokens/ | jq -r '.results[] | select(.user.username == "test") | .user.username, .key'
test
4f745a0b1c13168c95883ee004f17df7ef96e42a

Now using the test user token without any permissions via Graphql:

% curl -s -H "Authorization: Token 4f745a0b1c13168c95883ee004f17df7ef96e42a" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
http://localhost:8000/graphql/ \
--data '{"query": "query {asn(id:1) {asn}}"}'
{"data": {"asn": {"asn": 666}}}%
% curl -s -H "Authorization: Token 4f745a0b1c13168c95883ee004f17df7ef96e42a" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
http://localhost:8000/graphql/ \
--data '{"query": "query {provider(id:1) {name}}"}'
{"data": {"provider": {"name": "test-p"}}}%

In my understanding, if a user/token has no permissions, it should reject by default.

Version:

% curl -s -H "Authorization: Token 28a079fb8aa5e107583583dbeb7deb027121a15a" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
http://localhost:8000/api/status/ | jq -r '.'
{
  "django-version": "5.0.6",
  "installed-apps": {
    "debug_toolbar": "4.3.0",
    "django_filters": "24.2",
    "django_prometheus": "2.3.1",
    "django_rq": "2.10.2",
    "django_tables2": "2.7.0",
    "drf_spectacular": "0.27.2",
    "drf_spectacular_sidecar": "2024.5.1",
    "mptt": "0.16.0",
    "rest_framework": "3.15.1",
    "social_django": "5.4.1",
    "taggit": "5.0.1",
    "timezone_field": "6.1.0"
  },
  "netbox-version": "4.0.3",
  "plugins": {},
  "python-version": "3.11.6",
  "rq-workers-running": 2
}

This may be related with the issue#16228.

Expected Behavior

The system should reject GraphQL queries from users or tokens that do not have the necessary permissions.

Observed Behavior

During testing, it was discovered that if a user or token lacks permissions, the system does not enforce authentication for GraphQL queries. This was observed using a test user with no permissions, where GraphQL queries still returned sensitive data.

GraphQL queries are processed and data is returned even when the user or token has no permissions.

Further investigation is needed to confirm the scope and cause of the authentication bypass. Additional details will be provided upon request.

arthanson commented 4 months ago

@kiraum are you sure this is NetBox 4.0.3? This sounds like #16228 which was in NetBox 4.0.2 but fixed in 4.0.3

kiraum commented 4 months ago

@arthanson, I was running 4.0.2, but pulled new image, and via API/UI, I do see version 4.0.3:

image image image
unit@45f666ac0bd0:/opt/netbox$ head -3 docs/release-notes/version-4.0.md
# NetBox v4.0

## v4.0.3 (2024-05-22)
docker image inspect f876940be42f | jq -r '.[].Config.Labels."netbox.git-ref"'
3f345cdbee016243ab5162456ea5415472a2f2df

https://github.com/netbox-community/netbox/commit/3f345cdbee016243ab5162456ea5415472a2f2df

Should I rely on those versions or do we have a better (more reliable) way to check it?

I've destroyed the environment, and recreated to see if there wasn't left overs from the old build, but still the same.

DanSheps commented 4 months ago

I just tested this. I am not able to reproduce.

Please provide more comprehensive steps including what objects to create for testing against (I see you are querying ASN's but I don't see how to create that ASN specifically)

kiraum commented 4 months ago

@DanSheps , thanks for checking, follow the step by step:

» git clone git@github.com:netbox-community/netbox-docker.git
» cd netbox-docker
» tee docker-compose.override.yml <<EOF
services:
  netbox:
    ports:
      - 8000:8080
EOF
services:
  netbox:
    ports:
      - 8000:8080
» vi docker-compose.yml
» git diff
diff --git a/docker-compose.yml b/docker-compose.yml
index 958561f..afc6905 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,6 +1,6 @@
 services:
   netbox: &netbox
-    image: docker.io/netboxcommunity/netbox:${VERSION-v4.0-2.9.1}
+    image: docker.io/netboxcommunity/netbox:${VERSION-v4.0.3-2.9.1}
     depends_on:
     - postgres
     - redis
» docker-compose pull
» docker-compose up -d
» docker image ls
REPOSITORY               TAG            IMAGE ID       CREATED      SIZE
netboxcommunity/netbox   v4.0.3-2.9.1   f876940be42f   3 days ago   707MB
redis                    7-alpine       cdb453bbf446   4 days ago   42MB
postgres                 16-alpine      1220ef94dbc4   5 days ago   250MB
» docker image inspect f876940be42f | jq -r '.[].Config.Labels."netbox.git-ref"'
3f345cdbee016243ab5162456ea5415472a2f2df
» docker compose exec netbox /opt/netbox/netbox/manage.py createsuperuser
# created an user test via UI, without group/permissions (not superadmin)
# created token b97beb333cf5f381025440a4037b3284373b3ab3 associated with user test
# created a provider, just to have something to query

Here, I can get data without permissions set to the user:

» curl -s -H "Authorization: Token b97beb333cf5f381025440a4037b3284373b3ab3" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
http://localhost:8000/graphql/ \
--data '{"query": "query {provider(id:1) {name}}"}'
{"data": {"provider": {"name": "test-p"}}}%
» curl -s -H "Authorization: Token b97beb333cf5f381025440a4037b3284373b3ab3" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
http://localhost:8000/api/status/ | jq '.'
{
  "django-version": "5.0.6",
  "installed-apps": {
    "debug_toolbar": "4.3.0",
    "django_filters": "24.2",
    "django_prometheus": "2.3.1",
    "django_rq": "2.10.2",
    "django_tables2": "2.7.0",
    "drf_spectacular": "0.27.2",
    "drf_spectacular_sidecar": "2024.5.1",
    "mptt": "0.16.0",
    "rest_framework": "3.15.1",
    "social_django": "5.4.1",
    "taggit": "5.0.1",
    "timezone_field": "6.1.0"
  },
  "netbox-version": "4.0.3",
  "plugins": {},
  "python-version": "3.11.6",
  "rq-workers-running": 1
}

If I try to get user details with the token without permissions, I do see a reject:

» curl -s -H "Authorization: Token b97beb333cf5f381025440a4037b3284373b3ab3" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
http://localhost:8000/api/users/users/\?username\=test
{"detail":"You do not have permission to perform this action."}%

Using a superuser token, just to show the current permissions and tokens:

» curl -s -H "Authorization: Token 6065f2a66b6b6e08c4ea68b07976e998f6b29a67" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
http://localhost:8000/api/users/users/\?username\=test | jq '.'
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 2,
      "url": "http://localhost:8000/api/users/users/2/",
      "display": "test",
      "username": "test",
      "first_name": "",
      "last_name": "",
      "email": "",
      "is_staff": false,
      "is_active": true,
      "date_joined": "2024-05-27T20:25:24.362362Z",
      "last_login": null,
      "groups": [],
      "permissions": []
    }
  ]
}

» curl -s -H "Authorization: Token 6065f2a66b6b6e08c4ea68b07976e998f6b29a67" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
http://localhost:8000/api/users/tokens/ | jq -r '.'
{
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 1,
      "url": "http://localhost:8000/api/users/tokens/1/",
      "display": "b97beb333cf5f381025440a4037b3284373b3ab3",
      "user": {
        "id": 2,
        "url": "http://localhost:8000/api/users/users/2/",
        "display": "test",
        "username": "test"
      },
      "created": "2024-05-27T20:26:12.198312Z",
      "expires": null,
      "last_used": "2024-05-27T20:31:55.628047Z",
      "key": "b97beb333cf5f381025440a4037b3284373b3ab3",
      "write_enabled": true,
      "description": "",
      "allowed_ips": []
    },
    {
      "id": 2,
      "url": "http://localhost:8000/api/users/tokens/2/",
      "display": "6065f2a66b6b6e08c4ea68b07976e998f6b29a67",
      "user": {
        "id": 1,
        "url": "http://localhost:8000/api/users/users/1/",
        "display": "admin",
        "username": "admin"
      },
      "created": "2024-05-27T20:36:55.815834Z",
      "expires": null,
      "last_used": "2024-05-27T20:38:40.961444Z",
      "key": "6065f2a66b6b6e08c4ea68b07976e998f6b29a67",
      "write_enabled": true,
      "description": "",
      "allowed_ips": []
    }
  ]
}

Thanks, and please let me know if you need something else.

kiraum commented 4 months ago

Hi, I noticed that I left write enable during the latest test, follow the result with write_enable as false for the test token in the same environment/deploy:

» curl -s -H "Authorization: Token 6065f2a66b6b6e08c4ea68b07976e998f6b29a67" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
http://localhost:8000/api/users/tokens/ | jq -r '.'
{
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 1,
      "url": "http://localhost:8000/api/users/tokens/1/",
      "display": "b97beb333cf5f381025440a4037b3284373b3ab3",
      "user": {
        "id": 2,
        "url": "http://localhost:8000/api/users/users/2/",
        "display": "test",
        "username": "test"
      },
      "created": "2024-05-27T20:26:12.198312Z",
      "expires": null,
      "last_used": "2024-05-27T20:31:55.628047Z",
      "key": "b97beb333cf5f381025440a4037b3284373b3ab3",
      "write_enabled": false,
      "description": "",
      "allowed_ips": []
    },
    {
      "id": 2,
      "url": "http://localhost:8000/api/users/tokens/2/",
      "display": "6065f2a66b6b6e08c4ea68b07976e998f6b29a67",
      "user": {
        "id": 1,
        "url": "http://localhost:8000/api/users/users/1/",
        "display": "admin",
        "username": "admin"
      },
      "created": "2024-05-27T20:36:55.815834Z",
      "expires": null,
      "last_used": "2024-05-28T04:34:33.458954Z",
      "key": "6065f2a66b6b6e08c4ea68b07976e998f6b29a67",
      "write_enabled": true,
      "description": "",
      "allowed_ips": []
    }
  ]
}

» curl -s -H "Authorization: Token b97beb333cf5f381025440a4037b3284373b3ab3" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
http://localhost:8000/graphql/ \
--data '{"query": "query {provider(id:1) {name}}"}'
{"data": {"provider": {"name": "test-p"}}}%
github-actions[bot] commented 4 months ago

This is a reminder that additional information is needed in order to further triage this issue. If the requested details are not provided, the issue will soon be closed automatically.

kiraum commented 4 months ago

@DanSheps do you need more details? Received a message from @github telling if details are not provided issue will be resolved.

DanSheps commented 1 month ago

Alright,

The fundamental issue is that for a single ASN query, we have:

class IPAMQuery:
    @strawberry.field
    def asn(self, id: int) -> ASNType:
        return models.ASN.objects.get(pk=id)

the models.ASN.objects.get(pk=id) is just simply not calling restrict. AFAIK, all models are impacted by this.