From 5a410e565f584a7b57280a1f00fd9170af548c8f Mon Sep 17 00:00:00 2001 From: Ali Mashtizadeh Date: Sat, 1 Dec 2018 20:30:08 -0500 Subject: [PATCH] Cleanup and built a barebones command line interface --- README.md | 23 ++++++++++++++--------- duo_bypass.py => duo_activate.py | 22 ++++++++++++++++++++-- duo_gen.py | 24 ++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 11 deletions(-) rename duo_bypass.py => duo_activate.py (65%) mode change 100644 => 100755 create mode 100755 duo_gen.py diff --git a/README.md b/README.md index 4bb4818..4c629d9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ ## 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 write bots for MIT Services that require auth. +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 @@ -10,18 +13,20 @@ Install stuff, pip install -r requirements.txt ``` -Just grab the QR Code URL that starts with `duo://` and execute, +Just grab the QR Code URL and copy the string after value + +https://api-XXX.duosecurity.com/frame/qr?value={VALUE} ``` -python duo_bypass.py duo://urlhere +./duo_activate.py {VALUE} ``` -### How does this work? +If everything worked you can then generate a code by running: -It's pretty simple so I won't explain. The hard part was to read DUO's obfuscated code, because obfuscation makes things so secure. +``` +./duo_gen.py +``` -Why didn't I sniff? Because HTTPS and because they apparantly ignore trusted CA's on the Android Device and also the fact that I was too lazy to get a USB cable from my room and also that I didn't want to download a gigabyte of emulator. +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. -When I almost got all of it I realized I could have probably decompiled their Windows app, coz .NET and and coz they didn't obfuscate that. rip me. - -Anyway, it's 9 AM and I should sleep. \ No newline at end of file diff --git a/duo_bypass.py b/duo_activate.py old mode 100644 new mode 100755 similarity index 65% rename from duo_bypass.py rename to duo_activate.py index e758742..30a3204 --- a/duo_bypass.py +++ b/duo_activate.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python2.7 + import pyotp import requests import base64 @@ -9,21 +11,37 @@ if len(sys.argv) < 2: print "Usage: python duo_bypass.py "; exit() qr_url = sys.argv[1] -data = unquote(qr_url.split('=')[1]) +data = qr_url #unquote(qr_url.split('=')[1]) hostb64 = data.split('-')[1] +print "hostb64", hostb64 + host = base64.b64decode(hostb64 + '='*(-len(hostb64) % 4)) -code = data.split('-')[0].replace('duo://', '') +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) response = json.loads(r.text) +print "url", url +print "r", r +print "response", response secret = base64.b32encode(response['response']['hotp_secret']) +print "secret", secret + print "10 Next OneTime Passwords!" # Generate 10 Otps! hotp = pyotp.HOTP(secret) for _ in xrange(10): print hotp.at(_) + +f = open('duotoken.hotp', 'w') +f.write(secret + "\n") +f.write("0") +f.close() + diff --git a/duo_gen.py b/duo_gen.py new file mode 100755 index 0000000..c001ccd --- /dev/null +++ b/duo_gen.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python2.7 + +import pyotp +import requests +import base64 +import json +import sys +from urllib2 import unquote + +f = open("duotoken.hotp","r+"); +secret = f.readline()[0:-1] +offset = f.tell() +count = int(f.readline()) + +print "secret", secret +print "count", count + +hotp = pyotp.HOTP(secret) +print "Code:", hotp.at(count) + +f.seek(offset) +f.write(str(count + 1)) +f.close() +