PicoCTF——Most Cookie思路讲解

目录

一.PicoCTF平台及题目简介

二.源码分析(能看懂大概意思的都可以跳过这个部分了)

        1.导入模块

        2.探寻session加密密钥

        3.根目录函数

        4.Search路径函数

        5.Reset路径函数

        6.display路径函数

三.思路介绍

工具在这里啊!!!


一.PicoCTF平台及题目简介

        题目中给出了目标网站的后端源码,server.py文件如下。

from flask import Flask, render_template, request, url_for, redirect, make_response, flash, session
import random
app = Flask(__name__)
flag_value = open("./flag").read().rstrip()
title = "Most Cookies"
cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"]
app.secret_key = random.choice(cookie_names)

@app.route("/")
def main():
	if session.get("very_auth"):
		check = session["very_auth"]
		if check == "blank":
			return render_template("index.html", title=title)			
		else:
			return make_response(redirect("/display"))
	else:
		resp = make_response(redirect("/"))
		session["very_auth"] = "blank"
		return resp

@app.route("/search", methods=["GET", "POST"])
def search():
	if "name" in request.form and request.form["name"] in cookie_names:
		resp = make_response(redirect("/display"))
		session["very_auth"] = request.form["name"]
		return resp
	else:
		message = "That doesn't appear to be a valid cookie."
		category = "danger"
		flash(message, category)
		resp = make_response(redirect("/"))
		session["very_auth"] = "blank"
		return resp

@app.route("/reset")
def reset():
	resp = make_response(redirect("/"))
	session.pop("very_auth", None)
	return resp

@app.route("/display", methods=["GET"])
def flag():
	if session.get("very_auth"):
		check = session["very_auth"]
		if check == "admin":
			resp = make_response(render_template("flag.html", value=flag_value, title=title))
			return resp
		flash("That is a cookie! Not very special though...", "success")
		return render_template("not-flag.html", title=title, cookie_name=session["very_auth"])
	else:
		resp = make_response(redirect("/"))
		session["very_auth"] = "blank"
		return resp

if __name__ == "__main__":
	app.run()

二.源码分析(能看懂大概意思的都可以跳过这个部分了)

AI“专业“讲解:

这段代码是一个使用 Flask 框架编写的简单的 Web 应用程序。它的主要功能是展示一个名为 "Most Cookies" 的网页,用户可以在其中搜索不同类型的 cookie,并根据搜索结果显示相应的页面内容。下面是这段代码的功能和结构解析:

1.  **导入必要的模块和库**:
 
          *   使用 `from flask import Flask, render_template, request, url_for, redirect, make_response, flash, session` 导入了 Flask 框架以及其他需要的模块。
2.  **设置应用程序和密钥**:
    
    *   创建了一个 Flask 应用程序实例,并将其赋值给变量 `app`。
    *   使用 `random.choice(cookie_names)` 随机选择了一个 cookie 名称,并将其设置为应用程序的密钥 `app.secret_key`。这个密钥在会话管理中起到了重要作用,用于加密和验证会话数据。
3.  **定义路由和视图函数**:
    
    *   使用 `@app.route()` 装饰器定义了四个路由,分别处理不同的 HTTP 请求和 URL 路径。
    *   `main()` 函数处理主页请求,根据用户会话状态重定向到不同的页面。
    *   `search()` 函数处理搜索请求,根据用户输入的 cookie 名称在 cookie 名称列表中进行搜索,并根据搜索结果重定向到不同的页面。
    *   `reset()` 函数处理重置会话请求,清除了会话中的 "very_auth" 键,并重定向到主页。
    *   `flag()` 函数用于展示 flag 页面,根据用户会话状态和权限,显示不同的页面内容。
4.  **会话管理**:
    
    *   使用 `session` 对象来管理用户的会话状态和数据。会话数据通过加密和签名来保护,确保数据的完整性和安全性。
    *   在每个视图函数中都有相应的会话管理逻辑,根据用户的操作和请求来更新会话数据。
5.  **模板渲染和页面跳转**:
    
    *   使用 `render_template()` 函数来渲染 HTML 模板,生成网页内容。
    *   使用 `redirect()` 函数来进行页面重定向,根据用户的操作和请求跳转到不同的页面。
6.  **错误处理和消息提示**:
    
    *   使用 `flash()` 函数来显示消息提示,例如在搜索失败或权限不足时向用户显示相应的提示信息。
