• [De1CTF 2019]SSRF Me


    [De1CTF 2019]SSRF Me

    题目直接给了源代码和提示

    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')

    打开题目发现里面的东西就是题目中给的源代码

    直接审计代码,发现了三个路由,先看使用最广的/De1ta路由

    1. @app.route('/De1ta',methods=['GET','POST'])
    2. def challenge():
    3. action = urllib.unquote(request.cookies.get("action"))
    4. param = urllib.unquote(request.args.get("param", ""))
    5. sign = urllib.unquote(request.cookies.get("sign"))
    6. ip = request.remote_addr
    7. if(waf(param)):
    8. return "No Hacker!!!!"
    9. task = Task(action, param, sign, ip)
    10. return json.dumps(task.Exec())

    审计代码得知,我们需要传入三个值分别是action、param、sign,而param用get方法传值,action、sign用cookie传值,get传入的param需要经过waf函数,其次还要传入Task类对象,并执行Exec函数,先看一下waf函数

    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

    waf函数过滤了以gopher和file开有的协议,所以我们不能通过协议来读取文件,再看一下Exec函数

    1. def Exec(self):
    2. result = {}
    3. result['code'] = 500
    4. if (self.checkSign()):
    5. if "scan" in self.action:
    6. tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
    7. resp = scan(self.param)
    8. if (resp == "Connection Timeout"):
    9. result['data'] = resp
    10. else:
    11. print resp
    12. tmpfile.write(resp)
    13. tmpfile.close()
    14. result['code'] = 200
    15. if "read" in self.action:
    16. f = open("./%s/result.txt" % self.sandbox, 'r')
    17. result['code'] = 200
    18. result['data'] = f.read()
    19. if result['code'] == 500:
    20. result['data'] = "Action Error"
    21. else:
    22. result['code'] = 500
    23. result['msg'] = "Sign Error"
    24. return result

    它首先会通过一个checkSign方法检测登录,if通过以后要求传入的action必须要有关键词scan、read,才能够获取flag,看一下checkSign函数

    1. def checkSign(self):
    2. if (getSign(self.action, self.param) == self.sign):
    3. return True
    4. else:
    5. return False

    我们传入的action和param参数经过getSign函数之后与sign相等才会进行下一步,继续看一下getSign函数

    1. def getSign(action, param):
    2. return hashlib.md5(secert_key + param + action).hexdigest()

    发现getSign函数是secret_key、param、sction三者拼接后的md5,但是发现/geneSign路由,返回了getSign,可以生成我们需要的md5

    1. @app.route("/geneSign", methods=['GET', 'POST'])
    2. def geneSign():
    3. param = urllib.unquote(request.args.get("param", ""))
    4. action = "scan"
    5. return getSign(action, param)

    构造payload:/geneSign?param=flag.txt

    给了md5,但是这里只有scan功能,还需要read功能,/geneSign?param=flag.txt,返回的md5就是md5(secret_key+flag.txt+scan),即(secret_keyflag.txtscan),但如果我们param传入的值是flag.txtread(payload:/geneSign?param=flag.txtread),返回的值就是md5(secret_key+flag.txtread+scan),即(secret_keyflag.txtreadscan)。

    这样的话就满足了action需要有scan与read关键词的要求,给出的md5也就是sign的值,action传入的值直接为readscan,剩下的param只能等于flag.txt,准备完毕可以执行了。

     

  • 相关阅读:
    【Pytorch深度学习实战】(8)双向循环神经网络(BiRNN)
    神经网络梯度爆炸与梯度消失及其解决方法+实例
    C++标准模板(STL)- 类型支持 (运行时类型识别,type_info )
    AirPods跳转下一首歌的操作方法,“代”数不同,方法也不同
    create® 3入门教程-设置NTP
    数据库搭建与使用
    电机带宽的形象理解
    大数据性能测试方案-V1.0
    Centos7.9安装Nginx
    使用go开发的小tips
  • 原文地址:https://blog.csdn.net/m0_62905261/article/details/126746489