• [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,准备完毕可以执行了。

     

  • 相关阅读:
    在定义指针的时候,写成「int* p;」和「int *p;」哪个更好?
    Python判断一个整数是否是回文数的三种方法
    凉鞋的 Godot 笔记 201. 第三轮循环:引入变量
    Vue-(7)
    C++11特性-右值与右值引用
    渲染基础概念 unity shader 材质相关学习
    AOP结合注解实现项目中接口调用情况监控
    PMP-第二章-项目运作环境
    Vue Router 刷新当前页面
    Centos - DNS - 域名解析 服务搭建
  • 原文地址:https://blog.csdn.net/m0_62905261/article/details/126746489