Tumblr form_key decryption challenge !

jtrash01

Regular Member
Joined
Nov 5, 2013
Messages
271
Reaction score
127
Tumblr.com uses something called "form_key" to trust you as a real user performing actions on their website, and to be sure that your issuing that actions from a real browser.

From the browser, and looking into the source code It looks like the following:
Code:
[FONT=Verdana]<meta[/FONT][FONT=Verdana]name[/FONT][FONT=Verdana]=[/FONT][FONT=Verdana]"tumblr-form-key"[/FONT][FONT=Verdana]content[/FONT][FONT=Verdana]=[/FONT][FONT=Verdana]"[/FONT]z4YJnRsaGIi0ADdr9bG5OujIE[FONT=Verdana]"[/FONT][FONT=Verdana]id[/FONT][FONT=Verdana]=[/FONT][FONT=Verdana]"tumblr_form_key"[/FONT][FONT=Verdana]>
[/FONT][FONT=Verdana]<input type="hidden" name="form_key" value="[/FONT]z4YJnRsaGIi0ADdr9bG5OujIE[FONT=Verdana]"/>[/FONT]

But seems that Tumblr guys are obfuscating/encrypting that key from server side or through Javascript doing XHR's/Ajax posts.

The next code is a Python snippet that uses requests and lxml to get the source code and grab the keys. So If we try to get that key programatically...

Code:
import requests
from lxml import html
headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36',}
response = session.get('https://www.tumblr.com/login', headers)
r = html.fromstring(response.text)
form_key1 = r.xpath("//meta[@id='tumblr_form_key']/@content")[0]
form_key2 = r.xpath("//input[@name='form_key']")[0].value
print(form_key1)
[FONT=Verdana]print[/FONT][FONT=Verdana]([/FONT][FONT=Verdana]form_key2[/FONT][FONT=Verdana])[/FONT]

We'll get something encrypted like that:
Code:
!1231432124494|rHkFnkmN6Q9UGib2iF54lpl8rkM  #output for form_key1
!1231432124494|rHkFnkmN6Q9UGib2iF54lpl8rkM  #output for form_key2

Anyone know how or be able to get the decrypted key ???

I think that these javascript could be related to the key generation:
Code:
require=function r(t,n,e){function o(i,f){if(!n[i]){if(!t[i]){var c="function"==typeof require&&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);throw new Error("Cannot find module '"+i+"'")}var a=n[i]={exports:{}};t[i][0].call(a.exports,function(r){var n=t[i][1][r];return o(n?n:r)},a,a.exports,r,t,n,e)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<e.length;i++)o(e[i]);return o}({"f/LVtH":[function(r,t){"use strict";function n(r){return"function"==typeof r}function e(r){return"undefined"==typeof r}function o(r){var t,e;if(!n(r))throw new TypeError;return function(){return t?e:(t=!0,e=r.apply(this,arguments),r=null,e)}}function u(r){return!(!c||!c[r])}function i(r){var t=u(r);return t?function o(r){var u=n(r)?r.call(this,t):r;return e(u)?o:u}:function i(r,o){var u=n(o)?o.call(this,t):o;return e(u)?i:u}}function f(r){try{c=JSON.parse(p(r))}catch(t){c={}}}var c,a=Object.prototype,p=(a.toString,n(window.atob)?window.atob:function(r){var t,n,e,o,u={},i=0,f=0,c="",a=String.fromCharCode,p=r.length,s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";for(t=0;64>t;t++)u[s.charAt(t)]=t;for(e=0;p>e;e++)for(n=u[r.charAt(e)],i=(i<<6)+n,f+=6;f>=8;)((o=i>>>(f-=8)&255)||p-2>e)&&(c+=a(o));return c});t.exports=i,t.exports.bool=u,t.exports.setup=o(f)},{}],"prima/lib/flags":[function(r,t){t.exports=r("f/LVtH")},{}]},{},[]);

And:
Code:
[URL]https://secure.assets.tumblr.com/client/prod/app/vendor/index.js[/URL]

The login part still works with the "encrypted" key (!1231432124494|rHkFnkmN6Q9UGib2iF54lpl8rkM). And that's nice. But what If we try to send a photo post ?
I went to tumblr.com/new/photo and this POST is originated:

http://i.imgur.com/8zBVRxb.png

It Includes two notable headers... x-requested-with: XMLHttpRequest and x-tumblr-form-key and as you can see, it is "decrypted" (z4YJnRsaGIi0ADdr9bG5OujIE).
Then, when I filled the form and I click the button to publish the post. There are two POST requests named secure_form_key and update
The secure_form_key includes x-requested-with: XMLHttpRequest and x-tumblr-form-key in the header request, and returns an x-tumblr-secure-form-key:!331432128440332|ktlggMxE0ANeADZIGrnYpYQiEM as you can see here:

http://i.imgur.com/X7Ml1fE.png

Now that we have the x-tumblr-secure-form-key we can jump to the next request... named update. This time the requests is sent with a new header x-tumblr-puppies with the same value as the x-tumblr-secure-form-key So it shouldn't be a problem.
Code:
x-requested-with:XMLHttpRequestx-tumblr-form-key:z4YJnRsaGIi0ADdr9bG5OujIE
x-tumblr-puppies:!331432128440332|ktlggMxE0ANeADZIGrnYpYQiEM


http://i.imgur.com/sIiGgW2.png

The question is... How do we get the "decrypted", short, version -> x-tumblr-form-key:z4YJnRsaGIi0ADdr9bG5OujIE... It's possible to get it ?

Any help would be appreciated :) Thanks
 
Last edited:
Just use PhantomJS and drivers to connect it to Python, problem solved.
 
Just use PhantomJS and drivers to connect it to Python, problem solved.
No. This is exactly what I was trying to avoid.
And with PhantomJS, Selenium, HTMLUnit and so we'll still be getting the encrypted version of form_key...
I guess that If we could get the unencrypted version of form_key we could do every action in tumblr only with just http requests... that is faster, uses less hardware, and its more reliable, and would let us circumvent the Tumblr API's.
 
Last edited:
Have you figured this out? What news about it for today?
 
Back
Top