Giulio 4 years ago
commit
61f5bc4d17

+ 123 - 0
.gitignore

@@ -0,0 +1,123 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/

+ 2 - 0
README.md

@@ -0,0 +1,2 @@
+Basic challenges developed for Hackmeeting 0x14.
+https://hm.capturetheflag.it

+ 9 - 0
circus.ini

@@ -0,0 +1,9 @@
+[watcher:crypto2]
+cmd = /usr/local/bin/gunicorn -w 3 --bin 192.168.0.12:5000 crypto2:app
+working_dir = /home/ctf/rc4
+send_hup = true
+
+[watcher:web]
+cmd = /usr/local/bin/gunicorn -w 3 --bin 192.168.0.12:7000 web:app
+working_dir = /home/ctf/evilcorp
+send_hup = true

BIN
ctf.tgz


+ 43 - 0
ecb/crypto1/app.py

@@ -0,0 +1,43 @@
+import socket
+import base64
+import threading
+from Crypto.Cipher import AES
+
+flag = 'HM{3cb_0r4cl3}'
+key = '12345678abcdefgh'
+
+BIND_IP = '192.168.0.12'
+BIND_PORT = 8000
+
+def pad(raw):
+
+    if (len(raw) % 16 == 0):
+        return raw
+    padding_required = 16 - (len(raw) % 16)
+    padChar = b'\x00'
+    data = raw.encode('ascii') + padding_required * padChar
+    return data
+
+def handle_client(client_socket):
+    client_socket.send(bytes('I will send you AES(<input>+flag):\n'.encode('ascii')))
+    request = client_socket.recv(1024)
+    string = request.decode('ascii').rstrip()
+    cipher = AES.AESCipher(key, AES.MODE_ECB)
+    ciphertext = base64.b64encode(cipher.encrypt(pad(string+flag)))
+    client_socket.send(ciphertext)
+    client_socket.close()
+
+def tcp_server():
+    server = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
+    server.bind(( BIND_IP, BIND_PORT))
+    server.listen(5)
+    print("[*] Listening on %s:%d" % (BIND_IP, BIND_PORT))
+
+    while 1:
+        client, addr = server.accept()
+        print("[*] Accepted connection from: %s:%d" %(addr[0], addr[1]))
+        client_handler = threading.Thread(target=handle_client, args=(client,))
+        client_handler.start()
+
+if __name__ == '__main__':
+    tcp_server()

+ 37 - 0
ecb/crypto1/solution.py

@@ -0,0 +1,37 @@
+from base64 import b64decode
+import socket
+from Crypto.Cipher import AES
+
+server = '127.0.0.1'
+port = 8000
+
+chars = 'abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ{}_'
+flag_blocks = 2
+block_size = 16
+index = {}
+
+flag = ''
+
+for i in range(block_size-1, 1, -1):
+	for j in chars:
+		
+		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+		s.connect((server, port))
+		data = s.recv(4096) 
+		s.send(('a'*i).encode('ascii'))
+		value = b64decode(s.recv(4096)).hex()[0:32]
+		s.close()
+
+		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+		s.connect((server, port))
+		data = s.recv(4096) 
+		s.send(('a'*i+flag+j).encode('ascii'))
+		test = b64decode(s.recv(4096)).hex()[0:32]
+		s.close()
+
+		if value == test:
+			flag += j
+			break
+	print(flag)
+
+

BIN
evilcorp/evilcorp.sqlite3


+ 248 - 0
evilcorp/web/__init__.py

