Unprotected Transport of Credentials

Description

Unprotected Transport of Credentials occurs when software transmits authentication credentials (passwords, tokens, API keys, session identifiers) over an unencrypted channel. This commonly happens when applications use HTTP instead of HTTPS, send credentials via unencrypted protocols (FTP, Telnet, SMTP without STARTTLS), or embed credentials in URLs. Attackers who can observe network traffic can capture these credentials and impersonate legitimate users.

Risk

Credentials transmitted in plaintext are trivially intercepted through network sniffing, man-in-the-middle attacks, or compromised network infrastructure. This is especially dangerous on public WiFi networks, shared hosting environments, or networks with compromised devices. Once credentials are captured, attackers gain full access to user accounts. Session tokens transmitted unencrypted enable session hijacking. This vulnerability enables account takeover at scale when combined with traffic interception capabilities.

Solution

Always use TLS/HTTPS for transmitting credentials. Enforce HTTPS through HSTS headers and redirect all HTTP to HTTPS. Never include credentials in URLs (they appear in logs and browser history). Use secure, httpOnly cookies for session tokens. Implement certificate pinning for mobile applications. Use encrypted protocols for all services (SFTP, SSH, SMTPS). Configure servers to reject plaintext authentication attempts. Monitor for mixed content that could downgrade security.

Common Consequences

ImpactDetails
ConfidentialityScope: Credential Theft

Attackers capture plaintext credentials enabling account access.
AuthenticationScope: Session Hijacking

Intercepted session tokens allow immediate account takeover.
IntegrityScope: Account Compromise

Stolen credentials enable unauthorized actions as the victim.

Example Code + Solution Code

Vulnerable Code

# VULNERABLE: HTTP for authentication
import requests

def login_vulnerable(username, password):
    # Credentials sent over HTTP!
    response = requests.post(
        'http://api.example.com/login',  # Not HTTPS!
        data={'username': username, 'password': password}
    )
    return response.json()

# VULNERABLE: Credentials in URL
def api_call_vulnerable(api_key):
    # API key exposed in URL (logged, cached, in browser history)!
    response = requests.get(
        f'http://api.example.com/data?api_key={api_key}'
    )
    return response.json()

# VULNERABLE: Basic auth over HTTP
def fetch_data_vulnerable(username, password):
    response = requests.get(
        'http://api.example.com/data',
        auth=(username, password)  # Basic auth over HTTP!
    )
    return response.json()

# VULNERABLE: FTP for file transfer
from ftplib import FTP

def upload_vulnerable(host, username, password, file):
    # Unencrypted FTP!
    ftp = FTP(host)
    ftp.login(username, password)  # Plaintext credentials!
    ftp.storbinary(f'STOR {file}', open(file, 'rb'))
    ftp.quit()

# VULNERABLE: Unencrypted email with credentials
import smtplib

def send_email_vulnerable(smtp_host, username, password, message):
    # Plaintext SMTP!
    server = smtplib.SMTP(smtp_host, 25)
    server.login(username, password)  # Credentials in plaintext!
    server.send_message(message)
    server.quit()
// VULNERABLE: Java HTTP client
public class VulnerableClient {

    public String login(String username, String password) throws Exception {
        // HTTP connection - credentials exposed!
        URL url = new URL("http://api.example.com/login");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        conn.setRequestMethod("POST");
        conn.setDoOutput(true);

        String data = "username=" + URLEncoder.encode(username, "UTF-8") +
                     "&password=" + URLEncoder.encode(password, "UTF-8");

        try (OutputStream os = conn.getOutputStream()) {
            os.write(data.getBytes(StandardCharsets.UTF_8));
        }

        return readResponse(conn);
    }

    // VULNERABLE: Credentials in URL
    public String fetchData(String apiKey) throws Exception {
        // API key in URL - visible in logs!
        URL url = new URL("http://api.example.com/data?api_key=" + apiKey);
        return readUrl(url);
    }
}

// VULNERABLE: Ignoring certificate validation
public class InsecureSSLClient {

    public void connect() throws Exception {
        // Disable SSL verification - MitM possible!
        TrustManager[] trustAllCerts = new TrustManager[] {
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() { return null; }
                public void checkClientTrusted(X509Certificate[] certs, String authType) {}
                public void checkServerTrusted(X509Certificate[] certs, String authType) {}
            }
        };

        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    }
}

// VULNERABLE: Spring endpoint without HTTPS
@RestController
public class VulnerableController {

    // No transport security enforced
    @PostMapping("/login")
    public ResponseEntity<String> login(
            @RequestParam String username,
            @RequestParam String password) {
        // Accepts credentials over HTTP!
        return ResponseEntity.ok(authenticate(username, password));
    }
}
// VULNERABLE: HTTP API calls
async function loginVulnerable(username, password) {
    // HTTP - credentials exposed!
    const response = await fetch('http://api.example.com/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password })
    });
    return response.json();
}

