/** * @name OpenSSLMissingCNCheck * @kind problem * @problem.severity recommendation * @id cpp/openssl-missing-verify-callback * @tags security * external/cwe/cwe-273 * * The default `verify_callback` https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_verify.html * does not compare CN/hostnames so a TLS client can be MITMed. * * There are multiple stategies for verifying server certificates so this query assists with code * review but does not accurately find vulnerabilities, false positives are likely. * * False negatives include using verify_callback to ignore verification, such as always returning * a 1 without checking `preverify_ok`. */ import cpp import semmle.code.cpp.dataflow.TaintTracking class SslSetVerifyFunctionCall extends FunctionCall { SslSetVerifyFunctionCall() { this.getTarget().hasName("SSL_set_verify") } } class SslCtxSetVerifyFunctionCall extends FunctionCall { SslCtxSetVerifyFunctionCall() { this.getTarget().hasName("SSL_CTX_set_verify") } } class SslLikeSetVerifyFunctionCall extends FunctionCall { SslLikeSetVerifyFunctionCall() { this instanceof SslCtxSetVerifyFunctionCall or this instanceof SslSetVerifyFunctionCall } } class SslSet1HostFunctionCall extends FunctionCall { SslSet1HostFunctionCall() { this.getTarget().hasName("SSL_set1_host") } } class SslCtxSetCertVerifyCallbackFunctionCall extends FunctionCall { SslCtxSetCertVerifyCallbackFunctionCall() { this.getTarget().hasName("SSL_CTX_set_cert_verify_callback") } } class SslLikeCheckHostnameFunctionCall extends FunctionCall { SslLikeCheckHostnameFunctionCall() { this instanceof SslSet1HostFunctionCall or this instanceof SslCtxSetCertVerifyCallbackFunctionCall } } class SslPointerVariable extends Variable { SslPointerVariable() { this.hasDefinition() and this.getUnderlyingType().hasName("SSL *") } } class SslCtxPointerVariable extends Variable { SslCtxPointerVariable() { this.hasDefinition() and this.getUnderlyingType().hasName("SSL_CTX *") } } class SslLikePointerVariable extends Variable { SslLikePointerVariable() { this instanceof SslPointerVariable or this instanceof SslCtxPointerVariable } } class SslCtxNewClientFunctionCall extends FunctionCall { SslCtxNewClientFunctionCall() { this.getTarget().hasName("SSL_CTX_new") and exists(FunctionCall fc | fc.getTarget().getQualifiedName().matches("%_client_method") and TaintTracking::localTaint(DataFlow::exprNode(fc), DataFlow::exprNode(this.getArgument(0))) ) } } predicate sslPointerTaintStep(DataFlow::Node pred, DataFlow::Node succ) { // Propagate the taint from SSL <-> SSL_CTX. exists(SslLikePointerVariable ctx, FunctionCall fc | pred.asExpr() = ctx.getAnAccess() and pred.asExpr() = fc.getArgument(0) and succ.asExpr() = fc and // Simple to go back and forth. (fc.getTarget().hasName("SSL_new") or fc.getTarget().hasName("SSL_get_SSL_CTX")) ) or // Propagate the taint from argument to parameter use. exists(FunctionCall fc, Expr arg, Parameter p, int i | p = fc.getTarget().getAParameter() and i = p.getIndex() and arg = fc.getArgument(i) and arg = pred.asExpr() and succ.asExpr() = p.getAnAccess() ) } class SslCtxSetVerifyConfiguration extends TaintTracking::Configuration { SslCtxSetVerifyConfiguration() { this = "SslCtxSetVerifyConfiguration" } override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof SslCtxNewClientFunctionCall } override predicate isSink(DataFlow::Node node) { // False positives can be reduced by checking that argument(1) is not VERIFY_NONE. // This may lead to broader false negatives where code mistakenly sets VERIFY_NONE. exists(SslLikeSetVerifyFunctionCall fc | node.asExpr() = fc.getArgument(0)) } override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { sslPointerTaintStep(pred, succ) } } class SslCtxCheckHostnameConfiguration extends TaintTracking::Configuration { SslCtxCheckHostnameConfiguration() { this = "SslCtxCheckHostnameConfiguration" } override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof SslCtxNewClientFunctionCall } override predicate isSink(DataFlow::Node node) { exists(SslLikeCheckHostnameFunctionCall fc | node.asExpr() = fc.getArgument(0)) } override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { sslPointerTaintStep(pred, succ) } } predicate flowsToSetVerify(FunctionCall ctx, FunctionCall fc) { exists(SslCtxSetVerifyConfiguration conf | conf.hasFlow(DataFlow::exprNode(ctx), DataFlow::exprNode(fc.getArgument(0))) ) } predicate flowsToCheckHostname(FunctionCall ctx) { exists(SslCtxCheckHostnameConfiguration conf, FunctionCall fc | conf.hasFlow(DataFlow::exprNode(ctx), DataFlow::exprNode(fc.getArgument(0))) ) } from FunctionCall ctx, FunctionCall fc where flowsToSetVerify(ctx, fc) and not flowsToCheckHostname(ctx) and // Empty verify callback (this will not check CN). // False positives include intentional skipped verification and any // additional custom checking via accessing the peer certificate post-handshake. fc.getArgument(2).getValue() = "0" select ctx, "may be a client context without hostname verification because SSL_set1_host " + "and other indicators of leaf cert hostname checking are missing", fc