@@ -0,0 +1,248 @@
+from flask import Flask, g, request, render_template, redirect, current_app, send_file
+from flask_argon2 import Argon2
+from io import BytesIO
+import jwt
+import time
+import sqlite3
+import secrets
+
+app = Flask(__name__)
+app.config['PRIVATE_KEY'] = '''
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCo0oR5UXusQs7tvgcT5EOLB+2JaKXmmip3ViGijLHku2Y+gyas
+0KFYHEQjTgz2AH1N9sUu8tzLAxn+wun28qyF3Paswmx27J/pAmbX8v6g+oGur4Jz
+V6ZoM5PA5iD5UvWYcLBczB84GcqhQkLHh8n/sZXP9jXMnxjTPD4nuPuQ3wIDAQAB
+AoGAd0s6/RdNEu6qlmifS7kS2V2ixmRCRu9NbsJYRiqxUfXyS94VKCzMthxTMbdn
+hTXXVY44y/IlfvcUGWfWOABHU7JK5NfWbwJfH0dU2kNEf8LzPmf1DzGy6vj01i/u
+6KrSJMqJOW62NxQ1GjkvWVGgoy8RrHKzrkM7bnQ+i6JDVLECQQDX24V58LQwMmo2
+JDZHjEZLZlx4xQz3lzhrLOfn7B3zgspRrgufOp2SaL+nFaphUl4w8P9mo/FcDmqc
+R402Z4yTAkEAyDfCiZiGBgPm7mDOLiJ1Wpyc21fsF6zwoc56xSbeK+a3t9LxT00M
+1W+qZv6e89erUmGNl85CwFmoyMEPdIgmBQJAOOUZp2x0cge3yxF8ZRtqI9GVKhf2
+NQRc0JMDhTPNKTQeE61mTs/qXH7TlTy2rfRB83ByQSGRKox6OTr6044zlQJAKuCe
+Hb93PESLqRM8NG8WuL//a43ptqxHoC9K5XvMapRvVcOr//KdQ/w0/veabNgMDYls
+vEzkyLKqzctilu8tTQJBAMXgHKX62GhH54pB4GrLLS25JpxqUNChDuPGaMPilfCW
+WOsFVC93MPtLA/YaAJHKZNoaXulkb5q3jhlWxCpDAKM=
+-----END RSA PRIVATE KEY-----
+'''
+
+app.config['PUBLIC_KEY'] = '''-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCo0oR5UXusQs7tvgcT5EOLB+2J
+aKXmmip3ViGijLHku2Y+gyas0KFYHEQjTgz2AH1N9sUu8tzLAxn+wun28qyF3Pas
+wmx27J/pAmbX8v6g+oGur4JzV6ZoM5PA5iD5UvWYcLBczB84GcqhQkLHh8n/sZXP
+9jXMnxjTPD4nuPuQ3wIDAQAB
+-----END PUBLIC KEY-----'''
+
+argon2 = Argon2(app)
+
+flag1 = 'HM{sql_inj3ctions_4re_still_r3l3vant}'
+flag2 = 'HM{y0u_th0ught_p4ssword_ha5h1ng_is_enough?}'
+flag3 = 'HM{s3ssion_manag3ment_is_super_h4rd}'
+
+database = 'evilcorp.sqlite3'
+
+def check_session(cookies):
+
+    if 'session' not in cookies:
+        return False
+
+    session = cookies['session']
+    try:
+        session_decoded = jwt.decode(session, app.config['PUBLIC_KEY'])
+    except jwt.InvalidTokenError:
+        return False
+    return session_decoded
+
+def get_db():
+    db = getattr(g, '_database', None)
+    if db is None:
+        db = g._database = sqlite3.connect(database)
+    return db
+
+
+@app.teardown_appcontext
+def close_connection(exception):
+    db = getattr(g, '_database', None)
+    if db is not None:
+        db.close()
+
+@app.route('/', methods=['GET'])
+def show_index():
+    session = check_session(request.cookies)
+    if session:
+        authenticated = True
+        username = session['user']
+    else:
+        authenticated = False
+        username = None
+    return render_template('index.html', authenticated=authenticated, username=username)
+
+@app.route('/news', methods=['GET'])
+def show_news():
+    session = check_session(request.cookies)
+    if session:
+        authenticated = True
+        username = session['user']
+    else:
+        authenticated = False
+        username = None
+
+    try:
+        cur = get_db().execute('SELECT uid, title, body, images FROM news ORDER BY uid DESC')
+        news = cur.fetchall()
+    except sqlite3.Error as e:
+        print(e)
+        return 'Something went wrong, ping the admins'
+    
+    return render_template('news.html', news=news, authenticated=authenticated, username=username)
+
+@app.route('/images/<uid>', methods=['GET'])
+def show_image(uid):
+    try:
+        cur = get_db().execute('SELECT uid, name, body FROM images WHERE uid = ' + uid)
+        image = cur.fetchone()
+    except sqlite3.Error as e:
+        print(e)
+        return 'Something went wrong, ping the admins'
+    if not image:
+    	return 'No image found'
+    return send_file(BytesIO(image[2]), attachment_filename=image[1]+'.jpg', mimetype='image/jpg')
+
+@app.route('/login', methods=['GET'])
+def show_login():
+    return render_template('login.html')
+
+@app.route('/login', methods=['POST'])
+def login():
+
+    if 'username' not in request.form or 'password' not in request.form:
+        return 'All fields are required!'
+    username = request.form['username']
+    password = request.form['password']
+
+    #if not username.isalnum():
+    #    return 'Login failed'
+
+    if len(password) < 6:
+        return 'Login failed'
+
+    try:
+        cur = get_db().execute('SELECT uid, username, password FROM users WHERE username = ? LIMIT 1', (username,))
+        cur_user = cur.fetchone()
+    except sqlite3.Error as e:
+        print(e)
+        return 'Something went wrong, ping the admins'
+
+    if not cur_user:
+        return 'Login failed'
+
+    if not argon2.check_password_hash(cur_user[2], password):
+        return 'Login failed'
+
+    session = jwt.encode({'user': cur_user[1], 'admin': False, 'iat': int(time.time())}, key=app.config['PRIVATE_KEY'] , algorithm='RS256')
+
+    response = current_app.make_response(redirect('/user', 302))
+    response.set_cookie('session', value=session, path='/', httponly=True)
+    return response
+
+
+@app.route('/reset', methods=['GET'])
+def show_reset():
+    return render_template('reset.html')
+
+
+@app.route('/reset', methods=['POST'])
+def reset():
+    if 'email' not in request.form:
+        return 'Email is required required!'
+    email = request.form['email']
+
+    if len(email) < 6:
+        return 'Email too short'
+
+    try:
+        cur = get_db().execute('SELECT uid, email, username, recovered, admin FROM users WHERE email = ? LIMIT 1', (email,))
+        cur_user = cur.fetchone()
+    except sqlite3.Error as e:
+        print(e)
+        return 'Something went wrong, ping the admins'
+
+    if not cur_user:
+        return 'No user found'
+
+    if cur_user[4]:
+        return 'Nope'
+
+    if cur_user[3]:
+        return 'Password for this user has already been reset'
+
+    uid = cur_user[0]
+    token = secrets.token_hex()
+
+    try:
+        #cur = get_db().execute('UPDATE user SET recovery = ?, recovered = 1 WHERE email = ?', (token, email,))
+        cur = get_db().execute('UPDATE users SET recovery = ? WHERE uid = ?', (token, uid,))
+        get_db().commit()
+    except sqlite3.Error as e:
+        print(e)
+        return 'Something went wrong, ping the admins'
+
+    return 'Reset token sent to user email'
+
+@app.route('/reset2', methods=['POST'])
+def reset2():
+    if 'email' not in request.form or 'token' not in request.form:
+        return 'Both fields are required!'
+
+    email = request.form['email']
+    token = request.form['token']
+
+    try:
+        cur = get_db().execute('SELECT * FROM users WHERE recovery = ? AND email = ? AND recovered != 1', (token, email,))
+        cur_user = cur.fetchone()
+    except sqlite3.Error as e:
+        print(e)
+        return 'Something went wrong, ping the admins'
+
+    if not cur_user:
+        return 'Wrong email or token or password already reset one time'
+
+    else:
+        newpassword = secrets.token_urlsafe(16)
+        try:
+            cur = get_db().execute('UPDATE users SET password = ?, recovered = 1 WHERE recovery = ? AND email = ?', (argon2.generate_password_hash(newpassword), token, email,))
+            get_db().commit()
+        except sqlite3.Error as e:
+            print(e)
+            return 'Something went wrong, ping the admins'
+
+    return 'Your new password is ' + newpassword
+
+
+@app.route('/user', methods=['GET'])
+def show_user():
+    session = check_session(request.cookies)
+    if not session:
+        return redirect('/login', 302)
+    else:
+        if not session['admin']:
+            return render_template('user.html', authenticated=True, username=session['user'], flag2=flag2)
+        return 'Good job! Here\'s the last flag: ' + flag3
+
+@app.route('/logout', methods=['GET'])
+def logout():
+    response = current_app.make_response(redirect('/', 302))
+    for cookie in request.cookies:
+        response.set_cookie(cookie, value='', expires=0)
+    return response
+
+@app.route('/robots.txt', methods=['GET'])
+def show_robots():
+    return 'Disallow: /pub.asc'
+
+@app.route('/pub.asc', methods=['GET'])
+def show_pubkey():
+    return app.config['PUBLIC_KEY']
+
+if __name__ == "__main__":
+    app.run(host='0.0.0.0', port=7000)
+

