use anyhow::Result;
use clap::Parser;
use crate::ui;
#[derive(Parser)]
pub(crate) struct EscArgs {
#[command(flatten)]
pub auth: crate::shared_args::SmbAuth,
#[arg(long)]
pub ca: String,
}
pub(crate) async fn esc_registry_scan(a: EscArgs) -> Result<()> {
use crate::esc_registry::{esc10, esc11, esc16, esc6, esc7};
use dcerpc::rrp::RegistryClient;
use smb2_client::SmbClient;
let sp = ui::Spinner::start(format!("{} — SMB auth + \\winreg", a.auth.host));
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 mut reg = RegistryClient::connect(
&mut smb,
&a.auth.domain,
&a.auth.user,
&a.auth.password,
&a.auth.host,
)
.await
.map_err(|e| {
let msg = e.to_string();
if msg.contains("0xc00000ac") || msg.contains("open \\winreg") {
anyhow::anyhow!(
"\\winreg unreachable on {} — the Remote Registry service is stopped or \
disabled (STATUS_ILLEGAL_FUNCTION 0xC00000AC). Start it on the CA host \
(`Set-Service RemoteRegistry -StartupType Automatic; Start-Service RemoteRegistry`) \
then rerun. ESC1/2/3/4/9/13 don't need this — only ESC6/10/11/16 read \
registry state.",
a.auth.host
)
} else {
e.into()
}
})?;
sp.done("Remote Registry reachable");
ui::header(&format!("AD CS registry ESC checks — CA {}", a.ca));
let ca = format!(
"SYSTEM\\CurrentControlSet\\Services\\CertSvc\\Configuration\\{}",
a.ca
);
let mut hits = Vec::new();
let iflags = reg
.read_value(&ca, "InterfaceFlags")
.await
.ok()
.and_then(|v| v.as_dword())
.unwrap_or(0);
hits.extend(esc11(iflags));
let pm_root = format!("{ca}\\PolicyModules");
let policy = reg
.read_value(&pm_root, "Active")
.await
.map(|v| v.as_string())
.unwrap_or_else(|_| "CertificateAuthority_MicrosoftDefault.Policy".into());
let policy_key = format!("{pm_root}\\{policy}");
if let Ok(v) = reg.read_value(&policy_key, "EditFlags").await {
if let Some(d) = v.as_dword() {
hits.extend(esc6(d));
}
}
if let Ok(v) = reg.read_value(&policy_key, "DisableExtensionList").await {
hits.extend(esc16(&v.as_string()));
}
if let Ok(v) = reg.read_value(&ca, "Security").await {
hits.extend(esc7(&v.data));
}
let is_dc = reg
.read_value(
"SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters",
"DSA Working Directory",
)
.await
.is_ok()
|| reg
.read_value(
"SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters",
"Machine DN Name",
)
.await
.is_ok();
if is_dc {
match reg
.read_value(
"SYSTEM\\CurrentControlSet\\Services\\Kdc",
"StrongCertificateBindingEnforcement",
)
.await
{
Ok(v) => match v.as_dword() {
Some(d) => hits.extend(esc10(d)),
None => hits.push(crate::esc_registry::esc10_absent()),
},
Err(_) => hits.push(crate::esc_registry::esc10_absent()),
}
}
if hits.is_empty() {
ui::ok("no registry-based ESC (ESC6/10/11/16) exposure found");
} else {
for h in &hits {
ui::warn(&format!("{} — {}", h.id, h.title));
ui::field("detail", &h.detail);
}
ui::warn(&format!(
"{} registry-based ESC exposure(s) on {}",
hits.len(),
a.auth.host
));
}
Ok(())
}