Skip to content

Commit 48f082b

Browse files
authored
blog: domain resurrection prevention (#18559)
1 parent d38341c commit 48f082b

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed
58.4 KB
Loading
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
---
2+
title: Preventing Domain Resurrection Attacks
3+
description: PyPI now checks for expired domains to prevent domain resurrection attacks, a type of supply-chain attack where someone buys an expired domain and uses it to take over PyPI accounts through password resets.
4+
authors:
5+
- miketheman
6+
date: 2025-08-18
7+
tags:
8+
- security
9+
---
10+
11+
## Summary
12+
13+
PyPI now checks for expired domains to prevent domain resurrection attacks,
14+
a type of supply-chain attack where someone buys an expired domain
15+
and uses it to take over PyPI accounts through password resets.
16+
17+
These changes improve PyPI's overall account security posture,
18+
making it harder for attackers to exploit expired domain names
19+
to gain unauthorized access to accounts.
20+
21+
<!-- more -->
22+
23+
Since early June 2025, PyPI has unverified over 1,800 email addresses
24+
when their associated domains entered expiration phases.
25+
This isn't a perfect solution, but it closes off a significant attack vector
26+
where the majority of interactions would appear completely legitimate.
27+
28+
## Background
29+
30+
PyPI user accounts are linked to email addresses.
31+
Email addresses are tied to domain names;
32+
domain names can expire if unpaid, and someone else can purchase them.
33+
34+
During PyPI account registration, [users are required to verify their email addresses](https://policies.python.org/pypi.org/Terms-of-Service/#2-required-information)
35+
by clicking a link sent to the email address provided during registration.
36+
This verification ensures the address is valid and accessible to the user,
37+
and may be used to send important account-related information,
38+
such as password reset requests, or for PyPI Admins to use to contact the user.
39+
40+
PyPI considers the account holder's initially verified email address a strong indicator of account ownership.
41+
Coupled with a form of Two-Factor Authentication (2FA), this helps to further secure the account.
42+
43+
Once expired, an attacker could register the expired domain, set up an email server,
44+
issue a password reset request, and gain access to accounts associated with that domain name.
45+
46+
Accounts with any activity after [January 1 2024 will have 2FA enabled](2024-01-01-2fa-enforced.md),
47+
and an attacker would need to have either the second factor,
48+
or perform a full account recovery.
49+
50+
For older accounts prior to the 2FA requirement date,
51+
having an email address domain expire could lead to account takeover,
52+
which is what we're attempting to prevent,
53+
as well as minimize potential exposure if an email domain _does_ expire and change hands,
54+
regardless of whether the account has 2FA enabled.
55+
56+
This is not an imaginary attack - this has happened at least once for [a PyPI project](https://osv.dev/vulnerability/PYSEC-2022-199) back in 2022,
57+
and [other package ecosystems](https://blog.illustria.io/illustria-discovers-account-takeover-vulnerability-in-a-popular-package-affecting-1000-8aaaf61ebfc4).
58+
59+
**TL;DR: If a domain expires, don't consider email addresses associated with it verified any more.**
60+
61+
## Domain Expiration Timeframe
62+
63+
Here's a generalized flowchart of phases that domain name registrars often adhere to.
64+
There's typically a grace period before a domain is deleted.
65+
Read more about [ICANN's Expired Registration Recovery Policy (ERRP)](https://www.icann.org/resources/pages/registrant-about-errp-2018-12-07-en).
66+
67+
One way to visualize this can be seen in the below flowchart:
68+
69+
```mermaid
70+
flowchart TD
71+
A[Domain Active] -->|Expiration Date Reached| B{Owner Renews?}
72+
73+
B -->|Yes - Within Grace Period| A
74+
B -->|No| C[Renewal Grace Period<br/>0-45 days]
75+
76+
C -->|Owner Renews<br/>Regular Price| A
77+
C -->|No Renewal| D[Redemption Period<br/>30 days]
78+
79+
D -->|Owner Redeems<br/>High Fee $70-200+| A
80+
D -->|No Redemption| E[Pending Delete<br/>5 days]
81+
82+
E -->|Automatic| F[Domain Released]
83+
```
84+
85+
The word "expiration" might be a bit overloaded,
86+
as conceptually there is no specific state advertised that a domain name has expired,
87+
rather using other indicators we can infer what state the domain name is currently in.
88+
89+
Thanks to our friends at [Domainr](https://domainr.com/) (a Fastly service),
90+
we can use their [Status API](https://domainr.com/docs/api/v2/status#status-results)
91+
to issue periodic queries for any given domain, and act on the response.
92+
93+
The time interval we've chosen for now is 30 days, as per the flow above,
94+
there's a high likelihood that a domain is still in the Renewal Grace Period
95+
or Redemption Period when we check for status,
96+
and can react before it is released or changes hands.
97+
98+
_Note_: PyPI will not detect a non-expiring domain transfer,
99+
as we assume the parties are acting together to transfer a domain legitimately.
100+
101+
## PyPI Actions
102+
103+
After an initial bulk check period that took place in April 2025,
104+
PyPI will check daily for any domains in use for status changes,
105+
and update its internal database with the most recent status.
106+
107+
If a domain registration enters the redemption period,
108+
that's an indicator to PyPI that the previously verified email destinations may not be trusted,
109+
and will un-verify a previously-verified email address.
110+
PyPI will not issue a password reset request to addresses that have become unverified.
111+
112+
![Expired by date](../assets/2025-08-13-unverified-by-day.png)
113+
114+
Since the initial implementation early June 2025,
115+
PyPI has unverified over 1,800 email addresses (initial 1,500 excluded from chart),
116+
and will continue to do so daily, to protect both PyPI account holders,
117+
as well as the end users of PyPI packages.
118+
119+
## Recommendations for end users
120+
121+
If your PyPI account only has a **single verified** email address from a custom domain name,
122+
add a **second verified** email address from another notable domain (e.g. Gmail) to your account.
123+
124+
During a PyPI account recovery, PyPI may ask for other proofs,
125+
often via other services under the user's control.
126+
If the same email address is used on those other services, the recovery could appear legitimate.
127+
Ensure you have 2FA set on those services as well to prevent potential account takeovers.
128+
129+
## That's all for now, folks
130+
131+
While these changes are not foolproof,
132+
they decrease the likelihood of domain resurrections account takeovers.
133+
134+
Thanks to Eric Case at [Fastly](https://www.fastly.com/) for helping us understand some of the complexities
135+
and Samuel Giddins at [Ruby Central](https://rubycentral.org/) for their initial ideas of this approach,
136+
and the [OpenSSF Securing Software Repositories Working Group](https://repos.openssf.org/)
137+
for their collaborative guidance on repository security.
138+
139+
This effort would not be possible without the continued support from [Alpha-Omega](https://alpha-omega.dev/).
140+
141+
### Related reading
142+
143+
- [Wikipedia: Domain hijacking](https://en.wikipedia.org/wiki/Domain_hijacking)
144+
- [CAPEC-50: Password Recovery Exploitation](https://capec.mitre.org/data/definitions/50.html)
145+
- [CAPEC-695: Repo Jacking](https://capec.mitre.org/data/definitions/695.html)
146+
- [OpenSSF: Principles for Package Repository Security](https://repos.openssf.org/principles-for-package-repository-security)
147+
- [arxiv: What are Weak Links in the npm Supply Chain?](https://arxiv.org/abs/2112.10165)

0 commit comments

Comments
 (0)