7.  **启动应用程序**:
    
    *   在 `if __name__ == "__main__":` 条件下调用 `app.run()` 方法来运行应用程序,在调试模式下启动 Flask 服务器。

综上所述,这段代码实现了一个简单的 Web 应用程序,用于展示不同类型的 cookie,并根据用户的操作和权限显示相应的页面内容。

     

个人理解

  因为作者也不会太多,只是做完题浅浅记录一下,所以很多地方会讲个人不成熟(不够专业不全面)的理解。也只讲一些个人异或过的地方,太基础的代码就不提了。勿喷,没讲到的地方自查。别问,因为作者多半不会。

        1.导入模块

from flask import Flask, render_template, request, url_for, redirect, make_response, flash, session
import random

        2.探寻session加密密钥

app = Flask(__name__)
flag_value = open("./flag").read().rstrip()
title = "Most Cookies"
cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"]
app.secret_key = random.choice(cookie_names)

        cookie_names是备用密钥列表,通过随机数选择出其中一个字符串作为你的签名密钥。不太了解该session知识的可以先往下看。

        3.根目录函数

@app.route("/")
def main():
	if session.get("very_auth"):
		check = session["very_auth"]
		if check == "blank":
			return render_template("index.html", title=title)			
		else:
			return make_response(redirect("/display"))
	else:
		resp = make_response(redirect("/"))
		session["very_auth"] = "blank"
		return resp

@app.route("/")是一个路由,大概作用嘛就是使你在访问该网站的根目录的时候,服务器端会执行这个main函数来决定返回给你的内容。

main()函数里面的逻辑就是:先从你发送到服务器端的cookie字段检查看有没有very_auth这个键(python字典)。如果有,就进入第一个if语句里面,检查这个值是不是blank。如果是blank,则会使用render_template()方法返回给你一个名叫index.html的文件,从而显示在你的浏览器中,标题就是most cookie。

如果没有very_auth值,则会使用make_response()方法给你发送一个重定向HTTP数据包。也就是给你的session加个{"very_auth":"blank"},再让你重新访问根目录。

        4.Search路径函数

@app.route("/search", methods=["GET", "POST"])
def search():
	if "name" in request.form and request.form["name"] in cookie_names:
		resp = make_response(redirect("/display"))
		session["very_auth"] = request.form["name"]
		return resp
	else:
		message = "That doesn't appear to be a valid cookie."
		category = "danger"
		flash(message, category)
		resp = make_response(redirect("/"))
		session["very_auth"] = "blank"
		return resp

当你得到了index.html(如下图),输入一个值并且提交,那么这个值就会被保存在数据包中,在name变量中。

这个Get请求数据包对应的去访问/search路径,就会执行search()函数。该函数对name检查,符合条件会重定向到/display路径去,注意此时会将very_auth设置为name的值,也就讲该值限制在了cookie_names这个列表中了。(符合条件的HTPP数据包往来如下图)

不符合条件执行else后面的代码。假如我们输入1,回车。在POST请求中就有。

(不符合时的HTPP数据包往来如下图)

        5.Reset路径函数

@app.route("/reset")
def reset():
	resp = make_response(redirect("/"))
	session.pop("very_auth", None)
	return resp

点击界面上的reset键,你就会对该资源访问,服务器执行reset函数。这个函数就是把你的very_auth参数改为None(即不存在,可以理解为删除了),让你再去访问根目录。

HTTP数据包往来如下。

        6.display路径函数

@app.route("/display", methods=["GET"])
def flag():
	if session.get("very_auth"):
		check = session["very_auth"]
		if check == "admin":
			resp = make_response(render_template("flag.html", value=flag_value, title=title))
			return resp
		flash("That is a cookie! Not very special though...", "success")
		return render_template("not-flag.html", title=title, cookie_name=session["very_auth"])
	else:
		resp = make_response(redirect("/"))
		session["very_auth"] = "blank"
		return resp

这个函数是关键处,分析发现,该函数会检查very_auth是否存在,是否是admin,只有两者都满足才会返回给你flag.html(里面就有flag)。

三.思路介绍

        分析源码可以知道,我们要获取flag,最终是要带着session = {"very_auth":"admin"}去访问/display路径,让服务器端返回flag.html。而正常打开网页时,等价于在访问根目录,于是默认将该字段设置为blank。想要访问/display,就要利用/search和/中的重定向。

        利用search的方法就是在输入框中输入一个cookie_names列表中有的值,然后回车,就会被重定向到/display去了。

        默认大家都会熟练使用抓包工具了,拦截到去往/display的数据包,找到session。

       

        下面介绍一下这个题中的session。

