This commit is contained in:
Giulio 2019-05-14 16:29:52 +02:00
commit 61f5bc4d17
32 changed files with 1546 additions and 0 deletions

123
.gitignore vendored Normal file
View File

@ -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
README.md Normal file
View File

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

9
circus.ini Normal file
View File

@ -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 Normal file

Binary file not shown.

43
ecb/crypto1/app.py Normal file
View File

@ -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
ecb/crypto1/solution.py Normal file
View File

@ -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 Executable file

Binary file not shown.

248
evilcorp/web/__init__.py Normal file
View File

@ -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 Executable file

Binary file not shown.

7
evilcorp/web/pub.asc Normal file
View File

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

View File

@ -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 one or more lines are too long

View File

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

46
evilcorp/web/static/ui.js Normal file
View File

@ -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));

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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
evilcorp/wsgi.py Normal file
View File

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

242
rc4/crypto2/__init__.py Normal file
View File

@ -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
rc4/crypto2/solution.py Normal file
View File

@ -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)

View File

@ -0,0 +1 @@
/* Copyright 2014 Owen Versteeg; MIT licensed */body,textarea,input,select{background:0;border-radius:0;font:16px sans-serif;margin:0}.smooth{transition:all .2s}.btn,.nav a{text-decoration:none}.container{margin:0 20px;width:auto}label>*{display:inline}form>*{display:block;margin-bottom:10px}.btn{background:#999;border-radius:6px;border:0;color:#fff;cursor:pointer;display:inline-block;margin:2px 0;padding:12px 30px 14px}.btn:hover{background:#888}.btn:active,.btn:focus{background:#777}.btn-a{background:#0ae}.btn-a:hover{background:#09d}.btn-a:active,.btn-a:focus{background:#08b}.btn-b{background:#3c5}.btn-b:hover{background:#2b4}.btn-b:active,.btn-b:focus{background:#2a4}.btn-c{background:#d33}.btn-c:hover{background:#c22}.btn-c:active,.btn-c:focus{background:#b22}.btn-sm{border-radius:4px;padding:10px 14px 11px}.row{margin:1% 0;overflow:auto}.col{float:left}.table,.c12{width:100%}.c11{width:91.66%}.c10{width:83.33%}.c9{width:75%}.c8{width:66.66%}.c7{width:58.33%}.c6{width:50%}.c5{width:41.66%}.c4{width:33.33%}.c3{width:25%}.c2{width:16.66%}.c1{width:8.33%}h1{font-size:3em}.btn,h2{font-size:2em}.ico{font:33px Arial Unicode MS,Lucida Sans Unicode}.addon,.btn-sm,.nav,textarea,input,select{outline:0;font-size:14px}textarea,input,select{padding:8px;border:1px solid #ccc}textarea:focus,input:focus,select:focus{border-color:#5ab}textarea,input[type=text]{-webkit-appearance:none;}.addon{padding:8px 12px;box-shadow:0 0 0 1px #ccc}.nav,.nav .current,.nav a:hover{background:#000;color:#fff}.nav{height:24px;padding:11px 0 15px}.nav a{color:#aaa;padding-right:1em;position:relative;top:-1px}.nav .pagename{font-size:22px;top:1px}.btn.btn-close{background:#000;float:right;font-size:25px;margin:-54px 7px;display:none}@media(min-width:1310px){.container{margin:auto;width:1270px}}@media(max-width:870px){.row .col{width:100%}}@media(max-width:500px){.btn.btn-close{display:block}.nav{overflow:hidden}.pagename{margin-top:-11px}.nav:active,.nav:focus{height:auto}.nav div:before{background:#000;border-bottom:10px double;border-top:3px solid;content:'';float:right;height:4px;position:relative;right:3px;top:14px;width:20px}.nav a{padding:.5em 0;display:block;width:50%}}.table th,.table td{padding:.5em;text-align:left}.table tbody>:nth-child(2n-1){background:#ddd}.msg{padding:1.5em;background:#def;border-left:5px solid #59d}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 Executable file

Binary file not shown.

BIN
rc4/wep.sqlite3 Normal file

Binary file not shown.

4
rc4/wsgi.py Normal file
View File

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