Recently while browsing through some technical articles, I stumbled upon a piece of script that used cURL with a flag previously unknown to me. At first, I was not fully aware of what was the point of it, but once I understood the point, it became obvious to me that this can be a great idea for a short, but a possibly interesting blog post. Let’s see how cURL can enforce using HTTPS for a connection, even if somebody would try to prevent that from happening.

What is cURL?

I once heard that cURL is a Postman for hackers. There is some logic in this definition, and I often use cURL command to test how (or if) a particular API works during the development. It is often used for downloading stuff from the internet, eg. to fetch and run some installation scripts for various tools. It is quite robust and accepts many useful flags that you can use to make the result of this command vary significantly.

One of the flags, which is actually rarely used for security reasons, is --insecure which allows you to call “secure” servers that are identified by self-signed certificates. This is useful for testing, or blog posts like this. In real life, I would strongly advise against using this, so that you don’t accidentally send your secret information to a malicious party.

Why redirect HTTP?

You should have already heard about HTTPS and what this brings to the table. Long story short, HTTPS is commonly called HTTP Secure, but in fact, it means HTTP over SSL - a much safer way of communicating using the protocol. If you have an SSL certificate (and nowadays it’s easy enough to have it for all your applications), you should recommend users to switch to this way of sending and receiving data. Even better than the recommendation is to enforce such behavior, which can be done using HTTP redirects. All you need to do is make the HTTP server return 301 Moved Permanently status that will point to the HTTPS version of the same path. Their web browsers will handle the rest.

HTTP redirection in Go

Implementing an HTTP server that redirects all HTTP requests to HTTPS does not require a lot of code, and you have a good chance of understanding the whole thing even if you have never worked with the language:

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	defMux := http.NewServeMux()
	defMux.HandleFunc("/", redirectTo("https://localhost"))    // redirect all HTTP requests to HTTPS

	secMux := http.NewServeMux()
	secMux.HandleFunc("/", simpleHandler("Hello from HTTPS!")) // respond HTTPS with a message

	go serveHTTP(defMux)
	serveHTTPS(secMux)
}

func simpleHandler(msg string) http.HandlerFunc {
	return func(rw http.ResponseWriter, req *http.Request) {
		fmt.Fprint(rw, msg)
	}
}

func redirectTo(dst string) http.HandlerFunc {
	return func(rw http.ResponseWriter, req *http.Request) {
		http.Redirect(rw, req, dst, http.StatusMovedPermanently)
	}
}

func serveHTTPS(mux *http.ServeMux) {
	err := http.ListenAndServeTLS(":443", "server-cert.pem", "server-key.pem", mux)
	if err != nil {
		log.Fatalf("Secure server failed: %v", err)
	}
}

func serveHTTP(mux *http.ServeMux) {
	err := http.ListenAndServe(":80", mux)
	if err != nil {
		log.Fatalf("Secure server failed: %v", err)
	}
}

cURL redirects

We start testing our application by calling the HTTP server, but we don’t see the HTTPS response quite yet:

$ curl http://localhost
<a href="https://localhost">Permanent Redirect</a>.

Ah, yes. We got 301 in response, but it would be awful if we needed to rewrite all requests every time this happens. Fortunately, we can ask cURL to follow /redirects/ and do this for us with —location flag:

$ curl --location http://localhost
curl: (60) SSL certificate problem: self signed certificate
More details here: https://curl.haxx.se/docs/sslcerts.html
...

Oh no, we forgot about the certificates. As I described above, one last piece we are missing is —insecure flag (for development only!) that will pass us through to the server:

$ curl --location --insecure http://localhost
Hello from HTTPS!

Sweet victory! Nothing can ruin our moods now, correct?

Evil redirect scenario

If it is so easy to make HTTP -> HTTPS redirection, can it happen the other way? Unfortunately, it can, and that means you initiated a connection with security in mind, but you end up with data that is vulnerable! It can be a mistake on the application side or some malicious action that does this.

Evil redirect in Go

Let’s turn our implementation around with a few changes:

package main

...

func main() {
	defMux.HandleFunc("/", simpleHandler("Hello from HTTP!")) // respond HTTP with a message
	...
	secMux.HandleFunc("/", redirectTo("http://localhost"))    // redirect all HTTPS requests to HTTP
	...
}

cURL to the rescue

We start with a request towards the HTTP server, which is unsurprisingly returning its response:

$ curl http://localhost
Hello from HTTP!

What if we try the secured endpoint?

$  curl --location --insecure https://localhost
Hello from HTTP!

Aha! We’ve been tricked and we are served by the insecure handler again… Is there anything we can do? Actually, it is! cURL has an interesting flag that can be utilized here: --proto. What this allows you is to define a protocol that can be used throughout the call, so if we want to be super secure, —proto “=https" helps us out a lot here:

$ curl --location --insecure --proto "=https" https://localhost
curl: (1) Protocol "http" not supported or disabled in libcurl

We can sleep safely now because we have not submitted our credit card information to the presumably secure server. Thank you cURL!

Summary

To be honest, I don’t know how useful this can be on a day-to-day basis but looked like an interesting thing to investigate and understand. I would probably describe this as a cool trick to show off, but is this too silly to be featured on a small programming blog? Apparently, it is not. Do you have actual use cases for this? Please share and let me know, I’m very curious!