1. Log in to Mahara
2. Create a page
3. Put an "External Feed" block on the page
4. Set the "Feed location" to "localhost:389"

Expected result: This meaningless URL does nothing, and the block config harmlessly errors out and asks them for a valid URL.

Actual result: They see an error message that tells them whether the web server has port 389 (unencrypted LDAP) open or not. If the error they see ends with "Recv failure: Connection reset by peer", they know the web server has a process listening on 389. If they see "Failed to connect to... Connection refused" they know it is not.

My patch 4029 was to add the "CURLOPT_PROTOCOLS" option to our Curl requests. This has the main effect of preventing an attacker from using an HTTP redirect to make Curl send a request to a non-HTTP protocol. But it doesn't at all mitigate all the information-gathering attacks, because it only limits the protocol section of the URL (i.e. "https://") and the structure of the request Curl makes. Crucially, it does not limit the port of the URL, you can still enter "http://localhost:389".

As such, it didn't mitigate any of those information-gathering attacks.

Fortunately, I've also realized it should be fairly easy to prevent those information gathering attacks through two steps:

1. Stop printing the underlying Curl error message in the form. The different Curl error responses are the main way the attacker can gather information from this attack. We should instead print something generic like "That feed URL appears to be invalid."

2. Add a random sleep interval to this process, to hinder detection based on timing.

If we do that, then the external feed block will become mostly useless for network exploration. (It'll still have some potential for abuse, but only the much less useful capability of sending HTTP "GET" requests without being able to see the response.)

After doing a bit of researching into timing side channel attacks, I've learned that adding a random sleep() doesn't actually add much security. An attacker can just run multiple requests and average them out, and see which ones take longer on average.

More secure is to make your app delay its response for a pre-set amount of time, by making the request and then waiting out the rest of the pre-set delay. This can either be done with what's called a "WCET" (worst case execution time), i.e. pick a value that will be longer than a successful request; or it can be an unpredictable deterministic value, i.e. hash the input to an integer, and use that integer as your delay.

In testing, I found there was a dead giveaway when I found a valid DNS entry with a bad port number -- the block would take the full CURL timeout delay of 15 seconds to come back. So unfortunately, the only way to prevent information from leaking via timing, is to make sure that every unsuccessful request delays for 15 seconds before coming back. The good news is that at least this won't negatively impact people who enter a *correct* RSS URL. But it'll be annoying if you enter a URL with a typo.