Overview
Unencrypted data transmission is a critical security vulnerability. When your application communicates over HTTP instead of HTTPS, all data - including passwords, personal information, and session tokens - is sent in plain text and can be easily intercepted.
Current Vulnerability:
The Normo Unsecure PWA runs on HTTP (port 5000) without any SSL/TLS encryption. All data transmitted between the browser and server is completely unprotected and readable by anyone monitoring network traffic.
What is SSL/TLS?
SSL (Secure Sockets Layer) and TLS (Transport Layer Security) are cryptographic protocols that provide secure communication over a network. TLS is the modern, more secure successor to SSL.
While modern certificates use the TLS protocol, “SSL” remains a popular term among security professionals. You’ll often see “SSL/TLS” or “SSL certificate” used interchangeably.
Why SSL/TLS Matters
Encryption
Encrypts all data transmitted between client and server, making it unreadable to attackers
Authentication
Verifies the server’s identity using certificates from trusted Certificate Authorities (CAs)
Data Integrity
Ensures data hasn’t been tampered with during transmission
Trust & SEO
Browsers show a padlock icon, and search engines rank HTTPS sites higher
The Current Vulnerability
The Flask app currently runs without HTTPS:
if __name__ == "__main__" :
app.config[ "TEMPLATES_AUTO_RELOAD" ] = True
app.config[ "SEND_FILE_MAX_AGE_DEFAULT" ] = 0
app.run( debug = True , host = "0.0.0.0" , port = 5000 )
# Running on http://0.0.0.0:5000 - NO ENCRYPTION!
What attackers can see:
Usernames and passwords during login
Personal information (dates of birth)
User feedback and comments
Session cookies
All database queries and responses
Penetration Testing for This Vulnerability
Manual Testing
Wireshark Traffic Analysis
OpenSSL Certificate Check
# Test 1: Try accessing via HTTPS (should fail or show certificate error)
curl https://127.0.0.1:5000
# Test 2: Confirm HTTP works (insecure)
curl http://127.0.0.1:5000
# Test 3: Check if HTTP redirects to HTTPS (it shouldn't in vulnerable app)
curl -I http://127.0.0.1:5000
Testing Tools:
Browser Developer Tools : Check if the connection shows a padlock icon
Wireshark : Capture and analyze network packets to see unencrypted data
curl : Test HTTP vs HTTPS responses and redirects
OpenSSL : Verify certificate configuration and validity
Implementing SSL/TLS in Flask
Option 1: Self-Signed Certificate (Development)
For development and testing, you can create a self-signed certificate.
Self-signed certificates are only for development . Browsers will show security warnings, and users must manually accept them. Never use self-signed certificates in production!
Generate Self-Signed Certificate
Use OpenSSL to create a certificate and private key: openssl req -x509 -newkey rsa:4096 -nodes \
-out cert.pem \
-keyout key.pem \
-days 365 \
-subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"
This creates:
cert.pem: The SSL certificate
key.pem: The private key
Update Flask Configuration
Modify your Flask app to use the certificate: if __name__ == "__main__" :
app.config[ "TEMPLATES_AUTO_RELOAD" ] = True
app.config[ "SEND_FILE_MAX_AGE_DEFAULT" ] = 0
# Configure SSL/TLS
context = ( 'cert.pem' , 'key.pem' )
app.run(
debug = True ,
host = "0.0.0.0" ,
port = 5000 ,
ssl_context = context
)
Test HTTPS Connection
Access your app via HTTPS: # Browser: https://127.0.0.1:5000
# CLI:
curl -k https://127.0.0.1:5000
The -k flag tells curl to accept self-signed certificates.
Option 2: Using Python ssl Module
For more control over SSL configuration:
import ssl
if __name__ == "__main__" :
app.config[ "TEMPLATES_AUTO_RELOAD" ] = True
app.config[ "SEND_FILE_MAX_AGE_DEFAULT" ] = 0
# Create SSL context with more options
context = ssl.SSLContext(ssl. PROTOCOL_TLSv1_2 )
context.load_cert_chain( 'cert.pem' , 'key.pem' )
app.run(
debug = True ,
host = "0.0.0.0" ,
port = 5000 ,
ssl_context = context
)
Option 3: Production Certificate (Let’s Encrypt)
For production environments, use a certificate from a trusted Certificate Authority.
Install Certbot
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install certbot
# macOS
brew install certbot
Obtain Certificate
# For a domain you own
sudo certbot certonly --standalone -d yourdomain.com
# Certificates will be saved to:
# /etc/letsencrypt/live/yourdomain.com/fullchain.pem
# /etc/letsencrypt/live/yourdomain.com/privkey.pem
Configure Flask
if __name__ == "__main__" :
context = (
'/etc/letsencrypt/live/yourdomain.com/fullchain.pem' ,
'/etc/letsencrypt/live/yourdomain.com/privkey.pem'
)
app.run(
host = "0.0.0.0" ,
port = 443 , # Standard HTTPS port
ssl_context = context
)
Auto-Renewal
Let’s Encrypt certificates expire after 90 days. Set up auto-renewal: # Add to crontab
0 0 * * * certbot renew --quiet
Let’s Encrypt is a free, automated, and open Certificate Authority trusted by all major browsers.
Enforcing HTTPS
Once HTTPS is configured, force all HTTP traffic to redirect to HTTPS:
Using Flask-Talisman
pip install flask-talisman
from flask import Flask
from flask_talisman import Talisman
app = Flask( __name__ )
# Force HTTPS and set security headers
Talisman(app, force_https = True )
# Your routes here...
if __name__ == "__main__" :
context = ( 'cert.pem' , 'key.pem' )
app.run(
host = "0.0.0.0" ,
port = 5000 ,
ssl_context = context
)
Manual HTTP to HTTPS Redirect
from flask import Flask, request, redirect
app = Flask( __name__ )
@app.before_request
def before_request ():
# Redirect HTTP to HTTPS
if not request.is_secure:
url = request.url.replace( 'http://' , 'https://' , 1 )
return redirect(url, code = 301 )
# Your routes here...
Production Deployment Best Practices
Never run Flask’s built-in server in production! Use a production-grade server instead.
Using Gunicorn with Nginx
The recommended production setup:
Run Flask with Gunicorn
gunicorn -w 4 -b 127.0.0.1:8000 main:app
Configure Nginx as Reverse Proxy
/etc/nginx/sites-available/yourapp
server {
listen 80 ;
server_name yourdomain.com;
return 301 https://$ server_name $ request_uri ;
}
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $ host ;
proxy_set_header X-Real-IP $ remote_addr ;
proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for ;
proxy_set_header X-Forwarded-Proto $ scheme ;
}
}
Enable and Restart Nginx
sudo ln -s /etc/nginx/sites-available/yourapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Using Cloudflare
Cloudflare offers:
Free SSL/TLS certificates
Automatic HTTPS enforcement
DDoS protection
CDN for faster content delivery
Easy DNS management
Steps to use Cloudflare:
Sign up at cloudflare.com
Add your domain
Update your domain’s nameservers
Enable “Always Use HTTPS” in SSL/TLS settings
Set SSL/TLS encryption mode to “Full (strict)“
Complete Secure Example
Here’s a complete example with SSL/TLS properly configured:
from flask import Flask, render_template, request, redirect
from flask_talisman import Talisman
import ssl
import logging
# Configure logging
logging.basicConfig(
filename = 'app.log' ,
level = logging. INFO ,
format = ' %(asctime)s %(levelname)s : %(message)s '
)
app = Flask( __name__ )
# Force HTTPS and set security headers
Talisman(app,
force_https = True ,
strict_transport_security = True ,
strict_transport_security_max_age = 31536000
)
@app.route ( "/" )
def home ():
# Log the connection type
if request.is_secure:
app.logger.info( "Secure HTTPS connection" )
else :
app.logger.warning( "Insecure HTTP connection attempt" )
return render_template( "/index.html" )
if __name__ == "__main__" :
# Development: Self-signed certificate
context = ssl.SSLContext(ssl. PROTOCOL_TLSv1_2 )
context.load_cert_chain( 'cert.pem' , 'key.pem' )
app.run(
debug = False , # Disable debug in production
host = "0.0.0.0" ,
port = 5000 ,
ssl_context = context
)
Testing Your SSL/TLS Implementation
Certificate Verification
Security Headers Check
SSL Labs Test
# Check certificate details
openssl s_client -connect yourdomain.com:443 -showcerts
# Verify certificate chain
openssl s_client -connect yourdomain.com:443 -CApath /etc/ssl/certs
Troubleshooting Common Issues
Certificate Not Trusted Error
Problem : Browser shows “Your connection is not private”
Solution :
Development: This is expected with self-signed certificates
Production: Ensure you’re using a certificate from a trusted CA
Check that the certificate chain is complete
Mixed Content Warnings
Problem : Page loads over HTTPS but contains HTTP resources
Solution :
<!-- Bad: HTTP resource -->
< script src = "http://example.com/script.js" ></ script >
<!-- Good: HTTPS resource -->
< script src = "https://example.com/script.js" ></ script >
<!-- Better: Protocol-relative URL -->
< script src = "//example.com/script.js" ></ script >
Port 443 Permission Denied
Problem : Can’t bind to port 443 without root privileges
Solution :
# Option 1: Use authbind
sudo apt-get install authbind
sudo touch /etc/authbind/byport/443
sudo chmod 500 /etc/authbind/byport/443
sudo chown yourusername /etc/authbind/byport/443
authbind --deep gunicorn -b 0.0.0.0:443 main:app
# Option 2: Use a reverse proxy (Nginx) - Recommended
# Run Flask on port 8000, let Nginx handle 443
Best Practices Summary
Always use HTTPS in production
Never deploy a web application without SSL/TLS encryption
Use trusted certificates
Get free certificates from Let’s Encrypt or use a CDN like Cloudflare
Enforce HTTPS
Redirect all HTTP traffic to HTTPS automatically
Use modern TLS versions
Support only TLS 1.2 and TLS 1.3, disable older protocols
Implement HSTS
Use HTTP Strict Transport Security headers to force HTTPS
Test regularly
Use SSL Labs and other tools to verify your configuration
Keep certificates updated
Set up automatic renewal for Let’s Encrypt certificates
Additional Resources