图是用来说明session结构的。可以看到由“.”分割为三部分。

下面举例使用             “eyJ2ZXJ5X2F1dGgiOiJibGFuayJ9.ZiYpOA.zmBpLFgcFfmlaoC3mMCEybH_tg0”

  • 第一部分(session data):就是将session数据序列化然后经过base64编码得到的,示例base64解码就是{"very_auth": "blank"}
  • 第二部分(Timestamp):本质是时间戳。
  • 第三部分:看上去什么也不是,但是是由“ 数据部分+时间戳+密钥 ”作为参数混合生成的,这也就是拿来检验“安全”的地方,你可以读取session的值,但不能纂改。一旦改动前面两部分的任意一个,而没有修改第三部分(可以当成校验字段),最后服务器检测第三部分时就会失败,你注入的session就被当成无效的了。服务器检测校验字段的手段就是重新生成一次,然后拿两个进行比对。
  • 当然,这是因为你没有密钥。

        不是给出了一个密钥列表吗,我们任意找一个可行的session,再利用合适的工具不就可以找出加密密钥了!再拿来加密我们改过的数据生成session,就可以骗过服务器端了。

工具在这里啊!!!

也可以通过pip命令下载。

pip3 install flask-unsign[wordlist]

注意:下载过程中出错可以试一试先清理pip缓存,再次下载。

pip cache purge

wordlist是工具自带的一个密钥字典资源,收集了很多常用的密钥。不需要下载的话去掉命令最后的[wordlist]就好了(本题就不需要)。

工具可以通过命令行运行,flask-unsign + 参数    即可。举例如下。

  • flask-unsign --decode --cookie ' .......... '  解码出session数据信息。

  • flask-unsign --unsign  --cookie < cookie.txt  --wordlist wordlist.txt   就是找密钥啦。cookie.txt存放你拦截到的去往/display的session,wordlist.txt存放题中cookie_names的值,注意一行一个字符串哦!注意先进入到文件所在的路径哦!

  • flask-unsign --sign --cookie '{ ......... }' --secret  '......'         就是利用找到的密钥签名{"very_auth": "admin"}啦

  • 其他用法自行查看。      flask-unsign -h

注意:    似乎   --cookie  '{"very_auth": "admin"}' 和  "{'very_auth': 'admin'}" 得到的结果不一样哦。有个是错误的,结果以"ey"开头的就是对的。

 用新的session替换发送出去就成功了。   若是返回的是根目录界面的话可能是构造不成功,导致服务器验证失败后认为session无效。检查检查再试试。

已经结束了。


四.个人感受

        这题做了三天,先是看源码,慢慢查资料 ,慢慢搞清楚了代码的逻辑。结果看不懂session,查很多资料,但是各种各样的加密方法、签名方法搞得我头疼的一批,看了很多和题无关的文章。又是找工具,结果都没有加关键的salt就在那验证,自然是一个都过不了。

        如果想搞清楚其中的细节的可以去看Github上的flask项目的session.py,或者查看这个flask-unsign工具的源码,在Windows中使用pip下载后,应该就在python文件夹的lib库的site-packages文件里面,会有个flask_unsign文件夹。做得有点辛苦,也是作者目前太菜了,很多东西要理解的话就得去查资料。而且这个题似乎只有英文版WP,CSDN上居然没有文章。英文WP没看很懂,不过也有链接————WP。关于flask-unsign这个工具呢,这里也有工具作者的博客链接,里面详细讲解了他的思路和各种细节——————工具作者的博客

        学习工具的源码时,找到最关键的部分代码,主要用来试验出正确的密钥。想要初略了解的从这些代码出发就可以了。

         开始在AI的无效帮助下,发现了使用URLSafeTimedSerializer类的load方法来找出密钥,但是没有去查看Flask框架的源码,这些参数根本就不了解,也不知道应该设置为多少,所以怎么弄都是错,看来某个网站上的“Chatgpt 3.5”还是不够厉害啊,回答质量堪忧啊,看了server.py的源码都注意不到这些细节,真的是不行啊。

        如果真的感到文章对你有所帮助的uu,不妨点个赞再走呗,这将是对作者很大的激励,也会让作者有更多发表博客的动力。


  • 27
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值