BIN
evilcorp/web/evilcorp.sqlite3


+ 7 - 0
evilcorp/web/pub.asc

@@ -0,0 +1,7 @@
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCo0oR5UXusQs7tvgcT5EOLB+2J
+aKXmmip3ViGijLHku2Y+gyas0KFYHEQjTgz2AH1N9sUu8tzLAxn+wun28qyF3Pas
+wmx27J/pAmbX8v6g+oGur4JzV6ZoM5PA5iD5UvWYcLBczB84GcqhQkLHh8n/sZXP
+9jXMnxjTPD4nuPuQ3wIDAQAB
+-----END PUBLIC KEY-----
+

+ 248 - 0
evilcorp/web/static/menu.css

@@ -0,0 +1,248 @@
+body {
+    color: #777;
+}
+
+.pure-img-responsive {
+    max-width: 100%;
+    height: auto;
+}
+
+/*
+Add transition to containers so they can push in and out.
+*/
+#layout,
+#menu,
+.menu-link {
+    -webkit-transition: all 0.2s ease-out;
+    -moz-transition: all 0.2s ease-out;
+    -ms-transition: all 0.2s ease-out;
+    -o-transition: all 0.2s ease-out;
+    transition: all 0.2s ease-out;
+}
+
+/*
+This is the parent `<div>` that contains the menu and the content area.
+*/
+#layout {
+    position: relative;
+    left: 0;
+    padding-left: 0;
+}
+    #layout.active #menu {
+        left: 150px;
+        width: 150px;
+    }
+
+    #layout.active .menu-link {
+        left: 150px;
+    }
+/*
+The content `<div>` is where all your content goes.
+*/
+.content {
+    margin: 0 auto;
+    padding: 0 2em;
+    max-width: 800px;
+    margin-bottom: 50px;
+    line-height: 1.6em;
+}
+
+.header {
+     margin: 0;
+     color: #333;
+     text-align: center;
+     padding: 2.5em 2em 0;
+     border-bottom: 1px solid #eee;
+ }
+    .header h1 {
+        margin: 0.2em 0;
+        font-size: 3em;
+        font-weight: 300;
+    }
+     .header h2 {
+        font-weight: 300;
+        color: #ccc;
+        padding: 0;
+        margin-top: 0;
+    }
+
+.content-subhead {
+    margin: 50px 0 20px 0;
+    font-weight: 300;
+    color: #888;
+}
+
+
+
+/*
+The `#menu` `<div>` is the parent `<div>` that contains the `.pure-menu` that
+appears on the left side of the page.
+*/
+
+#menu {
+    margin-left: -150px; /* "#menu" width */
+    width: 150px;
+    position: fixed;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    z-index: 1000; /* so the menu or its navicon stays above all content */
+    background: #191818;
+    overflow-y: auto;
+    -webkit-overflow-scrolling: touch;
+}
+    /*
+    All anchors inside the menu should be styled like this.
+    */
+    #menu a {
+        color: #999;
+        border: none;
+        padding: 0.6em 0 0.6em 0.6em;
+    }
+
+    /*
+    Remove all background/borders, since we are applying them to #menu.
+    */
+     #menu .pure-menu,
+     #menu .pure-menu ul {
+        border: none;
+        background: transparent;
+    }
+
+    /*
+    Add that light border to separate items into groups.
+    */
+    #menu .pure-menu ul,
+    #menu .pure-menu .menu-item-divided {
+        border-top: 1px solid #333;
+    }
+        /*
+        Change color of the anchor links on hover/focus.
+        */
+        #menu .pure-menu li a:hover,
+        #menu .pure-menu li a:focus {
+            background: #333;
+        }
+
+    /*
+    This styles the selected menu item `<li>`.
+    */
+    #menu .pure-menu-selected,
+    #menu .pure-menu-heading {
+        background: #1f8dd6;
+    }
+        /*
+        This styles a link within a selected menu item `<li>`.
+        */
+        #menu .pure-menu-selected a {
+            color: #fff;
+        }
+
+    /*
+    This styles the menu heading.
+    */
+    #menu .pure-menu-heading {
+        font-size: 110%;
+        color: #fff;
+        margin: 0;
+    }
+
+/* -- Dynamic Button For Responsive Menu -------------------------------------*/
+
+/*
+The button to open/close the Menu is custom-made and not part of Pure. Here's
+how it works:
+*/
+
+/*
+`.menu-link` represents the responsive menu toggle that shows/hides on
+small screens.
+*/
+.menu-link {
+    position: fixed;
+    display: block; /* show this only on small screens */
+    top: 0;
+    left: 0; /* "#menu width" */
+    background: #000;
+    background: rgba(0,0,0,0.7);
+    font-size: 10px; /* change this value to increase/decrease button size */
+    z-index: 10;
+    width: 2em;
+    height: auto;
+    padding: 2.1em 1.6em;
+}
+
+    .menu-link:hover,
+    .menu-link:focus {
+        background: #000;
+    }
+
+    .menu-link span {
+        position: relative;
+        display: block;
+    }
+
+    .menu-link span,
+    .menu-link span:before,
+    .menu-link span:after {
+        background-color: #fff;
+        width: 100%;
+        height: 0.2em;
+    }
+
+        .menu-link span:before,
+        .menu-link span:after {
+            position: absolute;
+            margin-top: -0.6em;
+            content: " ";
+        }
+
+        .menu-link span:after {
+            margin-top: 0.6em;
+        }
+
+
+/* -- Responsive Styles (Media Queries) ------------------------------------- */
+
+/*
+Hides the menu at `48em`, but modify this based on your app's needs.
+*/
+@media (min-width: 48em) {
+
+    .header,
+    .content {
+        padding-left: 2em;
+        padding-right: 2em;
+    }
+
+    #layout {
+        padding-left: 150px; /* left col width "#menu" */
+        left: 0;
+    }
+    #menu {
+        left: 150px;
+    }
+
+    .menu-link {
+        position: fixed;
+        left: 150px;
+        display: none;
+    }
+
+    #layout.active .menu-link {
+        left: 150px;
+    }
+}
+
+@media (max-width: 48em) {
+    /* Only apply this when the window is small. Otherwise, the following
+    case results in extra padding on the left:
+        * Make the window small.
+        * Tap the menu to trigger the active state.
+        * Make the window large again.
+    */
+    #layout.active {
+        position: relative;
+        left: 150px;
+    }
+}

