Insecure SSL/TLS configuration (certificate validation disabled)
ID |
dart.network.insecure_ssl |
Severity |
critical |
Remediation Complexity |
trivial |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Network |
Language |
Dart |
Description
TLS certificate validation is unconditionally bypassed (badCertificateCallback / onBadCertificate / validateCertificate returns true always, a WebView server-trust handler proceeds unconditionally, or mixed-content / insecure-content flags are flipped on). Any man-in-the-middle with any certificate is trusted.
Rationale
Disabling TLS validation is the most common Flutter security bug. It typically appears during development ("my proxy keeps rejecting the self-signed cert!") and survives into production because the call site is unconditional. The consequence is total loss of transport security:
-
A captive-portal Wi-Fi attacker presents any cert and the app accepts it; all subsequent traffic decrypts and the attacker forges responses.
-
An installed malicious root CA on the device (typical in BYOD / spyware scenarios) gets used silently.
-
DNS spoofing combined with any cert succeeds.
Flutter does not use the OS proxy or trust store on most targets, so
pinning has to happen at the Dart layer. A trust-all callback defeats
this entirely and leaves no fallback. The same problem applies to
WebView: onReceivedServerTrustAuthRequest returning
ServerTrustAuthResponseAction.PROCEED unconditionally is the WebView
equivalent of badCertificateCallback ⇒ true.
The following code illustrates a vulnerable pattern detected by this rule:
class TrustAll {
HttpClient buildHttp() {
final c = HttpClient();
// VULNERABLE: Insecure SSL/TLS configuration (certificate validation disabled)
c.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
return c;
}
}
Remediation
Never blanket-trust. Either:
-
Validate normally: let
badCertificateCallbackdefault (returns false) and don’t override it. The OS / library validates the chain. -
Pin the certificate: load the expected leaf or root certificate bytes into a
SecurityContext(withTrustedRoots: false)viasetTrustedCertificatesBytes. For Dio, configure the same context viaIOHttpClientAdapter. For WebView, inspect the server cert in the trust callback and returnCANCELon any mismatch.
// Vulnerable
HttpClient()..badCertificateCallback = (_, __, ___) => true;
IOHttpClientAdapter(validateCertificate: (cert, host, port) => true);
// Fixed: pin a known root / leaf certificate
final ctx = SecurityContext(withTrustedRoots: false)
..setTrustedCertificatesBytes(pinnedRootCaBytes);
final client = HttpClient(context: ctx);
References
-
OWASP Top 10 2025 - A04 : Cryptographic Failures.
-
OWASP Top 10 2025 - A07 : Authentication Failures.
-
CWE-757 — Selection of Less-Secure Algorithm During Negotiation
-
HttpClient.badCertificateCallback — Dart API docs — Reference for the callback; documents that returning true is dangerous
-
Talsec — OWASP Mobile Top 10 for Flutter M5 — TLS-validation bypass and MitM scenarios for Flutter