这是一道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