File diff suppressed because it is too large
+ 10 - 0
evilcorp/web/static/pure.css


+ 1 - 0
evilcorp/web/static/robots.txt

@@ -0,0 +1 @@
+Disallow: /pub.asc

+ 46 - 0
evilcorp/web/static/ui.js

@@ -0,0 +1,46 @@
+(function (window, document) {
+
+    var layout   = document.getElementById('layout'),
+        menu     = document.getElementById('menu'),
+        menuLink = document.getElementById('menuLink'),
+        content  = document.getElementById('main');
+
+    function toggleClass(element, className) {
+        var classes = element.className.split(/\s+/),
+            length = classes.length,
+            i = 0;
+
+        for(; i < length; i++) {
+          if (classes[i] === className) {
+            classes.splice(i, 1);
+            break;
+          }
+        }
+        // The className is not found
+        if (length === classes.length) {
+            classes.push(className);
+        }
+
+        element.className = classes.join(' ');
+    }
+
+    function toggleAll(e) {
+        var active = 'active';
+
+        e.preventDefault();
+        toggleClass(layout, active);
+        toggleClass(menu, active);
+        toggleClass(menuLink, active);
+    }
+
+    menuLink.onclick = function (e) {
+        toggleAll(e);
+    };
+
+    content.onclick = function(e) {
+        if (menu.className.indexOf('active') !== -1) {
+            toggleAll(e);
+        }
+    };
+
+}(this, this.document));

+ 41 - 0
evilcorp/web/templates/index.html

@@ -0,0 +1,41 @@
+<!doctype html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="description" content="A layout example with a side menu that hides on mobile, just like the Pure website.">
+    <title>Evil Corp | Home</title>
+    
+    <link rel="stylesheet" href="/static/pure.css">    
+    <link rel="stylesheet" href="/static/menu.css">
+
+</head>
+<body>
+
+<div id="layout">
+
+    {% include 'nav.html' %}
+
+    <div id="main">
+        <div class="header">
+            <h1>Welcome to Evil Corp</h1>
+            <h2>We are a totally-legit, zero-bullshit company</h2>
+        </div>
+
+        <div class="content">
+            <h2 class="content-subhead">What's this site for?</h2>
+            <p>
+                This is out completely secure company website. Valid credentials are required to access most resources, but you can still visit our <a href="/news">News</a> area freely.
+                As in any other CMS, admins have superpowers: they can, for example, read super secret flags!
+            </p>
+        </div>
+    </div>
+</div>
+
+
+
+
+<script src="/static/ui.js"></script>
+
+</body>
+</html>

+ 54 - 0
evilcorp/web/templates/login.html

