dequis/purple-facebook

Two-factor no longer works?

Opened this issue ยท 64 comments

I reported this in the Workplace bug, but it looks like this is now happening on non-workplace. Two-factor no longer works either in app-password mode, nor in login-approval-code mode.

If you use an app password, you just get permission denied (tried with both "username" and "email" as username).

If you use normal password, Pidgin will tell you about the approval code, you then put it in, and it tries to connect for a minute or so, and then eventually you get invalid username or password (401).

Oh - so it looks like you can make it work, if, before you put in the 2fac code, you go to the site, then click the "did you log in", click yes, then click "save browser", THEN go back to pidgin and put in the code it works, at least for normal fb.

OH! And it works for Workplace too [but the alert comes up on non-workplace]. I'll leave this open for others to find and I'll comment on the workplace thread.

Aaaand, no that stopped working. :(

@dequis - as of now there's no Facebook or Workplace if you use 2fac (which ... I hope most people do). Pretty please for christmas all I want is a chat client? :) :)

Can't repro :/

19:44 <@dx|fb-2> ac on
19:44 <@root> Trying to get all accounts connected...
19:44 <@root> facebook - Logging in: Authenticating
19:44 <@root> facebook - Login error: Login approvals are on. Expect an SMS shortly with a code to use for log in (406)
19:44 <@root> facebook - Logging in: Signing off..
19:44 <@root> facebook - Logging in: Reconnecting in 5 seconds..
19:44 <@dx|fb-2> ac off
19:44 <@root> Deactivating all active (re)connections...
19:45 <@dx|fb-2> ac facebook set password '028470'
19:45 <@root> Setting changed successfully
19:45 <@dx|fb-2> ac facebook on
19:45 <@root> facebook - Logging in: Authenticating
19:45 <@root> facebook - Logging in: Fetching contacts
19:45 <@root> facebook - Logging in: Connecting
19:45 <@root> facebook - Logging in: Logged in
19:50 <@dx|fb-work2> ac add facebook [...]
19:50 <@root> Account successfully added with tag facebook
19:50 <@root> You can now use the /OPER command to enter the password
19:50 <@root> Password added to IM account facebook
19:50 <@dx|fb-work2> ac facebook set work on
19:50 <@root> work = `on'
19:50 <@dx|fb-work2> ac facebook on
19:50 <@root> facebook - Logging in: Authenticating
19:50 <@root> facebook - Login error: Login approvals are on. Expect an SMS shortly with a code to use for log in (406)
19:50 <@root> facebook - Logging in: Signing off..
19:50 <@root> facebook - Logging in: Reconnecting in 5 seconds..
19:50 <@dx|fb-work2> ac facebook off
19:50 <@root> Reconnect cancelled
19:50 <@dx|fb-work2> ac facebook set password 736178
19:50 <@root> Setting changed successfully
19:50 <@dx|fb-work2> ac facebook on
19:50 <@root> facebook - Logging in: Authenticating
19:50 <@root> facebook - Logging in: Fetching contacts
19:50 <@root> facebook - Logging in: Connecting
19:50 <@root> facebook - Logging in: Logged in

I should have sent you a debug log, I suck. Doing so now.

So I sent you pidgin debug logs, but just for fun I also tried bitlbee:

11:50 <@phil> account add facebook jaymzh "XXXXXXXXXXXXXXXXXX"
11:50 <@root> Account successfully added with tag facebook
11:50 <@phil> ac on
11:50 <@root> Trying to get all accounts connected...
11:50 <@root> facebook - Logging in: Authenticating
11:51 <@root> facebook - Login error: Login approvals are on. Expect an SMS shortly with a code to use for log in (406)
11:51 <@root> facebook - Logging in: Signing off..
11:51 <@root> facebook - Logging in: Reconnecting in 5 seconds..
11:51 <@root> facebook - Logging in: Authenticating
11:51 <@root> facebook - Login error: Login approvals are on. Expect an SMS shortly with a code to use for log in (406)
11:51 <@root> facebook - Logging in: Signing off..
11:51 <@root> facebook - Logging in: Reconnecting in 15 seconds..
11:51 <@phil> ac off
11:51 <@root> Deactivating all active (re)connections...
11:51 <@phil> ac facebook set password '881202'
11:51 <@root> Setting changed successfully
11:51 <@phil> ac on
11:51 <@root> Trying to get all accounts connected...
11:51 <@root> facebook - Logging in: Authenticating
11:51 <@root> facebook - Login error: Invalid username or password (401)
11:51 <@root> facebook - Logging in: Signing off..

I looked around, seems that there's a new auth path like this:

  • credentials_type=two_factor
  • first_factor=string copied from login_first_factor in error_data in the original error message (seems fairly constant for me),
  • twofactor_code=the code you enter manually I guess
  • password=twofactor_code

I drafted an implementation of this with a python script, but the server doesn't like something about it, says "error_msg":"An unknown error occurred (1)". I'm going to need to sniff actual requests to find what i'm doing wrong, and my debugging tooling is in a different continent... Seems unlikely that I'll have time to rebuild that soon.

FWIW my dirty request signing script (python2)

import cgi
from urllib import urlencode
import hashlib

FB_API_KEY = '256002347743983'
FB_API_SECRET = '374e60f8b9bb6b8cbb30f78030438895'

def fb_sig(data):
    params = ''.join(['%s=%s' % x for x in sorted(data.items())])
    data['sig'] = hashlib.md5(params + FB_API_SECRET).hexdigest()
    return data

data = {}
data['api_key'] = FB_API_KEY
data['format'] = 'json'
data['method'] = 'auth.login'
data['email'] =... '...'
data['password'] = '...'

#data['credentials_type'] = 'two_factor'
#data['error_detail_type'] = 'button_with_disabled'
#data['first_factor'] = '...'
#data['twofactor_code'] = '...'
#data['password'] = data['twofactor_code']

print("curl 'https://b-api.facebook.com/method/auth.login' -v --data-binary '" + urlencode(fb_sig(data)) + "'")

Replace the ..., run, run its output, uncomment block of #data, fill their .... Not really adding many details here because I'm sure some part is wrong. If I leave out credentials_type it works, but that's just because my "server" still supports the old method of using password for twofactor_code, I bet.

If you somehow manage to get it working, set the hidden setting token to the value of access_token. In purple it's accounts.xml stuff I think.

OK, I'm trying to play with this, but what is data['first_factor'] supposed to be? I treid setting it to data['password'] but that still gives me An unknown error occurred.

The error message of the first login attempt has a login_first_factor string in it, I copied that. It could be wrong, though.

Ah I see now, I missed that. So I tried that, and I tried both overriding data['password'] as you did, but also leaving it with the password, and both ways I get:

{"error_code":1,"error_msg":"An unknown error occurred (1)","request_args":[{"key":"method","value":"auth.login"},{"key":"api_key","value":"256002347743983"},{"key":"format","value":"json"},{"key":"first_factor","value":"XXXXXXXXXXXXXXX"},{"key":"credentials_type","value":"two_factor"},{"key":"sig","value":"YYYYYYYYYYYYYy"},{"key":"twofactor_code","value":"AAAAAAAAA"},{"key":"password","value":"--sanitized--"},{"key":"error_detail_type","value":"button_with_disabled"},{"key":"email","value":"phil\u0040ipom.com"}]}

BTW, for anyone else playing along at home, I modified the script slightly to make it a bit easier to use:

#!/usr/bin/python

import sys
import cgi
from urllib import urlencode
import hashlib
import getpass

FB_API_KEY = '256002347743983'
FB_API_SECRET = '374e60f8b9bb6b8cbb30f78030438895'

def fb_sig(data):
    params = ''.join(['%s=%s' % x for x in sorted(data.items())])
    data['sig'] = hashlib.md5(params + FB_API_SECRET).hexdigest()
    return data

data = {}
data['api_key'] = FB_API_KEY
data['format'] = 'json'
data['method'] = 'auth.login'
data['email'] = 'YOUR_EMAIL_HERE'

if len(sys.argv) > 1:
    data['credentials_type'] = 'two_factor'
    data['error_detail_type'] = 'button_with_disabled'
    data['first_factor'] = sys.argv[1]
    data['twofactor_code'] = sys.argv[2]
    data['password'] = data['twofactor_code']
else:
    data['password'] = getpass.getpass()

print("curl 'https://b-api.facebook.com/method/auth.login' -v --data-binary '" + urlencode(fb_sig(data)) + "'")

You can run it once with no argument, and then the second time pass in the first_factor as arg1 and the SMS code as the second arg, like:

$ fb_test.py
Password: <enter your password here>
curl ....
$ fb_test.py XXXXXX 123456
curl ....

Continuing to mess with this I made the script just do the requests for you and prompt you for the bits it needs:

#!/usr/bin/python

import sys
import cgi
from urllib import urlencode
import hashlib
import getpass
import httplib
import urllib
import json
from optparse import OptionParser

DEBUG = False

FB_API_KEY = '256002347743983'
FB_API_SECRET = '374e60f8b9bb6b8cbb30f78030438895'

def fb_sig(data):
    newdata = data.copy()
    params = ''.join(['%s=%s' % x for x in sorted(data.items())])
    newdata['sig'] = hashlib.md5(params + FB_API_SECRET).hexdigest()
    return newdata

def debug(msg):
    global DEBUG
    if DEBUG:
        print "DEBUG: %s", msg

parser = OptionParser()
parser.add_option('-d', '--debug', action='store_true', dest='debug', default=False)
(options, args) = parser.parse_args()

if options.debug:
    DEBUG = True

data = {}
data['api_key'] = FB_API_KEY
data['format'] = 'json'
data['method'] = 'auth.login'
data['email'] = ''
if data['email'] == '':
    print "ERROR: set an email address, please"
    sys.exit()
data['password'] = getpass.getpass()

headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "*/*"}
conn = httplib.HTTPSConnection('b-api.facebook.com:443')
params = urllib.urlencode(fb_sig(data))
conn.request('POST', '/method/auth.login', params, headers)
response = conn.getresponse()
debug("status, reason: %s, %s" % (response.status, response.reason))
response_data = response.read()
debug("undecoded response: %s" % response_data)
response = json.loads(response_data)
debug(response)
first_fac = json.loads(response['error_data'])['login_first_factor']

data['credentials_type'] = 'two_factor'
data['error_detail_type'] = 'button_with_disabled'
data['first_factor'] = first_fac
data['twofactor_code'] = getpass.getpass('Code: ')
data['password'] = data['twofactor_code']

params = urllib.urlencode(fb_sig(data))
conn.request('POST', '/method/auth.login', params, headers)
response = conn.getresponse()
debug("status, reason: %s, %s" % (response.status, response.reason))
response_data = response.read()
debug("undecoded response: %s" % response_data)
response = json.loads(response_data)
print response

Hey @dequis what's the easiest way to see what the official apps do? I can try debugging, but since it's all SSL, I'm not sure the easiest way to see what's going on..

Yeah I just need to rebuild my MITM setup, see:

I'm going to need to sniff actual requests to find what i'm doing wrong, and my debugging tooling is in a different continent... Seems unlikely that I'll have time to rebuild that soon.

For values of "soon" of "one week" which is over now. I'll look into this and other things this weekend, because apparently someone broke other parts of fb, sync sequences are sometimes gone and receiving messages goes on a different queue. Also the TLS 1.3 thing because why not.

FWIW I documented the stuff I did with earlier versions at https://wiki.dequis.org/notes/facebook/ - but it's been long enough that I bet the cert pinning methods changed again.

OK cool, thanks. If I get time to set that stuff up, I'll poke at it, but I'm guessing you'll get to the bottom of this faster than I will.

I didn't have a big enough block of time this weekend to try to setup the whole MITM setup unfortunately. Did you get a chance to look?

Nope, I only had enough time to sort the other issues (and one of them was just because someone else came up with the fix which was just bumping a version in the user agent lol).

On the bright side I am no longer technically-homeless!

Having a place to live is definitely more important than fixing my bugs! I'm glad you have a place to live :)

Thought I'd follow up here and see if you had made any progress. As always, happy to donate to you (or your favorite charity) to help the cause.

Hi! Sorry, nothing. My next two weekends are already fully booked and evenings ceased to exist. Also thanks for the offer (i genuinely appreciate it) but it wouldn't really buy me more time.

I just tried again (in case they reverted whatever thing they were rolling out), and neither normal Facebook nor workplace work with 2fac, so I (and probably others) still have no ability to use purple-facebook at all.

@jaymzh @dequis
Just active app password in FB.
Settings > Security and Login > App Password > Create an app password.
Then login with that password and your FB username.

It's working for me.

Thanks

This did not work for me when I filed the Issue, but it does work now! It even works on Workplace!

nice. works for me now, too. previously app passwords did not work.

{"error_code":1,"error_msg":"An unknown error occurred (1)","request_args":[{"key":"method","value":"auth.login"},{"key":"api_key","value":"256002347743983"},{"key":"format","value":"json"},{"key":"first_factor","value":"XXXXXXXXXXXXXXX"},{"key":"credentials_type","value":"two_factor"},{"key":"sig","value":"YYYYYYYYYYYYYy"},{"key":"twofactor_code","value":"AAAAAAAAA"},{"key":"password","value":"--sanitized--"},{"key":"error_detail_type","value":"button_with_disabled"},{"key":"email","value":"phil\u0040ipom.com"}]}

Hi. Have you solved this problem yet? please help me

Very late to the party but if someone needs this, these parameters should work

        base_data = {
            "fb_api_req_friendly_name": "authenticate",
            "locale": "en",
            "email": self.email,
            "password": self.password,
            "format": "json",
            "api_key": FB_ANDROID_MESSENGER_API_KEY,
            "method": "auth.login",
            "generate_session_cookies": "1",
            "generate_machine_id": "1",
            "device_id": self.device_id,
        }

        data = base_data.copy()
        data["twofactor_code"] = input("2fa code here: ")

        data["credentials_type"] = "two_factor"
        data["error_detail_type"] = "button_with_disabled"
        data["first_factor"] = error_data["login_first_factor"]
        data["password"] = data["twofactor_code"]
        data["userid"] = error_data["uid"]
        data["machine_id"] = error_data["machine_id"]
        # data_dict["currently_logged_in_userid"] = data_dict["0"] # does not need i think

@jackblk given that FB just killed app passwords, this is very timely.

Where you able to use this to somehow get the pidgin plugin to work?

Also where are you getting self.device_id from, @jackblk ?

OK, just shoving in a UUID seems to work. Combining @jackblk 's stuff with my previous script, this seems to work. Jam your email and a random UUID at the top

#!/usr/bin/python2.7

import sys
import cgi
from urllib import urlencode
import hashlib
import getpass
import httplib
import urllib
import json
from optparse import OptionParser

DEBUG = False
# put a UUID in here
MACHINE_ID = ''
# put your email in here
EMAIL = ''
FB_API_KEY = '256002347743983'
FB_API_SECRET = '374e60f8b9bb6b8cbb30f78030438895'


def fb_sig(data):
    newdata = data.copy()
    params = ''.join(['%s=%s' % x for x in sorted(data.items())])
    newdata['sig'] = hashlib.md5(params + FB_API_SECRET).hexdigest()
    return newdata

def debug(msg):
    global DEBUG
    if DEBUG:
        print "DEBUG: %s", msg

if EMAIL == '':
    print "ERROR: set an email address, please"
    sys.exit()

if MACHINE_ID == '':
    print "ERROR: set a machine id (to any UUID), please"
    sys.exit()

parser = OptionParser()
parser.add_option('-d', '--debug', action='store_true', dest='debug', default=False)
(options, args) = parser.parse_args()

if options.debug:
    DEBUG = True

data = {
    "fb_api_req_friendly_name": "authenticate",
    "locale": "en",
    "format": "json",
    "api_key": FB_API_KEY,
    "method": "auth.login",
    "generate_session_cookies": "1",
    "generate_machine_id": "1",
    "email": EMAIL,
    "device_id": MACHINE_ID,
}
data['password'] = getpass.getpass()

headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "*/*"}
conn = httplib.HTTPSConnection('b-api.facebook.com:443')
params = urllib.urlencode(fb_sig(data))
conn.request('POST', '/method/auth.login', params, headers)
response = conn.getresponse()
debug("status, reason: %s, %s" % (response.status, response.reason))
response_data = response.read()
debug("undecoded response: %s" % response_data)
response = json.loads(response_data)
debug(response)
error_data = json.loads(response['error_data'])
first_fac = error_data['login_first_factor']

data['credentials_type'] = 'two_factor'
data['error_detail_type'] = 'button_with_disabled'
data['first_factor'] = first_fac
data['twofactor_code'] = getpass.getpass('Code: ')
data['password'] = data['twofactor_code']
data['userid'] = error_data['uid']
data['machine_id'] = error_data['machine_id']

params = urllib.urlencode(fb_sig(data))
conn.request('POST', '/method/auth.login', params, headers)
response = conn.getresponse()
debug("status, reason: %s, %s" % (response.status, response.reason))
response_data = response.read()
debug("undecoded response: %s" % response_data)
response = json.loads(response_data)
print response

But doing as @dequis recommended and putting the resulting access_token into token into accounts.xml does not work for me, I still get a "Login approvals are on..." error. Also 'stoken' keeps showing up. I put the token into both token and stoken, but I haven't made it work yet. Anyone else?

@jackblk given that FB just killed app passwords, this is very timely.

Where you able to use this to somehow get the pidgin plugin to work?

Well app password is dead so my app is dead too, so I try to find a way for 2fa. I currently don't use pidgin anymore so I'm not sure ๐Ÿ˜ข. But the token & cookies are usable for my app.

OK, just shoving in a UUID seems to work. Combining @jackblk 's stuff with my previous script, this seems to work. Jam your email and a random UUID at the top

#!/usr/bin/python2.7

import sys
import cgi
from urllib import urlencode
import hashlib
import getpass
import httplib
import urllib
import json
from optparse import OptionParser

DEBUG = False
# put a UUID in here
MACHINE_ID = ''
# put your email in here
EMAIL = ''
FB_API_KEY = '256002347743983'
FB_API_SECRET = '374e60f8b9bb6b8cbb30f78030438895'


def fb_sig(data):
    newdata = data.copy()
    params = ''.join(['%s=%s' % x for x in sorted(data.items())])
    newdata['sig'] = hashlib.md5(params + FB_API_SECRET).hexdigest()
    return newdata

def debug(msg):
    global DEBUG
    if DEBUG:
        print "DEBUG: %s", msg

if EMAIL == '':
    print "ERROR: set an email address, please"
    sys.exit()

if MACHINE_ID == '':
    print "ERROR: set a machine id (to any UUID), please"
    sys.exit()

parser = OptionParser()
parser.add_option('-d', '--debug', action='store_true', dest='debug', default=False)
(options, args) = parser.parse_args()

if options.debug:
    DEBUG = True

data = {
    "fb_api_req_friendly_name": "authenticate",
    "locale": "en",
    "format": "json",
    "api_key": FB_API_KEY,
    "method": "auth.login",
    "generate_session_cookies": "1",
    "generate_machine_id": "1",
    "email": EMAIL,
    "device_id": 'aba9641c-fed9-4af8-8d32-d12e0dafd506',
}
data['password'] = getpass.getpass()

headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "*/*"}
conn = httplib.HTTPSConnection('b-api.facebook.com:443')
params = urllib.urlencode(fb_sig(data))
conn.request('POST', '/method/auth.login', params, headers)
response = conn.getresponse()
debug("status, reason: %s, %s" % (response.status, response.reason))
response_data = response.read()
debug("undecoded response: %s" % response_data)
response = json.loads(response_data)
debug(response)
error_data = json.loads(response['error_data'])
first_fac = error_data['login_first_factor']

data['credentials_type'] = 'two_factor'
data['error_detail_type'] = 'button_with_disabled'
data['first_factor'] = first_fac
data['twofactor_code'] = getpass.getpass('Code: ')
data['password'] = data['twofactor_code']
data['userid'] = error_data['uid']
data['machine_id'] = error_data['machine_id']

params = urllib.urlencode(fb_sig(data))
conn.request('POST', '/method/auth.login', params, headers)
response = conn.getresponse()
debug("status, reason: %s, %s" % (response.status, response.reason))
response_data = response.read()
debug("undecoded response: %s" % response_data)
response = json.loads(response_data)
print response

But doing as @dequis recommended and putting the resulting access_token into token into accounts.xml does not work for me, I still get a "Login approvals are on..." error. Also 'stoken' keeps showing up. I put the token into both token and stoken, but I haven't made it work yet. Anyone else?

I was able to use this script and add the token and it worked. This is what I added into accounts.xml in the settings section:
<setting name='token' type='string'>(value of access_token)</setting>

I was able to use this script and add the token and it worked. This is what I added into accounts.xml in the settings section: <setting name='token' type='string'>(value of access_token)</setting>

Aha!! I was missing the type attribute. Thanks!! That works!

Eh, I spoke to soon. It worked briefly. Then my account got locked. I unlocked it and now that gives me invalid username or password

Eh, I spoke to soon. It worked briefly. Then my account got locked. I unlocked it and now that gives me invalid username or password

Oh scratch that. Had to update the password in pidgin (didn't realize it used that once it had a token).

Yeah me too. But I was able to solve it this way:

  1. Put the new password into pidgin
  2. Close pidgin, open accounts.xml
  3. Clear the stoken and token setting (as in, delete all those settings, not the value)
  4. Reauthenticate, get new token, put new token in accounts.xml again
"device_id": 'aba9641c-fed9-4af8-8d32-d12e0dafd506',

I had to replace this with MACHINE_ID and generate my own uuid into MACHINE_ID, but then it worked! Thanks a lot for the script.

"device_id": 'aba9641c-fed9-4af8-8d32-d12e0dafd506',

I had to replace this with MACHINE_ID and generate my own uuid into MACHINE_ID, but then it worked! Thanks a lot for the script.

Ooops, fixed. Thanks!

I put some work into the script to make it a bit easier for people not familiar with python or dicts or other stuff. It will spit out errors in some common cases instead of crashing. It's also now in py3. Here's a script, that when you run it, looks like this:

$ pidgin-fb-login-test.py
Access Token generator for Facebook 2factor login

This tool will perform 2-factor login to FB and then print out an
access token needed for the FB plugin for bitlbee and pidgin. Take
the resulting code and put it in the "token" tag in accounts.xml

Password: 
Code: 
Access token: <.....>

You need to edit the script to add your email and a UUID (which you can get by running uuidgen), but that's it. The script is here:

https://gist.github.com/jaymzh/3ed8817cf8c20222ca09ce33a544b695

Question about this process that I didn't see an obvious answer to:

if one does:

$ grep -A90 -i prpl-facebook accounts.xml | sed -n '/<\/account>/q;p' | grep setting | egrep 'id|token'

you can see that there already exists some data that looks like it's trying to use in the script:

                        <setting name='cid' type='string'>${CID-base64}</setting>
                        <setting name='uid' type='string'>${UID-base10}</setting>
                        <setting name='did' type='string'>${DID-uuid}</setting>
                        <setting name='mid' type='string'>${MID-base10}</setting>
                        <setting name='token' type='string'>${TOKEN-base64}</setting>

or at the very least, is returned by the python script above:

{u'machine_id': u'${MID-base64}', u'uid': ${UID-base10},u'access_token': u'${TOKEN-base64}'

(the only one not in the response is "cid")

Should we be making sure to update those lines from the response into accounts.xml as well, or just TOKEN?

Also, would it make sense, if it's available, to just use the 'did' from accounts.xml in the python script?

just thinking out loud here...

So, the short answer is: as long as the token is set, it doesn't seem to matter much. And to be clear, this script is just a short-term stop gap until @grimmy get is PR up that implements this natively, otherwise I'd be trying to make it parse-and-edit the accounts.xml, but I didn't care that much for something that I hope dies in a week or two.

All that said, those are all good questions. I actually used mid as my device_id, but I'm not sure. I imagine, looking at the flow you're supposed to pass in a device_id, and then use the machine_id you get back when you auth, but I don't actually know

Yeah, sorry I'm super busy the next few days and not sure when I'll be able to finish this, but I'm hoping I'll have it done next week.

I put some work into the script to make it a bit easier for people not familiar with python or dicts or other stuff. It will spit out errors in some common cases instead of crashing. It's also now in py3. Here's a script, that when you run it, looks like this:

$ pidgin-fb-login-test.py
Access Token generator for Facebook 2factor login

This tool will perform 2-factor login to FB and then print out an
access token needed for the FB plugin for bitlbee and pidgin. Take
the resulting code and put it in the "token" tag in accounts.xml

Password: 
Code: 
Access token: <.....>

You need to edit the script to add your email and a UUID (which you can get by running uuidgen), but that's it. The script is here:

https://gist.github.com/jaymzh/3ed8817cf8c20222ca09ce33a544b695

actually you must use 'did' (device_id) mentioned in the accounts.xml instead of an random uuid, this is how worked on me

Tip: the stoken setting is actually the OAuth2 secret. Entering that in solved the problem of facebook straight up locking my account after using Pidgin

This is how I logged in successfully (a more detailed version for those who are begginers with pidgin):

  1. Setup the fb account from pidgin and add the account.
  2. Open the accounts.xml and check for 'did''s value and copy it
  3. Setup the python file, put your accounts.xml's 'did'(which indeed is an UUID) to the MACHINE_ID, write your email and save it
  4. Open the terminal and execute the python file
  5. Write down your password and the code you got from the SMS
  6. Copy the token, go to the value <setting name='token' /> and modify it so it must look something like this <settings name='token' type='string'>(the token you got)</setting>
  7. Save the accounts.xml file, then go to the pidgin>buddies>quit
  8. Open the pidgin again, so the program will login using the token instead of resending login request and deleting it.
  9. Now you have logged in successfully.

Yeah me too. But I was able to solve it this way:

1. Put the new password into pidgin

2. Close pidgin, open accounts.xml

3. Clear the stoken and token setting (as in, delete all those settings, not the value)

4. Reauthenticate, get new token, put new token in accounts.xml again

Suddenly unable to use 2fa again, using the script above.
(or using my merged script that parses accounts.xml, grabs the correct data, and call the fb login page: https://raw.githubusercontent.com/akhepcat/Miscellaneous/master/pidgin-fb-login-2fa.py )

Don't have enough skills to burp this and figure out what's changed, only that it's not working anymore for me.

@akhepcat it broke for me as well in the past 24h, but I have managed to get my bitlbee-facebook account back online by simply generating another token.

On my side, I keep getting 401 invalid username or password even tho I'm pretty sure I did everything right !

@Azizb750 's instructions with the script worked perfectly for me.

akorn commented

It's possible to extract the relevant IDs from bitlbee's nickname.xml too; the script (either script) spits out a token, account facebook token <......>, and account facebook on works again.

This is a tremendous quality of life improvement. Thanks a lot!

Ahhhh. Yes, the scripts are working just fine.

I was being too quick, and dismissing the "Did you just attempt to sign in" pop-up, which of course stops the SMS code send. Oops.

Ignoring that pop-up and waiting for the SMS made the flow work just fine. Can't believe i didn't try that sooner.

Hi.

I have tried several times to get this to work again. It was working before, but suddenly stopped working for some time ago.

Tried to follow Azizb750's instructions, but still no luck. But I need to quit Pidgin before editing the accounts.xml file, if not it seems like the token gets overwritten by the blank/default one.

When I start Pidgin after entering the token, I always get "An unknown error occurred (1)".

Any idea?

I've been banging my head against this one all night. Was about to give up until I got it to work. For reference, Im using Debian Stretch and Bitlbee via Bitlbee's debian repository.

Got to the point where I rewrote the pidgin token generation script script for bitlbee (bitlbee-purple uses a different location for accounts.xml and apparently doesn't properly know how to write to it, it's a known bug).

https://bugs.bitlbee.org/ticket/1327

I prefer the Glowing Bear frontend for weechat relay over Pidgin /w GTK2/3 or Finch /w tmux/screen. I had to do some system kludges because bitlbee encrypts passwords in its configuration and it doesn't let anyone beyond root and the bitlbee service user account touch the config. I'm able to handle the former by just prompting for the password using getpass.getpass instead of reading it from xml, but the latter has to be done from bitlbee and a shell. I also modified the xml to json conversion to account for the differences in the bitlbee configuration versus libpurple.

Here's my procedure before running the script:

In bitlbee

acc facebook del
save
acc add facebook email password
acc facebook on
acc facebook off
accept the 2FA challenge on website to prevent a lockout
save

The final save command will populate /var/lib/bitlbee/USERNAME.xml with the uid/gid/mid/cid. The UID will always be zero, but that's OK since it will update on the first JSON resquest/response and stay the same after that, along with the DID. The MID should change however during the process.

Command Line as non-privileged user

$ sudo chmod o+x /var/lib/bitlbee
$ sudo chmod o+r /var/lib/bitlbee/USERNAME.xml

Run the script

$ cat /etc/issue
Debian GNU/Linux 9 \n \l
$ dpkg -l | grep bitlbee-common
ii  bitlbee-common                       3.6-1+20211113+master+36-gcfe7243b-git amd64        IRC to other chat networks gateway (common files/docs)
$ dpkg -l | grep bitlbee-facebook
ii  bitlbee-facebook                     20210212~8bd37d9~44                    amd64        Facebook protocol plugin for BitlBee
$ python3 --version
Python 3.5.3
$ python3 bitlbee-fb-login-2fa.py

Initial Output

Config xml file is:  /var/lib/bitlbee/USERNAME.xml
Password:
DEBUG: %s Account UID: 0
DEBUG: %s Account DID: --sanitized--
DEBUG: %s Account MID: --sanitized--
Access Token generator for Facebook 2factor login

This tool will perform 2-factor login to FB and then print out an
access token needed for the FB plugin for bitlbee and pidgin. Take
the resulting code and set the token for facebook in bitlbee

DEBUG: %s status, reason: 200, OK
DEBUG: %s undecoded response: b'{"error_code":406,"error_msg":"Login approvals are on. Expect an SMS shortly with a code to use for log in (406)","error_data":"{\\"machine_id\\":\\"--sanitized--\\",\\"uid\\":--sanitized--,\\"login_first_factor\\":\\"FDl9pFNMcxFgpRek8qAKTsuSaRUpOLUZ\\",\\"support_uri\\":\\"https:\\\\\\/\\\\\\/m.facebook.com\\\\\\/two_factor\\\\\\/id_upload\\\\\\/mobile\\\\\\/?idd=AYjJQ0DH9SwUAxVb3I8bazawWtryrf1DsmTFnqjbvdmyUOhSM1XvMinRtmUenegi7NnakqzjSIA5LmQdjypePeNZ&nonce=iAXIqa3Tiebum5up&ext=1648541875&hash=AeT1dCEPQM3er3ZLa2E\\",\\"auth_token\\":\\"1648282675.tr.s:pw.tDBGAiEAxOGw0e2MlQLJzOdlM8G3Bm25GOkApuy2RcSDAcNthsECIQDch2GzWMlL4CJg4JqWoeg2iEyaSsuBYFNScjNgTpmMAw\\",\\"error_title\\":\\"Login Code Required\\",\\"error_message\\":\\"Generate a code from your authentication app or use the code we sent by text message to log in.\\"}","request_args":[{"key":"method","value":"auth.login"},{"key":"uid","value":"0"},{"key":"email","value":"--sanitized--"},{"key":"password","value":"--sanitized--"},{"key":"fb_api_req_friendly_name","value":"authenticate"},{"key":"format","value":"json"},{"key":"sig","value":"977a1ac7d0d1cb3e120f58ae7290f4b7"},{"key":"generate_session_cookies","value":"1"},{"key":"api_key","value":"256002347743983"},{"key":"device_id","value":"--sanitized--"},{"key":"locale","value":"en"},{"key":"generate_machine_id","value":"1"}]}'
Traceback (most recent call last):
  File "bitlbee-fb-login-2fa.py", line 148, in <module>
    response = json.loads(response_data)
  File "/usr/lib/python3.5/json/__init__.py", line 312, in loads
    s.__class__.__name__))
TypeError: the JSON object must be str, not 'bytes'

The script dies before it can load the first request's response data. Some quick googling tells me I need to use decode('utf-8'), so I modified the two instances in the script

from:

response = json.loads(response_data)

to
response = json.loads(response_data.decode('utf-8'))

After those changes, on the next attempt, we finally have something! We get prompted for the 2FA code (note: I wore out my welcome on SMS 2FA debugging, but as other people pointed out codes from your authenticator app work fine). After entering it we get another error, but we also get the Access Token!

DEBUG: %s {'error_data': '{"machine_id":"--sanitized--","uid":--sanitized--,"login_first_factor":"FDl9pFNMcxFgpRek8qAKTsuSaRUpOLUZ","support_uri":"https:\\/\\/m.facebook.com\\/two_factor\\/id_upload\\/mobile\\/?idd=AYjJQ0DH9SwUAxVb3I8bazawWtryrf1DsmTFnqjbvdmyUOhSM1XvMinRtmUenegi7NnakqzjSIA5LmQdjypePeNZ&nonce=iAXIqa3Tiebum5up&ext=1648541875&hash=AeT1dCEPQM3er3ZLa2E","auth_token":"1648282675.tr.s:pw.tDBGAiEAxOGw0e2MlQLJzOdlM8G3Bm25GOkApuy2RcSDAcNthsECIQDch2GzWMlL4CJg4JqWoeg2iEyaSsuBYFNScjNgTpmMAw","error_title":"Login Code Required","error_message":"Generate a code from your authentication app or use the code we sent by text message to log in."}', 'request_args': [{'key': 'method', 'value': 'auth.login'}, {'key': 'uid', 'value': '0'}, {'key': 'email', 'value': '--sanitized--'}, {'key': 'password', 'value': '--sanitized--'}, {'key': 'fb_api_req_friendly_name', 'value': 'authenticate'}, {'key': 'format', 'value': 'json'}, {'key': 'sig', 'value': '977a1ac7d0d1cb3e120f58ae7290f4b7'}, {'key': 'generate_session_cookies', 'value': '1'}, {'key': 'api_key', 'value': '256002347743983'}, {'key': 'device_id', 'value': '--sanitized--'}, {'key': 'locale', 'value': 'en'}, {'key': 'generate_machine_id', 'value': '1'}], 'error_msg': 'Login approvals are on. Expect an SMS shortly with a code to use for log in (406)', 'error_code': 406}
Code: --sanitized--
DEBUG: %s FB Account UID: --sanitized--
FB Account DID not present in error_data: equal to CONFIGFILE.xml did
DEBUG: %s FB Account MID: --sanitized-- (Different value)
DEBUG: %s status, reason: 200, OK
DEBUG: %s undecoded response: b'{"session_key":"5.2B_90p2gYtEZbQ.1648282680.44---sanitized--","uid":--sanitized--,"secret":"5322e5eea92b31d35fe5759b607175b7","access_token":"--sanitized--","machine_id":"OMw-YlVqiess-DymY0U9Bp_f","session_cookies":[{"name":"c_user","value":"--sanitized--","expires":"Sun, 26 Mar 2023 08:18:00 GMT","expires_timestamp":1679818680,"domain":".facebook.com","path":"\\/","secure":true},{"name":"xs","value":"44:2B_90p2gYtEZbQ:2:1648282680:-1:2878","expires":"Sun, 26 Mar 2023 08:18:00 GMT","expires_timestamp":1679818680,"domain":".facebook.com","path":"\\/","secure":true,"httponly":true},{"name":"fr","value":"0by5rp800nXJJa6VE.AWUm-omy2nw5qxZcc5R5aIqioiw.BiPsw4..AAA.0.0.BiPsw4.AWVoWUowGYE","expires":"Fri, 24 Jun 2022 08:17:58 GMT","expires_timestamp":1656058678,"domain":".facebook.com","path":"\\/","secure":true,"httponly":true},{"name":"datr","value":"OMw-YlVqiess-DymY0U9Bp_f","expires":"Mon, 25 Mar 2024 08:18:00 GMT","expires_timestamp":1711354680,"domain":".facebook.com","path":"\\/","secure":true,"httponly":true}],"user_storage_key":"632766dc3ef8444f125aabab0720310762e228d8523c8a8ad67395d8269c025f"}'
Access token: --sanitized--
Traceback (most recent call last):
  File "bitlbee-fb-login-2fa.py", line 193, in <module>
    if ( DID != response['device_id'] ):
KeyError: 'device_id'

I ran the following in bitlbee afterward

acc facebook set token ACCESS_TOKEN
acc facebook on

And it connects! Simply set your options and setup your control channel.

acc facebook set OPTION VALUE
/join &facebook
chan list
chan $N set auto_join true
acc facebook off
acc facebook on
save

To open the control channel on connect, you have to join the control channel, find it in the channel list, and set auto_join on the channel id to true. The final save command will automatically revert your changes to permissions on /var/lib/bitlbee and /var/lib/bitlbee/USERNAME.xml

Looking at the behavior of the script and the JSON requests, I think what is happening is that on the first JSON request, the Facebook API is generating a new DID on its end using uuidgen or something similar and expects the same UUID for the next JSON request/response. I noticed when I finally connected and saved my bitlbee config that the DID stayed the same after the initial response and after bitlbee connected and the config was saved. You still get the KeyError, but you should still get the Access Token since it's in a try/execpt block and the UUID-format DID does not appear to change.

I have uploaded my modified script for bitlbee users to a gist for reference:

https://gist.github.com/Tatsujin/953551fe38d8e38aac43b423998d3deb

I hope you find this useful. If you are using the pidgin script, you might want to try making the decode changes I suggested for the json_loads. Good luck!

Thanks, @Tatsujin - i've upated my script to include the utf-8 parsing as well.

https://raw.githubusercontent.com/akhepcat/Miscellaneous/master/pidgin-fb-login-2fa.py

Thanks akhepcat for creating this script.

I tried it, but got the following error:

Traceback (most recent call last):
File "./pidgin-fb-login-2fa.py", line 181, in
error_data = json.loads(response['error_data'].decode('utf-8'))
AttributeError: 'str' object has no attribute 'decode'

Thanks akhepcat for creating this script.

I tried it, but got the following error:

Traceback (most recent call last): File "./pidgin-fb-login-2fa.py", line 181, in error_data = json.loads(response['error_data'].decode('utf-8')) AttributeError: 'str' object has no attribute 'decode'

Ah, try removing the ".decode('utf-8')" from that line.

It worked here on my test, but maybe it's failing in some cases, and may not really be needed.

Hi and thanks for the quick reply.

Tried to remove the decode part, and then I get the token. But also the following error after entering the code:

FB Account DID not present in error_data: equal to accounts.xml did
Access token: ***
Traceback (most recent call last):
File "./pidgin-fb-login-2fa.py", line 212, in
if ( DID != response['device_id'] ):
KeyError: 'device_id'

Tried to add the token to accounts.xml manually, but still get the "An unknown error occurred (1)" when I tries to connect. This is probably not something wrong with the script, but something else. Tried to delete and set up the Facebook account again in Pidgin, but same issue. I get a security notice in Facebook when I try to connect, but I click "Yes, it was me" and "Save browser". But still no luck when try to connect.

Hi and thanks for the quick reply.

Tried to remove the decode part, and then I get the token. But also the following error after entering the code:

FB Account DID not present in error_data: equal to accounts.xml did Access token: *** Traceback (most recent call last): File "./pidgin-fb-login-2fa.py", line 212, in if ( DID != response['device_id'] ): KeyError: 'device_id'

Tried to add the token to accounts.xml manually, but still get the "An unknown error occurred (1)" when I tries to connect. This is probably not something wrong with the script, but something else. Tried to delete and set up the Facebook account again in Pidgin, but same issue. I get a security notice in Facebook when I try to connect, but I click "Yes, it was me" and "Save browser". But still no luck when try to connect.

Try the latest version, that should all be fixed now.

Hi and thanks for the quick reply.
Tried to remove the decode part, and then I get the token. But also the following error after entering the code:
FB Account DID not present in error_data: equal to accounts.xml did Access token: *** Traceback (most recent call last): File "./pidgin-fb-login-2fa.py", line 212, in if ( DID != response['device_id'] ): KeyError: 'device_id'
Tried to add the token to accounts.xml manually, but still get the "An unknown error occurred (1)" when I tries to connect. This is probably not something wrong with the script, but something else. Tried to delete and set up the Facebook account again in Pidgin, but same issue. I get a security notice in Facebook when I try to connect, but I click "Yes, it was me" and "Save browser". But still no luck when try to connect.

Try the latest version, that should all be fixed now.

Hi
I tried the script by @akhepcat on https://raw.githubusercontent.com/akhepcat/Miscellaneous/master/pidgin-fb-login-2fa.py, pasted the xml code in accounts.xml and I get this error:
OuxmPbFU6n
When I try to connect again, the error stays the same :( .

That happens, I think, when it's trying to sync your history and it's too big.

Go to edit account, advanced, uncheck "show unread messages". The connect, it should work. Once that's done, you can re-check that box and it should work.

That happens, I think, when it's trying to sync your history and it's too big.

Go to edit account, advanced, uncheck "show unread messages". The connect, it should work. Once that's done, you can re-check that box and it should work.

Hi. Thanks for the reply
Followed your instructions. I unchecked all the boxes in the "Advanced" tab and reconnect. The error stays the same :( .

image

kflu commented

I ported @jaymzh 's script to python3 and uploaded as a gist here: https://gist.github.com/kflu/1e7c358989b0556e99a919face1ca2f7

Changes:

  • Ported to Python3
  • Auto generate the MACHINE_ID UUID
  • Can set EMAIL from environment variable

USAGE:

FBEMAIL=<YOUR_EMAIL> python3 fb_get_token.py

If you have 2FA, it will ask for the 2FA code after you type in password.

OK, just shoving in a UUID seems to work. Combining @jackblk 's stuff with my previous script, this seems to work. Jam your email and a random UUID at the top

#!/usr/bin/python2.7

import sys
import cgi
from urllib import urlencode
import hashlib
import getpass
import httplib
import urllib
import json
from optparse import OptionParser

DEBUG = False
# put a UUID in here
MACHINE_ID = ''
# put your email in here
EMAIL = ''
FB_API_KEY = '256002347743983'
FB_API_SECRET = '374e60f8b9bb6b8cbb30f78030438895'


def fb_sig(data):
    newdata = data.copy()
    params = ''.join(['%s=%s' % x for x in sorted(data.items())])
    newdata['sig'] = hashlib.md5(params + FB_API_SECRET).hexdigest()
    return newdata

def debug(msg):
    global DEBUG
    if DEBUG:
        print "DEBUG: %s", msg

if EMAIL == '':
    print "ERROR: set an email address, please"
    sys.exit()

if MACHINE_ID == '':
    print "ERROR: set a machine id (to any UUID), please"
    sys.exit()

parser = OptionParser()
parser.add_option('-d', '--debug', action='store_true', dest='debug', default=False)
(options, args) = parser.parse_args()

if options.debug:
    DEBUG = True

data = {
    "fb_api_req_friendly_name": "authenticate",
    "locale": "en",
    "format": "json",
    "api_key": FB_API_KEY,
    "method": "auth.login",
    "generate_session_cookies": "1",
    "generate_machine_id": "1",
    "email": EMAIL,
    "device_id": MACHINE_ID,
}
data['password'] = getpass.getpass()

headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "*/*"}
conn = httplib.HTTPSConnection('b-api.facebook.com:443')
params = urllib.urlencode(fb_sig(data))
conn.request('POST', '/method/auth.login', params, headers)
response = conn.getresponse()
debug("status, reason: %s, %s" % (response.status, response.reason))
response_data = response.read()
debug("undecoded response: %s" % response_data)
response = json.loads(response_data)
debug(response)
error_data = json.loads(response['error_data'])
first_fac = error_data['login_first_factor']

data['credentials_type'] = 'two_factor'
data['error_detail_type'] = 'button_with_disabled'
data['first_factor'] = first_fac
data['twofactor_code'] = getpass.getpass('Code: ')
data['password'] = data['twofactor_code']
data['userid'] = error_data['uid']
data['machine_id'] = error_data['machine_id']

params = urllib.urlencode(fb_sig(data))
conn.request('POST', '/method/auth.login', params, headers)
response = conn.getresponse()
debug("status, reason: %s, %s" % (response.status, response.reason))
response_data = response.read()
debug("undecoded response: %s" % response_data)
response = json.loads(response_data)
print response

But doing as @dequis recommended and putting the resulting access_token into token into accounts.xml does not work for me, I still get a "Login approvals are on..." error. Also 'stoken' keeps showing up. I put the token into both token and stoken, but I haven't made it work yet. Anyone else?

BTW, I stopped using my script and used @akhepcat 's fork of my script, it's nicer in that it pulls the password automatically from the file and such.