Description
When requests and responses travel over HTTP without TLS protection, network observers can read their contents. An attacker on the network or in an intermediary position may capture login credentials, session cookies, and personal information through packet sniffing or a man-in-the-middle attack.
Potential impact
- Disclosure of credentials, session tokens, and personal information sent without encryption
- Account impersonation or session hijacking using stolen passwords or cookies
- Modification of requests and responses, including payment data or returned content
- Loss of trust and failure to meet applicable privacy or security requirements, such as PCI-DSS
Remediation
- If Go terminates external TLS connections directly, use
http.ListenAndServeTLS. If a trusted reverse proxy terminates TLS, verify both external HTTPS and the protected path between the proxy and application. - Use certificates issued by a trusted CA. In development or internal environments, manage trust for self-signed certificates appropriately.
- Set
Strict-Transport-Securityon HTTPS responses. It does not protect the first HTTP request before the browser knows the policy. - Use the HTTP port, such as 80, only to direct clients to HTTPS, such as port 443. Redirects cannot protect credentials or cookies already sent over HTTP; sensitive requests must start with HTTPS.
- Minimize sensitive data transmission and set
Secureon sensitive cookies.
Examples
Before
go
package main
import (
"log"
"net/http"
)
func loginHandler(w http.ResponseWriter, r *http.Request) {
// Simplified: real authentication must verify the password
username := r.FormValue("username")
password := r.FormValue("password")
// Plain HTTP can expose username and password in transit
log.Printf("login attempt: %s / %s", username, password)
w.Write([]byte("ok"))
}
func main() {
http.HandleFunc("/login", loginHandler)
// Before: serve HTTP without TLS
// Requests and responses travel in plaintext
if err := http.ListenAndServe(":80", nil); err != nil {
log.Fatal(err)
}
}
After
go
package main
import (
"log"
"net/http"
"net/url"
)
func loginHandler(w http.ResponseWriter, r *http.Request) {
// Real services require additional authentication and validation
username := r.FormValue("username")
log.Printf("login attempt for username=%q", username)
w.Write([]byte("ok"))
}
func main() {
http.HandleFunc("/login", loginHandler)
// Use port 80 only to redirect to HTTPS
go func() {
if err := http.ListenAndServe(":80", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
target := (&url.URL{
Scheme: "https",
Host: "www.example.com", // Fixed HTTPS host from deployment configuration
Path: r.URL.Path,
RawQuery: r.URL.RawQuery,
}).String()
http.Redirect(w, r, target, http.StatusMovedPermanently)
})); err != nil {
log.Fatal(err)
}
}()
// After: TLS with the actual certificate and key file paths
if err := http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil); err != nil {
log.Fatal(err)
}
}
Explanation:
- Before:
http.ListenAndServeserves/loginover plain HTTP. Login credentials can be intercepted in transit; this example also improperly logs the password. - After:
http.ListenAndServeTLSserves HTTPS. The redirect uses a fixed deployment host rather than the request'sHost.%qquotes the username so control characters do not break the log record, and the password is not logged.