@@ -0,0 +1,54 @@
+<!doctype html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="description" content="A layout example with a side menu that hides on mobile, just like the Pure website.">
+    <title>Evil Corp | Login</title>
+    
+    <link rel="stylesheet" href="/static/pure.css">    
+    <link rel="stylesheet" href="/static/menu.css">
+
+    <style>
+        .login {
+            margin-right: auto;
+            margin-left: auto;
+            width: 240px;
+        }
+
+        .pure-input {
+            width: 240px;
+        }
+    </style>
+</head>
+<body>
+
+<div id="layout">
+
+    {% include 'nav.html' %}
+
+    <div id="main">
+        <div class="header">
+            <h1>Evil Corp</h1>
+            <h2>Login to access our corporate resources</h2>
+        </div>
+        <div class="login">
+            <form class="pure-form" method="post" action="">
+                <fieldset class="pure-group">
+                    <input type="text" class="pure-input" placeholder="Username" name="username">
+                    <input type="password" class="pure-input" placeholder="Password" name="password">
+                </fieldset>
+                <button type="submit" class="pure-button pure-input pure-button-primary">Sign in</button>
+                <p><a href="/reset">Forgot password?</a></p>
+            </form>
+        </div>
+    </div>
+</div>
+
+
+
+
+<script src="/static/ui.js"></script>
+
+</body>
+</html>

+ 22 - 0
evilcorp/web/templates/nav.html

@@ -0,0 +1,22 @@
+    <!-- Menu toggle -->
+    <a href="#menu" id="menuLink" class="menu-link">
+        <!-- Hamburger icon -->
+        <span></span>
+    </a>
+
+    <div id="menu">
+        <div class="pure-menu">
+            <a class="pure-menu-heading" href="#">Evil Corp</a>
+
+            <ul class="pure-menu-list">
+                <li class="pure-menu-item"><a href="/" class="pure-menu-link">Home</a></li>
+                <li class="pure-menu-item"><a href="/news" class="pure-menu-link">News</a></li>
+                {% if authenticated %}
+                <li class="pure-menu-item"><a href="/user" class="pure-menu-link">{{ username }}</a></li> 
+                <li class="pure-menu-item"><a href="/logout" class="pure-menu-link">Logout</a></li>
+                {% else %}
+                <li class="pure-menu-item"><a href="/login" class="pure-menu-link">Login</a></li>
+                {% endif %}
+            </ul>
+        </div>
+    </div>

+ 41 - 0
evilcorp/web/templates/news.html

@@ -0,0 +1,41 @@
+<!doctype html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="description" content="A layout example with a side menu that hides on mobile, just like the Pure website.">
+    <title>Evil Corp | News</title>
+    
+    <link rel="stylesheet" href="/static/pure.css">    
+    <link rel="stylesheet" href="/static/menu.css">
+
+</head>
+<body>
+
+<div id="layout">
+
+    {% include 'nav.html' %}
+
+    <div id="main">
+        <div class="header">
+            <h1>Evil Corp News</h1>
+            <h2>Here we will keep you updated on how we improve the world!</h2>
+        </div>
+
+        {% for post in news %}
+        <div class="content">
+            <h2 class="content-subhead">{{ post[1] }}</h2>
+            {{ post[2]|safe }}
+            <img class="pure-img-responsive" src="/images/{{ post[3] }}">
+        </div>
+        {% endfor %}
+    </div>
+</div>
+
+
+
+
+<script src="/static/ui.js"></script>
+
+</body>
+</html>

+ 64 - 0
evilcorp/web/templates/reset.html

@@ -0,0 +1,64 @@
+<!doctype html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="description" content="A layout example with a side menu that hides on mobile, just like the Pure website.">
+    <title>Evil Corp | Password reset</title>
+    
+    <link rel="stylesheet" href="/static/pure.css">    
+    <link rel="stylesheet" href="/static/menu.css">
+
+    <style>
+        .login {
+            margin-right: auto;
+            margin-left: auto;
+            width: 240px;
+        }
+
+        .pure-input {
+            width: 240px;
+        }
+    </style>
+</head>
+<body>
+
+<div id="layout">
+
+    {% include 'nav.html' %}
+
+    <div id="main">
+        <div class="header">
+            <h1>Evil Corp</h1>
+            <h2>Can't remember your password?</h2>
+        </div>
+        <div class="login">
+            <form class="pure-form" method="post" action="">
+                <fieldset class="pure-group">
+                    <input type="email" class="pure-input" placeholder="Email" name="email">
+                </fieldset>
+                <button type="submit" class="pure-button pure-input pure-button-primary">Send</button>
+            </form>
+        </div>
+        <div class="header">
+            <h2>Already got a token?</h2>
+        </div>
+        <div class="login">
+            <form class="pure-form" method="post" action="/reset2">
+                <fieldset class="pure-group">
+                    <input type="email" class="pure-input" placeholder="Email" name="email">
+                    <input type="text" class="pure-input" placeholder="Reset Token" name="token">
+                </fieldset>
+                <button type="submit" class="pure-button pure-input pure-button-primary">Reset</button>
+            </form>
+        </div>
+    </div>
+</div>
+
+
+
+
+<script src="/static/ui.js"></script>
+
+</body>
+</html>

+ 46 - 0
evilcorp/web/templates/user.html

@@ -0,0 +1,46 @@
+<!doctype html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="description" content="A layout example with a side menu that hides on mobile, just like the Pure website.">
+    <title>Evil Corp | Home</title>
+    
+    <link rel="stylesheet" href="/static/pure.css">    
+    <link rel="stylesheet" href="/static/menu.css">
+
+</head>
+<body>
+
+<div id="layout">
+
+    {% include 'nav.html' %}
+
+    <div id="main">
+        <div class="header">
+            <h1>User Profile</h1>
+            <h2>Welcome back <italic>{{ username }}</italic></h2>
+        </div>
+
+        <div class="content">
+            <h2 class="content-subhead">Congratulations</h2>
+            <p>
+                Here's your second flag: <strong>{{ flag2 }}</strong>
+            </p>
+        </div>
+        <div class="content">
+            <h2 class="content-subhead">User options</h2>
+            <p>
+                Unfortunately we have yet to implement user functionalities. If you are an admin, please login as such to change site configuration.
+            </p>
+        </div>
+    </div>
+</div>
+
+
+
+
+<script src="/static/ui.js"></script>
+
+</body>
+</html>

