Skip to content

Instantly share code, notes, and snippets.

@Strikeskids
Created April 19, 2020 22:15
Show Gist options
  • Save Strikeskids/7d2ba252a4eeffa9729a644c90107021 to your computer and use it in GitHub Desktop.
Save Strikeskids/7d2ba252a4eeffa9729a644c90107021 to your computer and use it in GitHub Desktop.
PlaidCTF 2020 golf.so: server source
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport"
content="width=device-width, initial-scale=1" />
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet"
href="{{ url_for('static', filename='main.css') }}" />
</head>
<body>
<div class="main-header">
<nav class="topbar-navigation">
<a class="button" href="{{ url_for('scoreboard') }}">
Scoreboard
</a>
<h1>golf.so</h1>
<a class="button" href="{{ url_for('upload') }}">
Upload
</a>
</nav>
</div>
{% with messages = get_flashed_messages() %}{% if messages %}
<div class="center-box">
{% for message in messages %}
<div class="flashed-message">{{ message }}</div>
{% endfor %}
</div>
{% endif %}{% endwith %}
<div class="main-content">
{% block content %}{% endblock %}
</div>
</body>
</html>
PCTF{th0ugh_wE_have_cl1mBed_far_we_MusT_St1ll_c0ntinue_oNward}
PCTF{t0_get_a_t1ny_elf_we_5tick_1ts_hand5_in_its_ears_rtmlpntyea}
#!/usr/bin/env python3
from flask import (
Flask, g, request, redirect, escape, get_flashed_messages, flash,
session, render_template, url_for
)
import enum
import os
import sqlite3
import subprocess
import tempfile
import hashlib
import hashcash
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 2000
app.config['BITS'] = 0
app.config['RESOURCE'] = 'golf.so'
app.config['EXECUTION_TIMEOUT'] = 5
app.config['DATABASE'] = 'db.sqlite3'
app.config['FLAG_FILE'] = 'flag.txt'
app.config['EASY_FLAG_FILE'] = 'flag-easy.txt'
app.secret_key = 'todo: really secret'
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(app.config['DATABASE'])
db.row_factory = sqlite3.Row
return db
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
def get_scoreboard():
cur = get_db().execute(
'''
SELECT MIN(id) as id, MIN(size) as size, MIN(submission_time) as submission_time, team, hash
FROM uploads
WHERE success = 1
GROUP BY team
ORDER BY size ASC, submission_time ASC
LIMIT 50
'''
)
results = cur.fetchall()
cur.close()
return results
def add_upload_to_db(team, submission, response, hashcash_stamp):
size = len(submission)
hash_ = hashlib.sha256(submission).hexdigest()
db = get_db()
db.execute(
'''
INSERT INTO uploads (
submission_time, size, team, hash,
submission, success, response, hashcash
) VALUES (datetime('now'), ?, ?, ?, ?, ?, ?, ?)
''',
(
size, team, hash_, submission, response == ExitCode.WINNER,
response.name, hashcash_stamp,
),
)
db.commit()
@app.route('/', methods=['GET'])
def scoreboard():
return render_template('scoreboard.html', results=get_scoreboard())
@app.route('/upload', methods=['GET', 'POST'])
def upload():
if request.method == 'POST':
return do_upload()
return render_template('upload.html')
@app.errorhandler(413)
def request_entity_too_large(e):
flash('This is golf.so, not asteriods.so.')
return redirect(request.url)
def flag_for_submission(submission):
easy_required_submission_bytes = 300
required_submission_bytes = 194
total_flags = 2
LEVELS = (
('non-trivial', app.config['MAX_CONTENT_LENGTH']),
('considerable', 500),
('thoughtful', easy_required_submission_bytes),
('hand-crafted', 224),
('flag-worthy', required_submission_bytes),
('record-breaking', 193),
('astounding', 176),
('impossible', 112),
('actually-impossible', 0),
)
submission_length = len(submission)
level = max(i for i, c in enumerate(LEVELS) if c[1] >= submission_length)
cur_level_name, _ = LEVELS[level]
next_level_name, next_level_bytes = LEVELS[level+1]
bytes_remaining = submission_length - next_level_bytes
bytes_word = 'bytes' if bytes_remaining != 1 else 'byte'
given_flags = []
if submission_length <= easy_required_submission_bytes:
with open(app.config['EASY_FLAG_FILE'], 'r') as f:
given_flags.append(f.read())
if submission_length <= required_submission_bytes:
with open(app.config['FLAG_FILE'], 'r') as f:
given_flags.append(f.read())
response = ' '.join([
f'You made it to level {level}: {cur_level_name}!',
f'You have {bytes_remaining} {bytes_word} left to be {next_level_name}.',
f'This effort is worthy of {len(given_flags)}/{total_flags} flags.'
] + given_flags)
return response
def linear_scale(value, srca, srcb, dsta, dstb):
result = (value - srca) * (dstb - dsta) // (srcb - srca) + dsta
dstlo, dsthi = min(dsta, dstb), max(dsta, dstb)
return min(max(result, dstlo), dsthi)
def load_hashcash_ds_table():
return set(
row['hashcash']
for row in get_db().execute('select hashcash from uploads;')
)
with app.app_context():
hashcash_ds = load_hashcash_ds_table()
def check_ds(token):
if token in hashcash_ds:
return True
hashcash_ds.add(token)
return False
def do_upload():
team = request.form.get('team', '')
if team: session['team'] = team
hashcash_stamp = request.form.get('hashcash', None)
if 'file' not in request.files:
flash('File missing')
return redirect(request.url)
file = request.files['file']
if not file or not file.filename:
flash('File missing')
return redirect(request.url)
if app.config['BITS'] > 0:
if not hashcash_stamp:
flash('Hashcash missing')
return redirect(request.url)
if not hashcash.check(hashcash_stamp, app.config['RESOURCE'],
app.config['BITS'], ds_callback=check_ds):
flash('Hashcash invalid')
return redirect(request.url)
userso = tempfile.NamedTemporaryFile(
suffix='.so', dir=app.config['UPLOAD_FOLDER'], delete=False)
file.save(userso)
userso.close()
os.chmod(userso.name, 0o755)
with open(userso.name, 'rb') as f:
userso_data = f.read()
response, details = test_userso(userso.name)
os.remove(userso.name)
add_upload_to_db(team, userso_data, response, hashcash_stamp)
if response == ExitCode.WINNER:
flash(flag_for_submission(userso_data))
return redirect(url_for('scoreboard'))
if response == ExitCode.ASSERTION:
message = (
'Something went wrong, try again. If this persists, '
'contact an admin.'
)
elif response == ExitCode.LOSER:
message = (
'Your .so died unexpectedly.'
)
elif response == ExitCode.NOTHING:
message = (
'Your .so didn\'t seem to do anything.'
)
elif response == ExitCode.PROGRESS:
message = (
'That pwned something, all right. Remember the goal.'
)
else:
assert response == ExitCode.TIMEOUT
message = (
'Your program timed out while running. Remember the goal.'
)
if details is not None:
message += ' ' + details
flash(message)
return redirect(request.url)
@enum.unique
class ExitCode(enum.Enum):
ASSERTION = 5
LOSER = 6
NOTHING = 7
PROGRESS = 8
WINNER = 9
TIMEOUT = -1
def test_userso(filename):
with subprocess.Popen(
['./runso', filename],
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
close_fds=True,
) as p:
try:
message, _ = p.communicate(timeout=app.config['EXECUTION_TIMEOUT'])
message = message.decode('ascii', 'backslashreplace')
return ExitCode(p.returncode), message
except ValueError:
return ExitCode.ASSERTION, None
except subprocess.TimeoutExpired:
return ExitCode.TIMEOUT, None
finally:
p.kill()
#!/usr/bin/env python3
"""Implement Hashcash version 1 protocol in Python
+-------------------------------------------------------+
| Written by David Mertz; released to the Public Domain |
+-------------------------------------------------------+
Very hackily modified
Double spend database not implemented in this module, but stub
for callbacks is provided in the 'check()' function
The function 'check()' will validate hashcash v1 and v0 tokens, as well as
'generalized hashcash' tokens generically. Future protocol version are
treated as generalized tokens (should a future version be published w/o
this module being correspondingly updated).
A 'generalized hashcash' is implemented in the '_mint()' function, with the
public function 'mint()' providing a wrapper for actual hashcash protocol.
The generalized form simply finds a suffix that creates zero bits in the
hash of the string concatenating 'challenge' and 'suffix' without specifying
any particular fields or delimiters in 'challenge'. E.g., you might get:
>>> from hashcash import mint, _mint
>>> mint('foo', bits=16)
'1:16:040922:foo::+ArSrtKd:164b3'
>>> _mint('foo', bits=16)
'9591'
>>> from sha import sha
>>> sha('foo9591').hexdigest()
'0000de4c9b27cec9b20e2094785c1c58eaf23948'
>>> sha('1:16:040922:foo::+ArSrtKd:164b3').hexdigest()
'0000a9fe0c6db2efcbcab15157735e77c0877f34'
Notice that '_mint()' behaves deterministically, finding the same suffix
every time it is passed the same arguments. 'mint()' incorporates a random
salt in stamps (as per the hashcash v.1 protocol).
"""
import sys
from string import ascii_letters
from math import ceil, floor
from hashlib import sha1 as sha
from random import choice
from time import strftime, localtime, time
ERR = sys.stderr # Destination for error messages
DAYS = 60 * 60 * 24 # Seconds in a day
tries = [0] # Count hashes performed for benchmark
def check(stamp, resource=None, bits=None,
check_expiration=None, ds_callback=None):
"""Check whether a stamp is valid
Optionally, the stamp may be checked for a specific resource, and/or
it may require a minimum bit value, and/or it may be checked for
expiration, and/or it may be checked for double spending.
If 'check_expiration' is specified, it should contain the number of
seconds old a date field may be. Indicating days might be easier in
many cases, e.g.
>>> from hashcash import DAYS
>>> check(stamp, check_expiration=28*DAYS)
NOTE: Every valid (version 1) stamp must meet its claimed bit value
NOTE: Check floor of 4-bit multiples (overly permissive in acceptance)
"""
try:
stamp_bytes = stamp.encode('ascii')
except UnicodeError:
ERR.write('Invalid hashcash stamp!\n')
return False
if stamp.startswith('0:'): # Version 0
try:
date, res, suffix = stamp[2:].split(':')
except ValueError:
ERR.write("Malformed version 0 hashcash stamp!\n")
return False
if resource is not None and resource != res:
return False
elif check_expiration is not None:
good_until = strftime("%y%m%d%H%M%S", localtime(time()-check_expiration))
if date < good_until:
return False
elif callable(ds_callback) and ds_callback(stamp):
return False
elif type(bits) is not int:
return True
else:
hex_digits = int(floor(bits/4))
return sha(stamp_bytes).hexdigest().startswith('0'*hex_digits)
elif stamp.startswith('1:'): # Version 1
try:
claim, date, res, ext, rand, counter = stamp[2:].split(':')
stamp_bytes = stamp.encode('ascii')
except ValueError:
ERR.write("Malformed version 1 hashcash stamp!\n")
return False
if resource is not None and resource != res:
return False
elif type(bits) is int and bits > int(claim):
return False
elif check_expiration is not None:
good_until = strftime("%y%m%d%H%M%S", localtime(time()-check_expiration))
if date < good_until:
return False
elif callable(ds_callback) and ds_callback(stamp):
return False
else:
hex_digits = int(floor(int(claim)/4))
return sha(stamp_bytes).hexdigest().startswith('0'*hex_digits)
else: # Unknown ver or generalized hashcash
ERR.write("Unknown hashcash version: Minimal authentication!\n")
if type(bits) is not int:
return True
elif resource is not None and stamp.find(resource) < 0:
return False
else:
hex_digits = int(floor(bits/4))
return sha(stamp_bytes).hexdigest().startswith('0'*hex_digits)
def is_doublespent(stamp):
"""Placeholder for double spending callback function
The check() function may accept a 'ds_callback' argument, e.g.
check(stamp, "mertz@gnosis.cx", bits=20, ds_callback=is_doublespent)
This placeholder simply reports stamps as not being double spent.
"""
return False
html {
box-sizing: border-box;
font-size: 100%;
font-family: sans-serif;
-webkit-text-size-adjust: 100%;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
font-size: 1em;
margin: 0;
background-color: #F1F8E9;
}
.center-box, .main-content, .main-header {
max-width: 40em;
margin: 0 auto;
}
.main-content {
padding: 0 .2em;
}
.main-header > .topbar-navigation {
margin: 10px 0;
}
.topbar-navigation {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-items: center;
}
.topbar-navigation > h1 {
margin: 3px;
}
.topbar-navigation > .button {
padding: .4em;
margin: .2em;
flex: 0 auto;
border-radius: 5px;
text-decoration: none;
color: inherit;
background-color: rgba(230, 230, 230, .6);
}
.scoreboard {
width: 100%;
display: grid;
grid-column-gap: 1em;
grid-row-gap: .5em;
grid-template-columns: [rank] 2em [team] 1fr [size] 3em [hash] 5em;
}
.scoreboard > .sep {
grid-column: 1 / 5;
border-bottom: solid 1px black;
}
.scoreboard > .rank {
grid-column: rank;
justify-self: right;
}
.scoreboard > .size {
grid-column: size;
justify-self: left;
}
.scoreboard > .hash {
grid-column: hash;
}
.scoreboard > .team {
grid-column: team;
}
pre {
margin-left: 3em;
font-size: inherit;
}
button, input {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
input {
display: block;
width: 100%;
padding: .375rem .75rem;
border-radius: 5px;
border: 1px solid #cecece;
}
label {
display: inline-block;
margin-bottom: .5em;
}
form {
padding: 1em;
background-color: #F1F8E9;
border-radius: 5px;
border: 1px solid #cecece;
}
button {
padding: .375rem .75rem;
border-radius: 5px;
}
.form-group {
margin-bottom: .75em;
}
.form-group:last-child {
margin-bottom: 0;
}
.flashed-message {
border: 1px solid #cecece;
background-color: #fff7f0;
padding: .375rem .75rem;
margin-bottom: 1rem;
}
all: runso
runso: LDLIBS += -lseccomp
.PHONY: all
# Copied from https://flask.palletsprojects.com/en/1.1.x/deploying/wsgi-standalone/
server {
listen 80;
server_name _;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location / {
proxy_pass http://127.0.0.1:8000/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
#include <fcntl.h>
#include <seccomp.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <sys/ptrace.h>
#include <sys/prctl.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <unistd.h>
#define BUFSIZE 1024
#define EXIT_ASSERTION 5
#define EXIT_LOSER 6
#define EXIT_NOTHING 7
#define EXIT_PROGRESS 8
#define EXIT_WINNER 9
#define FATAL(...) \
do { \
fprintf(stderr, __VA_ARGS__); \
fputc('\n', stderr); \
exit(EXIT_ASSERTION); \
} while (0)
#undef assert
#define assert(exp) \
do { \
if (!(exp)) \
FATAL("assertion failed (%s:%d) %s", __FILE__, __LINE__, #exp); \
} while (0)
void run_exec(char *preload) {
int res;
res = ptrace(PTRACE_TRACEME, 0, 0, 0);
assert(res == 0);
res = raise(SIGSTOP);
assert(res == 0);
/* Initialize seccomp */
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); assert(ctx != NULL);
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(access), 0); assert(res == 0);
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(arch_prctl), 0); assert(res == 0);
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0); assert(res == 0);
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0); assert(res == 0);
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat), 0); assert(res == 0);
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getcwd), 0); assert(res == 0);
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0); assert(res == 0);
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 0); assert(res == 0);
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0); assert(res == 0);
res = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); assert(res == 0);
res = seccomp_rule_add(ctx, SCMP_ACT_TRACE(0), SCMP_SYS(execve), 0); assert(res == 0);
res = seccomp_rule_add(ctx, SCMP_ACT_TRACE(0), SCMP_SYS(openat), 0); assert(res == 0);
res = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
assert(res == 0);
res = seccomp_load(ctx);
assert(res == 0);
char preload_env[BUFSIZE];
int envlen = snprintf(preload_env, BUFSIZE, "LD_PRELOAD=%s", preload);
assert(envlen <= BUFSIZE);
char *env[] = {preload_env, NULL};
execle("/bin/true", "/bin/true", NULL, env);
}
size_t read_str(int memfd, char *buf, size_t bufsize, off_t addr) {
ssize_t count = 0, total = 0;
while ((count = pread(memfd, buf + total, bufsize - total, addr + total)) > 0) {
size_t len = strnlen(buf + total, count);
if (len < count) {
return len + total;
}
total += count;
assert(total <= bufsize);
if (total == bufsize) {
buf[bufsize-1] = 0;
return bufsize - 1;
}
}
FATAL("failed to read string");
return 0;
}
int check_execve(struct user_regs_struct *regs, int memfd) {
char buf[BUFSIZE];
read_str(memfd, buf, BUFSIZE, regs->rdi);
if (strcmp(buf, "/bin/true") == 0) {
return 0;
}
if (strcmp(buf, "/bin/sh") == 0) {
off_t pairs[2];
ssize_t bytes = pread(memfd, &pairs, sizeof(pairs), regs->rsi);
if (bytes != sizeof(pairs)) {
printf("It looks like you tried to "
"execve(\"/bin/sh\", garbage, ...).\n");
return EXIT_PROGRESS;
}
if (pairs[0] == 0) {
printf("It looks like you tried to "
"execve(\"/bin/sh\", [], ...).\n");
return EXIT_PROGRESS;
}
read_str(memfd, buf, BUFSIZE, pairs[0]);
if (strstr(buf, "sh") == NULL || pairs[1] != 0) {
printf("It looks like you tried to "
"execve(\"/bin/sh\", [\"%s\", ...], ...).", buf);
return EXIT_PROGRESS;
}
return EXIT_WINNER;
}
printf("It looks like you tried to execve(\"%s\", ...).\n", buf);
return EXIT_PROGRESS;
}
int check_open(int memfd, off_t pathaddr, int oflag) {
if ((oflag|O_WRONLY) == O_WRONLY || (oflag|O_RDWR) == O_RDWR) {
printf("It looks like you tried to open something.\n");
return EXIT_PROGRESS;
}
char buf[BUFSIZE];
read_str(memfd, buf, BUFSIZE, pathaddr);
if (strstr(buf, "flag") != NULL) {
printf("It looks like you tried to open(%s).\n", buf);
return EXIT_PROGRESS;
}
if (strstr(buf, "/proc") != NULL) {
printf("It looks like you tried to open(%s).\n", buf);
return EXIT_PROGRESS;
}
return 0;
}
int open_mem(pid_t pid) {
char filename[BUFSIZE];
int res = snprintf(filename, BUFSIZE, "/proc/%d/mem", pid);
assert(res <= BUFSIZE);
return open(filename, O_RDONLY);
}
int run_parent(pid_t pid) {
/* Wait for the PTRACE_TRACEME */
int wstatus;
pid_t wpid;
int res;
wpid = waitpid(pid, &wstatus, 0);
assert(wpid == pid && WIFSTOPPED(wstatus));
int memfd = open_mem(pid);
assert(memfd > 0);
res = ptrace(PTRACE_SETOPTIONS, pid, NULL,
PTRACE_O_EXITKILL|PTRACE_O_TRACESECCOMP);
assert(res == 0);
while (true) {
res = ptrace(PTRACE_CONT, pid, 0, 0);
assert(res == 0);
wpid = waitpid(pid, &wstatus, 0);
assert(wpid == pid);
if (WIFEXITED(wstatus)) {
if (WEXITSTATUS(wstatus) == EXIT_ASSERTION) {
return EXIT_ASSERTION;
}
if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
return EXIT_NOTHING;
}
return EXIT_LOSER;
}
if (WIFSIGNALED(wstatus)) {
if (WTERMSIG(wstatus) == SIGKILL) {
printf("It looks like you might have tripped the seccomp.\n");
}
return EXIT_LOSER;
}
assert(WIFSTOPPED(wstatus));
switch (WSTOPSIG(wstatus)) {
case SIGSEGV:
printf("You segfaulted.\n");
return EXIT_LOSER;
case SIGTRAP:
break;
default:
FATAL("Unexpected wstatus %x", wstatus);
}
assert((wstatus >> 8) == (SIGTRAP|(PTRACE_EVENT_SECCOMP<<8)));
struct user_regs_struct regs;
res = ptrace(PTRACE_GETREGS, pid, NULL, &regs);
assert(res == 0);
switch (regs.orig_rax) {
case SYS_execve: {
res = check_execve(&regs, memfd);
if (res) return res;
/* Need to continue because ptrace stops after exec */
res = ptrace(PTRACE_CONT, pid, 0, 0);
assert(res == 0);
wpid = waitpid(pid, &wstatus, 0);
assert(wpid == pid);
/* Need to re-open mem after exec */
close(memfd);
memfd = open_mem(pid);
assert(memfd > 0);
break;
}
case SYS_openat: {
res = check_open(memfd, regs.rsi, regs.rdx);
if (res) return res;
break;
}
default: {
FATAL("Unexpected syscall trap: %llu", regs.orig_rax);
}
}
}
}
int main(int argc, char **argv) {
assert(argc == 2);
pid_t pid = fork();
if (pid == -1) FATAL("fork");
if (pid == 0) {
run_exec(argv[1]);
return EXIT_FAILURE;
} else {
return run_parent(pid);
}
}
CREATE TABLE uploads (
id INTEGER PRIMARY KEY,
submission_time TEXT NOT NULL,
size INTEGER NOT NULL,
team TEXT,
hash TEXT NOT NULL,
submission BLOB NOT NULL,
response TEXT NOT NULL,
success BOOLEAN NOT NULL,
hashcash TEXT
);
CREATE INDEX scoreboard_relevance ON uploads (
success, size, submission_time, team, hash);
{% extends "base.html" %}
{% block title %}golf.so &ndash; Scoreboard{% endblock %}
{% block content %}
<div class="scoreboard">
<div class="team">Name</div>
<div class="size">Bytes</div>
<div class="hash">Hash</div>
{% for result in results %}
<div class="sep"></div>
<div class="rank">{{ loop.index }}</div>
<div class="team">{{ result.team }}</div>
<div class="size">{{ result.size }}</div>
<div class="hash">{{ result.hash[:8] }}</div>
{% endfor %}
</div>
{% endblock %}
{% extends "base.html" %}
{% block title %}golf.so &ndash; Upload{% endblock %}
{% block content %}
<h1>Upload</h1>
<p>
Upload a 64-bit ELF shared object of size at most
1024 bytes. It should spawn a shell (execute
<code>execve("/bin/sh", ["/bin/sh"], ...)</code>)
when used like
</p>
<pre><code>LD_PRELOAD=&lt;upload&gt; /bin/true</code></pre>
<form method="POST" enctype="multipart/form-data" class="upload-form">
<div class="form-group">
<label for="team">Team</label>
<input type="text" id="team" name="team"
value="{{ session.get('team') or '' }}"
placeholder="Enter your team" />
</div>
<div class="form-group">
<label for="file">File</label>
<input type="file" id="file" name="file" required="required" />
</div>
{% if config.BITS > 0 %}
<div class="form-group">
<label for="hashcash">
<code>hashcash -m -b {{ config.BITS }} -s {{ config.RESOURCE }}</code>
</label>
<input type="text" id="hashcash" name="hashcash"
placeholder="An unused token" required="required" />
</div>
{% endif %}
<div class="form-group">
<button type="submit">
Upload
</button>
</div>
</form>
{% endblock %}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment