1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

Tumblr form_key decryption challenge !

Discussion in 'General Programming Chat' started by jtrash01, May 30, 2015.

  1. jtrash01

    jtrash01 Regular Member

    Joined:
    Nov 5, 2013
    Messages:
    270
    Likes Received:
    117
    Location:
    BARCELONA, SPAIN
    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: May 30, 2015
  2. CakeLord

    CakeLord Newbie

    Joined:
    Jun 9, 2015
    Messages:
    10
    Likes Received:
    1
    Just use PhantomJS and drivers to connect it to Python, problem solved.
     
  3. jtrash01

    jtrash01 Regular Member

    Joined:
    Nov 5, 2013
    Messages:
    270
    Likes Received:
    117
    Location:
    BARCELONA, SPAIN
    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: Jun 28, 2015