CVE-2026-3012
Description
A flaw was found in Samba’s certificate auto-enrollment Group Policy handling. When certificate auto-enrollment is enabled, Samba may retrieve a CA certificate over an unencrypted HTTP connection and install it into the local trust store without proper verification. An attacker with the ability to intercept or redirect network traffic could exploit this behavior to supply a malicious certificate authority certificate, potentially allowing interception or spoofing of trusted communications.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Samba's certificate auto-enrollment Group Policy handler retrieves CA certificates over unencrypted HTTP without validation, allowing attackers to inject malicious CA certificates.
Vulnerability
A flaw exists in Samba's Group Policy certificate auto-enrollment feature. When enabled, Samba retrieves a CA certificate over an unencrypted HTTP connection and installs it into the local trust store without proper verification. This affects Samba implementations that enable certificate auto-enrollment via Group Policy [1][2].
Exploitation
An attacker with the ability to intercept or redirect network traffic (e.g., via a man-in-the-middle attack) can exploit this behavior. The attacker intercepts the HTTP request for the CA certificate and responds with a malicious CA certificate. Samba then installs this certificate into the trust store without validation [1][2].
Impact
Successful exploitation allows the attacker to supply a malicious certificate authority certificate that becomes trusted by the system. This enables interception or spoofing of trusted communications, potentially compromising the confidentiality and integrity of encrypted sessions [1][2].
Mitigation
As of the publication date (2026-05-27), no official fix has been released. Administrators should disable certificate auto-enrollment if not required, or ensure that network traffic for certificate retrieval is secured (e.g., by using HTTPS or enforcing certificate validation). Monitor Samba updates for a patched version [1][2].
AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2(expand)+ 1 more
- (no CPE)
- (no CPE)
Patches
4935e5a9ecd38CVE-2026-3012: gpo tests should use real certificates
2 files changed · +4 −5
python/samba/tests/gpo.py+4 −4 modified@@ -7062,15 +7062,15 @@ def test_gp_cert_auto_enroll_ext(self): ldb.add({'dn': certa_dn, 'objectClass': 'certificationAuthority', 'authorityRevocationList': ['XXX'], - 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I', + 'cACertificate': dummy_certificate(), 'certificateRevocationList': ['XXX'], }) # Write the dummy pKIEnrollmentService enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn) self.addCleanup(ldb.delete, enroll_dn) ldb.add({'dn': enroll_dn, 'objectClass': 'pKIEnrollmentService', - 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I', + 'cACertificate': dummy_certificate(), 'certificateTemplates': ['Machine'], 'dNSHostName': hostname, }) @@ -7673,15 +7673,15 @@ def test_advanced_gp_cert_auto_enroll_ext(self): ldb.add({'dn': certa_dn, 'objectClass': 'certificationAuthority', 'authorityRevocationList': ['XXX'], - 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I', + 'cACertificate': dummy_certificate(), 'certificateRevocationList': ['XXX'], }) # Write the dummy pKIEnrollmentService enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn) self.addCleanup(ldb.delete, enroll_dn) ldb.add({'dn': enroll_dn, 'objectClass': 'pKIEnrollmentService', - 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I', + 'cACertificate': dummy_certificate(), 'certificateTemplates': ['Machine'], 'dNSHostName': hostname, })
selftest/knownfail.d/gpo-auto-enrol+0 −1 modified@@ -1,2 +1 @@ ^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_advanced_gp_cert_auto_enroll_ext\(ad_dc:local\) -^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_gp_cert_auto_enroll_ext\(ad_dc:local\)
c03e7dcf5113CVE-2026-3012: gpo tests: fix test cleanup
1 file changed · +25 −17
python/samba/tests/gpo.py+25 −17 modified@@ -6951,6 +6951,7 @@ def test_gp_cert_auto_enroll_ext_without_ndes(self): confdn = 'CN=Public Key Services,CN=Services,CN=Configuration,%s' % base_dn ca_cn = '%s-CA' % hostname.replace('.', '-') certa_dn = 'CN=%s,CN=Certification Authorities,%s' % (ca_cn, confdn) + self.addCleanup(ldb.delete, certa_dn) ldb.add({'dn': certa_dn, 'objectClass': 'certificationAuthority', 'authorityRevocationList': ['XXX'], @@ -6959,6 +6960,7 @@ def test_gp_cert_auto_enroll_ext_without_ndes(self): }) # Write the dummy pKIEnrollmentService enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn) + self.addCleanup(ldb.delete, enroll_dn) ldb.add({'dn': enroll_dn, 'objectClass': 'pKIEnrollmentService', 'cACertificate': dummy_certificate(), @@ -6967,6 +6969,7 @@ def test_gp_cert_auto_enroll_ext_without_ndes(self): }) # Write the dummy pKICertificateTemplate template_dn = 'CN=Machine,CN=Certificate Templates,%s' % confdn + self.addCleanup(ldb.delete, template_dn) ldb.add({'dn': template_dn, 'objectClass': 'pKICertificateTemplate', }) @@ -7012,11 +7015,6 @@ def test_gp_cert_auto_enroll_ext_without_ndes(self): self.assertNotIn(b'Workstation', out, 'Workstation certificate not removed') - # Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate - ldb.delete(certa_dn) - ldb.delete(enroll_dn) - ldb.delete(template_dn) - # Unstage the Registry.pol file unstage_file(reg_pol) @@ -7027,6 +7025,7 @@ def test_gp_cert_auto_enroll_ext(self): 'MACHINE/REGISTRY.POL') cache_dir = self.lp.get('cache directory') store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb')) + self.addCleanup(store.log.close) machine_creds = Credentials() machine_creds.guess(self.lp) @@ -7059,6 +7058,7 @@ def test_gp_cert_auto_enroll_ext(self): confdn = 'CN=Public Key Services,CN=Services,CN=Configuration,%s' % base_dn ca_cn = '%s-CA' % hostname.replace('.', '-') certa_dn = 'CN=%s,CN=Certification Authorities,%s' % (ca_cn, confdn) + self.addCleanup(ldb.delete, certa_dn) ldb.add({'dn': certa_dn, 'objectClass': 'certificationAuthority', 'authorityRevocationList': ['XXX'], @@ -7067,6 +7067,7 @@ def test_gp_cert_auto_enroll_ext(self): }) # Write the dummy pKIEnrollmentService enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn) + self.addCleanup(ldb.delete, enroll_dn) ldb.add({'dn': enroll_dn, 'objectClass': 'pKIEnrollmentService', 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I', @@ -7075,12 +7076,16 @@ def test_gp_cert_auto_enroll_ext(self): }) # Write the dummy pKICertificateTemplate template_dn = 'CN=Machine,CN=Certificate Templates,%s' % confdn + self.addCleanup(ldb.delete, template_dn) ldb.add({'dn': template_dn, 'objectClass': 'pKICertificateTemplate', }) with TemporaryDirectory() as dname: - ext.process_group_policy([], gpos, dname, dname) + try: + ext.process_group_policy([], gpos, dname, dname) + except Exception as e: + self.fail(f"process_group_policy() raised {e}") ca_crt = os.path.join(dname, '%s.crt' % ca_cn) self.assertTrue(os.path.exists(ca_crt), 'Root CA certificate was not requested') @@ -7169,11 +7174,6 @@ def test_gp_cert_auto_enroll_ext(self): self.assertNotIn(b'Workstation', out, 'Workstation certificate not removed') - # Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate - ldb.delete(certa_dn) - ldb.delete(enroll_dn) - ldb.delete(template_dn) - # Unstage the Registry.pol file unstage_file(reg_pol) @@ -7626,6 +7626,7 @@ def test_advanced_gp_cert_auto_enroll_ext(self): 'MACHINE/REGISTRY.POL') cache_dir = self.lp.get('cache directory') store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb')) + self.addCleanup(store.log.close) machine_creds = Credentials() machine_creds.guess(self.lp) @@ -7667,6 +7668,8 @@ def test_advanced_gp_cert_auto_enroll_ext(self): confdn = 'CN=Public Key Services,CN=Services,CN=Configuration,%s' % base_dn ca_cn = '%s-CA' % hostname.replace('.', '-') certa_dn = 'CN=%s,CN=Certification Authorities,%s' % (ca_cn, confdn) + self.addCleanup(ldb.delete, certa_dn) + ldb.add({'dn': certa_dn, 'objectClass': 'certificationAuthority', 'authorityRevocationList': ['XXX'], @@ -7675,6 +7678,7 @@ def test_advanced_gp_cert_auto_enroll_ext(self): }) # Write the dummy pKIEnrollmentService enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn) + self.addCleanup(ldb.delete, enroll_dn) ldb.add({'dn': enroll_dn, 'objectClass': 'pKIEnrollmentService', 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I', @@ -7683,12 +7687,21 @@ def test_advanced_gp_cert_auto_enroll_ext(self): }) # Write the dummy pKICertificateTemplate template_dn = 'CN=Machine,CN=Certificate Templates,%s' % confdn + try: + ldb.delete(template_dn) + except _ldb.LdbError: + pass + + self.addCleanup(ldb.delete, template_dn) ldb.add({'dn': template_dn, 'objectClass': 'pKICertificateTemplate', }) with TemporaryDirectory() as dname: - ext.process_group_policy([], gpos, dname, dname) + try: + ext.process_group_policy([], gpos, dname, dname) + except Exception as e: + self.fail(f"process_group_policy() raised {e}") ca_list = [ca_cn, 'example0-com-CA', 'example1-com-CA', 'example2-com-CA'] for ca in ca_list: @@ -7751,11 +7764,6 @@ def test_advanced_gp_cert_auto_enroll_ext(self): self.assertNotIn(b'Workstation', out, 'Workstation certificate not removed') - # Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate - ldb.delete(certa_dn) - ldb.delete(enroll_dn) - ldb.delete(template_dn) - # Unstage the Registry.pol file unstage_file(reg_pol)
160e83193308CVE-2026-3012: gp_auto_enrol: skip CAs not found in LDAP
1 file changed · +10 −0
python/samba/gp/gp_cert_auto_enroll_ext.py+10 −0 modified@@ -452,11 +452,21 @@ def __read_cep_data(self, guid, ldb, end_point_information, # This is a basic configuration. cas = fetch_certification_authorities(ldb) for _ca in cas: + if 'cACertificate' not in _ca: + log.warning(f"ignoring CA '{_ca['name']}' with no " + "cACertificate in LDAP.") + continue + self.apply(guid, _ca, cert_enroll, _ca, ldb, trust_dir, private_dir) ca_names.append(_ca['name']) # If EndPoint.URI starts with "HTTPS//": elif ca['URL'].lower().startswith('https://'): + if 'cACertificate' not in ca: + log.warning(f"ignoring CA '{ca['name']}' " + f"({ca['URL']}) with no " + "cACertificate in LDAP.") + continue self.apply(guid, ca, cert_enroll, ca, ldb, trust_dir, private_dir, auth=ca['auth']) ca_names.append(ca['name'])
4c2db6489be1CVE-2026-3012: do not fetch certificate over http
2 files changed · +11 −45
python/samba/gp/gp_cert_auto_enroll_ext.py+9 −45 modified@@ -16,7 +16,6 @@ import os import operator -import requests from samba.gp.gpclass import gp_pol_ext, gp_applier, GPOSTATE from samba import Ldb from samba.dcerpc import misc @@ -195,58 +194,24 @@ def get_supported_templates(server): return out.strip().split() -def getca(ca, url, trust_dir): - """Fetch Certificate Chain from the CA.""" +def getca(ca, trust_dir): + """Fetch a certificate from LDAP.""" root_cert = os.path.join(trust_dir, '%s.crt' % ca['name']) root_certs = [] - - try: - r = requests.get(url=url, params={'operation': 'GetCACert', - 'message': 'CAIdentifier'}) - except requests.exceptions.ConnectionError: - log.warn('Could not connect to Network Device Enrollment Service.') - r = None - if r is None or r.content == b'' or r.headers['Content-Type'] == 'text/html': - log.warn('Unable to fetch root certificates (requires NDES).') - if 'cACertificate' in ca: - log.warn('Installing the server certificate only.') - der_certificate = base64.b64decode(ca['cACertificate']) - try: - cert = load_der_x509_certificate(der_certificate) - except TypeError: - cert = load_der_x509_certificate(der_certificate, - default_backend()) - cert_data = cert.public_bytes(Encoding.PEM) - with open(root_cert, 'wb') as w: - w.write(cert_data) - root_certs.append(root_cert) - return root_certs - - if r.headers['Content-Type'] == 'application/x-x509-ca-cert': - # Older versions of load_der_x509_certificate require a backend param + if 'cACertificate' in ca: + log.warn('Installing the server certificate only.') + der_certificate = base64.b64decode(ca['cACertificate']) try: - cert = load_der_x509_certificate(r.content) + cert = load_der_x509_certificate(der_certificate) except TypeError: - cert = load_der_x509_certificate(r.content, default_backend()) + cert = load_der_x509_certificate(der_certificate, + default_backend()) cert_data = cert.public_bytes(Encoding.PEM) with open(root_cert, 'wb') as w: w.write(cert_data) root_certs.append(root_cert) - elif r.headers['Content-Type'] == 'application/x-x509-ca-ra-cert': - certs = load_der_pkcs7_certificates(r.content) - for i in range(0, len(certs)): - cert = certs[i].public_bytes(Encoding.PEM) - filename, extension = root_cert.rsplit('.', 1) - dest = '%s.%d.%s' % (filename, i, extension) - with open(dest, 'wb') as w: - w.write(cert) - root_certs.append(dest) - else: - log.warn('getca: Wrong (or missing) MIME content type') - return root_certs - def find_global_trust_dir(): """Return the global trust dir using known paths from various Linux distros.""" for trust_dir in global_trust_dirs: @@ -266,11 +231,10 @@ def changed(new_data, old_data): def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'): """Install the root certificate chain.""" data = dict({'files': [], 'templates': []}, **ca) - url = 'http://%s/CertSrv/mscep/mscep.dll/pkiclient.exe?' % ca['hostname'] log.info("Try to get root or server certificates") - root_certs = getca(ca, url, trust_dir) + root_certs = getca(ca, trust_dir) data['files'].extend(root_certs) global_trust_dir = find_global_trust_dir() for src in root_certs:
selftest/knownfail.d/gpo-auto-enrol+2 −0 added@@ -0,0 +1,2 @@ +^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_advanced_gp_cert_auto_enroll_ext\(ad_dc:local\) +^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_gp_cert_auto_enroll_ext\(ad_dc:local\)
Vulnerability mechanics
Root cause
"Samba's certificate auto-enrollment fetched a CA certificate over an unencrypted HTTP connection and installed it into the local trust store without any verification."
Attack vector
An attacker with network access who can intercept or redirect HTTP traffic (adjacent network, CVSS AV:A) can exploit Samba's certificate auto-enrollment Group Policy handling. When certificate auto-enrollment is enabled, Samba's `getca()` function issued an unencrypted HTTP GET request to the NDES-like endpoint to retrieve a CA certificate [patch_id=2654243]. The response was trusted without any verification and installed into the local trust store. An attacker who can perform a man-in-the-middle attack on this HTTP connection can supply a malicious CA certificate, enabling interception or spoofing of trusted communications.
Affected code
The vulnerability is in `python/samba/gp/gp_cert_auto_enroll_ext.py`. The `getca()` function (lines 197–254 before the patch) fetched a CA certificate over plain HTTP from `http://%s/CertSrv/mscep/mscep.dll/pkiclient.exe?` [patch_id=2654243]. The `cert_enroll()` function constructed this URL and called `getca()` with it. The patches also modify `python/samba/tests/gpo.py` to use parseable certificates and improve test cleanup [patch_id=2654242][patch_id=2654241].
What the fix does
Patch [patch_id=2654243] removes the entire HTTP fetch path from `getca()`. The function no longer makes a `requests.get()` call to an HTTP URL; instead it only reads the CA certificate from the `cACertificate` attribute already obtained via LDAP. The commit message explains that there was no way to verify the HTTP-derived certificate other than comparing it to LDAP-derived certificates, and LDAP was already the fallback — so the patch makes LDAP the only path. Patch [patch_id=2654240] adds a guard in `__read_cep_data()` to skip CAs that lack a `cACertificate` attribute in LDAP, preventing attempts to fetch them via any other channel.
Preconditions
- configCertificate auto-enrollment Group Policy must be enabled on the Samba domain controller
- networkAttacker must be on the same adjacent network (CVSS AV:A) to intercept or redirect HTTP traffic
- authNo authentication required (CVSS PR:N) — the HTTP fetch is unauthenticated
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
0No linked articles in our index yet.