Make WordPress Core

Opened 2 years ago

Last modified 7 months ago

#56141 new enhancement

Enhance installer security

Reported by: smitka's profile smitka Owned by:
Milestone: Future Release Priority: high
Severity: major Version:
Component: Security Keywords: dev-feedback
Focuses: Cc:



The WP installer needs to implement security features to prevent unauthorized use. If the attacker finds an unfinished installer, he can finish the installation on behalf of the user and make malicious changes.

It was hard to find these unfinished installers in the past, but from 2018 Google Chrome requires all publicly trusted web certificates to be logged in Certificate Transparency Log. It is possible to parse CT log in realtime and target newly created websites. Usually, the SSL certificate is issued when a new hosting is set up. So, you can learn about most of the newly created websites from the CT log. The CT log is huge and reliable parsing should be challenging, but the methods are improving, and this attack is becoming more common.

With current methods, it can take less than a minute for an attacker to learn of and compromise a new site. The attacker needs only one HTTP request to compromise the site - send valid DB credentials to /wp-admin/setup-config.php?step=2. In this case, the WP installer creates wp-config.php with these DB credentials. The user can then install WP into an external database controlled by the attacker without noticing.

I analyzed a big-scale ongoing attack of this type and got access to the attacker database to further investigation. He can compromise hundreds of sites a day. I made an automated system to notify administrators of compromised sites. During three days, I sent more than 600 notifications and published the details on

Another installer issue, #52544, makes the installer publicly available too, but the possibility of exploitation is rather accidental and more noticeable.


I see two ways how to add additional protection without significant process changes and without bothering the user too much:

1) allow only particular DB hosts
2) add an installation key feature

For the first one, I made a PoC mu-plugin which controls allowed DB hosts via environment variables or configuration file.

Localhost + is allowed by default, so there is no change for many users. A web host can use the env variable to define their DB servers, so the process will be smooth if they use external DB. If the user wants to use any other server, he has an option to define them via constant in the wp-dbhosts.php file (it is not possible to use wp-config because it doesn't exist).

For the second one, you need to modify the installation workflow slightly. I made a modified setup-config.php as PoC:

It combines the first method - if the DB host is localhost or any server allowed by an environment variable, there is no change for users. If you want to use any other DB host, you have to fill "install key". The "install key" is generated into the install-key.php file, and the user can read it via FTP (the same way he uploaded the core files).

Demo how it works:

Attachments (3)

manual_wp_config.diff (4.2 KB) - added by Michi91 10 months ago.
non_private_network.PNG (53.0 KB) - added by Michi91 10 months ago.
manual_wp_config (2).diff (4.2 KB) - added by Michi91 10 months ago.
uploaded wrong patch... check this one :-)

Download all attachments as: .zip

Change History (18)

#1 @audrasjb
2 years ago

  • Version 6.0 deleted

This ticket was mentioned in Slack in #hosting-community by smitka. View the logs.

2 years ago

#3 @costdev
2 years ago

  • Keywords dev-feedback added
  • Milestone changed from Awaiting Review to 6.1
  • Priority changed from normal to high
  • Severity changed from normal to major

Milestoning for 6.1 and increasing priority/severity to get this ticket more attention.

As the description says, detecting new sites is now quicker and easier, and due to the recent increase in attack numbers (last month, 2000+ hit the honeypot, and many others will have happened to other sites), we should aim for a decision/resolution in this cycle.

#4 @lordgurke
2 years ago

Hi smitka,

For the install key: I would also read it from an environment variable, if it's set. With that, a hoster can generate this key automatically and display it within the customers backend. The installer will be secured by default, without a need for the customers to upload any additional files.
Also, maybe other projects might want to use such a feature and can then simply refer to a (standardized?) environment variable.

I also created such a request a few months ago in the forums, but this has been silently deleted without any reason. Maybe WordPress could provide a reason for this, because I have a feeling many other reports on problems ceased to exist that way.

#5 @smitka
2 years ago

Some updates about the ongoing attack. The main attackers change their strategy and generate unique DB credentials per vulnerable site. The result is that when I get access to their database, I cannot find new hacked sites to notify their admins anymore.