+ 4 - 0
evilcorp/wsgi.py

@@ -0,0 +1,4 @@
+from web import app
+
+if __name__ == "__main__":
+    app.run(host='192.168.0.12', port=7000)

+ 242 - 0
rc4/crypto2/__init__.py

@@ -0,0 +1,242 @@
+from flask import Flask, g, request, render_template, redirect, current_app
+from uuid import uuid4
+from flask_argon2 import Argon2
+from Crypto.Cipher import ARC4
+from binascii import hexlify, unhexlify
+import sqlite3
+import json
+import secrets
+app = Flask(__name__)
+argon2 = Argon2(app)
+
+flag = 'HM{h3r35_y0ur_2time_p4d}'
+
+database = 'wep.sqlite3'
+
+def get_db():
+    db = getattr(g, '_database', None)
+    if db is None:
+        db = g._database = sqlite3.connect(database)
+    return db
+
+@app.teardown_appcontext
+def close_connection(exception):
+    db = getattr(g, '_database', None)
+    if db is not None:
+        db.close()
+
+def check_session(cookies):
+
+    if 'session' not in cookies and 'iv' not in cookies or 'uid' not in cookies:
+        return False
+
+    try:
+        cur = get_db().execute('SELECT uid, username, iv, key FROM users WHERE uid = ? AND iv = ? LIMIT 1', (cookies['uid'], cookies['iv']))
+        cur_user = cur.fetchone()
+    except sqlite3.Error as e:
+    	print(e)
+    	return 'Something went wrong, ping the admins'
+
+    if not cur_user:
+        return False
+
+    uid = cur_user[0]
+    username = cur_user[1]
+    iv = cur_user[2]
+    key = cur_user[3]
+    cipher = ARC4.new(bytes(iv) + key)
+
+    try:
+        plaintext = cipher.decrypt(unhexlify(cookies['session']))
+        session = json.loads(plaintext)
+    except:
+        return False
+
+    session['key'] = key
+    session['iv'] = iv
+    return session
+
+
+@app.route('/', methods=['GET'])
+def index():
+    return render_template('index.html')
+
+
+@app.route('/register', methods=['GET'])
+def show_register():
+    return render_template('register.html')
+
+@app.route('/register', methods=['POST'])
+def register():
+
+    if 'username' not in request.form or 'password' not in request.form or 'password2' not in request.form:
+       return 'All fields are required!'
+    username = request.form['username']
+    password = request.form['password']
+    password2 = request.form['password2']
+
+    if not username.isalnum():
+    	return 'Username must be alphanumeric'
+
+    if password != password2:
+    	return 'Password confirmation does not match'
+
+    if len(password) < 6:
+    	return 'Password must be at least 6 chars!'
+
+    try:
+    	cur = get_db().execute('SELECT username FROM users WHERE username= ? LIMIT 1', (username,))
+    	cur_user = cur.fetchone()
+    except:
+    	return 'Something went wrong, ping the admins'
+
+    if cur_user:
+        return 'Username already exist'
+
+    try:
+        cur.execute('INSERT INTO users (uid, username, password, iv, key) VALUES(?, ?, ?, 0, ?)', (str(uuid4()), username, argon2.generate_password_hash(password), secrets.token_bytes(16),))
+        get_db().commit()
+    except sqlite3.Error as e:
+    	print(e)
+    	return 'Something went wrong, ping the admins'
+
+    return redirect('/login', 302)
+
+
+@app.route('/login', methods=['GET'])
+def show_login():
+    return render_template('login.html')
+
+
+@app.route('/login', methods=['POST'])
+def login():
+
+    if 'username' not in request.form or 'password' not in request.form:
+    	return 'All fields are required!'
+    username = request.form['username']
+    password = request.form['password']
+
+    if not username.isalnum():
+    	return 'Login failed'
+
+    if len(password) < 6:
+    	return 'Login failed'
+
+    try:
+        cur = get_db().execute('SELECT uid, username, password, iv, key FROM users WHERE username = ? LIMIT 1', (username,))
+        cur_user = cur.fetchone()
+    except sqlite3.Error as e:
+    	print(e)
+    	return 'Something went wrong, ping the admins'
+
+    if not cur_user:
+    	return 'Login failed'
+
+    if not argon2.check_password_hash(cur_user[2], password):
+    	return 'Login failed'
+
+    uid = cur_user[0]
+    username = cur_user[1]
+    iv = cur_user[3]+1
+    key = cur_user[4]
+
+    if iv >= 255:
+    	iv = 0
+
+    data = {
+        'username': username,
+        'description': 'No description yet',
+        'show_flag': False
+    }
+
+    cipher = ARC4.new(bytes(iv) + key)
+    
+    try:
+        ciphertext = cipher.encrypt(json.dumps(data).encode('ascii')).hex()
+    except:
+    	return 'Something went wrong, ping the admins'
+    
+    try:
+    	get_db().execute('UPDATE users SET iv = ? WHERE username = ?', (iv, username,))
+    	get_db().commit()
+    except sqlite3.Error as e:
+    	print(e)
+    	return 'Something went wrong, ping the admins'
+
+    response = current_app.make_response(redirect('/user', 302))
+    response.set_cookie('session', value=str(ciphertext), path='/', httponly=True)
+    response.set_cookie('uid', value=uid, path='/', httponly=True)
+    response.set_cookie('iv', value=str(iv), path='/', httponly=True)
+    return response
+
+@app.route('/user', methods=['GET'])
+def show_user():
+    session = check_session(request.cookies)
+    if not session:
+        return redirect('/login', 302)
+    if session['show_flag']:
+        return flag
+
+    return render_template('user.html', authenticated=True, username=session['username'], description=session['description'])
+
+@app.route('/user', methods=['POST'])
+def user():
+    session = check_session(request.cookies)
+    if not session:
+        return redirect('/login', 302)
+    if session['show_flag']:
+        return flag
+
+    if 'description' not in request.form:
+    	return 'Description field is mandatory!'
+
+    description = request.form['description']
+
+    if len(description) < 10 or len(description) > 200:
+    	return 'Description either too short (<10) or too long (>200)'
+
+    username = session['username']
+    key = session['key']
+    iv = session['iv']+1
+
+    if iv >= 255:
+    	iv = 0
+
+    data = {
+        'username': username,
+        'description': description,
+        'show_flag': False
+    }
+
+    cipher = ARC4.new(bytes(iv) + key)
+    
+    try:
+        ciphertext = cipher.encrypt(json.dumps(data).encode('ascii')).hex()
+    except:
+    	return 'Something went wrong, ping the admins'
+    
+    try:
+    	get_db().execute('UPDATE users SET iv = ? WHERE username = ?', (iv, username,))
+    	get_db().commit()
+    except sqlite3.Error as e:
+    	print(e)
+    	return 'Something went wrong, ping the admins'
+
+    response = current_app.make_response(redirect('/user', 302))
+    response.set_cookie('session', value=str(ciphertext), path='/', httponly=True)
+    response.set_cookie('iv', value=str(iv), path='/', httponly=True)
+    return response
+
+
+    return render_template('user.html', authenticated=True, username=session['username'], description=session['description'])
+
+@app.route('/logout', methods=['GET'])
+def logout():
+    response = current_app.make_response(redirect('/', 302))
+    for cookie in request.cookies:
+        response.set_cookie(cookie, value='', expires=0)
+    return response
+
+
+if __name__ == "__main__":
+    app.run(host='0.0.0.0')

+ 61 - 0
rc4/crypto2/solution.py

@@ -0,0 +1,61 @@
+import requests
+from binascii import hexlify, unhexlify
+
+url = 'http://192.168.0.222:5000'
+
+# Any registered username and password
+username = 'myBLfLEDraYh3Dq'
+password = '9GQLqu39EviKw'
+
+# default comment and any much longer string
+original = 'No description yet'
+description = 'stringadiversamapiulungastringadiversamapiulungastringa'.encode('ascii')
+
+# do login
+s = requests.session()
+r = s.post(url + '/login', data={'username': username, 'password': password})
+
+cur_iv = s.cookies['iv']
+
+ciphertext1 = s.cookies['session']
+
+# loop trough 256 requests to obtain a two time pad
+for i in range(int(cur_iv), int(cur_iv)+256):
+	s.post(url + '/user', data={'description': description})
+
+	if s.cookies['iv'] == cur_iv:
+		ciphertext2 = s.cookies['session']
+
+for i in range(0, len(ciphertext1), 2):
+	if ciphertext1[i:i+1] != ciphertext2[i:i+1]:
+		break
+
+# obtain new cookis with the original comment to flip the fals in true
+s.post(url + '/user', data={'description': original})
+
+cur_iv = s.cookies['iv']
+uid = s.cookies['uid']
+
+description = description.hex()
+ciphertext1 = ciphertext1[i:]
+ciphertext2 = ciphertext2[i:i+len(description)]
+
+# xor our known plaintext with the given ciphertext to recover part of the rc4 stream
+key = '{:x}'.format(int(ciphertext2, 16) ^ int(description, 16))[0:len(ciphertext1)]
+# xor our known rc4 stream with the default comment on the same iteration to decrypt the final parte of the cookie
+plaintext = unhexlify('{:x}'.format(int(key, 16) ^ int(ciphertext1, 16)))
+
+# print the plaintext and notice it has a show_flag parameter
+print('[*] Decrypted first cookie: ' + plaintext.decode('ascii'))
+
+# xor 'true ' with 'false' to calculate the value to use to flip the ciphertext
+flip = '{:x}'.format(int('true '.encode('ascii').hex(), 16) ^ int('false'.encode('ascii').hex(), 16))
+
+cur_session = s.cookies['session']
+# xor to obtain 'true ' from false'
+flip = '{:x}'.format(int(flip, 16) ^ int(cur_session[-12:-2], 16))
+session = cur_session[0:-12] + flip + cur_session[-2:]
+# get the flag!
+flag = requests.get(url + '/user', cookies={'iv': cur_iv, 'uid': uid, 'session': session})
+
+print('[*] Got the flag: ' + flag.text)

File diff suppressed because it is too large
+ 0 - 0
rc4/crypto2/static/min.css


+ 35 - 0
rc4/crypto2/templates/index.html

@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="UTF-8">
+		<meta name="viewport" content="width=device-width, initial-scale=1">
+
+		<title>HM0x14 - WE(P)B | Home</title>
+
+		<link href="static/min.css" rel="stylesheet" type="text/css">
+
+		<style>
+			.hero {
+				background: #eee;
+				padding: 20px;
+				border-radius: 10px;
+				margin-top: 1em;
+			}
+
+			.hero h1 {
+				margin-top: 0;
+				margin-bottom: 0.3em;
+			}
+		</style>
+	</head>
+	<body>
+		{% include 'navbar.html' %}
+		<div class="container">
+			<div class="hero">
+				<h1>Welcome</h1>
+				<p>I learnt WEP is so secure that i decided to use it for my session management, but why waste 24bit for the IV? 8 should be enough, no user is going to do that many requests anyway...</p>
+				<a class="btn btn-b" href="/register">Register</a>
+			</div>
+		</div>
+	</body>
+</html>

