mirror of
https://github.com/Harrison-Wells-Cyber/PS-Proxy
synced 2026-07-26 08:06:34 +00:00
Merge pull request #3 from Harrison-Wells-Cyber/codex/review-proxy-tool-for-internal-ad-environment
Change authentication method to better work in environments with tls inspection.
This commit is contained in:
@@ -160,6 +160,25 @@ connections during high-concurrency tools such as NetExec; the default is 256. W
|
||||
but host telemetry, PowerShell logging, AMSI, EDR, crash dumps, or pagefile
|
||||
behavior are outside the loader's control.
|
||||
|
||||
|
||||
### TLS inspection / enterprise decryption
|
||||
|
||||
If an authorized enterprise TLS inspection device presents a different leaf
|
||||
certificate to the Windows agent than the certificate loaded by the VPS server,
|
||||
agent certificate pinning will fail. The safest fix is to exempt the PS-Proxy
|
||||
server domain from TLS decryption so the agent sees the VPS certificate directly.
|
||||
|
||||
For controlled labs where decryption cannot be bypassed, pass the inspected leaf
|
||||
certificate SHA-256 DER hash explicitly:
|
||||
|
||||
```bash
|
||||
--agent-cert-pin-override <64-char-sha256-hex-pin>
|
||||
```
|
||||
|
||||
Only use this when you control and trust the inspection device. This pins the
|
||||
agent to the certificate it actually sees, not the certificate file loaded by the
|
||||
VPS server.
|
||||
|
||||
## Current implementation status
|
||||
|
||||
Implemented now:
|
||||
|
||||
@@ -34,6 +34,7 @@ func main() {
|
||||
port := flag.Int("port", 443, "TLS listener port")
|
||||
cert := flag.String("cert", "", "TLS fullchain PEM; defaults to /etc/letsencrypt/live/<domain>/fullchain.pem")
|
||||
key := flag.String("key", "", "TLS private key PEM; defaults to /etc/letsencrypt/live/<domain>/privkey.pem")
|
||||
agentCertPinOverride := flag.String("agent-cert-pin-override", "", "override SHA-256 DER certificate pin embedded in staged agents; use only when an authorized TLS inspection device presents a different leaf cert")
|
||||
tun := flag.String("tun", "psproxy0", "TUN interface name for the planned netstack data plane")
|
||||
agentTemplate := flag.String("agent-template", "agent/loader/agent.ps1.tmpl", "PowerShell agent loader template")
|
||||
agentAssemblyFile := flag.String("agent-assembly-b64-file", "", "file containing compressed/base64 PSProxy.Agent.dll; defaults to release/agent_assembly.b64 when present")
|
||||
@@ -74,6 +75,13 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatalf("certificate pin failed: %v", err)
|
||||
}
|
||||
if *agentCertPinOverride != "" {
|
||||
pin, err = normalizeCertPin(*agentCertPinOverride)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid --agent-cert-pin-override: %v", err)
|
||||
}
|
||||
log.Printf("WARNING: using operator-supplied agent certificate pin override")
|
||||
}
|
||||
assembly, err := loadAssemblyB64(*agentAssemblyFile)
|
||||
if err != nil {
|
||||
log.Fatalf("agent assembly load failed: %v", err)
|
||||
@@ -708,6 +716,17 @@ type multiFlag []string
|
||||
func (m *multiFlag) String() string { return strings.Join(*m, ",") }
|
||||
func (m *multiFlag) Set(v string) error { *m = append(*m, v); return nil }
|
||||
|
||||
func normalizeCertPin(pin string) (string, error) {
|
||||
normalized := strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(strings.TrimSpace(pin), ":", ""), " ", ""))
|
||||
if len(normalized) != 64 {
|
||||
return "", fmt.Errorf("pin must be 64 hex characters after removing colons/spaces")
|
||||
}
|
||||
if _, err := hex.DecodeString(normalized); err != nil {
|
||||
return "", fmt.Errorf("pin must be hex: %w", err)
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
func certPin(path string) (string, error) {
|
||||
pemBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
|
||||
@@ -124,3 +124,23 @@ func TestSingleListenerAcceptReturnsEOFAfterFirstConn(t *testing.T) {
|
||||
t.Fatal("second accept should return EOF")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeCertPin(t *testing.T) {
|
||||
pin := "BC:1D:96:47:91:A5:11:B4:95:26:EC:F2:25:35:37:F6:E6:17:0A:1A:19:4F:45:65:9E:88:0C:A7:A4:3D:6C:02"
|
||||
got, err := normalizeCertPin(pin)
|
||||
if err != nil {
|
||||
t.Fatalf("normalize pin failed: %v", err)
|
||||
}
|
||||
want := "bc1d964791a511b49526ecf2253537f6e6170a1a194f45659e880ca7a43d6c02"
|
||||
if got != want {
|
||||
t.Fatalf("unexpected normalized pin: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeCertPinRejectsInvalidPins(t *testing.T) {
|
||||
for _, pin := range []string{"abc", "zz1d964791a511b49526ecf2253537f6e6170a1a194f45659e880ca7a43d6c02"} {
|
||||
if _, err := normalizeCertPin(pin); err == nil {
|
||||
t.Fatalf("expected invalid pin %q to fail", pin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user