I sent over 2000 notifications last month and detected around 3000 different sites in the attacker's databases. I probably only discovered a fraction of the compromised sites. In some cases, the attacker was quicker than me, and he changed the admin email so I couldn't send the notifications. I only gained access to the database if the attacker attacked my honeypot servers.

@lordgurke, I agree it would be great to have the possibility to override some constants by the environment values. I have to use the Runkit PHP extension to override some values without user intervention. However, we still have to keep in mind that env variables can be listed in different places, such as phpinfo() output. The chances that this file will be available in the unfinished installation are minimal, but I would still prefer to avoid using them for the install key.

I should describe the function of my second proposal in more detail:

The host specifies allowed DB servers via env variable; the localhost and are allowed by default. If you use the allowed DB host during the installation, the install key is not required, and the process is unchanged for the majority of new manual installs.

The situation is different when you want to use an unlisted DB host. In this scenario, the installer will generate the install-key.php file and force the user to use it. The user can read it via FTP/SFTP/SSH, or the host can show it in the administration (maybe it would be a good idea to add another variable to add a link to the host documentation or administration).

There is no need to upload anything, and most users' installation process is the same (even if the host doesn't set the variables).

The flaw with this approach is that there is still a tiny chance that the attacker has set up webhosting with the same provider as the target site and can use the allowed DB host for malicious activity. To prevent this, you can make an "allowed DB names list" too. However, this demands more on the host, as it has to prepare unique env variables for each site.

BTW: it is possible to show the diff between the original setup-config.php and my PoC fix:

I also added some captions to my explainer video to make it clearer what's going on:

#6 follow-up: @desrosj
22 months ago

  • Milestone changed from 6.1 to Future Release

This one still needs more discussion. With 6.1 Beta 1 due out in a few days, it's a bit late to include this so I'm going to punt.

#7 in reply to: ↑ 6 @smitka
22 months ago

Replying to desrosj:

This one still needs more discussion. With 6.1 Beta 1 due out in a few days, it's a bit late to include this so I'm going to punt.

Is there any place to join the discussion?

#8 @audrasjb
22 months ago

@smitka the place to discuss this ticket is the ticket itself: here :)

This ticket was mentioned in Slack in #hosting-community by chaion07. View the logs.

19 months ago

This ticket was mentioned in Slack in #hosting-community by jadonn. View the logs.

19 months ago

#11 @Michi91
10 months ago

the localhost and are allowed by default.

What about allowing all Private Adress Spaces? rfc1918 - (10/8 prefix) - (172.16/12 prefix) - (192.168/16 prefix)

My prefered hoster has the mysql servers separated in 10.x.x.x and I can imagine that there are quite a lot hosters with the scheme.

Hosters could overwrite this default setting with the env variable if they would like to be more specified (and to make sure, that the bad guys dont rent db-servers at the hoster for site specific attacs)

#12 @smitka
10 months ago

It would be great to continue with this issue. The problem persists and affects many sites every day.

For example this is the list of hacked sites I found today:

My notifications system is not effective anymore, because the hackers usually change the admin email to theirselves.

#13 @Michi91
10 months ago

I would like to offer a different solution:

The hackers are running their db servers with public ip addresses.

My patch checks if the dbhost, that is defined during setup, is running in private network address space. It supports IPs and hostnames and also allows ENV defined network. The check is running AFTER a successful db connection was established, but before wp-config.php is saved.

If the db host not inside the private adress space, the wp-config.php needs to be created manually. Just like you have to do when the filesystem is not writable.


  • Doesnt require filesystem write permissions like your install key.
  • Less complex

What do you think about this @smitka ? And ofcourse what do the others think?

From my experience db-servers are usually localhost or in a private network. If someone is sceptical and thinks this solution could bother to many users, maybe we could collect telematic data and see how much % is not in a private address space?

10 months ago

uploaded wrong patch... check this one :-)

#14 @wet
7 months ago

Related: Donncha becomes aware of this long-standing issue

#15 @lordgurke
7 months ago

@Michi91 the wp_is_dbhost_in_private_network function is missing all IPv6 space. This would be an issue on our installations.
The following prefixes should be added to be treated as private:

  • fe80::/10
  • fc00::/7

But this should only be additional to the values of the ENV variable as proposed in the first post of the ticket, making it more suitable for shared hosting environments.

Note: See TracTickets for help on using tickets.