这是一道CTF赛题

这个也不错,是我喜欢的感觉,这种漏洞的描述就是简单高效。非常的精妙,但是要确认一下到底是什么地方是的路径的不统一呢,就是为什么 使用一个url编码就绕过了那个的识别呢

感觉这里和之前的那个url编码绕过差不多一个道理,因为

这里给新手宝宝们讲一下为什么是先过中间件再过路由,因为如果我们是先过路由的话,那我们是不是需要给每一个理由都写一个鉴权,这是非常的不合理的

所以逻辑就应该是,过全局的中间件,如果通过那就next() 然后丢到路由匹配,接着找到对应的路由的处理函数,接着把值传给路由函数进行处理,然后返回响应。

这里的问题就是,在中间件这里采用的是未解码的requesturi的形式,但是呢,然后去匹配api,那我们编码了之后肯定匹配不上的。

这个和 ..%2f

首先是先过鉴权,当然这里是白名单,就直接到了路由层,然后这里的 这个就会解码?然后赋值?其实我想确认一下,到底是从哪判断就是只允许接受两个参数的,然后从哪里开始进行解码的

对于很多web框架,我认为最重要的一点就是把鉴权和路由搞清楚,这样子会加快你的代审的进度,也加强了你的审能力。其实很多框架千奇百怪,不过就是,程序初始化时,注册路由,形成路由表,然后请求到的时候,看主程序逻辑,一般就是先过中间件,中间件可能会包括鉴权或者其他的一些限制,然后就直接next,就到了找路由,然后调用函数,返回response。

路由挂载入口

internal/server/server.go:31-383 - Build() 函数是路由挂载的核心:

func Build(db *gorm.DB, enableCompression bool) *gin.Engine {
    gin.SetMode(gin.ReleaseMode)
    
    router := gin.New()  // 创建 Gin 路由器
    
    // 挂载中间件
    if enableCompression {
        router.Use(gzip.Gzip(gzip.DefaultCompression))
    }
    router.Use(Logger(log.StandardLogger()), gin.Recovery())
    router.Use(TokenAuthMiddleware())
    
    // 挂载路由...
    
    return router
}

这里就是启动的时候挂载中间件的流程

// 1. Gzip 压缩中间件
router.Use(gzip.Gzip(gzip.DefaultCompression))

// 2. Logger 和 Recovery 中间件
router.Use(Logger(log.StandardLogger()), gin.Recovery())

// 3. 鉴权中间件(漏洞所在)
router.Use(TokenAuthMiddleware())

过完这些中间件之后,就可以看到路由挂载,路由+调用函数,都可以在其找到,可以自行下载源码查看。所以这里的逻辑就理清楚了,请求到,先过了中间件,尤其是这里的鉴权中间件。

这里出现的问题就在于这个鉴权的中间件

	return func(c *gin.Context) {
		userAccounts := config.GetConfig().UserAccounts
		if len(userAccounts) == 0 || !strings.HasPrefix(c.Request.RequestURI, "/api") {
			c.Next()
			return
		}

其他的代码我就不贴上了,只看关键部分。

大致的攻击流程可以概括为

攻击者发送: GET /%61pi/dashboard

Go 标准库解析:
  - RequestURI = "/%61pi/dashboard"  (原始)
  - URL.Path   = "/api/dashboard"    (已解码)

中间件检查 (server.go:413):
  - HasPrefix("/%61pi/dashboard", "/api") = false ❌
  - 绕过鉴权!

Gin 路由匹配:
  - 使用 URL.Path = "/api/dashboard"
  - 匹配到 router.GET("/api/dashboard", ...)
  - 执行处理函数 ✅

结果: 未鉴权访问受保护的 API

和AI对话路由