UniCTF Web全解(全速更新中)

UniCTF Web全解(全速更新中)
LuooUUniCTF Web 全解
GlyphWeaver
题目提示jinja2,说明存在SSTI注入。
注意这个CJK-friendly,上网查了一下,中日韩文友好,这实际上也意味着对Unicode兼容字符做了规范化处理,全角会变成半角。
waf显然是对原始输入进行检测,但是payload并没有被执行,说明这个预览仅进行一次渲染,需要寻找二次渲染触发点。而下面的Export Console很显然就是我们要找的触发点。
waf同样只检测原始输入,但是因为这里是创建一个包含信息的Task,所以在第一次渲染后,Motto里的圆角{}就变成了半角,再点右上角的Check Status,就会进行没有waf的二次渲染。
最终,圆角杀死了比赛。
1 | UniCTF{${uuid}}{e71cb3f2-6353-46d2-bab6-e60d96efd048} |
SecureDoc(Adobe XFA)
只允许上传PDF格式的文件,然后支持的功能里可以看到XFA-based …,这意味着后端会提取并解析PDF中的**XFA(XML Forms Architecture)**数据。
依然先贴出payload:
1 |
|
XFA是Adobe为了让PDF更强大而引入的新技术,它允许PDF内部嵌入一段XML数据来动态描述表单的布局、数据和交互逻辑。但是如果我们能控制XML数据,就可以控制XML解析器的行为。
结合脚本,讲讲XML的格式:
信头:
<?xml version="1.0" encoding="UTF-8"?>一份声明,标志着这是一个XML文档。
DTD:
1
2
3 <!DOCTYPE data [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<!DOCTYPE data[...]>一个文档类型定义,也是攻击将要发生的地方,我们可以在这里定义一些变量。
<!ENTITY ...>就是定义变量的具体位置,其中ENTITY意为实体(可以理解为变量),xxe则是变量名,SYSTEM是命令关键词,"file:///flag"是变量的值。整段连起来,
xxe这个变量的值就是系统中/flag文件的内容。XFA表单数据:
1 <xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<xdp:xdp>...</xdp:xdp>是Adobe XFA特有的标签,标志着其中是XFA表单数据。xmlns:xdp="..."是命名空间,xmlns即为XML NameSpace,它定义xdp这个名字代表其中的网址。我们命名的这个xdp只能是前缀,比如我定义a即<a:xdp>,后面的xdp是元素名、代表XFA数据包的根节点,是死的。但是最好就叫xdp,避免因为后端正则匹配触发不了解析器。http://ns.adobe.com/xdp/代表其中XFA表单数据都是Adobe XFA表单数据,这个也是死的。深入表单内部:
<config><present><pdf><interactive>1</interactive></pdf></present></config>
<config></config>代表其中的内容是一些配置。
<present></present>指定配置的具体方向,是偏向展示的配置。
<pdf></pdf>指定配置只对PDF生效。
<interactive>1</interactive>是一个交互性开关,1为True、0为False,True代表其中的变量可以被渲染,如果为False则原样输出。这段config可以不写的,写了可以确保兼容性。
<template>是表单模板,类似于html里的<body>。
<subform>类似于html里的<div>。
<field>定义一个输入框,类似于html里的<input type="text">
<value><text>定义输入框里的默认内容。在其中我们用一些文字包裹变量,就将代码包装成了合法的表单字段值。如果解析器处理完了XXE,
<text></text>中就会回显flag内容。
我们回头讲一下XXE。
XXE(XML External Entity Injection),顾名思义,就是XML外部实体注入,我们通过File://让变量指向外部文件,而服务器上的XML解析器刚好也加载了外部实体的功能,那么我们就能读到外部文件的内容。
包装成PDF即为:
1 | %PDF-1.4 |
那这道题发包即可,重定向就能看到flag。
1 | UniCTF{3b9469fd-c505-4b85-9504-e1ec0bcebb00} |
CloudDiag
SSRF题目。
SSRF(Server-Side Request Forgery,服务端请求伪造),是一种借服务器访问网址并返回结果的方法。比如这道题,我们因为无法直接访问
http://metadata:1338/这个内网地址,但是可以通过服务器访问。传统的SSRF通常用来攻击内网Redjs、MySQL、读取etc/passwd,但是这道题是云SSRF,目标是读取云主机元数据服务(Instance Metadata Service,IMDS)。MetaData(元数据)是什么?
当我们在云上创建一台虚拟机时,云厂商会给机器提供一个元数据服务(IMDS),让这台机器通过访问特定的内网地址(比如
169.254.169.254)来查询自己的信息。信息包括实例ID、位置区域、私有IP和公有IP、IAM角色凭证,我们可以通过SSRF获取这些信息。通过用服务器访问内网中的某些路径即可获得信息,但是路径是什么?
路径有固定格式,例如本题在AWS中,
http://metadata:1338/latest/meta-data/iam/security-credentials/,路径就是:/版本号(通常是latest)/元数据目录(通常是meta-data)/IAM(我们要找的东西)/安全凭证(security-credentials)/角色名
给了一个登录页面,题目大概率是有高权限的账号的,尝试弱口令爆破。
看到用户名root,密码root123。
有一个历史记录Legacy metadata check,点进去可以看到:
拿到了路径和角色名,前往/tasks接口拼接查询:
1 | http://metadata:1338/latest/meta-data/iam/security-credentials/clouddiag-instance-role |
拿到:
1 | { |
接下来我们就可以去/explorer读取S3存储桶。
关于
buckets和S3(Simple Storage Service):存储桶(Bucket)是对象的载体,可理解为存放对象的“容器”,且该“容器”无容量上限。对象以扁平化结构存放在存储桶中,无文件夹和目录的概念,用户可选择将对象存放到单个或多个存储桶中。
用一句话来说,存储桶就是一个公共的、没有目录结构的容器,而对象(Object)就是其中的文件。只需要拿到AK(角色名)、SK(密码)和Token就可以看到存储桶中的特定对象。
在这里填上相关信息,可以看到服务器拥有的Buckets:
选择clouddiag-secrets这个bucket,可以看到bucket中的objects:
再指定flags/runtime/flag-1f3ca9c225dc4b118d951dc844c4d229.txt这个object,即可得到flag:
1 | UniCTF{be8049b2-0ec7-47e8-84be-ef339425f3cd} |
ezUpload(Revenge)
文件上传题,⚠️ Upload Restrictions
- Max size: 1KB
- Forbidden chars: ? $ & ; | ` \ <
- No PHP code
<被ban了就可以告别php代码了。
看到响应头:
1 | Server: Apache/2.4.65 (Debian) |
于是考虑.htaccess。
关于
.htaccess:全称是
Hypertext Access,是Apache Web服务器(或者LiteSpeed)中使用的一种目录级别的配置文件。作用是允许在不修改全局配置(即httpd.conf)且不重启服务器的情况下,对特定目录及其子目录下的Web请求进行动态的配置控制。最大的特色是允许即时解析。要想
.htaccess文件生效首先需要主配置文件打开AllowOverride(AllowOverride All等),如果写了
AllowOverride None就无效了。还可以控制.htaccess文件具体控制的内容,比如写
1 AllowOverride FileInfo AuthConfig即只允许重写文件类型和权限认证相关的指令。
解法一:
这里给出.htaccess的payload:
1 | RewriteEngine On |
这个文件读取flag的逻辑是利用Apache内置的file函数把/flag读出来,存到环境变量里再读出来。
第一行RewriteEngine On:打开重写引擎,引入RewriteCond和RewriteRule。
第二行RewriteCond expr"file('/flag') =~ /(.+)/" :
RewriteCond:即Rewrite Condition,相当于if,后面接条件,如果满足条件就触发RewriteRule内容。expr"...":即expression,可以解析""里的表达式。file('/flag'):expr引擎提供的函数。=~意思是:左边的内容是否可以和右边的正则匹配。/(.+)/://为边界符,()为捕获组,即将匹配到的内容捕获并暂存,.代表任意单个字符,+代表前面的字符有出现过,所以.+即匹配任意一段不为空的文本。
第三行RewriteRule .* - [E=FLAG_CONTENT:%1]:
RewriteRule:执行动作。.*:匹配任意请求。-:不修改网址。[E=FLAG_CONTENT:%1]:[]即标志位,用来执行额外的特殊指令。中间内容即E=变量名:变量值,创建一个环境变量。%1是反向引用,提取上一行中()里的内容。
第四行Header set X-Test-Expr "%{FLAG_CONTEN}e":
-
Header:调用响应头模块。 -
set:设置。 -
X-Test-Expr:自定义Header名字。 -
"%{FLAG_CONTENT}e":Header的值。使用%{}e可以读取{}中环境变量的值。
新建任意文件上传,访问,看头即可。
解法二(仅针对非Revenge):
1 | Options +Indexes |
第一行Options +Indexes:开启Apache的目录浏览功能,由此可以访问/upload。
第二行DirectoryIndex /123.txt:设置目录索引,用户访问/upload时会默认展示/123.txt这个文件,如果没有就展示索引。因为看到了有一个test.txt,所以这里可以改成test.txt。
第三行Header set X-Flag "expr=%{file:/flag}":与前一种方法有异曲同工之妙,但是这个更简洁。
1 | UniCTF{7cb4c534-0166-499a-87ab-82678b300f7c} |
一鸣唱吧
依然有登录界面,可以尝试找一下高权限账号。
可以在主页看到上传,这里随便传一个空的a.txt,
可以看到文件重命名了,规律是UNiCTF2026xx,后缀名不变。所以这里我们直接fuff爆破,
先用seq创造00~99的字典,
1 | seq -w 0 99 > /tmp/num00.txt |
再通过kali自带的后缀名词典拼接爆破:
1 | ffuf -u |
最终看到:
看到UNiCTF202638.php是一个phpinfo,直接找环境变量,可以看到flag。
1 | UniCTF{d6c44bca-1d34-4146-bf93-6b481297d3d9} |
也许是非预期呢,预期解稍后再更吧。



