Weak TLS On Internal Services
How Weak TLS On Internal Services works
Internal TLS often encrypts without authenticating: expired or self-signed certificates, obsolete protocol versions, and clients that verify nothing. The pattern repeats across an estate - an intranet application whose certificate lapsed and whose users learned to click through the warning, a management appliance still presenting the self-signed certificate it generated at first boot, a vCenter or storage controller offering TLS 1.0 with 3DES because the profile was never touched, a monitoring agent built with verification disabled so it would stop complaining, and an internal root CA whose certificate was pushed to the machine trust store years ago and now has no owner.
Encryption without identity checking stops passive sniffing and nothing else. An attacker on the same segment presents any certificate, the client accepts it, and the session decrypts on the way through: administrator credentials, session cookies, API tokens and application payloads, plus the ability to modify the traffic in flight - the invoice-tampering scenario in the OWASP category. It is easy to miss because the browser padlock, the scanner summary and the change record all say TLS, and the failure only shows in the negotiated version, the certificate chain and the client’s verify flag. Enterprise PKI abuse such as certificate template escalation belongs to the Active Directory section of this wiki; here the concern is the transport and what trusts it.
Weak TLS On Internal Services in practice
Inventory what every internal listener presents
Start with breadth. The point is a list of endpoints ranked by protocol floor and certificate state, not a deep dive on one host.
# TLS listeners across the lab server VLAN
nmap -Pn -n --open -p 443,636,993,1433,3389,5986,8443,9443 -oG - 10.30.0.0/24 \
| awk '/Ports:.*open/ {print $2}' > /tmp/isr05-tls-hosts.txt
# protocol versions and cipher suites per endpoint
nmap -Pn --script ssl-enum-ciphers -p 443,636,3389,5986,8443 \
-oN /tmp/isr05-ciphers.txt -iL /tmp/isr05-tls-hosts.txt
# single-host detail, enumeration only, no exploitation
testssl.sh --protocols --cipher-default --fs --headers https://intranet.lab.internal
Grade lines showing SSLv3, TLS 1.0 or TLS 1.1, or suites containing RC4, 3DES, EXPORT or NULL, are the shortlist.
Pull the certificate facts into a sortable list
Version and suite are half the finding. The other half is who signed the certificate, when it expired, and whether the name matches what clients use.
while read -r host; do
echo "== $host"
openssl s_client -connect "$host:443" -servername "$host" </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName 2>/dev/null
done < /tmp/isr05-tls-hosts.txt | tee /tmp/isr05-certs.txt
# which endpoints fail an ordinary validation against the system trust store
openssl s_client -connect intranet.lab.internal:443 -servername intranet.lab.internal \
-verify_return_error </dev/null 2>&1 | grep -E 'verify (error|return code)'
Self-signed issuers, notAfter dates in the past, missing subjectAltName and 1024-bit keys all mean the same thing operationally: no client on the network is checking.
Prove the client does not validate
A weak certificate matters only if something accepts it. Demonstrate acceptance with a lab listener and a hosts-file entry rather than by redirecting live traffic.
Setup: a lab host on the same VLAN runs a listener with a self-signed
certificate for intranet.lab.internal. Point each client at it with a
hosts-file entry. ARP spoofing would reach the same position but moves
production sessions through the test box, so the hosts-file variant is
used instead - it proves acceptance, which is the whole question.
Client Result Verdict
------------------------------------ ------------------- --------------------
Browser, user clicks the warning session established validation advisory
curl (default) certificate error validates
curl -k session established validation disabled
python requests, verify=False session established validation disabled
Invoke-WebRequest with certificate session established validation disabled
callback returning $true
internal backup / monitoring agent session established validates nothing
Any row that completes a session against a certificate the test host
minted is an interception path: credentials and payload are readable
and modifiable. Grep the estate for -k, verify=False and callbacks that
return true; credentials found alongside them belong to the Secrets In
Internal Repositories And Scripts page.
Audit the trust stores and the internal CA
A trusted root nobody tracks is a permanent interception key. This step converts a scan into an ownership question.
# roots trusted machine-wide beyond the vendor baseline
Get-ChildItem Cert:\LocalMachine\Root |
Where-Object { $_.Issuer -notmatch 'Microsoft|DigiCert|GlobalSign|Baltimore|Entrust' } |
Select-Object Subject, Issuer, NotAfter, Thumbprint | Format-Table -AutoSize
# machine certificates already expired or expiring inside 30 days
Get-ChildItem Cert:\LocalMachine\My |
Where-Object { $_.NotAfter -lt (Get-Date).AddDays(30) } |
Select-Object Subject, Issuer, NotAfter
# RDP: self-signed host certificate, and is NLA required?
Get-CimInstance -Namespace root\cimv2\TerminalServices -ClassName Win32_TSGeneralSetting |
Select-Object TerminalName, SecurityLayer, UserAuthenticationRequired, SSLCertificateSHA1Hash
SecurityLayer 0 or 1 with a self-signed hash is the RDP credential-interception case from the OWASP category, stated in inventory terms.
How to fix and prevent Weak TLS On Internal Services
- Set a protocol floor and enforce it centrally
- TLS 1.2 minimum, AEAD suites only, RC4, 3DES, EXPORT and NULL disabled, applied through SCHANNEL policy on Windows and system crypto policy on Linux rather than per-application.
- Stage it: inventory first, then enforce, because the clients that break are exactly the ones with no other security controls.
- Issue internal certificates automatically from a tracked CA
- ACME or autoenrolment, short lifetimes, and a subjectAltName matching the name clients actually use, so validation succeeds honestly instead of being switched off.
- Turn validation on in every client
- Remove -k, verify=False and always-true certificate callbacks; distribute the internal root through the OS trust store so no application needs its own exception.
- Treat a client that cannot validate as an exception with an owner, not as a default.
- Inventory and prune the trust stores
- Anything in the machine root store beyond the managed baseline is an interception key; record who owns each internal CA and remove the ones that answer nobody.
- Re-scan on a schedule and alert on drift
- Expiry and downgrade return quietly, so run the version, suite and certificate scan monthly and report every endpoint below the floor with its owner.
Last updated