+ 43 - 0
rc4/crypto2/templates/login.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="UTF-8">
+		<meta name="viewport" content="width=device-width, initial-scale=1">
+
+		<title>HM0x14 - WE(P)B | Login</title>
+
+		<link href="static/min.css" rel="stylesheet" type="text/css">
+
+		<style>
+		input {
+			border: 1px solid #ccc;
+			box-shadow: inset 0 1px 3px #ddd;
+			border-radius: 2px;
+			vertical-align: middle;
+			-webkit-box-sizing: border-box;
+			-moz-box-sizing: border-box;
+			box-sizing: border-box;
+			width: 240px;
+			max-width: 100%;
+			padding: 0.5em 0.6em;
+			margin-bottom: 0.5em;
+		}
+		</style>
+	</head>
+	<body>
+	{% include 'navbar.html' %}
+		<div class="container">
+			<div class="row">
+    			<div class="col c6">
+    			<h1>Login</h1>
+    			<hr>
+    				<form method="post" action="">
+    					<input type="text" name="username" id="username" placeholder="Username">
+						<input type="password" name="password" id="password" placeholder="Password">
+						<input type="submit" class="btn btn-a btn-sm smooth" value="Login">
+    				</form>
+    			</div>
+    		</div>
+		</div>
+	</body>
+</html>

+ 13 - 0
rc4/crypto2/templates/navbar.html

@@ -0,0 +1,13 @@
+		<nav class="nav" tabindex="-1" onclick="this.focus()">
+			<div class="container">
+				<a class="pagename current" href="#">HM0x14 - WE(P)B</a>
+				{% if authenticated %}
+				<a href="/user">{{ username }}</a> 
+				<a href="/logout">Logout</a>
+				{% else %}
+				<a href="/">Home</a>
+				<a href="/login">Login</a> 
+				<a href="/register">Register</a>
+				{% endif %}
+			</div>
+		</nav>

+ 44 - 0
rc4/crypto2/templates/register.html

@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="UTF-8">
+		<meta name="viewport" content="width=device-width, initial-scale=1">
+
+		<title>HM0x14 - WE(P)B | Register</title>
+
+		<link href="static/min.css" rel="stylesheet" type="text/css">
+
+		<style>
+		input {
+			border: 1px solid #ccc;
+			box-shadow: inset 0 1px 3px #ddd;
+			border-radius: 2px;
+			vertical-align: middle;
+			-webkit-box-sizing: border-box;
+			-moz-box-sizing: border-box;
+			box-sizing: border-box;
+			width: 240px;
+			max-width: 100%;
+			padding: 0.5em 0.6em;
+			margin-bottom: 0.5em;
+		}
+		</style>
+	</head>
+	<body>
+	{% include 'navbar.html' %}
+		<div class="container">
+			<div class="row">
+    			<div class="col c6">
+    			<h1>Register</h1>
+    			<hr>
+    				<form method="post" action="">
+    					<input type="text" name="username" id="username" placeholder="Username">
+						<input type="password" name="password" id="password" placeholder="Password">
+						<input type="password" name="password2" id="password2" placeholder="repeat Password">
+						<input type="submit" class="btn btn-a btn-sm smooth" value="Register">
+    				</form>
+    			</div>
+    		</div>
+		</div>
+	</body>
+</html>

+ 56 - 0
rc4/crypto2/templates/user.html

@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="UTF-8">
+		<meta name="viewport" content="width=device-width, initial-scale=1">
+
+		<title>HM0x14 - WE(P)B | User</title>
+
+		<link href="static/min.css" rel="stylesheet" type="text/css">
+
+		<style>
+		.hero {
+			background: #eee;
+			padding: 20px;
+			border-radius: 10px;
+			margin-top: 1em;
+		}
+		
+		input,textarea {
+			border: 1px solid #ccc;
+			box-shadow: inset 0 1px 3px #ddd;
+			border-radius: 2px;
+			vertical-align: middle;
+			-webkit-box-sizing: border-box;
+			-moz-box-sizing: border-box;
+			box-sizing: border-box;
+			width: 320px;
+			max-width: 100%;
+			padding: 0.5em 0.6em;
+			margin-bottom: 0.5em;
+		}
+		</style>
+	</head>
+	<body>
+	{% include 'navbar.html' %}
+		<div class="container">
+			<div class="row">
+    			<div class="col c6">
+    			<h1>Update description</h1>
+    			<hr>
+    				<form method="post" action="">
+    					<textarea name="description" id="description" rows="12">{{ description }}</textarea>
+						<input type="submit" class="btn btn-a btn-sm smooth" value="Update">
+    				</form>
+    			</div>
+				<div class="col c6">
+    			<h1>Profile description</h1>
+    			<hr>
+    				<div class="hero">
+    					<p>{{ description }}</p>
+    				</div>
+    			</div>
+    		</div>
+		</div>
+	</body>
+</html>

BIN
rc4/crypto2/wep.sqlite3


BIN
rc4/wep.sqlite3


+ 4 - 0
rc4/wsgi.py

@@ -0,0 +1,4 @@
+from crypto2 import app
+
+if __name__ == "__main__":
+    app.run(host='192.168.0.12')

Some files were not shown because too many files changed in this diff