// VULNERABLE: Credentials in URL
function fetchDataVulnerable(apiKey) {
    // API key in URL!
    return fetch(`http://api.example.com/data?api_key=${apiKey}`)
        .then(res => res.json());
}

// VULNERABLE: WebSocket without TLS
function connectWebSocketVulnerable(token) {
    // Unencrypted WebSocket!
    const ws = new WebSocket(`ws://api.example.com/ws?token=${token}`);
    return ws;
}

// VULNERABLE: Node.js with disabled SSL verification
const https = require('https');

const agent = new https.Agent({
    rejectUnauthorized: false  // Disables certificate validation!
});

async function fetchInsecure(url) {
    return fetch(url, { agent });
}

// VULNERABLE: Express without HTTPS redirect
const express = require('express');
const app = express();

app.post('/login', (req, res) => {
    // No HTTPS enforcement!
    const { username, password } = req.body;
    // ...
});

// VULNERABLE: Setting cookies without Secure flag
app.post('/login', (req, res) => {
    const token = generateToken(req.body.username);

    // Cookie without Secure flag - sent over HTTP!
    res.cookie('session', token, { httpOnly: true });

    res.json({ success: true });
});

Fixed Code

# SAFE: HTTPS for all credential transmission
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.ssl_ import create_urllib3_context
import ssl

def login_safe(username, password):
    """Always use HTTPS for credentials."""
    response = requests.post(
        'https://api.example.com/login',  # HTTPS!
        data={'username': username, 'password': password},
        verify=True  # Verify SSL certificate
    )
    return response.json()

# SAFE: API key in header, not URL
def api_call_safe(api_key):
    """Credentials in header, over HTTPS."""
    response = requests.get(
        'https://api.example.com/data',
        headers={'Authorization': f'Bearer {api_key}'}  # In header!
    )
    return response.json()

# SAFE: Basic auth over HTTPS
def fetch_data_safe(username, password):
    response = requests.get(
        'https://api.example.com/data',  # HTTPS!
        auth=(username, password)
    )
    return response.json()

# SAFE: SFTP instead of FTP
import paramiko

def upload_safe(host, username, key_path, file):
    """Use SFTP with key authentication."""
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.RejectPolicy())

    # Load known hosts
    ssh.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))

    # Use key authentication
    private_key = paramiko.RSAKey.from_private_key_file(key_path)
    ssh.connect(host, username=username, pkey=private_key)

    sftp = ssh.open_sftp()
    sftp.put(file, f'/uploads/{os.path.basename(file)}')
    sftp.close()
    ssh.close()

# SAFE: TLS for email
import smtplib
import ssl

def send_email_safe(smtp_host, username, password, message):
    """Use TLS for SMTP."""
    context = ssl.create_default_context()

    # Option 1: SMTP with STARTTLS
    with smtplib.SMTP(smtp_host, 587) as server:
        server.starttls(context=context)
        server.login(username, password)
        server.send_message(message)

    # Option 2: SMTPS (implicit TLS)
    # with smtplib.SMTP_SSL(smtp_host, 465, context=context) as server:
    #     server.login(username, password)
    #     server.send_message(message)

# SAFE: Enforce minimum TLS version
import ssl
import urllib3

class SecureHTTPAdapter(HTTPAdapter):
    def init_poolmanager(self, *args, **kwargs):
        context = create_urllib3_context(ssl_version=ssl.TLSVersion.TLSv1_2)
        kwargs['ssl_context'] = context
        return super().init_poolmanager(*args, **kwargs)

session = requests.Session()
session.mount('https://', SecureHTTPAdapter())
// SAFE: Java HTTPS client
public class SecureClient {

    public String login(String username, String password) throws Exception {
        // HTTPS connection
        URL url = new URL("https://api.example.com/login");
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

        // Optionally enforce TLS version
        SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
        sslContext.init(null, null, new SecureRandom());
        conn.setSSLSocketFactory(sslContext.getSocketFactory());

        conn.setRequestMethod("POST");
        conn.setDoOutput(true);

        String data = "username=" + URLEncoder.encode(username, "UTF-8") +
                     "&password=" + URLEncoder.encode(password, "UTF-8");

        try (OutputStream os = conn.getOutputStream()) {
            os.write(data.getBytes(StandardCharsets.UTF_8));
        }

        return readResponse(conn);
    }

    // SAFE: API key in header
    public String fetchData(String apiKey) throws Exception {
        URL url = new URL("https://api.example.com/data");
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

        // API key in Authorization header
        conn.setRequestProperty("Authorization", "Bearer " + apiKey);

        return readResponse(conn);
    }
}

// SAFE: Certificate pinning
public class CertificatePinningClient {

