HackTheBox | Secret

文章描述了一次针对HackTheBox平台Secret靶机的渗透测试过程,涉及了nmap端口扫描、源码下载、git日志分析以获取token,以及通过Node.js应用的逻辑漏洞进行权限提升,最终通过生成coredump文件并解析获取root权限的过程。

HackTheBox | Secret

nmap扫描,开放22、80、3000;其中3000端口为Node.js的站点

image-20230105191130677

访问80端口跟3000端口,都是如下页面

image-20230105232921348

可以下载源码

image-20230105232935558

下载并解压源码,看到.git目录

image-20230105233035266

使用git log命令查看git提交日志,看到removed .env for security reasons

image-20230105233133569

先查看当前的.env文件

image-20230105233425939

执行git show 67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78查看该次commit的具体提交,获取到一个token

gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE

image-20230105233822750

在最后一次commit中提到了可以view logs from server,所以也检查一下该次的具体提交内容

由于不太懂node.js,所以先放着

commit e297a2797a5f62b6011654cf6fb6ccb6712d2d5b (HEAD -> master)
Author: dasithsv <dasithsv@gmail.com>
Date:   Thu Sep 9 00:03:27 2021 +0530

    now we can view logs from server 😃

diff --git a/routes/private.js b/routes/private.js
index 1347e8c..cf6bf21 100644
--- a/routes/private.js
+++ b/routes/private.js
@@ -11,10 +11,10 @@ router.get('/priv', verifytoken, (req, res) => {

     if (name == 'theadmin'){
         res.json({
-            role:{
-
-                role:"you are admin",
-                desc : "{flag will be here}"
+            creds:{
+                role:"admin",
+                username:"theadmin",
+                desc : "welcome back admin,"
             }
         })
     }
@@ -26,7 +26,32 @@ router.get('/priv', verifytoken, (req, res) => {
             }
         })
     }
+})
+

+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) {
@@ -40,4 +65,4 @@ router.use(function (req, res, next) {
 });


-module.exports = router
\ No newline at end of file
+module.exports = router

查看源码,index.js中可以了解基本逻辑

const express = require('express');
const app = express();
const mongoose = require('mongoose');
const dotenv = require('dotenv')
const privRoute = require('./routes/private')
const bodyParser = require('body-parser')

app.use(express.static('public'))
app.use('/assets', express.static(__dirname + 'public/assets'))
app.use('/download', express.static(__dirname + 'public/source'))

app.set('views', './src/views')
app.set('view engine', 'ejs')


// import routs
const authRoute = require('./routes/auth');
const webroute = require('./src/routes/web')

dotenv.config();
//connect db

mongoose.connect(process.env.DB_CONNECT, { useNewUrlParser: true }, () =>
    console.log("connect to db!")
);

//middle ware
app.use(express.json());
app.use('/api/user',authRoute)
app.use('/api/', privRoute)
app.use('/', webroute)

app.listen(3000, () => console.log("server up and running"));

routes文件夹下找到private.js,发现和前面git show最后一次commit的内容基本一致;可以向/priv或者/logs页面提交请求,对于提交的内容会调用verifytoken

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
            }
        })
    }
})


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

verifytoken中看到主要使用jwt.verify方法进行验证,所以需要向指定页面/priv/logs发送包含jwt token的请求

# verifytoken
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");
    }
};

auth.js中看到jwt的格式,payload部分需要包含_idnameemail

image-20230106000557418

使用在线网站https://jwt.io/,生成jwt token。根据之前对private.js代码的分析,可以知道用户名为theadmin,没有对id和email进行验证,所以先随便写;在signature部分,将之前在.env获取到的TOKEN写到第三个字段

image-20230106001130653

使用BP抓包,发送数据

image-20230106001412799

对一些关键字段进行解释

  • /api/priv:在index.js中其实只写了三条路由,分别是到authRoute->/api/userprivRoute->/apiwebRoute->/,看常量定义和路径,privRoute对应routes中的private.js,结合private.js中的两条,所以请求/api/priv

  • auth-token:在auth.js中其实可以看到res.header('auth-token', token).send(token);,所以在HTTP请求中字段名为auth-token

请求/api/logs,同样成功通过认证

image-20230106001859070

在logs的函数部分,提到了参数file,用于git log命令拼接,所以可以尝试命令截断执行

image-20230106002107798

反弹shell

image-20230106002857625

image-20230106002840526

bd6a3f9c29090aacf16faa43412053a9

由于不知道dasith用户的密码,所以无法执行sudo -l命令检查sudo特权

image-20230106090238418

检查SUID置位的文件,发现特殊文件/opt/count

image-20230106090335217

同目录下存在code.cvalgrind.log,检查三个文件的时间,也比较相近。valgrind.log可能是count的执行历史记录

image-20230106090703470

code.c可能是源码文件

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/limits.h>

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;
}

在main函数中,看到Enable coredump generation,对应代码prctl(PR_SET_DUMPABLE, 1);

    // drop privs to limit file write
    setuid(getuid());
    // Enable coredump generation
    prctl(PR_SET_DUMPABLE, 1);

利用搜索引擎,大概了解到这两行代码是为了生成一个内存的状态文件,参考https://www.cnblogs.com/hazir/p/linxu_core_dump.html

image-20230106091849827

尝试生成core dump文件;运行./count,输入文件名/etc/shadow

image-20230106094923755

另开一个shell接收新的反弹shell,在该shell中查找count进程,然后发送SIGSEGV信号,在count的执行页面能够看到Segmentation fault

image-20230106095049894

但是没有找到core dump文件。在网上看到的说一般会在可执行程序的同一个目录下,或者/usr/share/apport/路径下,但是都没有看到。

在wp中看到存储于/var/crash/路径下

image-20230106095305112

根据wp的提示,在主机上找到了apport-unpack工具

image-20230106100413003

unpack生成的crash文件

image-20230106100627350

查看CoreDump可以获取到一些文件内容

image-20230106100700550

同样可以获取到root用户的私钥文件;注意,此处生成crash文件时,需要先将原来的crash文件删除

image-20230106100842085

获取到root的SSH私钥

image-20230106101311889

将ssh私钥保存到文件中,并将文件权限修改为600,使用ssh私钥登录,拿到root权限

image-20230106101517199

【电动车】基于多目标优化遗传算法NSGAII的峰谷分时电价引导下的电动汽车充电负荷优化研究(Matlab代码实现)内容概要:本文围绕“基于多目标优化遗传算法NSGA-II的峰谷分时电价引导下的电动汽车充电负荷优化研究”展开,利用Matlab代码实现优化模型,旨在通过峰谷分时电价机制引导电动汽车有序充电,降低电网负荷波动,提升能源利用效率。研究融合了多目标优化思想与遗传算法NSGA-II,兼顾电网负荷均衡性、用户充电成本和充电满意度等多个目标,构建了科学合理的数学模型,并通过仿真验证了方法的有效性与实用性。文中还提供了完整的Matlab代码实现路径,便于复现与进一步研究。; 适合人群:具备一定电力系统基础知识和Matlab编程能力的高校研究生、科研人员及从事智能电网、电动汽车调度相关工作的工程技术人员。; 使用场景及目标:①应用于智能电网中电动汽车充电负荷的优化调度;②服务于峰谷电价政策下的需求侧管理研究;③为多目标优化算法在能源系统中的实际应用提供案例参考; 阅读建议:建议读者结合Matlab代码逐步理解模型构建与算法实现过程,重点关注NSGA-II算法在多目标优化中的适应度函数设计、约束处理及Pareto前沿生成机制,同时可尝试调整参数或引入其他智能算法进行对比分析,以深化对优化策略的理解。
为解决客户端提供的`client_secret`与`PaymentIntent`关联的`client_secret`不匹配问题,可以采取以下方法: ### 1. 确认生成逻辑正确性 在服务器端生成`PaymentIntent`时,要确保生成逻辑准确无误。参考示例代码,在PHP中创建`PaymentIntent`对象并获取`client_secret`: ```php <?php require '../vendor/autoload.php'; // This is your test secret API key. \Stripe\Stripe::setApiKey('sk_test_***'); function calculateOrderAmount(array $items): int { // Replace this constant with a calculation of the order's amount // Calculate the order total on the server to prevent // customers from directly manipulating the amount on the client return 1400; } header('Content-Type: application/json'); try { // retrieve JSON from POST body $json_str = file_get_contents('php://input'); $json_obj = json_decode($json_str); $paymentIntent = \Stripe\PaymentIntent::create([ 'amount' => calculateOrderAmount($json_obj->items), 'currency' => 'usd', ]); $output = [ 'clientSecret' => $paymentIntent->client_secret, ]; echo json_encode($output); } catch (Error $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } ``` 要保证在生成订单金额、货币类型等参数时没有错误,并且正确地将`client_secret`返回给客户端。 ### 2. 检查数据传输过程 在数据从服务器传输到客户端的过程中,可能会出现数据丢失或损坏的情况。可以通过以下方式检查: - **日志记录**:在服务器端记录生成的`client_secret`,在客户端记录接收到的`client_secret`,对比两者是否一致。 - **数据校验**:添加一些简单的校验机制,如哈希值,确保数据在传输过程中没有被篡改。 ### 3. 排查客户端使用情况 确保客户端在使用`client_secret`时没有误操作。比如,检查是否在页面刷新、多次提交等情况下使用了旧的`client_secret`。客户端在获取到新的`PaymentIntent`对象及其中的`client_secret`后,应该及时更新使用的`client_secret`。 ### 4. 确认业务流程一致性 在整个支付业务流程中,要保证客户端和服务器端的操作同步。比如,在用户访问支付页面、输入信用卡信息、创建`client_secret`等环节,确保各个步骤按顺序正确执行。参考支付流程: 1. 用户访问支付页面(可由商家网站或Stripe生成)。 2. 系统向服务器发送订单信息,返回新的`PaymentIntent`对象监控交易。 3. 用户在支付页面输入信用卡信息。 4. 创建`client_secret`,包含在`PaymentIntent`对象中。 5. Stripe尝试将支付重定向到用户卡网络(最终到发卡银行)。 6. 银行检查资金并接受或拒绝支付请求。 7. 若成功,转账在收款银行启动。 8. Webhook将支付状态发送到服务器。 9. 支付状态显示给用户。 要保证每个步骤都正确执行,避免因流程混乱导致`client_secret`不匹配。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值