Nextcloud: \OCA\DAV\CardDAV\ImageExportPlugin allows serving arbitrary data with user-defined or empty mimetype

ID H1:163338
Type hackerone
Reporter lukasreschke
Modified 2016-12-03T21:59:28


The SabreDAV plugin \OCA\DAV\CardDAV\ImageExportPlugin is used for displaying pictures of a VCF. It registers on a GET request on a CardDAV element and acts when the query parameter photo is sent.

The logic can be seen below: ``` /* * Intercepts GET requests on addressbook urls ending with ?photo. * * @param RequestInterface $request * @param ResponseInterface $response * @return bool|void / function httpGet(RequestInterface $request, ResponseInterface $response) {

$queryParams = $request->getQueryParameters();
// TODO: in addition to photo we should also add logo some point in time
if (!array_key_exists('photo', $queryParams)) {
    return true;

$path = $request->getPath();
$node = $this->server->tree->getNodeForPath($path);

if (!($node instanceof Card)) {
    return true;

$this->server->transactionType = 'carddav-image-export';

// Checking ACL, if available.
if ($aclPlugin = $this->server->getPlugin('acl')) {
    /** @var \Sabre\DAVACL\Plugin $aclPlugin */
    $aclPlugin->checkPrivileges($path, '{DAV:}read');

if ($result = $this->getPhoto($node)) {
    $response->setHeader('Content-Type', $result['Content-Type']);


    // Returning false to break the event chain
    return false;
return true;

} ```

As can be seen the the content-type is read from $this->getPhoto($node) as well as the body, looking at it's implementation shows that the data is directly read from the vCard:

``` function getPhoto(Card $node) { // TODO: this is kind of expensive - load carddav data from database and parse it // we might want to build up a cache one day try { $vObject = $this->readCard($node->get()); if (!$vObject->PHOTO) { return false; }

    $photo = $vObject->PHOTO;
    $type = $this->getType($photo);

    $val = $photo->getValue();
    if ($photo->getValueType() === 'URI') {
        $parsed = \Sabre\URI\parse($val);
        //only allow data://
        if ($parsed['scheme'] !== 'data') {
            return false;
        if (substr_count($parsed['path'], ';') === 1) {
            list($type,) = explode(';', $parsed['path']);
        $val = file_get_contents($val);
    return [
        'Content-Type' => $type,
        'body' => $val
} catch(\Exception $ex) {
return false;

} ```

This means if somebody uploads a VCF with the following content this will deliver the content <html><font color="red">test</font></html> with an empty Content-Type. The photo is a base64 encoding of before mentioned string.

BEGIN:VCARD VERSION:3.0 FN:test UID:5cf6e5e2-ec37-4798-abb7-3c261eda92c9 PHOTO;ENCODING=b:PGh0bWw+PGZvbnQgY29sb3I9InJlZCI+dGVzdDwvZm9udD48L2h0bWw+ END:VCARD

Then it's sufficient to just access, the easiest to reproduce this is by enabling debug mode and using Internet Explorer since we employ CSP which largely mitigates the issue.

As another remark, we should replace the file_get_contents with another implementation. This seems currently like a too risky implementation for me.