docker创建容器,打开
提示我们需要localhost,但是我们使用XFF不可以,同时题目也给了附件,是go语言的,不是很会,这两天学了一下,来看下这个题目,复现下
题目给的附件:
打开看了main.go,这个源码是缺了一部分的,但是不影响做题(题目附件中缺了templates,赛后出题人将所有的源码放在了github,复现题目没问题)
之前没碰过go,边学边做吧
本题目用了一个gin框架
Gin是一个使用Go开发的web框架,简洁,性能好,开箱即用,方便扩展。
package main
import (
"html/template"
"loginme/middleware"
"loginme/route"
"loginme/templates"
"github.com/gin-gonic/gin"
)
//Gin是一个使用Go开发的web框架
func main() {
gin.SetMode(gin.ReleaseMode) //选择release模式
r := gin.Default() //创建实例
templ := template.Must(template.New("").ParseFS(templates.Templates, "*.tmpl")) //模板解析
r.SetHTMLTemplate(templ) //该方法会gin实实例绑定一个模板引擎(内部其实是设置了engine的HTMLRender属性),也就是模板渲染
// 通过use设置全局中间件
// 设置日志中间件,主要用于打印请求日志
r.Use(gin.Logger())
// 设置Recovery中间件,主要用于拦截paic错误,不至于导致进程崩掉
r.Use(gin.Recovery())
//一个新的路由
authorized := r.Group("/admin")
//调用函数middleware.LocalRequired(),其实是个waf
authorized.Use(middleware.LocalRequired())
{
authorized.GET("/index", route.Login)
}
r.GET("/", route.Index)
r.Run(":9999") //运行服务器,监听9999端口
}
首先是创建了一个gin实例,然后是在/admin
路由下,调用middleware.LocalRequired()
,跟进,发现这个是一个简单的防护
func LocalRequired() gin.HandlerFunc {
return func(c *gin.Context) {
//获取数据报头部,并且判断
if c.GetHeader("x-forwarded-for") != "" || c.GetHeader("x-client-ip") != "" {
c.AbortWithStatus(403)
return
}
ip := c.ClientIP()
if ip == "127.0.0.1" {
c.Next()
} else {
c.AbortWithStatus(401)
}
}
}
请求头中不能有x-forwarded-for
或者x-client-ip
,不然返回403
同时还使用ClientIP()
,用来判断ip是否等于127.0.0.1,这个可以通过X-Real-IP:127.0.0.1
进行绕过
绕过这个之后,继续分析
接下来跳到route.Login
,跟进
func Login(c *gin.Context) {
idString, flag := c.GetQuery("id") //gin的获取url query参数
if !flag {
idString = "1"
}
id, err := strconv.Atoi(idString) //用于将字符串类型转换为int类型,整了之后,id就是int类型的数据了
if err != nil { //当出现不等于nil的时候,说明出现某些错误了,这个地方是调用方法出错的时候应该怎么做
id = 1
}
TargetUser := structs.Admin //一个结构体
for _, user := range structs.Users { //循环,看id等于几,然后将TargetUser赋值
if user.Id == id {
TargetUser = user
}
}
age := TargetUser.Age
if age == "" {
age, flag = c.GetQuery("age")
if !flag {
age = "forever 18 (Tell me the age)"
}
}
if err != nil {
c.AbortWithError(500, err)
}
html := fmt.Sprintf(templates.AdminIndexTemplateHtml, age) //格式化字符串并赋值给新串
if err != nil {
c.AbortWithError(500, err)
}
//Parse()方法用来解析、评估模板中需要执行的action,其中需要评估的部分都使用{{}}包围,并将评估后(解析后)的结果赋值给tmpl。
tmpl, err := template.New("admin_index").Parse(html)
if err != nil {
c.AbortWithError(500, err)
}
//将对象实例应用到已经解析的tmpl模板
tmpl.Execute(c.Writer, TargetUser)
}
这一段,会获取url中的参数id
,进行参数类型转换
同时还定义了TargetUser
,为一个结构体,注意这个结构体,跟进
var Users = []UserInfo{
{
Id: 1,
Username: "Grandpa Lu",
Age: "22",
Password: "hack you!",
},
{
Id: 2,
Username: "Longlone",
Age: "??",
Password: "i don't know",
},
{
Id: 3,
Username: "Teacher Ma",
Age: "20",
Password: "guess",
},
}
var Admin = UserInfo{
Id: 0,
Username: "Admin",
Age: "",
Password: "flag{}",
}
在之后,会将传入的id和users中的id进行对比,id=0时,还是Username=“Admin”
接下来传参age,因为Admin中的Age
默认是空的,所以会执行age传参
if age == "" {
age, flag = c.GetQuery("age")
if !flag {
age = "forever 18 (Tell me the age)"
}
}
格式化字符串
html := fmt.Sprintf(templates.AdminIndexTemplateHtml, age) //格式化字符串并赋值给新串
接着往下走,有个渲染
tmpl, err := template.New("admin_index").Parse(html)
go语言模板渲染支持传入一个结构体的实例来渲染它的字段,就有可能造成信息泄露
而在go语言中使用的是{{.name}}
代表要应用的对象,所以可以让age={{.Password}}
抓包修改