mirror of
https://github.com/juanfont/headscale.git
synced 2026-01-12 04:10:32 +01:00
[Bug] Registration of duplicate NodeKeys leads to DoS / Spoofing #1086
Closed
opened 2025-12-29 02:28:11 +01:00 by adam
·
6 comments
No Branch/Tag Specified
main
update_flake_lock_action
gh-pages
kradalby/release-v0.27.2
dependabot/go_modules/golang.org/x/crypto-0.45.0
dependabot/go_modules/github.com/opencontainers/runc-1.3.3
copilot/investigate-headscale-issue-2788
copilot/investigate-visibility-issue-2788
copilot/investigate-issue-2833
copilot/debug-issue-2846
copilot/fix-issue-2847
dependabot/go_modules/github.com/go-viper/mapstructure/v2-2.4.0
dependabot/go_modules/github.com/docker/docker-28.3.3incompatible
kradalby/cli-experiement3
doc/0.26.1
doc/0.25.1
doc/0.25.0
doc/0.24.3
doc/0.24.2
doc/0.24.1
doc/0.24.0
kradalby/build-docker-on-pr
topic/docu-versioning
topic/docker-kos
juanfont/fix-crash-node-id
juanfont/better-disclaimer
update-contributors
topic/prettier
revert-1893-add-test-stage-to-docs
add-test-stage-to-docs
remove-node-check-interval
fix-empty-prefix
fix-ephemeral-reusable
bug_report-debuginfo
autogroups
logs-to-stderr
revert-1414-topic/fix_unix_socket
rename-machine-node
port-embedded-derp-tests-v2
port-derp-tests
duplicate-word-linter
update-tailscale-1.36
warn-against-apache
ko-fi-link
more-acl-tests
fix-typo-standalone
parallel-nolint
tparallel-fix
rerouting
ssh-changelog-docs
oidc-cleanup
web-auth-flow-tests
kradalby-gh-runner
fix-proto-lint
remove-funding-links
go-1.19
enable-1.30-in-tests
0.16.x
cosmetic-changes-integration
tmp-fix-integration-docker
fix-integration-docker
configurable-update-interval
show-nodes-online
hs2021
acl-syntax-fixes
ts2021-implementation
fix-spurious-updates
unstable-integration-tests
mandatory-stun
embedded-derp
prtemplate-fix
v0.28.0-beta.1
v0.27.2-rc.1
v0.27.1
v0.27.0
v0.27.0-beta.2
v0.27.0-beta.1
v0.26.1
v0.26.0
v0.26.0-beta.2
v0.26.0-beta.1
v0.25.1
v0.25.0
v0.25.0-beta.2
v0.24.3
v0.25.0-beta.1
v0.24.2
v0.24.1
v0.24.0
v0.24.0-beta.2
v0.24.0-beta.1
v0.23.0
v0.23.0-rc.1
v0.23.0-beta.5
v0.23.0-beta.4
v0.23.0-beta3
v0.23.0-beta2
v0.23.0-beta1
v0.23.0-alpha12
v0.23.0-alpha11
v0.23.0-alpha10
v0.23.0-alpha9
v0.23.0-alpha8
v0.23.0-alpha7
v0.23.0-alpha6
v0.23.0-alpha5
v0.23.0-alpha4
v0.23.0-alpha4-docker-ko-test9
v0.23.0-alpha4-docker-ko-test8
v0.23.0-alpha4-docker-ko-test7
v0.23.0-alpha4-docker-ko-test6
v0.23.0-alpha4-docker-ko-test5
v0.23.0-alpha-docker-release-test-debug2
v0.23.0-alpha-docker-release-test-debug
v0.23.0-alpha4-docker-ko-test4
v0.23.0-alpha4-docker-ko-test3
v0.23.0-alpha4-docker-ko-test2
v0.23.0-alpha4-docker-ko-test
v0.23.0-alpha3
v0.23.0-alpha2
v0.23.0-alpha1
v0.22.3
v0.22.2
v0.23.0-alpha-docker-release-test
v0.22.1
v0.22.0
v0.22.0-alpha3
v0.22.0-alpha2
v0.22.0-alpha1
v0.22.0-nfpmtest
v0.21.0
v0.20.0
v0.19.0
v0.19.0-beta2
v0.19.0-beta1
v0.18.0
v0.18.0-beta4
v0.18.0-beta3
v0.18.0-beta2
v0.18.0-beta1
v0.17.1
v0.17.0
v0.17.0-beta5
v0.17.0-beta4
v0.17.0-beta3
v0.17.0-beta2
v0.17.0-beta1
v0.17.0-alpha4
v0.17.0-alpha3
v0.17.0-alpha2
v0.17.0-alpha1
v0.16.4
v0.16.3
v0.16.2
v0.16.1
v0.16.0
v0.16.0-beta7
v0.16.0-beta6
v0.16.0-beta5
v0.16.0-beta4
v0.16.0-beta3
v0.16.0-beta2
v0.16.0-beta1
v0.15.0
v0.15.0-beta6
v0.15.0-beta5
v0.15.0-beta4
v0.15.0-beta3
v0.15.0-beta2
v0.15.0-beta1
v0.14.0
v0.14.0-beta2
v0.14.0-beta1
v0.13.0
v0.13.0-beta3
v0.13.0-beta2
v0.13.0-beta1
upstream/v0.12.4
v0.12.4
v0.12.3
v0.12.2
v0.12.2-beta1
v0.12.1
v0.12.0-beta2
v0.12.0-beta1
v0.11.0
v0.10.8
v0.10.7
v0.10.6
v0.10.5
v0.10.4
v0.10.3
v0.10.2
v0.10.1
v0.10.0
v0.9.3
v0.9.2
v0.9.1
v0.9.0
v0.8.1
v0.8.0
v0.7.1
v0.7.0
v0.6.1
v0.6.0
v0.5.2
v0.5.1
v0.5.0
v0.4.0
v0.3.6
v0.3.5
v0.3.4
v0.3.3
v0.3.2
v0.3.1
v0.3.0
v0.2.2
v0.2.1
v0.2.0
v0.1.1
v0.1.0
Labels
Clear labels
CLI
DERP
DNS
Nix
OIDC
SSH
bug
database
documentation
duplicate
enhancement
faq
good first issue
grants
help wanted
might-come
needs design doc
needs investigation
no-stale-bot
out of scope
performance
policy 📝
pull-request
question
regression
routes
stale
tags
tailscale-feature-gap
well described ❤️
wontfix
Mirrored from GitHub Pull Request
Milestone
No items
No Milestone
Projects
Clear projects
No project
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: starred/headscale#1086
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @mjwrona on GitHub (Aug 15, 2025).
Is this a support request?
Is there an existing issue for this?
Current Behavior
This issue affects only the main branch at commit
30cec3aand has not yet been included in a release.In pull request #2628, a change was introduced that modifies how a re-registration request is handled:
30cec3aa2b/hscontrol/auth.go (L36-L58)Under the new registration flow, the system does not properly verify whether the
RegisterRequest.NodeKeyprovided by the client actually belongs to the requesting user if a validregReq.Auth.AuthKeyis included.Although there is an appropriate validation implemented later in the code, it is never executed because the logic performs an early return before reaching it:
30cec3aa2b/hscontrol/auth.go (L92-L94)As a result, Tailscale can't handle multiple nodes with the same NodeKey. In such cases, it arbitrarily selects one of them (for example,

