How to reverse-engineer and automate any Meteor app you find

This tutorial will teach you how to discover what subscriptions and method calls a Meteor app has, and how to use them for batch operations. As an example, we'll automate Atmosphere, the Meteor package repository.

Intro

A while ago I registered a bunch of Meteor Developer organization names corresponding to 3rd party libraries (e.g. codemirror or keyboard) to reserve them for community integrations.

A month later, Atmosphere implemented support for organization pages, like this one for the "slickgrid" library (which we haven't yet integrated with Meteor properly).

SlickGrid organization page

The problem

But now it turned out that I had reserved about 130 organization names, and all had blank pages! Manually going through each organization in my Meteor Accounts, then navigating to https://atmosphere.com/orgname, editing the profile, changing the name, and saving - not fun.

Meteor Accounts orgs

Automating Atmosphere

Atmosphere probably knows what organizations I'm in, and that should be part of my user profile. Let's see. Go to Atmosphere, open the browser console, and run

Meteor.user()

Indeed - my user object has organizations: Array[136]. Delving into that array only shows the name and id of the orgs though, so we don't know anything about their profile information (Name, Description, Website, Twitter, GitHub).

Meteor.user()

But we do have that profile information for my user account, so there must be a subscription that gets it. How do we find the subscriptions that a Meteor app uses? Easy - we look in Chrome's Network tab, filtering for WebSocket connections, and reload the page:

Meteor.subscribe()

Chrome highlights in green "sub" messages, and we see that one of them has the parameter slickgrid - the organization whose page we're on. Let's test that:

Meteor.users.find().fetch()

Indeed, the second result shows the organization profile. To get the profile of a different organization, we run Meteor.subscribe() and pass the parameter we saw in the WebSocket list:

Meteor.subscribe('user', 'keyboard')
Meteor.users.find().fetch()

We'll now see three results: my user profile, the profile of "slickgrid", and the profile of the "keyboard" organization. We have all the pieces we need.

Putting it all together

We want to iterate through the organizations I'm a member of, and set the profile.name to "AVAILABLE - contact @dandv" if the name is empty. First, let's subscribe to all these organizations:

Meteor.users.find().fetch()[0].organizations.forEach(function (org) {
  Meteor.subscribe('user', org.name)
})

If you click again on the WebSocket request in Chrome, you'll see a lot of new "sub" messages. Meteor.users.find().fetch() confirms that we have 130+ user objects (NOTE the exact number was 133, not 136. Not sure why the difference - please leave a comment if you know).

Now we only need to issue the Meteor.call that updates the organization profile. How do we figure that one out? Simply by searching the source code of Atmosphere for "Meteor.call". It's minified, but Chrome does a decent job at auto-prettifying it, and we find Meteor.call('updateProfile'). Therefore,

Meteor.users.find().forEach(function (user) {
  if (user.isOrganization && !('name' in user.profile))
    Meteor.call('updateProfile', user._id, {profile: {
      name: 'AVAILABLE - contact @dandv',
    }})
})

Ooops, that fails with a SimpleSchema validation error. We need to explicitly pass the other profile parameters. No problem:

Meteor.users.find().forEach(function (user) {
  if (user.isOrganization && !('name' in user.profile))
    Meteor.call('updateProfile', user._id, {profile: {
      name: 'AVAILABLE - contact @dandv',
      description: user.profile.description || '',
      twitter: user.profile.twitter || '',
      website: user.profile.website || '',
      github: user.profile.github || ''
    }})
})

Methods

Meteor methods can be seen among the WebSocket frames by looking for "msg":"method". For example, when you add a username to an organization in your Meteor account, you can see this:

{
    "msg":"method",
    "method":"addOrganizationMember",
    "params":["jspdf", "splendido"],
    "id": "2"
}

(If the output looks uglier than that, vote for the Chrome team to implement this feature request for prettifying Websocket frame dumps.) That method name, however, failed when running Meteor.call('addOrganizationMember', 'jspdf', 'splendido'), with an error suggesting the method wasn't found (404). So this part is TBD.

Conclusion

That's it! A way to think about Meteor apps you find in the wild, reverse engineer their subscriptions and methods, and use them to your indubitably nefarious purposes. ;-)

Of course, what I've done here applies to any web app that you could automate from the browser using its REST API and methods. Meteor just makes things easier.

The goal, obviously, is not to do anything actually nefarious, but to perform batch operations like the one I needed to do - set something for all records you have access to, for example. A well-secured Meteor app won't let you do anything you shouldn't be doing - see my answer to Is Meteor secure on Quora.

My tags:
 
Popular tags: