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

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

可以下载源码

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

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

先查看当前的.env文件

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

在最后一次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部分需要包含_id、name、email

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

使用BP抓包,发送数据

对一些关键字段进行解释
-
/api/priv:在index.js中其实只写了三条路由,分别是到authRoute->/api/user、privRoute->/api和webRoute->/,看常量定义和路径,privRoute对应routes中的private.js,结合private.js中的两条,所以请求/api/priv -
auth-token:在auth.js中其实可以看到res.header('auth-token', token).send(token);,所以在HTTP请求中字段名为auth-token
请求/api/logs,同样成功通过认证

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

反弹shell


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

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

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

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

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

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

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

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

unpack生成的crash文件

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

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

获取到root的SSH私钥

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

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

被折叠的 条评论
为什么被折叠?