admin1orstaging1), leading to unpredictable behavior.Expected Behavior
Owner verification early in the registration process
The registration workflow should verify node ownership at an early stage to prevent unauthorized associations. This ensures that the
RegisterRequest.NodeKeyis explicitly tied to the correct user before any other registration logic proceeds.Database-level unique constraint on NodeKey
Enforcing a unique constraint on NodeKey at the database layer would prevent multiple records from sharing the same identifier. Tailscale relies on
map[key.NodePublic]in several parts of its codebase, this would reflect that behavior.Steps To Reproduce
30cec3a.attacker.admin.tailscale status --jsonattacker, but replace the NodeKey with the one belonging to admin.In my test, the NodeKey was hardcoded directly into the Tailscale client to simulate the tampering.
tailscale statuson the affected network will randomly return either theattackeroradminentry for the same node, leading to spoofing behavior.Environment
Runtime environment
Debug information
Spoofed NodeKey:
"node_key": "nodekey:b28ee415a1789cb9326ef2c7f82a096c501c053d948a4690ab56aa3058859d79"headscale-nodes.json
@kradalby commented on GitHub (Sep 5, 2025):
Hello! good find, I am trying to replicate this as a integration test before fixing it, but I am having trouble to do so.
You can see the current implementation behind the screenshot in #2759.
I opted for implementing it with auth keys, but I am not sure if you used another auth method?
@kradalby commented on GitHub (Sep 9, 2025):
When I have time, I'm going to rebase the test branch (#2759) on top of the new (large) changes to main and try again. It would be really useful to have a test failing so we know what to fix, let me know if you have some time to figure out how I can reproduce it.
Unfortunately compiling and hardcoding the node key to the client is unfeasible in our tests, and if we cant add it to the tests it will be hard to ensure we do not break this in the future.
@mjwrona commented on GitHub (Sep 9, 2025):
Hey @kradalby
I quickly reviewed your code. From what i can see you're registering the admin node first and after that the attacker node.
Try to switch the order - first register the attacker and after that the admin otherwise the admin won't be locked out.
For the integration test it's fine to use the correct key pair instead of hardcoding the public key as the result should be the same.
Let me know if that works, if not i can check it more in detail
@kradalby commented on GitHub (Sep 17, 2025):
I'm having a look at this today again, I've rebased the testing branch off the current main, I'm still having some trouble. Could you test if you find it present on the current main too?
I'm not 100% sure if we are going to be able to produce this in the integration tests, but will try a bit more.
@mjwrona commented on GitHub (Sep 18, 2025):
In the current main branch this is already fixed.
The code that i have reported:
30cec3aa2b/hscontrol/auth.go (L31-L58)VS the current main:
bd35fcf338/hscontrol/auth.go (L31-L59)As you can see the function
handleExistingNodeis now called before reachinghandleRegisterWithAuthKey@kradalby commented on GitHub (Sep 18, 2025):
Great, I’ll closed this as fixed then.