    public void connect(String host) throws Exception {
        // Expected certificate pin
        String expectedPin = "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";

        URL url = new URL("https://" + host + "/api");
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

        conn.connect();

        // Verify certificate
        Certificate[] certs = conn.getServerCertificates();
        X509Certificate cert = (X509Certificate) certs[0];

        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] publicKeyHash = digest.digest(cert.getPublicKey().getEncoded());
        String actualPin = "sha256/" + Base64.getEncoder().encodeToString(publicKeyHash);

        if (!actualPin.equals(expectedPin)) {
            throw new SSLException("Certificate pin mismatch!");
        }
    }
}

// SAFE: Spring Security with HTTPS enforcement
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // Require HTTPS for all requests
            .requiresChannel(channel -> channel
                .anyRequest().requiresSecure()
            )
            // HSTS header
            .headers(headers -> headers
                .httpStrictTransportSecurity(hsts -> hsts
                    .includeSubDomains(true)
                    .maxAgeInSeconds(31536000)
                )
            );

        return http.build();
    }
}

// SAFE: Secure cookie configuration
@Configuration
public class CookieConfig {

    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setUseSecureCookie(true);   // Only over HTTPS
        serializer.setUseHttpOnlyCookie(true); // Not accessible via JS
        serializer.setSameSite("Strict");      // CSRF protection
        return serializer;
    }
}
// SAFE: HTTPS for all requests
async function loginSafe(username, password) {
    const response = await fetch('https://api.example.com/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password })
    });
    return response.json();
}

// SAFE: API key in header
function fetchDataSafe(apiKey) {
    return fetch('https://api.example.com/data', {
        headers: {
            'Authorization': `Bearer ${apiKey}`
        }
    }).then(res => res.json());
}

// SAFE: Secure WebSocket
function connectWebSocketSafe(token) {
    // WSS = WebSocket Secure
    const ws = new WebSocket('wss://api.example.com/ws');

    ws.onopen = () => {
        // Send token after connection established
        ws.send(JSON.stringify({ type: 'auth', token }));
    };

    return ws;
}

// SAFE: Express with HTTPS enforcement
const express = require('express');
const helmet = require('helmet');

const app = express();

// Force HTTPS in production
app.use((req, res, next) => {
    if (process.env.NODE_ENV === 'production' && !req.secure) {
        return res.redirect(301, `https://${req.headers.host}${req.url}`);
    }
    next();
});

// HSTS header
app.use(helmet.hsts({
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
}));

// SAFE: Secure cookies
app.post('/login', (req, res) => {
    const token = generateToken(req.body.username);

    res.cookie('session', token, {
        httpOnly: true,   // Not accessible via JavaScript
        secure: true,     // Only sent over HTTPS
        sameSite: 'strict', // CSRF protection
        maxAge: 3600000   // 1 hour
    });

    res.json({ success: true });
});

// SAFE: Node.js HTTPS server
const https = require('https');
const fs = require('fs');

const options = {
    key: fs.readFileSync('server.key'),
    cert: fs.readFileSync('server.cert'),
    minVersion: 'TLSv1.2'
};

https.createServer(options, app).listen(443);

// Redirect HTTP to HTTPS
const http = require('http');
http.createServer((req, res) => {
    res.writeHead(301, { Location: `https://${req.headers.host}${req.url}` });
    res.end();
}).listen(80);

// SAFE: Certificate pinning in Node.js
const https = require('https');
const crypto = require('crypto');

const EXPECTED_PIN = 'sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';

function fetchWithPinning(url) {
    return new Promise((resolve, reject) => {
        const req = https.get(url, (res) => {
            const cert = res.socket.getPeerCertificate();
            const publicKey = cert.pubkey;

            const hash = crypto
                .createHash('sha256')
                .update(publicKey)
                .digest('base64');

            if (`sha256/${hash}` !== EXPECTED_PIN) {
                reject(new Error('Certificate pin mismatch'));
                return;
            }

            // Process response...
            resolve(res);
        });

        req.on('error', reject);
    });
}

Exploited in the Wild

Firesheep (2010)

Tool demonstrated mass session hijacking on unencrypted WiFi networks, forcing widespread HTTPS adoption.

SSL Stripping Attacks

Attackers downgrade HTTPS to HTTP to intercept credentials on public networks.

Government Surveillance

Mass surveillance programs exploited unencrypted credential transmission for intelligence gathering.


Tools to test/exploit


CVE Examples

  • CVE-2020-8945 — Credential exposure over unencrypted connection.

  • CVE-2019-3462 — APT package manager HTTP vulnerability.

  • Numerous application-specific credential exposure issues.


References

  1. MITRE. "CWE-523: Unprotected Transport of Credentials." https://cwe.mitre.org/data/definitions/523.html

  2. OWASP. "Transport Layer Protection Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html