From 389909b4247cd6c327632f1965aaa5a94cf0e6c9 Mon Sep 17 00:00:00 2001 From: Andreas Thienemann Date: Wed, 20 Mar 2019 00:48:54 +0100 Subject: [PATCH 1/5] Allow for different hotp files/secrets Just call as ./gen_hotp.py . If filename is omitted, use default duotoken.hotp. --- duo_gen.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/duo_gen.py b/duo_gen.py index c001ccd..49c185c 100755 --- a/duo_gen.py +++ b/duo_gen.py @@ -7,7 +7,12 @@ import json import sys from urllib2 import unquote -f = open("duotoken.hotp","r+"); +if len(sys.argv) == 2: + file = sys.argv[1] +else: + file = "duotoken.hotp" + +f = open(file, "r+"); secret = f.readline()[0:-1] offset = f.tell() count = int(f.readline()) From 5cb56001048efd84d377fcae3459961bf5d8c9ca Mon Sep 17 00:00:00 2001 From: Andreas Thienemann Date: Wed, 20 Mar 2019 00:51:29 +0100 Subject: [PATCH 2/5] Update activation code. The duo activation process has changed, the server now expects way more information from the client as well as a new URL scheme. Make the server happy, provide some fake data. Save server response as a response.json file for later use. Sanity check response, abort if no secret was passed. --- duo_activate.py | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/duo_activate.py b/duo_activate.py index 30a3204..48dbf5b 100755 --- a/duo_activate.py +++ b/duo_activate.py @@ -11,26 +11,35 @@ if len(sys.argv) < 2: print "Usage: python duo_bypass.py "; exit() qr_url = sys.argv[1] -data = qr_url #unquote(qr_url.split('=')[1]) -hostb64 = data.split('-')[1] +host = 'api-%s' % (qr_url.split('/')[2].split('-')[1],) +code = qr_url.rsplit('/',1)[1] -print "hostb64", hostb64 +url = 'https://{host}/push/v2/activation/{code}?customer_protocol=1'.format(host=host, code=code) +headers = {'User-Agent': 'okhttp/2.7.5'} +data = {'jailbroken': 'false', + 'architecture': 'armv7', + 'region': 'US', + 'app_id': 'com.duosecurity.duomobile', + 'full_disk_encryption': 'true', + 'passcode_status': 'true', + 'platform': 'Android', + 'app_version': '3.23.0', + 'app_build_number': '323001', + 'version': '8.1', + 'manufacturer': 'unknown', + 'language': 'en', + 'model': 'Pixel C', + 'security_patch_level': '2018-12-01'} -host = base64.b64decode(hostb64 + '='*(-len(hostb64) % 4)) -code = data.split('-')[0] - -print "host", host -print "code", code - -url = 'https://{host}/push/v2/activation/{code}'.format(host=host, code=code) -r = requests.post(url) +r = requests.post(url, headers=headers, data=data) response = json.loads(r.text) -print "url", url -print "r", r -print "response", response -secret = base64.b32encode(response['response']['hotp_secret']) +try: + secret = base64.b32encode(response['response']['hotp_secret']) +except KeyError: + print response + sys.exit(1) print "secret", secret @@ -45,3 +54,6 @@ f.write(secret + "\n") f.write("0") f.close() +with open('response.json', 'w') as resp: + resp.write(r.text) + From aa4807cc6a81601922d799cedc6ec339e5bf135b Mon Sep 17 00:00:00 2001 From: Andreas Thienemann Date: Wed, 20 Mar 2019 11:41:31 +0100 Subject: [PATCH 3/5] Python 3 support --- duo_activate.py | 14 +++++++------- duo_gen.py | 12 ++++-------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/duo_activate.py b/duo_activate.py index 48dbf5b..6d2ed44 100755 --- a/duo_activate.py +++ b/duo_activate.py @@ -1,14 +1,14 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python3 import pyotp import requests import base64 import json import sys -from urllib2 import unquote if len(sys.argv) < 2: - print "Usage: python duo_bypass.py "; exit() + print("Usage: python duo_bypass.py ") + sys.exit() qr_url = sys.argv[1] @@ -38,16 +38,16 @@ response = json.loads(r.text) try: secret = base64.b32encode(response['response']['hotp_secret']) except KeyError: - print response + print(response) sys.exit(1) -print "secret", secret +print("secret", secret) -print "10 Next OneTime Passwords!" +print("10 Next OneTime Passwords!") # Generate 10 Otps! hotp = pyotp.HOTP(secret) for _ in xrange(10): - print hotp.at(_) + print(hotp.at(_)) f = open('duotoken.hotp', 'w') f.write(secret + "\n") diff --git a/duo_gen.py b/duo_gen.py index 49c185c..d4da768 100755 --- a/duo_gen.py +++ b/duo_gen.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python3 import pyotp -import requests -import base64 -import json import sys -from urllib2 import unquote if len(sys.argv) == 2: file = sys.argv[1] @@ -17,11 +13,11 @@ secret = f.readline()[0:-1] offset = f.tell() count = int(f.readline()) -print "secret", secret -print "count", count +print("secret", secret) +print("count", count) hotp = pyotp.HOTP(secret) -print "Code:", hotp.at(count) +print("Code:", hotp.at(count)) f.seek(offset) f.write(str(count + 1)) From 75bec452a420e5d98065b822ca051ec2ae2e2f0f Mon Sep 17 00:00:00 2001 From: Andreas Thienemann Date: Wed, 20 Mar 2019 11:57:20 +0100 Subject: [PATCH 4/5] Add qr code generator for third party apps. --- README.md | 14 ++++++++++---- duo_export.py | 24 ++++++++++++++++++++++++ duo_gen.py | 1 - 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100755 duo_export.py diff --git a/README.md b/README.md index 4c629d9..d630831 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ ## Duo One Time Password Generator -This is a little script I put together after I reverse engineered the Duo 2FA -Mobile App and figured out how their auth flow works. This can be ported into -probably a useful desktop app or chrome extention and can probably be used to +This is a little script I put together after I reverse engineered the Duo 2FA +Mobile App and figured out how their auth flow works. This can be ported into +probably a useful desktop app or chrome extention and can probably be used to write bots for MIT Services that require auth. ### Usage @@ -27,6 +27,12 @@ If everything worked you can then generate a code by running: ./duo_gen.py ``` -Warning: These are HOTP tokens and generate codes increments a counter. If you +Warning: These are HOTP tokens and generate codes increments a counter. If you get too far out of sync with the server it will stop accepting your codes. +``` +./duo_export.py +``` + +Export the duo hotp secret as a QR code for inclusion in third-party hotp apps +like freeotp. diff --git a/duo_export.py b/duo_export.py new file mode 100755 index 0000000..d69683d --- /dev/null +++ b/duo_export.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +import pyotp +import pyqrcode +import json +import base64 +import sys + +file_json = "response.json" + +with open('response.json', "r") as f: + response = json.loads(f.read())['response'] + +with open('duotoken.hotp', "r") as f: + counter = int(f.readlines()[1]) + +label = response['customer_name'] +issuer = 'Duo' +# base32 encoded hotp secret, with the padding ("=") stripped. +secret = base64.b32encode(bytes(response['hotp_secret'], 'utf-8')).decode('utf-8').replace('=', '') +qrdata = 'otpauth://hotp/{label}?secret={secret}&issuer={issuer}&counter={counter}'.format(label=label, secret=secret, issuer=issuer, counter=counter) +qrcode = pyqrcode.create(qrdata) +print(qrcode.terminal(quiet_zone=1)) +print(qrdata) diff --git a/duo_gen.py b/duo_gen.py index d4da768..da92bc2 100755 --- a/duo_gen.py +++ b/duo_gen.py @@ -22,4 +22,3 @@ print("Code:", hotp.at(count)) f.seek(offset) f.write(str(count + 1)) f.close() - From a29fbe22cf1ae617482e718ef5e426873e6a1d8b Mon Sep 17 00:00:00 2001 From: Andreas Thienemann Date: Wed, 20 Mar 2019 14:49:35 +0100 Subject: [PATCH 5/5] Improve gitignore. Do not store token secret. --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7e99e36..9a23e3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -*.pyc \ No newline at end of file +*.pyc +duotoken.hotp +response.json